summaryrefslogtreecommitdiffstats
path: root/tests/misc
diff options
context:
space:
mode:
Diffstat (limited to 'tests/misc')
-rwxr-xr-xtests/misc/arch.sh27
-rwxr-xr-xtests/misc/base64.pl214
-rwxr-xr-xtests/misc/basename.pl92
-rwxr-xr-xtests/misc/basenc.pl292
-rwxr-xr-xtests/misc/close-stdout.sh65
-rwxr-xr-xtests/misc/comm.pl186
-rwxr-xr-xtests/misc/coreutils.sh36
-rwxr-xr-xtests/misc/dircolors.pl76
-rwxr-xr-xtests/misc/dirname.pl72
-rwxr-xr-xtests/misc/echo.sh99
-rwxr-xr-xtests/misc/expand.pl191
-rwxr-xr-xtests/misc/false-status.sh31
-rwxr-xr-xtests/misc/fold.pl39
-rwxr-xr-xtests/misc/invalid-opt.pl103
-rwxr-xr-xtests/misc/join.pl342
-rwxr-xr-xtests/misc/kill.sh63
-rwxr-xr-xtests/misc/mknod.sh39
-rwxr-xr-xtests/misc/mktemp.pl206
-rwxr-xr-xtests/misc/nl.sh113
-rwxr-xr-xtests/misc/nohup.sh125
-rwxr-xr-xtests/misc/numfmt.pl1125
-rwxr-xr-xtests/misc/paste.pl74
-rwxr-xr-xtests/misc/pathchk.sh38
-rwxr-xr-xtests/misc/printenv.sh83
-rwxr-xr-xtests/misc/read-errors.sh95
-rwxr-xr-xtests/misc/realpath.sh111
-rwxr-xr-xtests/misc/selinux.sh65
-rwxr-xr-xtests/misc/sleep.sh55
-rwxr-xr-xtests/misc/stdbuf.sh125
-rwxr-xr-xtests/misc/sync.sh58
-rwxr-xr-xtests/misc/tee.sh145
-rwxr-xr-xtests/misc/time-style.sh119
-rwxr-xr-xtests/misc/tsort.pl66
-rwxr-xr-xtests/misc/unexpand.pl135
-rwxr-xr-xtests/misc/usage_vs_getopt.sh96
-rwxr-xr-xtests/misc/write-errors.sh65
-rwxr-xr-xtests/misc/xattr.sh130
-rwxr-xr-xtests/misc/xstrtol.pl49
-rwxr-xr-xtests/misc/yes.sh59
39 files changed, 5104 insertions, 0 deletions
diff --git a/tests/misc/arch.sh b/tests/misc/arch.sh
new file mode 100755
index 0000000..ec240d1
--- /dev/null
+++ b/tests/misc/arch.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Ensure that arch output is equal to uname -m
+
+# Copyright (C) 2007-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ arch
+
+arch > out || fail=1
+uname -m > exp || fail=1
+
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/misc/base64.pl b/tests/misc/base64.pl
new file mode 100755
index 0000000..63e6c6b
--- /dev/null
+++ b/tests/misc/base64.pl
@@ -0,0 +1,214 @@
+#!/usr/bin/perl
+# Exercise base{32,64}.
+
+# Copyright (C) 2006-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use strict;
+
+(my $program_name = $0) =~ s|.*/||;
+
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+# Return the encoding of a string of N 'a's.
+sub enc64($)
+{
+ my ($n) = @_;
+ my %remainder = ( 0 => '', 1 => 'YQ==', 2 => 'YWE=' );
+ return 'YWFh' x ($n / 3) . $remainder{$n % 3};
+}
+
+sub enc32($)
+{
+ my ($n) = @_;
+ my %remainder = ( 0 => '', 1 => 'ME======', 2 => 'MFQQ====',
+ 3 => 'MFQWC===', 4 => 'MFQWCYI=');
+ return 'MFQWCYLB' x ($n / 5) . $remainder{$n % 5};
+}
+
+# Function reference to appropriate encoder
+my $enc;
+
+# An encoded string of length 4KB, using 3K "a"s.
+my $a3k;
+my @a3k_nl;
+
+# Return a copy of S, with newlines inserted every WIDTH bytes.
+# Ensure that the result (if not the empty string) is newline-terminated.
+sub wrap($$)
+{
+ my ($s, $width) = @_;
+ $s =~ s/(.{$width})/$1\n/g;
+ substr ($s, -1, 1) ne "\n"
+ and $s .= "\n";
+ return $s;
+}
+
+my @Tests;
+
+sub gen_tests($)
+{
+ my ($prog) = @_;
+ my $try_help = "Try '$prog --help' for more information.\n";
+ @Tests=
+ (
+ ['empty', {IN=>''}, {OUT=>""}],
+ ['inout1', {IN=>'a'x1}, {OUT=>&$enc(1)."\n"}],
+ ['inout2', {IN=>'a'x2}, {OUT=>&$enc(2)."\n"}],
+ ['inout3', {IN=>'a'x3}, {OUT=>&$enc(3)."\n"}],
+ ['inout4', {IN=>'a'x4}, {OUT=>&$enc(4)."\n"}],
+ ['inout5', {IN=>'a'x5}, {OUT=>&$enc(5)."\n"}],
+ ['wrap', '--wrap 0', {IN=>'a'}, {OUT=>&$enc(1)}],
+ ['wrap-zero', '--wrap 08', {IN=>'a'}, {OUT=>&$enc(1)."\n"}],
+ ['wrap5-39', '--wrap=5', {IN=>'a' x 39}, {OUT=>wrap &$enc(39),5}],
+ ['wrap5-40', '--wrap=5', {IN=>'a' x 40}, {OUT=>wrap &$enc(40),5}],
+ ['wrap5-41', '--wrap=5', {IN=>'a' x 41}, {OUT=>wrap &$enc(41),5}],
+ ['wrap5-42', '--wrap=5', {IN=>'a' x 42}, {OUT=>wrap &$enc(42),5}],
+ ['wrap5-43', '--wrap=5', {IN=>'a' x 43}, {OUT=>wrap &$enc(43),5}],
+ ['wrap5-44', '--wrap=5', {IN=>'a' x 44}, {OUT=>wrap &$enc(44),5}],
+ ['wrap5-45', '--wrap=5', {IN=>'a' x 45}, {OUT=>wrap &$enc(45),5}],
+ ['wrap5-46', '--wrap=5', {IN=>'a' x 46}, {OUT=>wrap &$enc(46),5}],
+
+ ['wrap-bad-1', '-w0x0', {IN=>''}, {OUT=>""},
+ {ERR_SUBST => 's/base..:/base..:/'},
+ {ERR => "base..: invalid wrap size: '0x0'\n"}, {EXIT => 1}],
+ ['wrap-bad-2', '-w1k', {IN=>''}, {OUT=>""},
+ {ERR_SUBST => 's/base..:/base..:/'},
+ {ERR => "base..: invalid wrap size: '1k'\n"}, {EXIT => 1}],
+ ['wrap-bad-3', '-w-1', {IN=>''}, {OUT=>""},
+ {ERR_SUBST => 's/base..:/base..:/'},
+ {ERR => "base..: invalid wrap size: '-1'\n"}, {EXIT => 1}],
+
+ ['buf-1', '--decode', {IN=>&$enc(1)}, {OUT=>'a' x 1}],
+ ['buf-2', '--decode', {IN=>&$enc(2)}, {OUT=>'a' x 2}],
+ ['buf-3', '--decode', {IN=>&$enc(3)}, {OUT=>'a' x 3}],
+ ['buf-4', '--decode', {IN=>&$enc(4)}, {OUT=>'a' x 4}],
+ # 4KB worth of input.
+ ['buf-4k0', '--decode', {IN=>&$enc(3072+0)}, {OUT=>'a' x (3072+0)}],
+ ['buf-4k1', '--decode', {IN=>&$enc(3072+1)}, {OUT=>'a' x (3072+1)}],
+ ['buf-4k2', '--decode', {IN=>&$enc(3072+2)}, {OUT=>'a' x (3072+2)}],
+ ['buf-4k3', '--decode', {IN=>&$enc(3072+3)}, {OUT=>'a' x (3072+3)}],
+ ['buf-4km1','--decode', {IN=>&$enc(3072-1)}, {OUT=>'a' x (3072-1)}],
+ ['buf-4km2','--decode', {IN=>&$enc(3072-2)}, {OUT=>'a' x (3072-2)}],
+ ['buf-4km3','--decode', {IN=>&$enc(3072-3)}, {OUT=>'a' x (3072-3)}],
+ ['buf-4km4','--decode', {IN=>&$enc(3072-4)}, {OUT=>'a' x (3072-4)}],
+
+ # Exercise the case in which the final base-64 byte is
+ # in a buffer all by itself.
+ ['b4k-1', '--decode', {IN=>$a3k_nl[1]}, {OUT=>'a' x (3072+0)}],
+ ['b4k-2', '--decode', {IN=>$a3k_nl[2]}, {OUT=>'a' x (3072+0)}],
+ ['b4k-3', '--decode', {IN=>$a3k_nl[3]}, {OUT=>'a' x (3072+0)}],
+
+ ['ext-op1', 'a b', {IN=>''}, {EXIT=>1},
+ {ERR => "$prog: extra operand 'b'\n" . $try_help}],
+ # Again, with more option arguments
+ ['ext-op2', '-di --wrap=40 a b', {IN=>''}, {EXIT=>1},
+ {ERR => "$prog: extra operand 'b'\n" . $try_help}],
+ );
+
+ if ($prog eq "base64")
+ {
+ push @Tests, (
+ ['baddecode', '--decode', {IN=>'a'}, {OUT=>""},
+ {ERR_SUBST => 's/.*: invalid input//'}, {ERR => "\n"}, {EXIT => 1}],
+ ['baddecode2', '--decode', {IN=>'ab'}, {OUT=>"i"},
+ {ERR_SUBST => 's/.*: invalid input//'}, {ERR => "\n"}, {EXIT => 1}],
+ ['baddecode3', '--decode', {IN=>'Zzz'}, {OUT=>"g<"},
+ {ERR_SUBST => 's/.*: invalid input//'}, {ERR => "\n"}, {EXIT => 1}],
+ ['baddecode4', '--decode', {IN=>'Zz='}, {OUT=>"g"},
+ {ERR_SUBST => 's/.*: invalid input//'}, {ERR => "\n"}, {EXIT => 1}],
+ ['baddecode5', '--decode', {IN=>'Z==='}, {OUT=>""},
+ {ERR_SUBST => 's/.*: invalid input//'}, {ERR => "\n"}, {EXIT => 1}]
+ );
+ }
+
+ # For each non-failing test, create a --decode test using the
+ # expected output as input. Also, add tests inserting newlines.
+ my @new;
+ foreach my $t (@Tests)
+ {
+ my $exit_val;
+ my $in;
+ my @out;
+
+ # If the test has a single option of "--decode", then skip it.
+ !ref $t->[1] && $t->[1] eq '--decode'
+ and next;
+
+ foreach my $e (@$t)
+ {
+ ref $e && ref $e eq 'HASH'
+ or next;
+ defined $e->{EXIT}
+ and $exit_val = $e->{EXIT};
+ defined $e->{IN}
+ and $in = $e->{IN};
+ if (defined $e->{OUT})
+ {
+ my $t = $e->{OUT};
+ push @out, $t;
+ my $len = length $t;
+ foreach my $i (0..$len)
+ {
+ my $u = $t;
+ substr ($u, $i, 0) = "\n";
+ push @out, $u;
+ 10 <= $i
+ and last;
+ }
+ }
+ }
+ $exit_val
+ and next;
+
+ my $i = 0;
+ foreach my $o (@out)
+ {
+ push @new, ["d$i-$t->[0]", '--decode', {IN => $o}, {OUT => $in}];
+ ++$i;
+ }
+ }
+ push @Tests, @new;
+}
+
+my $save_temps = $ENV{DEBUG};
+my $verbose = $ENV{VERBOSE};
+
+my $fail = 0;
+foreach my $prog (qw(base32 base64))
+ {
+ $enc = $prog eq "base32" ? \&enc32 : \&enc64;
+
+ # Construct an encoded string of length 4KB, using 3K "a"s.
+ $a3k = &$enc(3072);
+ @a3k_nl = ();
+ # A few copies, each with different number of newlines at the start.
+ for my $k (0..3)
+ {
+ (my $t = $a3k) =~ s/^/"\n"x $k/e;
+ push @a3k_nl, $t;
+ }
+
+ gen_tests($prog);
+
+ $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
+ if ($fail != 0)
+ {
+ last;
+ }
+ }
+
+exit $fail;
diff --git a/tests/misc/basename.pl b/tests/misc/basename.pl
new file mode 100755
index 0000000..4f76a21
--- /dev/null
+++ b/tests/misc/basename.pl
@@ -0,0 +1,92 @@
+#!/usr/bin/perl
+# Test basename.
+# Copyright (C) 2006-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use strict;
+use File::stat;
+
+(my $program_name = $0) =~ s|.*/||;
+
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+my $stat_single = stat('/');
+my $stat_double = stat('//');
+my $double_slash = ($stat_single->dev == $stat_double->dev
+ && $stat_single->ino == $stat_double->ino) ? '/' : '//';
+
+my $prog = 'basename';
+
+my @Tests =
+ (
+ ['fail-1', {ERR => "$prog: missing operand\n"
+ . "Try '$prog --help' for more information.\n"}, {EXIT => '1'}],
+ ['fail-2', qw(a b c), {ERR => "$prog: extra operand 'c'\n"
+ . "Try '$prog --help' for more information.\n"}, {EXIT => '1'}],
+
+ ['a', qw(d/f), {OUT => 'f'}],
+ ['b', qw(/d/f), {OUT => 'f'}],
+ ['c', qw(d/f/), {OUT => 'f'}],
+ ['d', qw(d/f//), {OUT => 'f'}],
+ ['e', qw(f), {OUT => 'f'}],
+ ['f', qw(/), {OUT => '/'}],
+ ['g', qw(//), {OUT => "$double_slash"}],
+ ['h', qw(///), {OUT => '/'}],
+ ['i', qw(///a///), {OUT => 'a'}],
+ ['j', qw(''), {OUT => ''}],
+ ['k', qw(aa a), {OUT => 'a'}],
+ ['l', qw(-a a b), {OUT => "a\nb"}],
+ ['m', qw(-s a aa ba ab), {OUT => "a\nb\nab"}],
+ ['n', qw(a-a -a), {OUT => 'a'}],
+ ['1', qw(f.s .s), {OUT => 'f'}],
+ ['2', qw(fs s), {OUT => 'f'}],
+ ['3', qw(fs fs), {OUT => 'fs'}],
+ ['4', qw(fs/ s), {OUT => 'f'}],
+ ['5', qw(dir/file.suf .suf), {OUT => 'file'}],
+ ['6', qw(// /), {OUT => "$double_slash"}],
+ ['7', qw(// //), {OUT => "$double_slash"}],
+ ['8', qw(fs x), {OUT => 'fs'}],
+ ['9', qw(fs ''), {OUT => 'fs'}],
+ ['10', qw(fs/ s/), {OUT => 'fs'}],
+
+ # Exercise -z option.
+ ['z0', qw(-z a), {OUT => "a\0"}],
+ ['z1', qw(--zero a), {OUT => "a\0"}],
+ ['z2', qw(-za a b), {OUT => "a\0b\0"}],
+ ['z3', qw(-z ba a), {OUT => "b\0"}],
+ ['z4', qw(-z -s a ba), {OUT => "b\0"}],
+ );
+
+# Append a newline to end of each expected 'OUT' string.
+# Skip -z tests, i.e., those whose 'OUT' string has a trailing '\0'.
+my $t;
+foreach $t (@Tests)
+ {
+ my $arg1 = $t->[1];
+ my $e;
+ foreach $e (@$t)
+ {
+ $e->{OUT} = "$e->{OUT}\n"
+ if ref $e eq 'HASH' and exists $e->{OUT}
+ and not $e->{OUT} =~ /\0$/;
+ }
+ }
+
+my $save_temps = $ENV{SAVE_TEMPS};
+my $verbose = $ENV{VERBOSE};
+
+my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;
diff --git a/tests/misc/basenc.pl b/tests/misc/basenc.pl
new file mode 100755
index 0000000..de20d2d
--- /dev/null
+++ b/tests/misc/basenc.pl
@@ -0,0 +1,292 @@
+#!/usr/bin/perl
+# Exercise basenc.
+
+# Copyright (C) 2006-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+# This test exercises the various encoding (other than base64/32).
+# It also does not test the general options (e.g. --wrap), as that code is
+# shared and tested in base64.
+
+use strict;
+
+(my $program_name = $0) =~ s|.*/||;
+my $prog = 'basenc';
+
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+
+my $base64_in = "\x54\x0f\xdc\xf0\x0f\xaf\x4a";
+my $base64_out = "VA/c8A+vSg==";
+my $base64url_out = $base64_out;
+$base64url_out =~ y|+/|-_|;
+my $base64url_out_nl = $base64url_out;
+$base64url_out_nl =~ s/(..)/\1\n/g; # add newline every two characters
+
+
+# Bug 49741:
+# The input is 'abc' in base64, in an 8K buffer (larger than 1024*5,
+# the buffer size which caused the bug).
+my $base64_bug49741_in = "YWJj" x 2000 ;
+my $base64_bug49741_out = "abc" x 2000 ;
+
+
+my $base32_in = "\xfd\xd8\x07\xd1\xa5";
+my $base32_out = "7XMAPUNF";
+my $x = $base32_out;
+$x =~ y|ABCDEFGHIJKLMNOPQRSTUVWXYZ234567|0123456789ABCDEFGHIJKLMNOPQRSTUV|;
+my $base32hex_out = $x;
+
+# base32 with padding and newline
+my $base32_in2 = "\xFF\x00";
+my $base32_out2 = "74AA====";
+$x = $base32_out2;
+$x =~ y|ABCDEFGHIJKLMNOPQRSTUVWXYZ234567|0123456789ABCDEFGHIJKLMNOPQRSTUV|;
+my $base32hex_out2 = $x;
+my $base32hex_out2_nl = $x;
+$base32hex_out2_nl =~ s/(...)/\1\n/g; # Add newline every 3 characters
+
+my $base16_in = "\xfd\xd8\x07\xd1\xa5";
+my $base16_out = "FDD807D1A5";
+
+my $z85_in = "\x86\x4F\xD2\x6F\xB5\x59\xF7\x5B";
+my $z85_out = 'HelloWorld';
+
+my $base2lsbf_ab = "1000011001000110";
+my $base2lsbf_ab_nl = $base2lsbf_ab;
+$base2lsbf_ab_nl =~ s/(...)/\1\n/g; # Add newline every 3 characters
+my $base2msbf_ab = "0110000101100010";
+my $base2msbf_ab_nl = $base2msbf_ab;
+$base2msbf_ab_nl =~ s/(...)/\1\n/g; # Add newline every 3 characters
+
+my $try_help = "Try '$prog --help' for more information.\n";
+
+my @Tests =
+(
+ # These are mainly for higher coverage
+ ['help', '--help', {IN=>''}, {OUT=>""}, {OUT_SUBST=>'s/.*//sm'}],
+
+ # Typical message is " unrecognized option '--foobar'", but on
+ # Open/NetBSD it is " unknown option -- foobar".
+ ['error', '--foobar', {IN=>''}, {OUT=>""}, {EXIT=>1},
+ {ERR=>"$prog: foobar\n" . $try_help },
+ {ERR_SUBST=>"s/(unrecognized|unknown) option [-' ]*foobar[' ]*/foobar/"}],
+
+ ['noenc', '', {IN=>''}, {EXIT=>1},
+ {ERR=>"$prog: missing encoding type\n" . $try_help }],
+
+ ['extra', '--base64 A B', {IN=>''}, {EXIT=>1},
+ {ERR=>"$prog: extra operand 'B'\n" . $try_help}],
+
+
+ ['empty1', '--base64', {IN=>''}, {OUT=>""}],
+ ['empty2', '--base64url', {IN=>''}, {OUT=>""}],
+ ['empty3', '--base32', {IN=>''}, {OUT=>""}],
+ ['empty4', '--base32hex', {IN=>''}, {OUT=>""}],
+ ['empty5', '--base16', {IN=>''}, {OUT=>""}],
+ ['empty6', '--base2msbf', {IN=>''}, {OUT=>""}],
+ ['empty7', '--base2lsbf', {IN=>''}, {OUT=>""}],
+ ['empty8', '--z85', {IN=>''}, {OUT=>""}],
+
+
+
+
+ ['b64_1', '--base64', {IN=>$base64_in}, {OUT=>$base64_out}],
+ ['b64_2', '--base64 -d', {IN=>$base64_out}, {OUT=>$base64_in}],
+ ['b64_3', '--base64 -d -i', {IN=>'&'.$base64_out},{OUT=>$base64_in}],
+
+ ['b64u_1', '--base64url', {IN=>$base64_in}, {OUT=>$base64url_out}],
+ ['b64u_2', '--base64url -d', {IN=>$base64url_out}, {OUT=>$base64_in}],
+ ['b64u_3', '--base64url -di', {IN=>'&'.$base64url_out} , {OUT=>$base64_in}],
+ ['b64u_4', '--base64url -di', {IN=>'/'.$base64url_out.'+'},{OUT=>$base64_in}],
+ ['b64u_5', '--base64url -d', {IN=>$base64url_out_nl}, {OUT=>$base64_in}],
+ ['b64u_6', '--base64url -di', {IN=>$base64url_out_nl}, {OUT=>$base64_in}],
+ # ensure base64url fails to decode base64 input with "+" and "/"
+ ['b64u_7', '--base64url -d', {IN=>$base64_out},
+ {EXIT=>1}, {ERR=>"$prog: invalid input\n"}],
+
+ ['b64_bug49741', '--base64 -d', {IN=>$base64_bug49741_in},
+ {OUT=>$base64_bug49741_out}],
+
+
+
+ ['b32_1', '--base32', {IN=>$base32_in}, {OUT=>$base32_out}],
+ ['b32_2', '--base32 -d', {IN=>$base32_out}, {OUT=>$base32_in}],
+ ['b32_3', '--base32 -d -i', {IN=>'&'.$base32_out},{OUT=>$base32_in}],
+ ['b32_4', '--base32', {IN=>$base32_in2}, {OUT=>$base32_out2}],
+ ['b32_5', '--base32 -d', {IN=>$base32_out2}, {OUT=>$base32_in2}],
+ ['b32_6', '--base32 -d -i', {IN=>$base32_out2}, {OUT=>$base32_in2}],
+
+
+
+ ['b32h_1', '--base32hex', {IN=>$base32_in}, {OUT=>$base32hex_out}],
+ ['b32h_2', '--base32hex -d', {IN=>$base32hex_out}, {OUT=>$base32_in}],
+ ['b32h_3', '--base32hex -d -i', {IN=>'/'.$base32hex_out}, {OUT=>$base32_in}],
+ ['b32h_4', '--base32hex -d -i', {IN=>'W'.$base32hex_out}, {OUT=>$base32_in}],
+ ['b32h_5', '--base32hex -d', {IN=>$base32hex_out.'W'}, , {OUT=>$base32_in},
+ {EXIT=>1}, {ERR=>"$prog: invalid input\n"}],
+ ['b32h_6', '--base32hex -d', {IN=>$base32hex_out.'/'}, {OUT=>$base32_in},
+ {EXIT=>1}, {ERR=>"$prog: invalid input\n"}],
+ ['b32h_7', '--base32hex', {IN=>$base32_in2}, {OUT=>$base32hex_out2}],
+ ['b32h_8', '--base32hex -d', {IN=>$base32hex_out2}, {OUT=>$base32_in2}],
+ ['b32h_9', '--base32hex -di', {IN=>$base32hex_out2}, {OUT=>$base32_in2}],
+ ['b32h_10', '--base32hex -d', {IN=>$base32hex_out2_nl}, {OUT=>$base32_in2}],
+ ['b32h_11', '--base32hex -di', {IN=>$base32hex_out2_nl}, {OUT=>$base32_in2}],
+
+
+
+ ['b16_1', '--base16', {IN=>$base16_in}, {OUT=>$base16_out}],
+ ['b16_2', '--base16 -d', {IN=>$base16_out}, {OUT=>$base16_in}],
+ ['b16_3', '--base16 -d -i', {IN=>'&'. $base16_out}, {OUT=>$base16_in}],
+ ['b16_4', '--base16 -d -i', {IN=>$base16_out.'G'}, {OUT=>$base16_in}],
+ ['b16_5', '--base16 -d', {IN=>'.'}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['b16_6', '--base16 -d', {IN=>'='}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['b16_7', '--base16 -d', {IN=>'G'}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['b16_8', '--base16 -d', {IN=>"AB\nCD"}, {OUT=>"\xAB\xCD"}],
+
+
+
+ ['b2m_1', '--base2m', {IN=>"\xC1"}, {OUT=>"11000001"}],
+ ['b2m_2', '--base2m -d', {IN=>'11000001'}, {OUT=>"\xC1"}],
+ ['b2m_3', '--base2m -d', {IN=>"110\n00001"}, {OUT=>"\xC1"}],
+ ['b2m_4', '--base2m -di', {IN=>"110x00001"}, {OUT=>"\xC1"}],
+ ['b2m_5', '--base2m -d', {IN=>"110x00001"}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['b2m_6', '--base2m -d', {IN=>"11000001x"}, {OUT=>"\xC1"}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['b2m_7', '--base2m -d', {IN=>"1"}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['b2m_8', '--base2m -d', {IN=>"1000100"}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['b2m_9', '--base2m -d', {IN=>"100010000000000"}, {OUT=>"\x88"}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['b2m_10','--base2m', {IN=>"ab"}, {OUT=>$base2msbf_ab}],
+ ['b2m_11','--base2m -d', {IN=>$base2msbf_ab}, {OUT=>"ab"}],
+ ['b2m_12','--base2m -d', {IN=>$base2msbf_ab_nl}, {OUT=>"ab"}],
+
+
+ ['b2l_1', '--base2l', {IN=>"\x83"}, {OUT=>"11000001"}],
+ ['b2l_2', '--base2l -d', {IN=>'11000001'}, {OUT=>"\x83"}],
+ ['b2l_3', '--base2l -d', {IN=>"110\n00001"}, {OUT=>"\x83"}],
+ ['b2l_4', '--base2l -di', {IN=>"110x00001"}, {OUT=>"\x83"}],
+ ['b2l_5', '--base2l -d', {IN=>"110x00001"}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['b2l_6', '--base2l -d', {IN=>"11000001x"}, {OUT=>"\x83"}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['b2l_7', '--base2l -d', {IN=>"1"}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['b2l_8', '--base2l -d', {IN=>"1000100"}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['b2l_9', '--base2l -d', {IN=>"100010000000000"}, {OUT=>"\x11"}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['b2l_10','--base2l', {IN=>"ab"}, {OUT=>$base2lsbf_ab}],
+ ['b2l_11','--base2l -d', {IN=>$base2lsbf_ab}, {OUT=>"ab"}],
+ ['b2l_12','--base2l -d', {IN=>$base2lsbf_ab_nl}, {OUT=>"ab"}],
+
+
+
+
+
+ ['z85_1', '--z85', {IN=>$z85_in}, {OUT=>$z85_out}],
+ ['z85_2', '--z85 -d', {IN=>$z85_out}, {OUT=>$z85_in}],
+ ['z85_3', '--z85 -d -i', {IN=>'~'. $z85_out}, {OUT=>$z85_in}],
+ ['z85_4', '--z85 -d -i', {IN=>' '. $z85_out}, {OUT=>$z85_in}],
+ ['z85_5', '--z85 -d', {IN=>'%j$qP'}, {OUT=>"\xFF\xDD\xBB\x99"}],
+ ['z85_6', '--z85 -d -i', {IN=>'%j~$qP'}, {OUT=>"\xFF\xDD\xBB\x99"}],
+
+ # z85 encoding require input to be multiple of 5 octets
+ ['z85_7', '--z85 -d', {IN=>'hello'}, {OUT=>"5jXu"}],
+ ['z85_8', '--z85 -d', {IN=>'helloX'}, {OUT=>"5jXu"}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['z85_9', '--z85 -d', {IN=>"he\nl\nlo"}, {OUT=>"5jXu"}],
+
+ # Invalid input characters (space ~ ")
+ ['z85_10', '--z85 -d', {IN=>' j$qP'}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['z85_11', '--z85 -d', {IN=>'%j$q~'}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['z85_12', '--z85 -d', {IN=>'%j$"P'}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+
+ # Invalid length (binary input must be a multiple of 4 octets,
+ # z85-encoded input must be a multiple of 5 octets)
+ ['z85_20', '--z85', {IN=>'A'}, {EXIT=>1},
+ {ERR=>"$prog: invalid input (length must be multiple of 4 characters)\n"}],
+ ['z85_21', '--z85', {IN=>'AB'}, {EXIT=>1},
+ {ERR=>"$prog: invalid input (length must be multiple of 4 characters)\n"}],
+ ['z85_22', '--z85', {IN=>'ABC'}, {EXIT=>1},
+ {ERR=>"$prog: invalid input (length must be multiple of 4 characters)\n"}],
+ ['z85_23', '--z85', {IN=>'ABCD'}, {OUT=>'k%^}b'}],
+ ['z85_24', '--z85', {IN=>'ABCDE'}, {EXIT=>1},
+ {ERR=>"$prog: invalid input (length must be multiple of 4 characters)\n"}],
+
+ ['z85_30', '--z85 -d', {IN=>'A'}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['z85_31', '--z85 -d', {IN=>'AB'}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['z85_32', '--z85 -d', {IN=>'ABC'}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['z85_33', '--z85 -d', {IN=>'ABCD'}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['z85_34', '--z85 -d', {IN=>'ABCDE'}, {OUT=>"\x71\x61\x9e\xb6"}],
+ ['z85_35', '--z85 -d', {IN=>'ABCDEF'},{OUT=>"\x71\x61\x9e\xb6"},
+ {EXIT=>1}, {ERR=>"$prog: invalid input\n"}],
+
+ # largest possible value
+ ['z85_40', '--z85', {IN=>"\xFF\xFF\xFF\xFF"},{OUT=>"%nSc0"}],
+ ['z85_41', '--z85 -d', {IN=>"%nSc0"}, {OUT=>"\xFF\xFF\xFF\xFF"}],
+ # Invalid encoded data - will decode to more than 0xFFFFFFFF
+ ['z85_42', '--z85 -d', {IN=>"%nSc1"}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['z85_43', '--z85 -d', {IN=>"%nSd0"}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['z85_44', '--z85 -d', {IN=>"%nTc0"}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['z85_45', '--z85 -d', {IN=>"%oSc0"}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['z85_46', '--z85 -d', {IN=>'$nSc0'}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+ ['z85_47', '--z85 -d', {IN=>'#0000'}, {EXIT=>1},
+ {ERR=>"$prog: invalid input\n"}],
+);
+
+# Prepend the command line argument and append a newline to end
+# of each expected 'OUT' string.
+my $t;
+
+Test:
+foreach $t (@Tests)
+ {
+ foreach my $e (@$t)
+ {
+ ref $e && ref $e eq 'HASH' && defined $e->{OUT_SUBST}
+ and next Test;
+ }
+
+ push @$t, {OUT_SUBST=>'s/\n$//s'};
+ }
+
+
+
+my $save_temps = $ENV{DEBUG};
+my $verbose = $ENV{VERBOSE};
+
+my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
+
+exit $fail;
diff --git a/tests/misc/close-stdout.sh b/tests/misc/close-stdout.sh
new file mode 100755
index 0000000..4bff9e2
--- /dev/null
+++ b/tests/misc/close-stdout.sh
@@ -0,0 +1,65 @@
+#!/bin/sh
+# Ensure that several programs work fine, even with stdout initially closed.
+# This is effectively a test of closeout.c's close_stdout function.
+
+# Copyright (C) 2004-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ rm
+
+p=$abs_top_builddir
+
+
+# Ensure these exit successfully, even though stdout is closed,
+# because they generate no output.
+touch a
+cp a b >&- || fail=1
+test -f b || fail=1
+chmod o-w . >&- || fail=1
+ln a c >&- || fail=1
+rm c >&- || fail=1
+mkdir d >&- || fail=1
+mv d e >&- || fail=1
+rmdir e >&- || fail=1
+touch e >&- || fail=1
+sleep 0 >&- || fail=1
+"$p/src/true" >&- || fail=1
+"$p/src/printf" '' >&- || fail=1
+
+# If >&- works, ensure these fail, because stdout is closed and they
+# *do* generate output. >&- apparently does not work in HP-UX 11.23.
+# This test is ineffective unless /dev/stdout also works.
+if "$p/src/test" -w /dev/stdout >/dev/null &&
+ "$p/src/test" ! -w /dev/stdout >&-; then
+ returns_ 1 "$p/src/printf" 'foo' >&- 2>/dev/null || fail=1
+ returns_ 1 cp --verbose a b >&- 2>/dev/null || fail=1
+ rm -Rf tmpfile-?????? || fail=1
+ returns_ 1 mktemp tmpfile-XXXXXX >&- 2>/dev/null || fail=1
+ returns_ 1 mktemp tmpfile-XXXXXX -q >&- 2>/dev/null || fail=1
+ case $(echo tmpfile-??????) in 'tmpfile-??????') ;; *) fail=1 ;; esac
+fi
+
+# Likewise for /dev/full, if /dev/full works.
+if test -w /dev/full && test -c /dev/full; then
+ returns_ 1 "$p/src/printf" 'foo' >/dev/full 2>/dev/null || fail=1
+ returns_ 1 cp --verbose a b >/dev/full 2>/dev/null || fail=1
+ rm -Rf tmpdir-?????? || fail=1
+ returns_ 1 mktemp -d tmpdir-XXXXXX >/dev/full 2>/dev/null || fail=1
+ returns_ 1 mktemp -d -q tmpdir-XXXXXX >/dev/full 2>/dev/null || fail=1
+ case $(echo tmpfile-??????) in 'tmpfile-??????') ;; *) fail=1 ;; esac
+fi
+
+Exit $fail
diff --git a/tests/misc/comm.pl b/tests/misc/comm.pl
new file mode 100755
index 0000000..aaaf465
--- /dev/null
+++ b/tests/misc/comm.pl
@@ -0,0 +1,186 @@
+#!/usr/bin/perl
+# -*- perl -*-
+# Test comm
+
+# Copyright (C) 2008-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+require 5.003;
+use strict;
+
+(my $program_name = $0) =~ s|.*/||;
+
+my $prog = 'comm';
+
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+my @inputs = ({IN=>{a=>"1\n3\n3\n3"}}, {IN=>{b=>"2\n2\n3\n3\n3"}});
+my @zinputs = ({IN=>{za=>"1\0003\0003\0003"}},
+ {IN=>{zb=>"2\0002\0003\0003\0003"}});
+
+my @Tests =
+ (
+ # basic operation
+ ['basic', @inputs, {OUT=>"1\n\t2\n\t2\n\t\t3\n\t\t3\n\t\t3\n"} ],
+ ['zbasic', '-z', @zinputs, {OUT=>"1\0\t2\0\t2\0\t\t3\0\t\t3\0\t\t3\0"} ],
+
+ # suppress lines unique to file 1
+ ['opt-1', '-1', @inputs, {OUT=>"2\n2\n\t3\n\t3\n\t3\n"} ],
+ ['zopt-1', '-z', '-1', @zinputs, {OUT=>"2\0002\000\t3\000\t3\000\t3\000"} ],
+
+ # suppress lines unique to file 2
+ ['opt-2', '-2', @inputs, {OUT=>"1\n\t3\n\t3\n\t3\n"} ],
+ ['zopt-2', '-z', '-2', @zinputs, {OUT=>"1\000\t3\000\t3\000\t3\000"} ],
+
+ # suppress lines that appear in both files
+ ['opt-3', '-3', @inputs, {OUT=>"1\n\t2\n\t2\n"} ],
+ ['zopt-3', '-z', '-3', @zinputs, {OUT=>"1\000\t2\000\t2\000"} ],
+
+ # suppress lines unique to file 1 and lines unique to file 2
+ ['opt-12', '-1', '-2', @inputs, {OUT=>"3\n3\n3\n"} ],
+ ['zopt-12', '-12z', @zinputs, {OUT=>"3\0003\0003\000"} ],
+
+ # suppress lines unique to file 1 and those that appear in both files
+ ['opt-13', '-1', '-3', @inputs, {OUT=>"2\n2\n"} ],
+ ['zopt-13', '-13z', @zinputs, {OUT=>"2\0002\000"} ],
+
+ # suppress lines unique to file 2 and those that appear in both files
+ ['opt-23', '-2', '-3', @inputs, {OUT=>"1\n"} ],
+ ['zopt-23', '-23z', @zinputs, {OUT=>"1\000"} ],
+
+ # suppress all output
+ ['opt-123', '-1', '-2', '-3', @inputs, {OUT=>""} ],
+
+ # show summary: 1 only in file1, 2 only in file2, 3 in both files
+ ['total-all', '--total', @inputs, {OUT=>"1\n\t2\n\t2\n\t\t3\n\t\t3\n\t\t3\n"
+ . "1\t2\t3\ttotal\n"} ],
+
+ # show summary only, suppressing regular output
+ ['total-123', '--total', '-123', @inputs, {OUT=>"1\t2\t3\ttotal\n"} ],
+
+ # invalid missing command line argument (1)
+ ['missing-arg1', $inputs[0], {EXIT=>1},
+ {ERR => "$prog: missing operand after 'a'\n"
+ . "Try '$prog --help' for more information.\n"}],
+
+ # invalid missing command line argument (both)
+ ['missing-arg2', {EXIT=>1},
+ {ERR => "$prog: missing operand\n"
+ . "Try '$prog --help' for more information.\n"}],
+
+ # invalid extra command line argument
+ ['extra-arg', @inputs, 'no-such', {EXIT=>1},
+ {ERR => "$prog: extra operand 'no-such'\n"
+ . "Try '$prog --help' for more information.\n"}],
+
+ # out-of-order input
+ ['ooo', {IN=>{a=>"1\n3"}}, {IN=>{b=>"3\n2"}}, {EXIT=>1},
+ {OUT => "1\n\t\t3\n\t2\n"},
+ {ERR => "$prog: file 2 is not in sorted order\n"
+ . "$prog: input is not in sorted order\n"}],
+
+ # out-of-order input, fatal
+ ['ooo2', '--check-order', {IN=>{a=>"1\n3"}}, {IN=>{b=>"3\n2"}}, {EXIT=>1},
+ {OUT => "1\n\t\t3\n"},
+ {ERR => "$prog: file 2 is not in sorted order\n"}],
+
+ # out-of-order input, ignored
+ ['ooo3', '--nocheck-order', {IN=>{a=>"1\n3"}}, {IN=>{b=>"3\n2"}},
+ {OUT => "1\n\t\t3\n\t2\n"}],
+
+ # both inputs out-of-order
+ ['ooo4', {IN=>{a=>"3\n1\n0"}}, {IN=>{b=>"3\n2\n0"}}, {EXIT=>1},
+ {OUT => "\t\t3\n1\n0\n\t2\n\t0\n"},
+ {ERR => "$prog: file 1 is not in sorted order\n".
+ "$prog: file 2 is not in sorted order\n"
+ . "$prog: input is not in sorted order\n"}],
+
+ # both inputs out-of-order on last pair
+ ['ooo5', {IN=>{a=>"3\n1"}}, {IN=>{b=>"3\n2"}}, {EXIT=>1},
+ {OUT => "\t\t3\n1\n\t2\n"},
+ {ERR => "$prog: file 1 is not in sorted order\n".
+ "$prog: file 2 is not in sorted order\n"
+ . "$prog: input is not in sorted order\n"}],
+
+ # first input out-of-order extended
+ ['ooo5b', {IN=>{a=>"0\n3\n1"}}, {IN=>{b=>"2\n3"}}, {EXIT=>1},
+ {OUT => "0\n\t2\n\t\t3\n1\n"},
+ {ERR => "$prog: file 1 is not in sorted order\n"
+ . "$prog: input is not in sorted order\n"}],
+
+ # second input out-of-order extended
+ ['ooo5c', {IN=>{a=>"0\n3"}}, {IN=>{b=>"2\n3\n1"}}, {EXIT=>1},
+ {OUT => "0\n\t2\n\t\t3\n\t1\n"},
+ {ERR => "$prog: file 2 is not in sorted order\n"
+ . "$prog: input is not in sorted order\n"}],
+
+ # both inputs out-of-order, but fully pairable
+ ['ooo6', {IN=>{a=>"2\n1\n0"}}, {IN=>{b=>"2\n1\n0"}}, {EXIT=>0},
+ {OUT => "\t\t2\n\t\t1\n\t\t0\n"}],
+
+ # both inputs out-of-order, fully pairable, but forced to fail
+ ['ooo7', '--check-order', {IN=>{a=>"2\n1\n0"}}, {IN=>{b=>"2\n1\n0"}},
+ {EXIT=>1},
+ {OUT => "\t\t2\n"},
+ {ERR => "$prog: file 1 is not in sorted order\n"}],
+
+ # out-of-order, line 2 is a prefix of line 1
+ # until coreutils-7.2, this test would fail -- no disorder detected
+ ['ooo-prefix', '--check-order', {IN=>{a=>"Xa\nX\n"}}, {IN=>{b=>""}},
+ {EXIT=>1},
+ {OUT => "Xa\n"},
+ {ERR => "$prog: file 1 is not in sorted order\n"}],
+
+ # alternate delimiter: ','
+ ['delim-comma', '--output-delimiter=,', @inputs,
+ {OUT=>"1\n,2\n,2\n,,3\n,,3\n,,3\n"} ],
+
+ # two-character alternate delimiter: '++'
+ ['delim-2char', '--output-delimiter=++', @inputs,
+ {OUT=>"1\n++2\n++2\n++++3\n++++3\n++++3\n"} ],
+
+ # NUL delimiter
+ ['delim-empty', '--output-delimiter=', @inputs,
+ {OUT=>"1\n\0002\n\0002\n\000\0003\n\000\0003\n\000\0003\n"} ],
+ ['zdelim-empty', '-z', '-z --output-delimiter=', @zinputs,
+ {OUT=>"1\000\0002\000\0002\000\000\0003\000\000\0003\000\000\0003\000"} ],
+ ['total-delim-empty', '--total --output-delimiter=', @inputs,
+ {OUT=>"1\n\0002\n\0002\n\000\0003\n\000\0003\n\000\0003\n"
+ . "1\0002\0003\000total\n"} ],
+
+ # invalid dual delimiter
+ ['delim-dual', '--output-delimiter=,', '--output-delimiter=+', @inputs,
+ {EXIT=>1}, {ERR => "$prog: multiple output delimiters specified\n"}],
+
+ # valid dual delimiter specification
+ ['delim-dual2', '--output-delimiter=,', '--output-delimiter=,', @inputs,
+ {OUT=>"1\n,2\n,2\n,,3\n,,3\n,,3\n"} ],
+
+ # show summary, zero-terminated
+ ['totalz-all', '--total', '-z', @zinputs,
+ {OUT=>"1\000\t2\000\t2\000\t\t3\000\t\t3\000\t\t3\000"
+ . "1\t2\t3\ttotal\000"} ],
+
+ # show summary only (-123), zero-terminated and with ',' as delimiter
+ ['totalz-123', '--total', '-z123', '--output-delimiter=,', @zinputs,
+ {OUT=>"1,2,3,total\000"} ],
+ );
+
+my $save_temps = $ENV{DEBUG};
+my $verbose = $ENV{VERBOSE};
+
+my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;
diff --git a/tests/misc/coreutils.sh b/tests/misc/coreutils.sh
new file mode 100755
index 0000000..ffcce84
--- /dev/null
+++ b/tests/misc/coreutils.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+# Verify behavior of separate coreutils multicall binary
+
+# Copyright (C) 2014-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ coreutils
+
+test -s "$abs_top_builddir/src/coreutils.h" \
+ || skip_ "multicall binary is disabled"
+
+# Yes outputs all its params so is good to verify argv manipulations
+echo 'y' > exp
+coreutils --coreutils-prog=yes | head -n10 | uniq > out
+compare exp out || fail=1
+
+# Ensure if incorrect program passed, we diagnose
+echo "coreutils: unknown program 'blah'" > exp
+coreutils --coreutils-prog='blah' --help 2>err && fail=1
+compare exp err || fail=1
+
+Exit $fail
diff --git a/tests/misc/dircolors.pl b/tests/misc/dircolors.pl
new file mode 100755
index 0000000..f33e893
--- /dev/null
+++ b/tests/misc/dircolors.pl
@@ -0,0 +1,76 @@
+#!/usr/bin/perl
+# Simple dircolors tests.
+
+# Copyright (C) 1998-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use strict;
+
+(my $program_name = $0) =~ s|.*/||;
+
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+my @Tests =
+ (
+ ['a', '-b', {IN => {k => "exec\n"}},
+ {ERR => "dircolors: k:1: invalid line; missing second token\n"},
+ {EXIT => 1}],
+ ['quote', '-b', {IN => "exec 'echo Hello;:'\n"},
+ {OUT => "LS_COLORS='ex='\\''echo Hello;\\:'\\'':';\n"
+ . "export LS_COLORS\n"}],
+ ['other-wr', '-b', {IN => "owt 40;33\n"},
+ {OUT => "LS_COLORS='tw=40;33:';\nexport LS_COLORS\n"}],
+ ['term-1', '-b', {IN => "TERM none\nowt 40;33\n"},
+ {OUT => "LS_COLORS='tw=40;33:';\nexport LS_COLORS\n"}],
+ ['term-2', '-b', {IN => "TERM non*\nowt 40;33\n"},
+ {OUT => "LS_COLORS='tw=40;33:';\nexport LS_COLORS\n"}],
+ ['term-3', '-b', {IN => "TERM nomatch\nowt 40;33\n"},
+ {OUT => "LS_COLORS='';\nexport LS_COLORS\n"}],
+ ['term-4', '-b', {IN => "TERM N*match\nowt 40;33\n"},
+ {OUT => "LS_COLORS='';\nexport LS_COLORS\n"}],
+
+ ['colorterm-1', '-b', {ENV => 'COLORTERM=any'},
+ {IN => "COLORTERM ?*\nowt 40;33\n"},
+ {OUT => "LS_COLORS='tw=40;33:';\nexport LS_COLORS\n"}],
+
+ ['colorterm-2', '-b', {ENV => 'COLORTERM='},
+ {IN => "COLORTERM ?*\nowt 40;33\n"},
+ {OUT => "LS_COLORS='';\nexport LS_COLORS\n"}],
+
+ ['print-clash1', '-p', '--print-ls',
+ {ERR => "dircolors: options --print-database and --print-ls-colors " .
+ "are mutually exclusive\n" .
+ "Try 'dircolors --help' for more information.\n"},
+ {EXIT => 1}],
+ ['print-clash2', '-b', '--print-database',
+ {ERR => "dircolors: the options to output non shell syntax,\n" .
+ "and to select a shell syntax are mutually exclusive\n" .
+ "Try 'dircolors --help' for more information.\n"},
+ {EXIT => 1}],
+
+ ['print-ls-colors', '--print-ls-colors', {IN => "OWT 40;33\n"},
+ {OUT => "\x1B[40;33mtw\t40;33\x1B[0m\n"}],
+
+ # CAREFUL: always specify the -b option, unless explicitly testing
+ # for csh syntax output, or --print-ls-color output.
+ );
+
+my $save_temps = $ENV{DEBUG};
+my $verbose = $ENV{VERBOSE};
+
+my $prog = 'dircolors';
+my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;
diff --git a/tests/misc/dirname.pl b/tests/misc/dirname.pl
new file mode 100755
index 0000000..e509c6b
--- /dev/null
+++ b/tests/misc/dirname.pl
@@ -0,0 +1,72 @@
+#!/usr/bin/perl
+# Test "dirname".
+
+# Copyright (C) 2006-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use strict;
+use File::stat;
+
+(my $program_name = $0) =~ s|.*/||;
+
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+my $stat_single = stat('/');
+my $stat_double = stat('//');
+my $double_slash = ($stat_single->dev == $stat_double->dev
+ && $stat_single->ino == $stat_double->ino) ? '/' : '//';
+
+my $prog = 'dirname';
+
+my @Tests =
+ (
+ ['fail-1', {ERR => "$prog: missing operand\n"
+ . "Try '$prog --help' for more information.\n"}, {EXIT => '1'}],
+
+ ['a', qw(d/f), {OUT => 'd'}],
+ ['b', qw(/d/f), {OUT => '/d'}],
+ ['c', qw(d/f/), {OUT => 'd'}],
+ ['d', qw(d/f//), {OUT => 'd'}],
+ ['e', qw(f), {OUT => '.'}],
+ ['f', qw(/), {OUT => '/'}],
+ ['g', qw(//), {OUT => "$double_slash"}],
+ ['h', qw(///), {OUT => '/'}],
+ ['i', qw(//a//), {OUT => "$double_slash"}],
+ ['j', qw(///a///), {OUT => '/'}],
+ ['k', qw(///a///b), {OUT => '///a'}],
+ ['l', qw(///a//b/), {OUT => '///a'}],
+ ['m', qw(''), {OUT => '.'}],
+ ['n', qw(a/b c/d), {OUT => "a\nc"}],
+ );
+
+# Append a newline to end of each expected 'OUT' string.
+my $t;
+foreach $t (@Tests)
+ {
+ my $arg1 = $t->[1];
+ my $e;
+ foreach $e (@$t)
+ {
+ $e->{OUT} = "$e->{OUT}\n"
+ if ref $e eq 'HASH' and exists $e->{OUT};
+ }
+ }
+
+my $save_temps = $ENV{SAVE_TEMPS};
+my $verbose = $ENV{VERBOSE};
+
+my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;
diff --git a/tests/misc/echo.sh b/tests/misc/echo.sh
new file mode 100755
index 0000000..1cd9231
--- /dev/null
+++ b/tests/misc/echo.sh
@@ -0,0 +1,99 @@
+#!/bin/sh
+# basic tests for echo
+
+# Copyright (C) 2018-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+prog='env echo'
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ echo
+
+
+# Verify the methods of specifying "Escape":
+# Note 4 octal digits are allowed (unlike printf which uses up to 3)
+printf '%s\n' . . . . . | tr . '\033' > exp
+$prog -n -e '\x1b\n\e\n\33\n\033\n\0033\n' > out || fail=1
+compare exp out || fail=1
+
+# Incomplete hex escapes are output as is (unlike printf)
+printf '\\x\n' > exp
+$prog -n -e '\x\n' > out || fail=1
+compare exp out || fail=1
+
+# Always output -- (unlike printf)
+$prog -- 'foo' > out || fail=1
+$prog -n -e -- 'foo\n' >> out || fail=1
+cat <<\EOF > exp
+-- foo
+-- foo
+EOF
+compare exp out || fail=1
+
+# Ensure \c stops processing
+$prog -e 'foo\n\cbar' > out || fail=1
+printf 'foo\n' > exp
+compare exp out || fail=1
+
+# With POSIXLY_CORRECT:
+# only -n as the first (separate) option enables option processing
+# -E is ignored
+# escapes are processed by default
+POSIXLY_CORRECT=1 $prog -n -E 'foo\n' > out || fail=1
+POSIXLY_CORRECT=1 $prog -nE 'foo' >> out || fail=1
+POSIXLY_CORRECT=1 $prog -E -n 'foo' >> out || fail=1
+POSIXLY_CORRECT=1 $prog --version >> out || fail=1
+cat <<\EOF > exp
+foo
+-nE foo
+-E -n foo
+--version
+EOF
+compare exp out || fail=1
+
+# Further test coverage.
+# Output a literal '-' (on a line itself).
+$prog - > out || fail=1
+# Output a literal backslash '\', no newline.
+$prog -n -e '\\' >> out || fail=1
+# Output an empty line (merely to have a newline after the previous test).
+$prog >> out || fail=1
+# Test other characters escaped by a backslash:
+# \a hex 07 alert, bell
+# \b hex 08 backspace
+# \e hex 1b escape
+# \f hex 0c form feed
+# \n hex 0a new line
+# \r hex 0d carriage return
+# \t hex 09 horizontal tab
+# \v hex 0b vertical tab
+# Convert output, yet checking the exit status of $prog.
+{ $prog -n -e '\a\b\e\f\n\r\t\v' || touch fail; } | od -tx1 >> out || fail=1
+test '!' -f fail || fail=1
+# Output hex values which contain hexadecimal characters to test hextobin().
+# Hex values 4a through 4f are ASCII "JKLMNO".
+$prog -n -e '\x4a\x4b\x4c\x4d\x4e\x4f\x4A\x4B\x4C\x4D\x4E\x4F' >> out || fail=1
+# Output another newline.
+$prog >> out || fail=1
+cat <<\EOF > exp
+-
+\
+0000000 07 08 1b 0c 0a 0d 09 0b
+0000010
+JKLMNOJKLMNO
+EOF
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/misc/expand.pl b/tests/misc/expand.pl
new file mode 100755
index 0000000..06261ac
--- /dev/null
+++ b/tests/misc/expand.pl
@@ -0,0 +1,191 @@
+#!/usr/bin/perl
+# Exercise expand.
+
+# Copyright (C) 2004-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use strict;
+
+my $limits = getlimits ();
+my $UINTMAX_OFLOW = $limits->{UINTMAX_OFLOW};
+
+(my $program_name = $0) =~ s|.*/||;
+my $prog = 'expand';
+
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+my @Tests =
+ (
+ ['t1', '--tabs=3', {IN=>"a\tb"}, {OUT=>"a b"}],
+ ['t2', '--tabs=3,6,9', {IN=>"a\tb\tc\td\te"}, {OUT=>"a b c d e"}],
+ ['t3', '--tabs="3 6 9"', {IN=>"a\tb\tc\td\te"}, {OUT=>"a b c d e"}],
+ # Leading space/commas are silently ignored; Mixing space/commas is allowed.
+ # (a side-effect of allowing direct "-3,9" parameter).
+ ['t4', '--tabs=", 3,6 9"', {IN=>"a\tb\tc\td\te"}, {OUT=>"a b c d e"}],
+ # tab stops parameter without values
+ ['t5', '--tabs=""', {IN=>"a\tb\tc"}, {OUT=>"a b c"}],
+ ['t6', '--tabs=","', {IN=>"a\tb\tc"}, {OUT=>"a b c"}],
+ ['t7', '--tabs=" "', {IN=>"a\tb\tc"}, {OUT=>"a b c"}],
+ ['t8', '--tabs="/"', {IN=>"a\tb\tc"}, {OUT=>"a b c"}],
+
+ # Input field wider than the specified tab list
+ ['if', '--tabs=6,9', {IN=>"a\tbbbbbbbbbbbbb\tc"},
+ {OUT=>"a bbbbbbbbbbbbb c"}],
+
+ ['i1', '--tabs=3 -i', {IN=>"\ta\tb"}, {OUT=>" a\tb"}],
+ ['i2', '--tabs=3 -i', {IN=>" \ta\tb"}, {OUT=>" a\tb"}],
+
+ # Undocumented feature:
+ # treat "expand -7" as "expand --tabs 7" ,
+ # and "expand -90" as "expand --tabs 90",
+ ['u1', '-3', {IN=>"a\tb\tc"}, {OUT=>"a b c"}],
+ ['u2', '-4 -9', {IN=>"a\tb\tc"}, {OUT=>"a b c"}],
+ ['u3', '-11', {IN=>"a\tb\tc"}, {OUT=>"a b c"}],
+ # Test all digits (for full code coverage)
+ ['u4', '-2 -6', {IN=>"a\tb\tc"}, {OUT=>"a b c"}],
+ ['u5', '-7', {IN=>"a\tb"}, {OUT=>"a b"}],
+ ['u6', '-8', {IN=>"a\tb"}, {OUT=>"a b"}],
+ # This syntax is handled internally as "-3, -9"
+ ['u7', '-3,9', {IN=>"a\tb\tc"}, {OUT=>"a b c"}],
+
+ # Multiple non-empty files
+ ['f1', '--tabs=4',
+ {IN=>{"in1" => "a\tb\n"}}, {IN=>{"in2" => "c\td\n"}},
+ {OUT=>"a b\nc d\n"}],
+ # Multiple files, first file is empty
+ ['f2', '--tabs=4',
+ {IN=>{"in1" => ""}}, {IN=>{"in2" => "c\td\n"}},
+ {OUT=>"c d\n"}],
+ # Multiple files, second file is empty
+ ['f3', '--tabs=4',
+ {IN=>{"in1" => "a\tb\n"}}, {IN=>{"in2" => ""}},
+ {OUT=>"a b\n"}],
+
+
+ # Test '\b' (back-space) - subtract one column.
+ #
+ # Note:
+ # In a terminal window, 'expand' will appear to erase the 'a' characters
+ # due to overwriting them with spaces:
+ #
+ # $ printf 'aaa\b\b\bc\td\n'
+ # caa d
+ # $ printf 'aaa\b\b\bc\td\n' | expand
+ # c d
+ #
+ # However the characters are all printed:
+ #
+ # $ printf 'aaa\b\b\bc\td\n' | expand | od -An -ta
+ # a a a bs bs bs c sp sp sp sp sp sp sp d nl
+ #
+ # If users ever report a problem with these tests and just
+ # copy&paste from the terminal, their report will be confusing
+ # (the 'a' will not appear).
+ #
+ # To see an example, enable the 'b-confusing' test, and examine the
+ # reported log:
+ #
+ # expand.pl: test b-confusing: stdout mismatch
+ # *** b-confusing.2 Fri Jun 24 15:43:21 2016
+ # --- b-confusing.O Fri Jun 24 15:43:21 2016
+ # ***************
+ # *** 1 ****
+ # ! c d
+ # --- 1 ----
+ # ! c d
+ #
+ # ['b-confusing','', {IN=>"aaa\b\b\bc\td\n"}, {OUT=>"c d\n"}],
+
+ ['b1','', {IN=>"aaa\b\b\bc\td\n"}, {OUT=>"aaa\b\b\bc d\n"}],
+
+ # \b as first character, when column is zero
+ ['b2','', {IN=>"\bc\td"}, {OUT=>"\bc d"}],
+
+ # Testing tab list adjusted due to backspaces
+ # ('b3' is the baseline without backspaces).
+ ['b3','--tabs 2,4,6,10',
+ {IN=>"1\t2\t3\t4\t5\n" .
+ "a\tb\tc\td\te\n"},
+ {OUT=>"1 2 3 4 5\n" .
+ "a b c d e\n"}],
+
+ # On screen this will appear the same as 'b3'
+ ['b4','--tabs 2,4,6,10',
+ {IN=>"1\t2\t3\t4\t5\n" .
+ "a\tbHELLO\b\b\b\b\b\tc\td\te\n"},
+ {OUT=>"1 2 3 4 5\n" .
+ "a bHELLO\b\b\b\b\b c d e\n"}],
+
+ # On screen on 'bHE' will appear (LLO overwritten by spaces),
+ # 'c' should align with 4, 'd' with 5:
+ # 1 2 3 4 5
+ # a bHE c d e
+ ['b5','--tabs 2,4,6,10',
+ {IN=>"1\t2\t3\t4\t5\n" .
+ "a\tbHELLO\b\b\b\tc\td\te\n"},
+ {OUT=>"1 2 3 4 5\n" .
+ "a bHELLO\b\b\b c d e\n"}],
+
+ # Test the trailing '/' feature which specifies the
+ # tab size to use after the last specified stop
+ ['trail1', '--tabs=1,/5', {IN=>"\ta\tb\tc"}, {OUT=>" a b c"}],
+ ['trail2', '--tabs=2,/5', {IN=>"\ta\tb\tc"}, {OUT=>" a b c"}],
+ ['trail3', '--tabs=1,2,/5', {IN=>"\ta\tb\tc"}, {OUT=>" a b c"}],
+ ['trail4', '--tabs=/5', {IN=>"\ta\tb"}, {OUT=>" a b"}],
+ ['trail5', '--tabs=//5', {IN=>"\ta\tb"}, {OUT=>" a b"}],
+ ['trail5a','--tabs=+/5', {IN=>"\ta\tb"}, {OUT=>" a b"}],
+ ['trail6', '--tabs=/,/5', {IN=>"\ta\tb"}, {OUT=>" a b"}],
+ ['trail7', '--tabs=,/5', {IN=>"\ta\tb"}, {OUT=>" a b"}],
+ ['trail8', '--tabs=1 -t/5', {IN=>"\ta\tb\tc"}, {OUT=>" a b c"}],
+ ['trail9', '--tab=1,2 -t/5',{IN=>"\ta\tb\tc"}, {OUT=>" a b c"}],
+
+ # Test incremental trailing '+' feature which specifies that
+ # tab stops should continue every increment
+ ['incre0', '--tab=1,+5', {IN=>"+\t\ta\tb"}, {OUT=>"+ a b"}],
+ ['incre1', '--tabs=1,+5', {IN=>"\ta\tb\tc"}, {OUT=>" a b c"}],
+ ['incre2', '--tabs=2,+5', {IN=>"\ta\tb\tc"}, {OUT=>" a b c"}],
+ ['incre3', '--tabs=1,2,+5', {IN=>"\ta\tb\tc"}, {OUT=>" a b c"}],
+ ['incre4', '--tabs=+5', {IN=>"\ta\tb"}, {OUT=>" a b"}],
+ ['incre5', '--tabs=++5', {IN=>"\ta\tb"}, {OUT=>" a b"}],
+ ['incre5a','--tabs=/+5', {IN=>"\ta\tb"}, {OUT=>" a b"}],
+ ['incre6', '--tabs=+,+5', {IN=>"\ta\tb"}, {OUT=>" a b"}],
+ ['incre7', '--tabs=,+5', {IN=>"\ta\tb"}, {OUT=>" a b"}],
+ ['incre8', '--tabs=1 -t+5', {IN=>"\ta\tb\tc"}, {OUT=>" a b c"}],
+ ['incre9', '--tab=1,2 -t+5',{IN=>"\ta\tb\tc"}, {OUT=>" a b c"}],
+
+
+ # Test errors
+ ['e1', '--tabs="a"', {IN=>''}, {OUT=>''}, {EXIT=>1},
+ {ERR => "$prog: tab size contains invalid character(s): 'a'\n"}],
+ ['e2', "-t $UINTMAX_OFLOW", {IN=>''}, {OUT=>''}, {EXIT=>1},
+ {ERR => "$prog: tab stop is too large '$UINTMAX_OFLOW'\n"}],
+ ['e3', '--tabs=0', {IN=>''}, {OUT=>''}, {EXIT=>1},
+ {ERR => "$prog: tab size cannot be 0\n"}],
+ ['e4', '--tabs=3,3', {IN=>''}, {OUT=>''}, {EXIT=>1},
+ {ERR => "$prog: tab sizes must be ascending\n"}],
+ ['e5', '--tabs=/3,6,8', {IN=>''}, {OUT=>''}, {EXIT=>1},
+ {ERR => "$prog: '/' specifier only allowed with the last value\n"}],
+ ['e6', '-t/3 -t/6', {IN=>''}, {OUT=>''}, {EXIT=>1},
+ {ERR => "$prog: '/' specifier only allowed with the last value\n"}],
+ ['e7', '--tabs=3/', {IN=>''}, {OUT=>''}, {EXIT=>1},
+ {ERR => "$prog: '/' specifier not at start of number: '/'\n"}],
+ );
+
+my $save_temps = $ENV{DEBUG};
+my $verbose = $ENV{VERBOSE};
+
+my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;
diff --git a/tests/misc/false-status.sh b/tests/misc/false-status.sh
new file mode 100755
index 0000000..7c4f8e2
--- /dev/null
+++ b/tests/misc/false-status.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+# ensure that false exits nonzero even with --help or --version
+# and ensure that true exits nonzero when it can't write --help or --version
+
+# Copyright (C) 2003-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ false true
+
+returns_ 1 env false --version > /dev/null || fail=1
+returns_ 1 env false --help > /dev/null || fail=1
+
+if test -w /dev/full && test -c /dev/full; then
+ returns_ 1 env true --version > /dev/full || fail=1
+ returns_ 1 env true --help > /dev/full || fail=1
+fi
+
+Exit $fail
diff --git a/tests/misc/fold.pl b/tests/misc/fold.pl
new file mode 100755
index 0000000..a94072f
--- /dev/null
+++ b/tests/misc/fold.pl
@@ -0,0 +1,39 @@
+#!/usr/bin/perl
+# Exercise fold.
+
+# Copyright (C) 2003-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use strict;
+
+(my $program_name = $0) =~ s|.*/||;
+
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+my @Tests =
+ (
+ ['s1', '-w2 -s', {IN=>"a\t"}, {OUT=>"a\n\t"}],
+ ['s2', '-w4 -s', {IN=>"abcdef d\n"}, {OUT=>"abcd\nef d\n"}],
+ ['s3', '-w4 -s', {IN=>"a cd fgh\n"}, {OUT=>"a \ncd \nfgh\n"}],
+ ['s4', '-w4 -s', {IN=>"abc ef\n"}, {OUT=>"abc \nef\n"}],
+ );
+
+my $save_temps = $ENV{DEBUG};
+my $verbose = $ENV{VERBOSE};
+
+my $prog = 'fold';
+my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;
diff --git a/tests/misc/invalid-opt.pl b/tests/misc/invalid-opt.pl
new file mode 100755
index 0000000..872cbf8
--- /dev/null
+++ b/tests/misc/invalid-opt.pl
@@ -0,0 +1,103 @@
+#!/usr/bin/perl
+# exercise the 'invalid option' handling code in each program
+
+# Copyright (C) 2008-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+require 5.003;
+use strict;
+
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+my %exit_status =
+ (
+ ls => 2,
+ dir => 2,
+ vdir => 2,
+ test => 2,
+ chroot => 125,
+ echo => 0,
+ env => 125,
+ expr => 0,
+ nice => 125,
+ nohup => 125,
+ runcon => 125,
+ sort => 2,
+ stdbuf => 125,
+ test => 0,
+ timeout => 125,
+ true => 0,
+ tty => 2,
+ printf => 0,
+ printenv => 2,
+ );
+
+my %expected_out =
+ (
+ echo => "-/\n",
+ expr => "-/\n",
+ printf => "-/",
+ );
+
+my %expected_err =
+ (
+ false => '',
+ stty => '',
+ );
+
+my $fail = 0;
+my @built_programs = split ' ', $ENV{built_programs};
+foreach my $prog (@built_programs)
+ {
+ $prog eq '['
+ and next;
+
+ my $try = "Try '$prog --help' for more information.\n";
+ my $x = $exit_status{$prog};
+ defined $x
+ or $x = 1;
+
+ my $out = $expected_out{$prog};
+ defined $out
+ or $out = '';
+
+ my $err = $expected_err{$prog};
+ defined $err
+ or $err = $x == 0 ? '' : "$prog: invalid option -- /\n$try";
+
+ # Accommodate different syntax in glibc's getopt
+ # diagnostics by filtering out single quotes.
+ # Also accommodate BSD getopt.
+ my $err_subst = "s,'/',/,; s,unknown,invalid,";
+
+ # Depending on how this script is run, stty emits different
+ # diagnostics. Don't bother checking them.
+ $prog eq 'stty'
+ and $err_subst = 's/(.|\n)*//ms';
+
+ my @Tests = (["$prog-invalid-opt", '-/', {OUT=>$out},
+ {ERR_SUBST => $err_subst},
+ {EXIT=>$x}, {ERR=>$err}]);
+
+ my $save_temps = $ENV{DEBUG};
+ my $verbose = $ENV{VERBOSE};
+
+ my $f = run_tests ($prog, \$prog, \@Tests, $save_temps, $verbose);
+ $f
+ and $fail = 1;
+ }
+
+exit $fail;
diff --git a/tests/misc/join.pl b/tests/misc/join.pl
new file mode 100755
index 0000000..2ca8567
--- /dev/null
+++ b/tests/misc/join.pl
@@ -0,0 +1,342 @@
+#!/usr/bin/perl
+# Test join.
+
+# Copyright (C) 2008-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use strict;
+
+my $limits = getlimits ();
+
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+my $prog = 'join';
+
+my $delim = chr 0247;
+sub t_subst ($)
+{
+ (my $s = $_[0]) =~ s/:/$delim/g;
+ return $s;
+}
+
+my @tv = (
+# test name
+# flags file-1 file-2 expected output expected return code
+#
+['1a', '-a1', ["a 1\n", "b\n"], "a 1\n", 0],
+['1b', '-a2', ["a 1\n", "b\n"], "b\n", 0], # Got "\n"
+['1c', '-a1 -a2', ["a 1\n", "b\n"], "a 1\nb\n", 0], # Got "a 1\n\n"
+['1d', '-a1', ["a 1\nb\n", "b\n"], "a 1\nb\n", 0],
+['1e', '-a2', ["a 1\nb\n", "b\n"], "b\n", 0],
+['1f', '-a2', ["b\n", "a\nb\n"], "a\nb\n", 0],
+
+['2a', '-a1 -e .', ["a\nb\nc\n", "a x y\nb\nc\n"], "a x y\nb\nc\n", 0],
+['2b', '-a1 -e . -o 2.1,2.2,2.3', ["a\nb\nc\n", "a x y\nb\nc\n"],
+ "a x y\nb . .\nc . .\n", 0],
+['2c', '-a1 -e . -o 2.1,2.2,2.3', ["a\nb\nc\nd\n", "a x y\nb\nc\n"],
+ "a x y\nb . .\nc . .\n. . .\n", 0],
+
+['3a', '-t:', ["a:1\nb:1\n", "a:2:\nb:2:\n"], "a:1:2:\nb:1:2:\n", 0],
+
+# operate on whole line (as sort does by default)
+['3b', '-t ""', ["a 1\nb 1\n", "a 1\nb 2\n"], "a 1\n", 0],
+# use NUL as the field delimiter
+['3c', '-t "\\0"', ["a\0a\n", "a\0b\n"], "a\0a\0b\n", 0],
+
+# Just like -a1 and -a2 when there are no pairable lines
+['4a', '-v 1', ["a 1\n", "b\n"], "a 1\n", 0],
+['4b', '-v 2', ["a 1\n", "b\n"], "b\n", 0],
+
+['4c', '-v 1', ["a 1\nb\n", "b\n"], "a 1\n", 0],
+['4d', '-v 2', ["a 1\nb\n", "b\n"], "", 0],
+['4e', '-v 2', ["b\n", "a 1\nb\n"], "a 1\n", 0],
+['5a', '-a1 -e - -o 1.1,2.2',
+ ["a 1\nb 2\n", "a 11\nb\n"], "a 11\nb -\n", 0],
+['5b', '-a1 -e - -o 1.1,2.2',
+ ["apr 15\naug 20\ndec 18\nfeb 05\n", "apr 06\naug 14\ndate\nfeb 15"],
+ "apr 06\naug 14\ndec -\nfeb 15\n", 0],
+['5c', '-a1 -e - -o 1.1,2.2',
+ ["aug 20\ndec 18\n", "aug 14\ndate\nfeb 15"],
+ "aug 14\ndec -\n", 0],
+['5d', '-a1 -e - -o 1.1,2.2',
+ ["dec 18\n", ""], "dec -\n", 0],
+['5e', '-a2 -e - -o 1.1,2.2',
+ ["apr 15\naug 20\ndec 18\nfeb 05\n", "apr 06\naug 14\ndate\nfeb 15\n"],
+ "apr 06\naug 14\n- -\nfeb 15\n", 0],
+['5f', '-a2 -e - -o 2.2,1.1',
+ ["apr 15\naug 20\ndec 18\nfeb 05\n", "apr 06\naug 14\ndate\nfeb 15\n"],
+ "06 apr\n14 aug\n- -\n15 feb\n", 0],
+['5g', '-a1 -e - -o 2.2,1.1',
+ ["apr 15\naug 20\ndec 18\nfeb 05\n", "apr 06\naug 14\ndate\nfeb 15\n"],
+ "06 apr\n14 aug\n- dec\n15 feb\n", 0],
+
+['5h', '-a1 -e - -o 2.2,1.1',
+ ["apr 15\naug 20\ndec 18\nfeb 05\n", "apr 06\naug 14\ndate\n"],
+ "06 apr\n14 aug\n- dec\n- feb\n", 0],
+['5i', '-a1 -e - -o 1.1,2.2',
+ ["apr 15\naug 20\ndec 18\nfeb 05\n", "apr 06\naug 14\ndate\n"],
+ "apr 06\naug 14\ndec -\nfeb -\n", 0],
+
+['5j', '-a2 -e - -o 2.2,1.1',
+ ["apr 15\naug 20\ndec 18\nfeb 05\n", "apr 06\naug 14\ndate\n"],
+ "06 apr\n14 aug\n- -\n", 0],
+['5k', '-a2 -e - -o 2.2,1.1',
+ ["apr 15\naug 20\ndec 18\nfeb 05\n", "apr 06\naug 14\ndate\n"],
+ "06 apr\n14 aug\n- -\n", 0],
+
+['5l', '-a1 -e - -o 2.2,1.1',
+ ["apr 15\naug 20\ndec 18\n", "apr 06\naug 14\ndate\nfeb 15\n"],
+ "06 apr\n14 aug\n- dec\n", 0],
+['5m', '-a2 -e - -o 2.2,1.1',
+ ["apr 15\naug 20\ndec 18\n", "apr 06\naug 14\ndate\nfeb 15\n"],
+ "06 apr\n14 aug\n- -\n15 -\n", 0],
+
+['6a', '-e -',
+ ["a 1\nb 2\nd 4\n", "a 21\nb 22\nc 23\nf 26\n"],
+ "a 1 21\nb 2 22\n", 0],
+['6b', '-a1 -e -',
+ ["a 1\nb 2\nd 4\n", "a 21\nb 22\nc 23\nf 26\n"],
+ "a 1 21\nb 2 22\nd 4\n", 0],
+['6c', '-a1 -e -',
+ ["a 21\nb 22\nc 23\nf 26\n", "a 1\nb 2\nd 4\n"],
+ "a 21 1\nb 22 2\nc 23\nf 26\n", 0],
+
+['7a', '-a1 -e . -o 2.7',
+ ["a\nb\nc\n", "a x y\nb\nc\n"], ".\n.\n.\n", 0],
+
+['8a', '-a1 -e . -o 0,1.2',
+ ["a\nb\nc\nd G\n", "a x y\nb\nc\ne\n"],
+ "a .\nb .\nc .\nd G\n", 0],
+['8b', '-a1 -a2 -e . -o 0,1.2',
+ ["a\nb\nc\nd G\n", "a x y\nb\nc\ne\n"],
+ "a .\nb .\nc .\nd G\ne .\n", 0],
+
+# From David Dyck
+['9a', '', [" a 1\n b 2\n", " a Y\n b Z\n"], "a 1 Y\nb 2 Z\n", 0],
+
+# -o 'auto'
+['10a', '-a1 -a2 -e . -o auto',
+ ["a 1 2\nb 1\nd 1 2\n", "a 3 4\nb 3 4\nc 3 4\n"],
+ "a 1 2 3 4\nb 1 . 3 4\nc . . 3 4\nd 1 2 . .\n", 0],
+['10b', '-a1 -a2 -j3 -e . -o auto',
+ ["a 1 2\nb 1\nd 1 2\n", "a 3 4\nb 3 4\nc 3 4\n"],
+ "2 a 1 . .\n. b 1 . .\n2 d 1 . .\n4 . . a 3\n4 . . b 3\n4 . . c 3\n"],
+['10c', '-a1 -1 1 -2 4 -e. -o auto',
+ ["a 1 2\nb 1\nd 1 2\n", "a 3 4\nb 3 4\nc 3 4\n"],
+ "a 1 2 . . .\nb 1 . . . .\nd 1 2 . . .\n"],
+['10d', '-a2 -1 1 -2 4 -e. -o auto',
+ ["a 1 2\nb 1\nd 1 2\n", "a 3 4\nb 3 4\nc 3 4\n"],
+ ". . . a 3 4\n. . . b 3 4\n. . . c 3 4\n"],
+['10e', '-o auto',
+ ["a 1 2\nb 1 2 discard\n", "a 3 4\nb 3 4 discard\n"],
+ "a 1 2 3 4\nb 1 2 3 4\n"],
+['10f', '-t, -o auto',
+ ["a,1,,2\nb,1,2\n", "a,3,4\nb,3,4\n"],
+ "a,1,,2,3,4\nb,1,2,,3,4\n"],
+
+# For -v2, print the match field correctly with the default output format,
+# when that match field is different between file 1 and file 2. Fixed in 8.10
+['v2-order', '-v2 -2 2', ["", "2 1\n"], "1 2\n", 0],
+
+# From Tim Smithers: fixed in 1.22l
+['trailing-sp', '-t: -1 1 -2 1', ["a:x \n", "a:y \n"], "a:x :y \n", 0],
+
+# From Paul Eggert: fixed in 1.22n
+['sp-vs-blank', '', ["\f 1\n", "\f 2\n"], "\f 1 2\n", 0],
+
+# From Paul Eggert: fixed in 1.22n (this would fail on Solaris7,
+# with LC_ALL set to en_US).
+# Unfortunately, that Solaris7's en_US locale folds case (making
+# the first input file sorted) is not portable, so this test would
+# fail on e.g. Linux systems, because the input to join isn't sorted.
+# ['lc-collate', '', ["a 1a\nB 1B\n", "B 2B\n"], "B 1B 2B\n", 0],
+
+# Based on a report from Antonio Rendas. Fixed in 2.0.9.
+['8-bit-t', t_subst "-t:",
+ [t_subst "a:1\nb:1\n", t_subst "a:2:\nb:2:\n"],
+ t_subst "a:1:2:\nb:1:2:\n", 0],
+
+# fields > SIZE_MAX are silently interpreted as SIZE_MAX
+['bigfield1', "-1 $limits->{UINTMAX_OFLOW} -2 2",
+ ["a\n", "b\n"], " a b\n", 0],
+['bigfield2', "-1 $limits->{SIZE_OFLOW} -2 2",
+ ["a\n", "b\n"], " a b\n", 0],
+
+# FIXME: change this to ensure the diagnostic makes sense
+['invalid-j', '-j x', ["", ""], "", 1,
+ "$prog: invalid field number: 'x'\n"],
+
+# With ordering check, inputs in order
+['chkodr-1', '--check-order',
+ [" a 1\n b 2\n", " a Y\n b Z\n"], "a 1 Y\nb 2 Z\n", 0],
+
+# Without check, inputs in order
+['chkodr-2', '--nocheck-order',
+ [" a 1\n b 2\n", " a Y\n b Z\n"], "a 1 Y\nb 2 Z\n", 0],
+
+# Without check, both inputs out of order (in fact, in reverse order)
+# but all pairable. Support for this is a GNU extension.
+['chkodr-3', '--nocheck-order',
+ [" b 1\n a 2\n", " b Y\n a Z\n"], "b 1 Y\na 2 Z\n", 0],
+
+# The extension should work without --nocheck-order, since that is the
+# default.
+['chkodr-4', '',
+ [" b 1\n a 2\n", " b Y\n a Z\n"], "b 1 Y\na 2 Z\n", 0],
+
+# With check, both inputs out of order (in fact, in reverse order)
+['chkodr-5', '--check-order',
+ [" b 1\n a 2\n", " b Y\n a Z\n"], "", 1,
+ "$prog: chkodr-5.1:2: is not sorted: a 2\n"],
+
+# Similar, but with only file 2 not sorted.
+['chkodr-5b', '--check-order',
+ [" a 2\n b 1\n", " b Y\n a Z\n"], "", 1,
+ "$prog: chkodr-5b.2:2: is not sorted: a Z\n"],
+
+# Similar, but with the offending line having length 0 (excluding newline).
+['chkodr-5c', '--check-order',
+ [" a 2\n b 1\n", " b Y\n\n"], "", 1,
+ "$prog: chkodr-5c.2:2: is not sorted: \n"],
+
+# Similar, but elicit a warning for each input file (without --check-order).
+['chkodr-5d', '',
+ ["a\nx\n\n", "b\ny\n\n"], "", 1,
+ "$prog: chkodr-5d.1:3: is not sorted: \n" .
+ "$prog: chkodr-5d.2:3: is not sorted: \n" .
+ "$prog: input is not in sorted order\n"
+ ],
+
+# Similar, but make it so each offending line has no newline.
+['chkodr-5e', '',
+ ["a\nx\no", "b\ny\np"], "", 1,
+ "$prog: chkodr-5e.1:3: is not sorted: o\n" .
+ "$prog: chkodr-5e.2:3: is not sorted: p\n" .
+ "$prog: input is not in sorted order\n"
+ ],
+
+# Without order check, both inputs out of order and some lines
+# unpairable. This is NOT supported by the GNU extension. All that
+# we really care about for this test is that the return status is
+# zero, since that is the only way to actually verify that the
+# --nocheck-order option had any effect. We don't actually want to
+# guarantee that join produces this output on stdout.
+['chkodr-6', '--nocheck-order',
+ [" b 1\n a 2\n", " b Y\n c Z\n"], "b 1 Y\n", 0],
+
+# Before 6.10.143, this would mistakenly fail with the diagnostic:
+# join: File 1 is not in sorted order
+['chkodr-7', '-12', ["2 a\n1 b\n", "2 c\n1 d"], "", 0],
+
+# After 8.9, join doesn't report disorder by default
+# when comparing against an empty input file.
+['chkodr-8', '', ["2 a\n1 b\n", ""], "", 0],
+
+# Test '--header' feature
+['header-1', '--header',
+ [ "ID Name\n1 A\n2 B\n", "ID Color\n1 red\n"], "ID Name Color\n1 A red\n", 0],
+
+# '--header' with '--check-order' : The header line is out-of-order but the
+# actual data is in order. This join should succeed.
+['header-2', '--header --check-order',
+ ["ID Name\n1 A\n2 B\n", "ID Color\n2 green\n"],
+ "ID Name Color\n2 B green\n", 0],
+
+# '--header' with '--check-order' : The header line is out-of-order AND the
+# actual data out-of-order. This join should fail.
+['header-3', '--header --check-order',
+ ["ID Name\n2 B\n1 A\n", "ID Color\n2 blue\n"], "ID Name Color\n", 1,
+ "$prog: header-3.1:3: is not sorted: 1 A\n"],
+
+# '--header' with specific output format '-o'.
+# output header line should respect the requested format
+['header-4', '--header -o "0,1.3,2.2"',
+ ["ID Group Name\n1 Foo A\n2 Bar B\n", "ID Color\n2 blue\n"],
+ "ID Name Color\n2 B blue\n", 0],
+
+# '--header' always outputs headers from the first file
+# even if the headers from the second file don't match
+['header-5', '--header',
+ [ "ID1 Name\n1 A\n2 B\n", "ID2 Color\n1 red\n"],
+ "ID1 Name Color\n1 A red\n", 0],
+
+# '--header' doesn't check order of a header
+# even if there is no header in the second file
+['header-6', '--header -a1',
+ [ "ID1 Name\n1 A\n", ""],
+ "ID1 Name\n1 A\n", 0],
+
+# Zero-terminated lines
+['z1', '-z',
+ ["a\0c\0e\0", "a\0b\0c\0"], "a\0c\0", 0],
+
+# not zero-terminated, but related to the code change:
+# the old readlinebuffer() auto-added '\n' to the last line.
+# the new readlinebuffer_delim() does not.
+# Ensure it doesn't matter.
+['z2', '',
+ ["a\nc\ne\n", "a\nb\nc"], "a\nc\n", 0],
+['z3', '',
+ ["a\nc\ne", "a\nb\nc"], "a\nc\n", 0],
+# missing last NUL at the end of the last line (=end of file)
+['z4', '-z',
+ ["a\0c\0e", "a\0b\0c"], "a\0c\0", 0],
+# With -z, embedded newlines are treated as field separators.
+# Note '\n' are converted to ' ' in this case.
+['z5', '-z -a1 -a2',
+ ["a\n\n1\0c 3\0", "a 2\0b\n8\0c 9\0"], "a 1 2\0b 8\0c 3 9\0"],
+# One can avoid field processing like:
+['z6', '-z -t ""',
+ ["a\n1\n\0", "a\n1\n\0"], "a\n1\n\0"],
+
+);
+
+# Convert the above old-style test vectors to the newer
+# format used by Coreutils.pm.
+
+my @Tests;
+foreach my $t (@tv)
+ {
+ my ($test_name, $flags, $in, $exp, $ret, $err_msg) = @$t;
+ my $new_ent = [$test_name, $flags];
+ if (!ref $in)
+ {
+ push @$new_ent, {IN=>$in};
+ }
+ elsif (ref $in eq 'HASH')
+ {
+ # ignore
+ }
+ else
+ {
+ foreach my $e (@$in)
+ {
+ push @$new_ent, {IN=>$e};
+ }
+ }
+ push @$new_ent, {OUT=>$exp};
+ $ret
+ and push @$new_ent, {EXIT=>$ret}, {ERR=>$err_msg};
+ push @Tests, $new_ent;
+ }
+
+@Tests = triple_test \@Tests;
+
+my $save_temps = $ENV{DEBUG};
+my $verbose = $ENV{VERBOSE};
+
+my $fail = run_tests ($prog, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;
diff --git a/tests/misc/kill.sh b/tests/misc/kill.sh
new file mode 100755
index 0000000..34d6578
--- /dev/null
+++ b/tests/misc/kill.sh
@@ -0,0 +1,63 @@
+#!/bin/sh
+# Validate kill operation
+
+# Copyright (C) 2015-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ kill
+
+# params required
+returns_ 1 env kill || fail=1
+returns_ 1 env kill -TERM || fail=1
+
+# Invalid combinations
+returns_ 1 env kill -l -l || fail=1
+returns_ 1 env kill -l -t || fail=1
+returns_ 1 env kill -l -s 1 || fail=1
+returns_ 1 env kill -t -s 1 || fail=1
+
+# signal sending
+returns_ 1 env kill -0 no_pid || fail=1
+env kill -0 $$ || fail=1
+env kill -s0 $$ || fail=1
+env kill -n0 $$ || fail=1 # bash compat
+env kill -CONT $$ || fail=1
+env kill -Cont $$ || fail=1
+returns_ 1 env kill -cont $$ || fail=1
+env kill -0 -1 || fail=1 # to group
+
+# table listing
+env kill -l || fail=1
+env kill -t || fail=1
+env kill -L || fail=1 # bash compat
+env kill -t TERM HUP || fail=1
+
+# Verify (multi) name to signal number and vice versa
+SIGTERM=$(env kill -l HUP TERM | tail -n1) || fail=1
+test $(env kill -l "$SIGTERM") = TERM || fail=1
+
+# Verify we only consider the lower "signal" bits,
+# to support ksh which just adds 256 to the signal value
+STD_TERM_STATUS=$(expr "$SIGTERM" + 128)
+KSH_TERM_STATUS=$(expr "$SIGTERM" + 256)
+test $(env kill -l $STD_TERM_STATUS $KSH_TERM_STATUS | uniq) = TERM || fail=1
+
+# Verify invalid signal spec is diagnosed
+returns_ 1 env kill -l -1 || fail=1
+returns_ 1 env kill -l -1 0 || fail=1
+returns_ 1 env kill -l INVALID TERM || fail=1
+
+Exit $fail
diff --git a/tests/misc/mknod.sh b/tests/misc/mknod.sh
new file mode 100755
index 0000000..de8d4f6
--- /dev/null
+++ b/tests/misc/mknod.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+# Ensure that mknod, mkfifo, mkdir -m MODE work with a restrictive umask
+
+# Copyright (C) 2004-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ mknod
+
+# Ensure fifos are supported
+mkfifo_or_skip_ fifo
+
+umask 777
+
+mknod -m 734 f1 p || fail=1
+mode=$(ls -dgo f1|cut -b-10)
+test $mode = prwx-wxr-- || fail=1
+
+mkfifo -m 734 f2 || fail=1
+mode=$(ls -dgo f2|cut -b-10)
+test $mode = prwx-wxr-- || fail=1
+
+mkdir -m 734 f3 || fail=1
+mode=$(ls -dgo f3|cut -b-10)
+test $mode = drwx-wxr-- || test $mode = drwx-wsr-- || fail=1
+
+Exit $fail
diff --git a/tests/misc/mktemp.pl b/tests/misc/mktemp.pl
new file mode 100755
index 0000000..01dcf61
--- /dev/null
+++ b/tests/misc/mktemp.pl
@@ -0,0 +1,206 @@
+#!/usr/bin/perl
+# Test "mktemp".
+
+# Copyright (C) 2007-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use strict;
+
+(my $ME = $0) =~ s|.*/||;
+
+sub check_tmp($$)
+{
+ my ($file, $file_or_dir) = @_;
+
+ my (undef, undef, $mode, undef) = stat $file
+ or die "$ME: failed to stat $file: $!\n";
+ my $required_mode;
+ if ($file_or_dir eq 'D') {
+ -d $file or die "$ME: $file isn't a directory\n";
+ -x $file or die "$ME: $file isn't owner-searchable\n";
+ $required_mode = 0700;
+ } elsif ($file_or_dir eq 'F') {
+ -f $file or die "$ME: $file isn't a regular file\n";
+ $required_mode = 0600;
+ }
+ -r $file or die "$ME: $file isn't owner-readable\n";
+ -w $file or die "$ME: $file isn't owner-writable\n";
+ ($mode & 0777) == $required_mode
+ or die "$ME: $file doesn't have required permissions\n";
+
+ $file_or_dir eq 'D'
+ and do { rmdir $file or die "$ME: failed to rmdir $file: $!\n" };
+ $file_or_dir eq 'F'
+ and do { unlink $file or die "$ME: failed to unlink $file: $!\n" };
+}
+
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+my $prog = 'mktemp';
+my $bad_dir = 'no/such/dir';
+
+my @Tests =
+ (
+ # test-name, [option, option, ...] {OUT=>"expected-output"}
+ #
+ ['too-many', '-q a b',
+ {ERR=>"$prog: too many templates\n"
+ . "Try '$prog --help' for more information.\n"}, {EXIT => 1} ],
+
+ ['too-few-x', '-q foo.XX', {EXIT => 1},
+ {ERR=>"$prog: too few X's in template 'foo.XX'\n"}],
+
+ ['1f', 'bar.XXXX', {OUT => "bar.ZZZZ\n"},
+ {OUT_SUBST => 's,\.....$,.ZZZZ,'},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ check_tmp $f, 'F'; }}
+ ],
+
+ ['2f', '-- -XXXX', {OUT => "-ZZZZ\n"},
+ {OUT_SUBST => 's,-....$,-ZZZZ,'},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ check_tmp $f, 'F'; }}
+ ],
+
+ # Create a temporary directory.
+ ['1d', '-d f.XXXX', {OUT => "f.ZZZZ\n"},
+ {OUT_SUBST => 's,\.....$,.ZZZZ,'},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ check_tmp $f, 'D'; }}
+ ],
+
+ # Use a template consisting solely of X's
+ ['1d-allX', '-d XXXX', {OUT => "ZZZZ\n"},
+ {PRE => sub {mkdir 'XXXX',0755 or die "XXXX: $!\n"}},
+ {OUT_SUBST => 's,^....$,ZZZZ,'},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ check_tmp $f, 'D'; rmdir 'XXXX' or die "rmdir XXXX: $!\n"; }}
+ ],
+
+ # Test -u
+ ['uf', '-u f.XXXX', {OUT => "f.ZZZZ\n"},
+ {OUT_SUBST => 's,\.....$,.ZZZZ,'},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ -e $f and die "dry-run created file"; }}],
+ ['ud', '-d --dry-run d.XXXX', {OUT => "d.ZZZZ\n"},
+ {OUT_SUBST => 's,\.....$,.ZZZZ,'},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ -e $f and die "dry-run created directory"; }}],
+
+ # Test bad templates
+ ['invalid-tl', '-t a/bXXXX',
+ {ERR=>"$prog: invalid template, 'a/bXXXX', "
+ . "contains directory separator\n"}, {EXIT => 1} ],
+
+ ['invalid-t2', '--tmpdir=a /bXXXX',
+ {ERR=>"$prog: invalid template, '/bXXXX'; "
+ . "with --tmpdir, it may not be absolute\n"}, {EXIT => 1} ],
+
+ # Suffix after X.
+ ['suffix1f', 'aXXXXb', {OUT=>"aZZZZb\n"},
+ {OUT_SUBST=>'s,a....b,aZZZZb,'},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ check_tmp $f, 'F'; }}],
+ ['suffix1d', '-d aXXXXb', {OUT=>"aZZZZb\n"},
+ {OUT_SUBST=>'s,a....b,aZZZZb,'},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ check_tmp $f, 'D'; }}],
+ ['suffix1u', '-u aXXXXb', {OUT=>"aZZZZb\n"},
+ {OUT_SUBST=>'s,a....b,aZZZZb,'},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ -e $f and die "dry-run created file"; }}],
+
+ ['suffix2f', 'aXXXXaaXXXXa', {OUT=>"aXXXXaaZZZZa\n"},
+ {OUT_SUBST=>'s,a....a$,aZZZZa,'},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ check_tmp $f, 'F'; }}],
+ ['suffix2d', '-d --suffix= aXXXXaaXXXX', {OUT=>"aXXXXaaZZZZ\n"},
+ {OUT_SUBST=>'s,a....$,aZZZZ,'},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ check_tmp $f, 'D'; }}],
+
+ ['suffix3f', '--suffix=b aXXXX', {OUT=>"aZZZZb\n"},
+ {OUT_SUBST=>'s,a....b,aZZZZb,'},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ check_tmp $f, 'F'; }}],
+
+ ['suffix4f', '--suffix=X aXXXX', {OUT=>"aZZZZX\n"},
+ {OUT_SUBST=>'s,^a....,aZZZZ,'},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ check_tmp $f, 'F'; }}],
+
+ ['suffix5f', '--suffix /b aXXXX', {EXIT=>1},
+ {ERR=>"$prog: invalid suffix '/b', contains directory separator\n"}],
+
+ ['suffix6f', 'aXXXX/b', {EXIT=>1},
+ {ERR=>"$prog: invalid suffix '/b', contains directory separator\n"}],
+
+ ['suffix7f', '--suffix= aXXXXb', {EXIT=>1},
+ {ERR=>"$prog: with --suffix, template 'aXXXXb' must end in X\n"}],
+ ['suffix7d', '-d --suffix=aXXXXb ""', {EXIT=>1},
+ {ERR=>"$prog: with --suffix, template '' must end in X\n"}],
+
+ ['suffix8f', 'aXXXX --suffix=b', {OUT=>"aZZZZb\n"},
+ {OUT_SUBST=>'s,^a....,aZZZZ,'},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ check_tmp $f, 'F'; }}],
+
+ ['suffix9f', 'aXXXX --suffix=b', {EXIT=>1},
+ {ENV=>"POSIXLY_CORRECT=1"},
+ {ERR=>"$prog: too many templates\n"
+ . "Try '$prog --help' for more information.\n"}],
+
+ ['suffix10f', 'aXXb', {EXIT => 1},
+ {ERR=>"$prog: too few X's in template 'aXXb'\n"}],
+ ['suffix10d', '-d --suffix=X aXX', {EXIT => 1},
+ {ERR=>"$prog: too few X's in template 'aXXX'\n"}],
+
+ ['suffix11f', '--suffix=.txt', {OUT=>"./tmp.ZZZZZZZZZZ.txt\n"},
+ {ENV=>"TMPDIR=."},
+ {OUT_SUBST=>'s,\..{10}\.,.ZZZZZZZZZZ.,'},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ check_tmp $f, 'F'; }}],
+
+
+ # Test template with subdirectory
+ ['tmp-w-slash', '--tmpdir=. a/bXXXX',
+ {PRE => sub {mkdir 'a',0755 or die "a: $!\n"}},
+ {OUT_SUBST => 's,b....$,bZZZZ,'},
+ {OUT => "./a/bZZZZ\n"},
+ {POST => sub { my ($f) = @_; defined $f or return; chomp $f;
+ check_tmp $f, 'F'; unlink $f; rmdir 'a' or die "rmdir a: $!\n" }}
+ ],
+
+ ['priority-t-tmpdir', "-t -p $bad_dir foo.XXX",
+ {ENV=>"TMPDIR=."},
+ {OUT_SUBST => 's,....$,.ZZZ,'},
+ {OUT => "./foo.ZZZ\n"},
+ ],
+
+ ['pipe-bad-tmpdir',
+ {ENV => "TMPDIR=$bad_dir"},
+ {ERR_SUBST => "s,($bad_dir/)[^']+': .*,\$1...,"},
+ {ERR => "$prog: failed to create file via template '$bad_dir/...\n"},
+ {EXIT => 1}],
+ ['pipe-bad-tmpdir-u', '-u', {OUT => "$bad_dir/tmp.ZZZZZZZZZZ\n"},
+ {ENV => "TMPDIR=$bad_dir"},
+ {OUT_SUBST => 's,\..{10}$,.ZZZZZZZZZZ,'}],
+ );
+
+my $save_temps = $ENV{DEBUG};
+my $verbose = $ENV{VERBOSE};
+
+my $fail = run_tests ($ME, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;
diff --git a/tests/misc/nl.sh b/tests/misc/nl.sh
new file mode 100755
index 0000000..a1e9480
--- /dev/null
+++ b/tests/misc/nl.sh
@@ -0,0 +1,113 @@
+#!/bin/sh
+# exercise nl functionality
+
+# Copyright (C) 2002-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ nl
+getlimits_
+
+echo a | nl > out || fail=1
+echo b | nl -s%n >> out || fail=1
+echo c | nl -n ln >> out || fail=1
+echo d | nl -n rn >> out || fail=1
+echo e | nl -n rz >> out || fail=1
+echo === >> out
+printf 'a\n\n' | nl > t || fail=1; cat -A t >> out
+cat <<\EOF > exp
+ 1 a
+ 1%nb
+1 c
+ 1 d
+000001 e
+===
+ 1^Ia$
+ $
+EOF
+compare exp out || fail=1
+
+# Ensure numbering reset at each delimiter.
+# coreutils <= v8.25 only reset at a page header.
+printf '%s\n' '\:\:\:' a '\:\:' b '\:' c > in.txt || framework_failure_
+nl -ha -fa in.txt > out.tmp || fail=1
+nl -p -ha -fa in.txt >> out.tmp || fail=1
+sed '/^$/d' < out.tmp > out || framework_failure_
+cat <<\EOF > exp
+ 1 a
+ 1 b
+ 1 c
+ 1 a
+ 2 b
+ 3 c
+EOF
+compare exp out || fail=1
+
+# Ensure we only indicate overflow when needing to output overflowed numbers
+returns_ 1 nl -v$INTMAX_OFLOW /dev/null || fail=1
+printf '%s\n' a \\:\\: b > in.txt || framework_failure_
+nl -v$INTMAX_MAX in.txt > out || fail=1
+cat <<EOF > exp
+$INTMAX_MAX a
+
+$INTMAX_MAX b
+EOF
+compare exp out || fail=1
+returns_ 1 nl -p -v$INTMAX_MAX in.txt > out || fail=1
+
+# Test negative iteration
+returns_ 1 nl -i$INTMAX_UFLOW /dev/null || fail=1
+printf '%s\n' a b > in.txt || framework_failure_
+nl -v$INTMAX_MAX -i$INTMAX_MIN in.txt > out || fail=1
+cat <<EOF > exp
+$INTMAX_MAX a
+ -1 b
+EOF
+compare exp out || fail=1
+printf '%s\n' a b c > in.txt || framework_failure_
+returns_ 1 nl -v$INTMAX_MAX -i$INTMAX_MIN in.txt > out || fail=1
+
+# Test GNU extension to --section-delimiter, of disabling section matching
+printf '%s\n' a '\:\:' c > in.txt || framework_failure_
+nl -d '' in.txt > out || fail=1
+cat <<\EOF > exp
+ 1 a
+ 2 \:\:
+ 3 c
+EOF
+compare exp out || fail=1
+
+# Test GNU extension to --section-delimiter, of supporting strings longer than 2
+printf '%s\n' a foofoo c > in.txt || framework_failure_
+nl -d 'foo' in.txt > out || fail=1
+cat <<EOF > exp
+ 1 a
+
+ 1 c
+EOF
+compare exp out || fail=1
+
+# Ensure single char delimiters assume a following ':' character (as per POSIX)
+# coreutils <= v8.32 didn't match single char delimiters at all
+printf '%s\n' a x:x: c > in.txt || framework_failure_
+nl -d 'x' in.txt > out || fail=1
+cat <<EOF > exp
+ 1 a
+
+ 1 c
+EOF
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/misc/nohup.sh b/tests/misc/nohup.sh
new file mode 100755
index 0000000..3a598af
--- /dev/null
+++ b/tests/misc/nohup.sh
@@ -0,0 +1,125 @@
+#!/bin/sh
+# test nohup
+
+# Copyright (C) 2003-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ nohup
+
+
+nohup sh -c 'echo stdout; echo stderr 1>&2' 2>err || fail=1
+
+# Be careful. The results of the above nohup command
+# change depending on whether stdin and stdout are redirected.
+if test -t 1; then
+ test "$(cat nohup.out)" = stdout || fail=1
+ if test -t 0; then
+ echo 'nohup: ignoring input and appending output to 'nohup.out'\'
+ else
+ echo 'nohup: appending output to 'nohup.out'\'
+ fi >exp || framework_failure_
+else
+ # Here it should not even exist.
+ test -f nohup.out && fail=1
+ if test -t 0; then
+ echo 'nohup: ignoring input' >exp
+ else
+ rm -f exp
+ fi || framework_failure_
+fi
+echo 'stderr' >> exp || framework_failure_
+
+compare exp err || fail=1
+rm -f nohup.out err exp
+# ----------------------
+
+# Be careful. The results of the following nohup command
+# change depending on whether stderr is redirected.
+nohup sh -c 'echo stdout; echo stderr 1>&2' >out || fail=1
+if test -t 2; then
+ test "$(cat out|tr '\n' -)" = stdout-stderr- || fail=1
+else
+ test "$(cat out|tr '\n' -)" = stdout- || fail=1
+fi
+# It must *not* exist.
+test -f nohup.out && fail=1
+rm -f nohup.out err
+# ----------------------
+
+# Bug present through coreutils 8.0: failure to print advisory message
+# to stderr must be fatal. Requires stdout to be terminal.
+if test -w /dev/full && test -c /dev/full; then
+(
+ # POSIX shells immediately exit the subshell on exec error.
+ # So check we can write to /dev/tty before the exec, which
+ # isn't possible if we've no controlling tty for example.
+ test -c /dev/tty && >/dev/tty || exit 0
+
+ exec >/dev/tty
+ test -t 1 || exit 0
+ returns_ 125 nohup echo hi 2> /dev/full || fail=1
+ test -f nohup.out || fail=1
+ compare /dev/null nohup.out || fail=1
+ rm -f nohup.out
+ exit $fail
+) || fail=1
+fi
+
+nohup no-such-command 2> err
+errno=$?
+if test -t 1; then
+ test $errno = 127 || fail=1
+ # It must exist.
+ test -f nohup.out || fail=1
+ # It must be empty.
+ compare /dev/null nohup.out || fail=1
+fi
+
+cat <<\EOF > exp || framework_failure_
+nohup: appending output to 'nohup.out'
+nohup: cannot run command 'no-such-command': No such file or directory
+EOF
+# Disable these comparisons. Too much variation in 2nd line.
+# compare exp err || fail=1
+rm -f nohup.out err exp
+# ----------------------
+
+touch k; chmod 0 k
+nohup ./k 2> err
+errno=$?
+test $errno = 126 || fail=1
+if test -t 1; then
+ # It must exist.
+ test -f nohup.out || fail=1
+ # It must be empty.
+ compare /dev/null nohup.out || fail=1
+fi
+
+cat <<\EOF > exp || framework_failure_
+nohup: appending output to 'nohup.out'
+nohup: cannot run command './k': Permission denied
+EOF
+# Disable these comparisons. Too much variation in 2nd line.
+# compare exp err || fail=1
+
+# Make sure it fails with exit status of 125 when given too few arguments,
+# except that POSIX requires 127 in this case.
+returns_ 125 nohup >/dev/null 2>&1 || fail=1
+export POSIXLY_CORRECT=1
+returns_ 127 nohup >/dev/null 2>&1 || fail=1
+unset POSIXLY_CORRECT
+
+Exit $fail
diff --git a/tests/misc/numfmt.pl b/tests/misc/numfmt.pl
new file mode 100755
index 0000000..86fb78f
--- /dev/null
+++ b/tests/misc/numfmt.pl
@@ -0,0 +1,1125 @@
+#!/usr/bin/perl
+# Basic tests for "numfmt".
+
+# Copyright (C) 2012-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use strict;
+
+(my $program_name = $0) =~ s|.*/||;
+my $prog = 'numfmt';
+
+my $limits = getlimits ();
+
+# TODO: add localization tests with "grouping"
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+my $locale = $ENV{LOCALE_FR_UTF8};
+! defined $locale || $locale eq 'none'
+ and $locale = 'C';
+
+my $try = "Try '$prog --help' for more information.\n";
+
+my @Tests =
+ (
+ ['1', '1234', {OUT => "1234"}],
+ ['2', '--from=si 1K', {OUT => "1000"}],
+ ['3', '--from=iec 1K', {OUT => "1024"}],
+ ['4', '--from=auto 1K', {OUT => "1000"}],
+ ['5', '--from=auto 1Ki', {OUT => "1024"}],
+ ['5.1', '--from=iec-i 1Ki', {OUT => "1024"}],
+
+ ['6', {IN_PIPE => "1234\n"}, {OUT => "1234"}],
+ ['7', '--from=si', {IN_PIPE => "2K\n"}, {OUT => "2000"}],
+ ['7a', '--invalid=fail', {IN_PIPE => "no_NL"}, {OUT => "no_NL"},
+ {ERR => "$prog: invalid number: 'no_NL'\n"},
+ {EXIT => '2'}],
+
+ ['8', '--to=si 2000', {OUT => "2.0K"}],
+ ['9', '--to=si 2001', {OUT => "2.1K"}],
+ ['10', '--to=si 1999', {OUT => "2.0K"}],
+ ['11', '--to=si --round=down 2001', {OUT => "2.0K"}],
+ ['12', '--to=si --round=down 1999', {OUT => "1.9K"}],
+ ['13', '--to=si --round=up 1901', {OUT => "2.0K"}],
+ ['14', '--to=si --round=down 1901', {OUT => "1.9K"}],
+ ['15', '--to=si --round=nearest 1901', {OUT => "1.9K"}],
+ ['16', '--to=si --round=nearest 1945', {OUT => "1.9K"}],
+ ['17', '--to=si --round=nearest 1955', {OUT => "2.0K"}],
+
+ ['18', '--to=iec 2048', {OUT => "2.0K"}],
+ ['19', '--to=iec 2049', {OUT => "2.1K"}],
+ ['20', '--to=iec 2047', {OUT => "2.0K"}],
+ ['21', '--to=iec --round=down 2049', {OUT => "2.0K"}],
+ ['22', '--to=iec --round=down 2047', {OUT => "1.9K"}],
+ ['23', '--to=iec --round=up 2040', {OUT => "2.0K"}],
+ ['24', '--to=iec --round=down 2040', {OUT => "1.9K"}],
+ ['25', '--to=iec --round=nearest 1996', {OUT => "1.9K"}],
+ ['26', '--to=iec --round=nearest 1997', {OUT => "2.0K"}],
+ ['27', '--to=iec-i 2048', {OUT => "2.0Ki"}],
+
+ ['neg-1', '-- -1234', {OUT => "-1234"}],
+ ['neg-2', '--padding=5 -- -1234', {OUT => "-1234"}],
+ ['neg-3', '--padding=6 -- -1234', {OUT => " -1234"}],
+ ['neg-4', '--to=iec -- 9100 -9100', {OUT => "8.9K\n-8.9K"}],
+ ['neg-5', '-- -0.1', {OUT => "-0.1"}],
+ ['neg-6', '-- -0', {OUT => "0"}],
+ ['neg-7', '-- -0.-1',
+ {ERR => "$prog: invalid number: '-0.-1'\n"},
+ {EXIT => '2'}],
+
+ ['float-1', '1.1', {OUT => "1.1"}],
+ ['float-2', '1.22', {OUT => "1.22"}],
+ ['float-3', '1.22.',
+ {ERR => "$prog: invalid suffix in input: '1.22.'\n"},
+ {EXIT => '2'}],
+
+ ['unit-1', '--from-unit=512 4', {OUT => "2048"}],
+ ['unit-2', '--to-unit=512 2048', {OUT => "4"}],
+ ['unit-3', '--from-unit=512 --from=si 4M', {OUT => "2048000000"}],
+ ['unit-4', '--from-unit=512 --from=iec --to=iec 4M', {OUT => "2.0G"}],
+ ['unit-5', '--from-unit=AA --from=iec --to=iec 4M',
+ {ERR => "$prog: invalid unit size: 'AA'\n"},
+ {EXIT => '1'}],
+ ['unit-6', '--from-unit=54W --from=iec --to=iec 4M',
+ {ERR => "$prog: invalid unit size: '54W'\n"},
+ {EXIT => '1'}],
+ ['unit-7', '--from-unit=K 30', {OUT=>"30000"}],
+ ['unit-7.1', '--from-unit=Ki 30', {OUT=>"30720"}],
+ ['unit-7.2', '--from-unit=i 0',
+ {ERR => "$prog: invalid unit size: 'i'\n"},
+ {EXIT => '1'}],
+ ['unit-7.3', '--from-unit=1i 0',
+ {ERR => "$prog: invalid unit size: '1i'\n"},
+ {EXIT => '1'}],
+ ['unit-8', '--from-unit='.$limits->{UINTMAX_OFLOW}.' --to=iec 30',
+ {ERR => "$prog: invalid unit size: '$limits->{UINTMAX_OFLOW}'\n"},
+ {EXIT => '1'}],
+ ['unit-9', '--from-unit=0 1',
+ {ERR => "$prog: invalid unit size: '0'\n"},
+ {EXIT => '1'}],
+ ['unit-10', '--to-unit=0 1',
+ {ERR => "$prog: invalid unit size: '0'\n"},
+ {EXIT => '1'}],
+
+ # Test Suffix logic
+ ['suf-1', '4000', {OUT=>'4000'}],
+ ['suf-2', '4J',
+ {ERR => "$prog: invalid suffix in input: '4J'\n"},
+ {EXIT => '2'}],
+ ['suf-2.1', '4M',
+ {ERR => "$prog: rejecting suffix " .
+ "in input: '4M' (consider using --from)\n"},
+ {EXIT => '2'}],
+ ['suf-3', '--from=si 4M', {OUT=>'4000000'}],
+ ['suf-4', '--from=si 4J',
+ {ERR => "$prog: invalid suffix in input: '4J'\n"},
+ {EXIT => '2'}],
+ ['suf-5', '--from=si 4MJ',
+ {ERR => "$prog: invalid suffix in input '4MJ': 'J'\n"},
+ {EXIT => '2'}],
+
+ ['suf-6', '--from=iec 4M', {OUT=>'4194304'}],
+ ['suf-7', '--from=auto 4M', {OUT=>'4000000'}],
+ ['suf-8', '--from=auto 4Mi', {OUT=>'4194304'}],
+ ['suf-9', '--from=auto 4MiJ',
+ {ERR => "$prog: invalid suffix in input '4MiJ': 'J'\n"},
+ {EXIT => '2'}],
+ ['suf-10', '--from=auto 4JiJ',
+ {ERR => "$prog: invalid suffix in input: '4JiJ'\n"},
+ {EXIT => '2'}],
+
+ # characters after a white space are OK - printed as-is
+ ['suf-11', '"4 M"', {OUT=>'4 M'}],
+
+ # Custom suffix
+ ['suf-12', '--suffix=Foo 70Foo', {OUT=>'70Foo'}],
+ ['suf-13', '--suffix=Foo 70', {OUT=>'70Foo'}],
+ ['suf-14', '--suffix=Foo --from=si 70K', {OUT=>'70000Foo'}],
+ ['suf-15', '--suffix=Foo --from=si 70KFoo', {OUT=>'70000Foo'}],
+ ['suf-16', '--suffix=Foo --to=si 7000Foo', {OUT=>'7.0KFoo'}],
+ ['suf-17', '--suffix=Foo --to=si 7000Bar',
+ {ERR => "$prog: invalid suffix in input: '7000Bar'\n"},
+ {EXIT => '2'}],
+ ['suf-18', '--suffix=Foo --to=si 7000FooF',
+ {ERR => "$prog: invalid suffix in input: '7000FooF'\n"},
+ {EXIT => '2'}],
+ # space(s) between number and suffix. Note only field 1 is used
+ # by default so specify the NUL delimiter to consider the whole "line".
+ ['suf-19', "-d '' --from=si '4.0 K'", {OUT => "4000"}],
+
+ ## GROUPING
+
+ # "C" locale - no grouping (locale-specific tests, below)
+ ['grp-1', '--from=si --grouping 7M', {OUT=>'7000000'}],
+ ['grp-2', '--from=si --to=si --grouping 7M',
+ {ERR => "$prog: grouping cannot be combined with --to\n"},
+ {EXIT => '1'}],
+
+
+ ## Padding
+ ['pad-1', '--padding=10 5', {OUT=>' 5'}],
+ ['pad-2', '--padding=-10 5', {OUT=>'5 '}],
+ ['pad-3', '--padding=A 5',
+ {ERR => "$prog: invalid padding value 'A'\n"},
+ {EXIT => '1'}],
+ ['pad-3.1', '--padding=0 5',
+ {ERR => "$prog: invalid padding value '0'\n"},
+ {EXIT => '1'}],
+ ['pad-3.2', "--padding=$limits->{LONG_MIN} 0",
+ {ERR => "$prog: invalid padding value '$limits->{LONG_MIN}'\n"},
+ {EXIT => '1'}],
+ ['pad-4', '--padding=10 --to=si 50000', {OUT=>' 50K'}],
+ ['pad-5', '--padding=-10 --to=si 50000', {OUT=>'50K '}],
+
+ # padding too narrow
+ ['pad-6', '--padding=2 --to=si 1000', {OUT=>'1.0K'}],
+
+
+ # Padding + suffix
+ ['pad-7', '--padding=10 --suffix=foo --to=si 50000',
+ {OUT=>' 50Kfoo'}],
+ ['pad-8', '--padding=-10 --suffix=foo --to=si 50000',
+ {OUT=>'50Kfoo '}],
+
+
+ # Delimiters
+ ['delim-1', '--delimiter=: --from=auto 40M:', {OUT=>'40000000:'}],
+ ['delim-2', '--delimiter="" --from=auto "40 M"',{OUT=>'40000000'}],
+ ['delim-3', '--delimiter=" " --from=auto "40M Foo"',{OUT=>'40000000 Foo'}],
+ ['delim-4', '--delimiter=: --from=auto 40M:60M', {OUT=>'40000000:60M'}],
+ ['delim-5', '-d: --field=2 --from=auto :40M:60M', {OUT=>':40000000:60M'}],
+ ['delim-6', '-d: --field 3 --from=auto 40M:60M', {OUT=>"40M:60M"}],
+ ['delim-err-1', '-d,, --to=si 1', {EXIT=>1},
+ {ERR => "$prog: the delimiter must be a single character\n"}],
+
+ #Fields
+ ['field-1', '--field A',
+ {ERR => "$prog: invalid field value 'A'\n$try"},
+ {EXIT => '1'}],
+ ['field-2', '--field 2 --from=auto "Hello 40M World 90G"',
+ {OUT=>'Hello 40000000 World 90G'}],
+ ['field-3', '--field 3 --from=auto "Hello 40M World 90G"',
+ {OUT=>"Hello 40M "},
+ {ERR=>"$prog: invalid number: 'World'\n"},
+ {EXIT => 2},],
+ # Last field - no text after number
+ ['field-4', '--field 4 --from=auto "Hello 40M World 90G"',
+ {OUT=>"Hello 40M World 90000000000"}],
+ # Last field - a delimiter after the number
+ ['field-5', '--field 4 --from=auto "Hello 40M World 90G "',
+ {OUT=>"Hello 40M World 90000000000 "}],
+
+ # Mix Fields + Delimiters
+ ['field-6', '--delimiter=: --field 2 --from=auto "Hello:40M:World:90G"',
+ {OUT=>"Hello:40000000:World:90G"}],
+
+ # not enough fields
+ ['field-8', '--field 3 --to=si "Hello World"', {OUT=>"Hello World"}],
+
+ # Multiple fields
+ ['field-range-1', '--field 2,4 --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1000 2.0K 3000 4.0K 5000"}],
+
+ ['field-range-2', '--field 2-4 --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1000 2.0K 3.0K 4.0K 5000"}],
+
+ ['field-range-3', '--field 1,2,3-5 --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1.0K 2.0K 3.0K 4.0K 5.0K"}],
+
+ ['field-range-4', '--field 1-5 --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1.0K 2.0K 3.0K 4.0K 5.0K"}],
+
+ ['field-range-5', '--field 1-3,5 --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1.0K 2.0K 3.0K 4000 5.0K"}],
+
+ ['field-range-6', '--field 3- --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1000 2000 3.0K 4.0K 5.0K"}],
+
+ ['field-range-7', '--field -3 --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1.0K 2.0K 3.0K 4000 5000"}],
+
+ ['field-range-8', '--field 1-2,4-5 --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1.0K 2.0K 3000 4.0K 5.0K"}],
+ ['field-range-9', '--field 4-5,1-2 --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1.0K 2.0K 3000 4.0K 5.0K"}],
+
+ ['field-range-10','--field 1-3,2-4 --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1.0K 2.0K 3.0K 4.0K 5000"}],
+ ['field-range-11','--field 2-4,1-3 --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1.0K 2.0K 3.0K 4.0K 5000"}],
+
+ ['field-range-12','--field 1-1,3-3 --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1.0K 2000 3.0K 4000 5000"}],
+
+ ['field-range-13', '--field 1,-2 --to=si "1000 2000 3000"',
+ {OUT=>"1.0K 2.0K 3000"}],
+
+ ['field-range-14', '--field -2,4- --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1.0K 2.0K 3000 4.0K 5.0K"}],
+ ['field-range-15', '--field -2,-4 --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1.0K 2.0K 3.0K 4.0K 5000"}],
+ ['field-range-16', '--field 2-,4- --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1000 2.0K 3.0K 4.0K 5.0K"}],
+ ['field-range-17', '--field 4-,2- --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1000 2.0K 3.0K 4.0K 5.0K"}],
+
+ # white space are valid field separators
+ # (undocumented? but works in cut as well).
+ ['field-range-18', '--field "1,2 4" --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1.0K 2.0K 3000 4.0K 5000"}],
+
+ # Unlike 'cut', a lone '-' means 'all fields', even as part of a list
+ # of fields.
+ ['field-range-19','--field 3,- --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1.0K 2.0K 3.0K 4.0K 5.0K"}],
+
+ ['all-fields-1', '--field=- --to=si "1000 2000 3000 4000 5000"',
+ {OUT=>"1.0K 2.0K 3.0K 4.0K 5.0K"}],
+
+ ['field-range-err-1', '--field -foo --to=si 10',
+ {EXIT=>1}, {ERR=>"$prog: invalid field value 'foo'\n$try"}],
+ ['field-range-err-2', '--field --3 --to=si 10',
+ {EXIT=>1}, {ERR=>"$prog: invalid field range\n$try"}],
+ ['field-range-err-3', '--field 0 --to=si 10',
+ {EXIT=>1}, {ERR=>"$prog: fields are numbered from 1\n$try"}],
+ ['field-range-err-4', '--field 3-2 --to=si 10',
+ {EXIT=>1}, {ERR=>"$prog: invalid decreasing range\n$try"}],
+ ['field-range-err-6', '--field - --field 1- --to=si 10',
+ {EXIT=>1}, {ERR=>"$prog: multiple field specifications\n"}],
+ ['field-range-err-7', '--field -1 --field 1- --to=si 10',
+ {EXIT=>1}, {ERR=>"$prog: multiple field specifications\n"}],
+ ['field-range-err-8', '--field -1 --field 1,2,3 --to=si 10',
+ {EXIT=>1}, {ERR=>"$prog: multiple field specifications\n"}],
+ ['field-range-err-9', '--field 1- --field 1,2,3 --to=si 10',
+ {EXIT=>1}, {ERR=>"$prog: multiple field specifications\n"}],
+ ['field-range-err-10','--field 1,2,3 --field 1- --to=si 10',
+ {EXIT=>1}, {ERR=>"$prog: multiple field specifications\n"}],
+ ['field-range-err-11','--field 1-2-3 --to=si 10',
+ {EXIT=>1}, {ERR=>"$prog: invalid field range\n$try"}],
+ ['field-range-err-12','--field 0-1 --to=si 10',
+ {EXIT=>1}, {ERR=>"$prog: fields are numbered from 1\n$try"}],
+ ['field-range-err-13','--field '.$limits->{UINTMAX_MAX}.',22 --to=si 10',
+ {EXIT=>1}, {ERR=>"$prog: field number " .
+ "'".$limits->{UINTMAX_MAX}."' is too large\n$try"}],
+
+ # Auto-consume white-space, setup auto-padding
+ ['whitespace-1', '--to=si --field 2 "A 500 B"', {OUT=>"A 500 B"}],
+ ['whitespace-2', '--to=si --field 2 "A 5000 B"', {OUT=>"A 5.0K B"}],
+ ['whitespace-3', '--to=si " 500"', {OUT=>" 500"}],
+ ['whitespace-4', '--to=si " 6500"', {OUT=>" 6.5K"}],
+ # NOTE: auto-padding is not enabled if the value is on the first
+ # field and there's no white-space before it.
+ ['whitespace-5', '--to=si "6000000"', {OUT=>"6.0M"}],
+ # but if there is whitespace, assume auto-padding is desired.
+ ['whitespace-6', '--to=si " 6000000"', {OUT=>" 6.0M"}],
+
+ # auto-padding - lines have same padding-width
+ # (padding_buffer will be alloc'd just once)
+ ['whitespace-7', '--to=si --field 2',
+ {IN_PIPE=>"rootfs 100000\n" .
+ "udevxx 2000000\n"},
+ {OUT =>"rootfs 100K\n" .
+ "udevxx 2.0M"}],
+ # auto-padding - second line requires a
+ # larger padding (padding-buffer needs to be realloc'd)
+ ['whitespace-8', '--to=si --field 2',
+ {IN_PIPE=>"rootfs 100000\n" .
+ "udev 20000000\n"},
+ {OUT =>"rootfs 100K\n" .
+ "udev 20M"}],
+
+
+ # Corner-cases:
+ # weird mix of identical suffix,delimiters
+ # The priority is:
+ # 1. delimiters (and fields) are parsed (in process_line()
+ # 2. optional custom suffix is removed (in process_suffixed_number())
+ # 3. Remaining suffixes must be valid SI/IEC (in human_xstrtol())
+
+ # custom suffix comes BEFORE SI/IEC suffix,
+ # so these are 40 of "M", not 40,000,000.
+ ['mix-1', '--suffix=M --from=si 40M', {OUT=>"40M"}],
+
+ # These are forty-million Ms .
+ ['mix-2', '--suffix=M --from=si 40MM', {OUT=>"40000000M"}],
+
+ ['mix-3', '--suffix=M --from=auto 40MM', {OUT=>"40000000M"}],
+ ['mix-4', '--suffix=M --from=auto 40MiM', {OUT=>"41943040M"}],
+ ['mix-5', '--suffix=M --to=si --from=si 4MM', {OUT=>"4.0MM"}],
+
+ # This might be confusing to the user, but it's legit:
+ # The M in the output is the custom suffix, not Mega.
+ ['mix-6', '--suffix=M 40', {OUT=>"40M"}],
+ ['mix-7', '--suffix=M 4000000', {OUT=>"4000000M"}],
+ ['mix-8', '--suffix=M --to=si 4000000', {OUT=>"4.0MM"}],
+
+ # The output 'M' is the custom suffix.
+ ['mix-10', '--delimiter=M --suffix=M 40', {OUT=>"40M"}],
+
+ # The INPUT 'M' is a delimiter (delimiters are top priority)
+ # The output contains one M for custom suffix, and one 'M' delimiter.
+ ['mix-11', '--delimiter=M --suffix=M 40M', {OUT=>"40MM"}],
+
+ # Same as above, the "M" is NOT treated as a mega SI prefix,
+ ['mix-12', '--delimiter=M --from=si --suffix=M 40M', {OUT=>"40MM"}],
+
+ # The 'M' is treated as a delimiter, and so the input value is '4000'
+ ['mix-13', '--delimiter=M --to=si --from=auto 4000M5000M9000',
+ {OUT=>"4.0KM5000M9000"}],
+ # 'M' is the delimiter, so the second input field is '5000'
+ ['mix-14', '--delimiter=M --field 2 --from=auto --to=si 4000M5000M9000',
+ {OUT=>"4000M5.0KM9000"}],
+
+
+
+ ## Header testing
+
+ # header - silently ignored with command line parameters
+ ['header-1', '--header --to=iec 4096', {OUT=>"4.0K"}],
+
+ # header warning with --debug
+ ['header-2', '--debug --header --to=iec 4096', {OUT=>"4.0K"},
+ {ERR=>"$prog: --header ignored with command-line input\n"}],
+
+ ['header-3', '--header=A',
+ {ERR=>"$prog: invalid header value 'A'\n"},
+ {EXIT => 1},],
+ ['header-4', '--header=0',
+ {ERR=>"$prog: invalid header value '0'\n"},
+ {EXIT => 1},],
+ ['header-5', '--header=-6',
+ {ERR=>"$prog: invalid header value '-6'\n"},
+ {EXIT => 1},],
+ ['header-6', '--debug --header --to=iec',
+ {IN_PIPE=>"size\n5000\n90000\n"},
+ {OUT=>"size\n4.9K\n88K"}],
+ ['header-7', '--debug --header=3 --to=iec',
+ {IN_PIPE=>"hello\nworld\nsize\n5000\n90000\n"},
+ {OUT=>"hello\nworld\nsize\n4.9K\n88K"}],
+ # header, but no actual content
+ ['header-8', '--header=2 --to=iec',
+ {IN_PIPE=>"hello\nworld\n"},
+ {OUT=>"hello\nworld"}],
+ # not enough header lines
+ ['header-9', '--header=3 --to=iec',
+ {IN_PIPE=>"hello\nworld\n"},
+ {OUT=>"hello\nworld"}],
+
+
+ ## human_strtod testing
+
+ # NO_DIGITS_FOUND
+ ['strtod-1', '--from=si "foo"',
+ {ERR=>"$prog: invalid number: 'foo'\n"},
+ {EXIT=> 2}],
+ ['strtod-2', '--from=si ""',
+ {ERR=>"$prog: invalid number: ''\n"},
+ {EXIT=> 2}],
+
+ # FRACTION_NO_DIGITS_FOUND
+ ['strtod-5', '--from=si 12.',
+ {ERR=>"$prog: invalid number: '12.'\n"},
+ {EXIT=>2}],
+ ['strtod-6', '--from=si 12.K',
+ {ERR=>"$prog: invalid number: '12.K'\n"},
+ {EXIT=>2}],
+
+ # whitespace is not allowed after decimal-point
+ ['strtod-6.1', '--from=si --delimiter=, "12. 2"',
+ {ERR=>"$prog: invalid number: '12. 2'\n"},
+ {EXIT=>2}],
+
+ # INVALID_SUFFIX
+ ['strtod-9', '--from=si 12.2J',
+ {ERR=>"$prog: invalid suffix in input: '12.2J'\n"},
+ {EXIT=>2}],
+
+ # VALID_BUT_FORBIDDEN_SUFFIX
+ ['strtod-10', '12M',
+ {ERR => "$prog: rejecting suffix " .
+ "in input: '12M' (consider using --from)\n"},
+ {EXIT=>2}],
+
+ # MISSING_I_SUFFIX
+ ['strtod-11', '--from=iec-i 12M',
+ {ERR => "$prog: missing 'i' suffix in input: " .
+ "'12M' (e.g Ki/Mi/Gi)\n"},
+ {EXIT=>2}],
+
+ #
+ # Test double_to_human()
+ #
+
+ # 1K and smaller
+ ['dbl-to-human-1','--to=si 800', {OUT=>"800"}],
+ ['dbl-to-human-2','--to=si 0', {OUT=>"0"}],
+ ['dbl-to-human-2.1','--to=si 999', {OUT=>"999"}],
+ ['dbl-to-human-2.2','--to=si 1000', {OUT=>"1.0K"}],
+ #NOTE: the following are consistent with "ls -lh" output
+ ['dbl-to-human-2.3','--to=iec 999', {OUT=>"999"}],
+ ['dbl-to-human-2.4','--to=iec 1023', {OUT=>"1023"}],
+ ['dbl-to-human-2.5','--to=iec 1024', {OUT=>"1.0K"}],
+ ['dbl-to-human-2.6','--to=iec 1025', {OUT=>"1.1K"}],
+ ['dbl-to-human-2.7','--to=iec 0', {OUT=>"0"}],
+ # no "i" suffix if output has no suffix
+ ['dbl-to-human-2.8','--to=iec-i 0', {OUT=>"0"}],
+
+ # values resulting in "N.Nx" output
+ ['dbl-to-human-3','--to=si 8000', {OUT=>"8.0K"}],
+ ['dbl-to-human-3.1','--to=si 8001', {OUT=>"8.1K"}],
+ ['dbl-to-human-4','--to=si --round=down 8001', {OUT=>"8.0K"}],
+
+ ['dbl-to-human-5','--to=si --round=down 3500', {OUT=>"3.5K"}],
+ ['dbl-to-human-6','--to=si --round=nearest 3500', {OUT=>"3.5K"}],
+ ['dbl-to-human-7','--to=si --round=up 3500', {OUT=>"3.5K"}],
+
+ ['dbl-to-human-8','--to=si --round=down 3501', {OUT=>"3.5K"}],
+ ['dbl-to-human-9','--to=si --round=nearest 3501', {OUT=>"3.5K"}],
+ ['dbl-to-human-10','--to=si --round=up 3501', {OUT=>"3.6K"}],
+
+ ['dbl-to-human-11','--to=si --round=nearest 3550', {OUT=>"3.6K"}],
+ ['dbl-to-human-12','--to=si --from=si 999.89K', {OUT=>"1.0M"}],
+ ['dbl-to-human-13','--to=si --from=si 9.9K', {OUT=>"9.9K"}],
+ ['dbl-to-human-14','--to=si 9900', {OUT=>"9.9K"}],
+ ['dbl-to-human-15','--to=iec --from=si 3.3K', {OUT=>"3.3K"}],
+ ['dbl-to-human-16','--to=iec --round=down --from=si 3.3K', {OUT=>"3.2K"}],
+
+ # values resulting in 'NNx' output
+ ['dbl-to-human-17','--to=si 9999', {OUT=>"10K"}],
+ ['dbl-to-human-18','--to=si --round=down 35000', {OUT=>"35K"}],
+ ['dbl-to-human-19','--to=iec 35000', {OUT=>"35K"}],
+ ['dbl-to-human-20','--to=iec --round=down 35000', {OUT=>"34K"}],
+ ['dbl-to-human-21','--to=iec 35000000', {OUT=>"34M"}],
+ ['dbl-to-human-22','--to=iec --round=down 35000000', {OUT=>"33M"}],
+ ['dbl-to-human-23','--to=si 35000001', {OUT=>"36M"}],
+ ['dbl-to-human-24','--to=si --from=si 9.99M', {OUT=>"10M"}],
+ ['dbl-to-human-25','--to=si --from=iec 9.99M', {OUT=>"11M"}],
+ ['dbl-to-human-25.1','--to=iec 99999', {OUT=>"98K"}],
+
+ # values resulting in 'NNNx' output
+ ['dbl-to-human-26','--to=si 999000000000', {OUT=>"999G"}],
+ ['dbl-to-human-27','--to=iec 999000000000', {OUT=>"931G"}],
+ ['dbl-to-human-28','--to=si 123600000000000', {OUT=>"124T"}],
+ ['dbl-to-human-29','--to=si 998123', {OUT=>"999K"}],
+ ['dbl-to-human-30','--to=si --round=nearest 998123', {OUT=>"998K"}],
+ ['dbl-to-human-31','--to=si 99999', {OUT=>"100K"}],
+ ['dbl-to-human-32','--to=iec 102399', {OUT=>"100K"}],
+ ['dbl-to-human-33','--to=iec-i 102399', {OUT=>"100Ki"}],
+
+
+ # Default --round=from-zero
+ ['round-1','--to-unit=1024 -- 6000 -6000',
+ {OUT=>"6\n-6"}],
+ ['round-2','--to-unit=1024 -- 6000.0 -6000.0',
+ {OUT=>"5.9\n-5.9"}],
+ ['round-3','--to-unit=1024 -- 6000.00 -6000.00',
+ {OUT=>"5.86\n-5.86"}],
+ ['round-4','--to-unit=1024 -- 6000.000 -6000.000',
+ {OUT=>"5.860\n-5.860"}],
+ ['round-5','--to-unit=1024 -- 6000.0000 -6000.0000',
+ {OUT=>"5.8594\n-5.8594"}],
+ # --round=up
+ ['round-1-up','--round=up --to-unit=1024 -- 6000 -6000',
+ {OUT=>"6\n-5"}],
+ ['round-2-up','--round=up --to-unit=1024 -- 6000.0 -6000.0',
+ {OUT=>"5.9\n-5.8"}],
+ ['round-3-up','--round=up --to-unit=1024 -- 6000.00 -6000.00',
+ {OUT=>"5.86\n-5.85"}],
+ ['round-4-up','--round=up --to-unit=1024 -- 6000.000 -6000.000',
+ {OUT=>"5.860\n-5.859"}],
+ ['round-5-up','--round=up --to-unit=1024 -- 6000.0000 -6000.0000',
+ {OUT=>"5.8594\n-5.8593"}],
+ # --round=down
+ ['round-1-down','--round=down --to-unit=1024 -- 6000 -6000',
+ {OUT=>"5\n-6"}],
+ ['round-2-down','--round=down --to-unit=1024 -- 6000.0 -6000.0',
+ {OUT=>"5.8\n-5.9"}],
+ ['round-3-down','--round=down --to-unit=1024 -- 6000.00 -6000.00',
+ {OUT=>"5.85\n-5.86"}],
+ ['round-4-down','--round=down --to-unit=1024 -- 6000.000 -6000.000',
+ {OUT=>"5.859\n-5.860"}],
+ ['round-5-down','--round=down --to-unit=1024 -- 6000.0000 -6000.0000',
+ {OUT=>"5.8593\n-5.8594"}],
+ # --round=towards-zero
+ ['round-1-to-zero','--ro=towards-zero --to-u=1024 -- 6000 -6000',
+ {OUT=>"5\n-5"}],
+ ['round-2-to-zero','--ro=towards-zero --to-u=1024 -- 6000.0 -6000.0',
+ {OUT=>"5.8\n-5.8"}],
+ ['round-3-to-zero','--ro=towards-zero --to-u=1024 -- 6000.00 -6000.00',
+ {OUT=>"5.85\n-5.85"}],
+ ['round-4-to-zero','--ro=towards-zero --to-u=1024 -- 6000.000 -6000.000',
+ {OUT=>"5.859\n-5.859"}],
+ ['round-5-to-zero','--ro=towards-zero --to-u=1024 -- 6000.0000 -6000.0000',
+ {OUT=>"5.8593\n-5.8593"}],
+ # --round=nearest
+ ['round-1-near','--ro=nearest --to-u=1024 -- 6000 -6000',
+ {OUT=>"6\n-6"}],
+ ['round-2-near','--ro=nearest --to-u=1024 -- 6000.0 -6000.0',
+ {OUT=>"5.9\n-5.9"}],
+ ['round-3-near','--ro=nearest --to-u=1024 -- 6000.00 -6000.00',
+ {OUT=>"5.86\n-5.86"}],
+ ['round-4-near','--ro=nearest --to-u=1024 -- 6000.000 -6000.000',
+ {OUT=>"5.859\n-5.859"}],
+ ['round-5-near','--ro=nearest --to-u=1024 -- 6000.0000 -6000.0000',
+ {OUT=>"5.8594\n-5.8594"}],
+
+
+ # Leading zeros weren't handled appropriately before 8.24
+ ['leading-1','0000000000000000000000000001', {OUT=>"1"}],
+ ['leading-2','.1', {OUT=>"0.1"}],
+ ['leading-3','bad.1',
+ {ERR => "$prog: invalid number: 'bad.1'\n"},
+ {EXIT => 2}],
+ ['leading-4','..1',
+ {ERR => "$prog: invalid suffix in input: '..1'\n"},
+ {EXIT => 2}],
+ ['leading-5','1.',
+ {ERR => "$prog: invalid number: '1.'\n"},
+ {EXIT => 2}],
+
+ # precision override
+ ['precision-1','--format=%.4f 9991239123 --to=si', {OUT=>"9.9913G"}],
+ ['precision-2','--format=%.1f 9991239123 --to=si', {OUT=>"10.0G"}],
+ ['precision-3','--format=%.1f 1', {OUT=>"1.0"}],
+ ['precision-4','--format=%.1f 1.12', {OUT=>"1.2"}],
+ ['precision-5','--format=%.1f 9991239123 --to-unit=G', {OUT=>"10.0"}],
+ ['precision-6','--format="% .1f" 9991239123 --to-unit=G', {OUT=>"10.0"}],
+ ['precision-7','--format=%.-1f 1.1',
+ {ERR => "$prog: invalid precision in format '%.-1f'\n"},
+ {EXIT => 1}],
+ ['precision-8','--format=%.+1f 1.1',
+ {ERR => "$prog: invalid precision in format '%.+1f'\n"},
+ {EXIT => 1}],
+ ['precision-9','--format="%. 1f" 1.1',
+ {ERR => "$prog: invalid precision in format '%. 1f'\n"},
+ {EXIT => 1}],
+
+ # debug warnings
+ ['debug-1', '--debug 4096', {OUT=>"4096"},
+ {ERR=>"$prog: no conversion option specified\n"}],
+ # '--padding' is a valid conversion option - no warning should be printed
+ ['debug-1.1', '--debug --padding 10 4096', {OUT=>" 4096"}],
+ ['debug-2', '--debug --grouping --from=si 4.0K', {OUT=>"4000"},
+ {ERR=>"$prog: grouping has no effect in this locale\n"}],
+
+ # dev-debug messages - the actual messages don't matter
+ # just ensure the program works, and for code coverage testing.
+ ['devdebug-1', '---debug --from=si 4.9K', {OUT=>"4900"},
+ {ERR=>""},
+ {ERR_SUBST=>"s/.*//msg"}],
+ ['devdebug-2', '---debug 4900', {OUT=>"4900"},
+ {ERR=>""},
+ {ERR_SUBST=>"s/.*//msg"}],
+ ['devdebug-3', '---debug --from=auto 4Mi', {OUT=>"4194304"},
+ {ERR=>""},
+ {ERR_SUBST=>"s/.*//msg"}],
+ ['devdebug-4', '---debug --to=si 4000000', {OUT=>"4.0M"},
+ {ERR=>""},
+ {ERR_SUBST=>"s/.*//msg"}],
+ ['devdebug-5', '---debug --to=si --padding=5 4000000', {OUT=>" 4.0M"},
+ {ERR=>""},
+ {ERR_SUBST=>"s/.*//msg"}],
+ ['devdebug-6', '---debug --suffix=Foo 1234Foo', {OUT=>"1234Foo"},
+ {ERR=>""},
+ {ERR_SUBST=>"s/.*//msg"}],
+ ['devdebug-7', '---debug --suffix=Foo 1234', {OUT=>"1234Foo"},
+ {ERR=>""},
+ {ERR_SUBST=>"s/.*//msg"}],
+ ['devdebug-9', '---debug --grouping 10000', {OUT=>"10000"},
+ {ERR=>""},
+ {ERR_SUBST=>"s/.*//msg"}],
+ ['devdebug-10', '---debug --format %f 10000', {OUT=>"10000"},
+ {ERR=>""},
+ {ERR_SUBST=>"s/.*//msg"}],
+ ['devdebug-11', '---debug --format "%\'-10f" 10000',{OUT=>"10000 "},
+ {ERR=>""},
+ {ERR_SUBST=>"s/.*//msg"}],
+
+ # Invalid parameters
+ ['help-1', '--foobar',
+ {ERR=>"$prog: unrecognized option\n$try"},
+ {ERR_SUBST=>"s/option.*/option/; s/unknown/unrecognized/"},
+ {EXIT=>1}],
+
+ ## Format string - check error detection
+ ['fmt-err-1', '--format ""',
+ {ERR=>"$prog: format '' has no % directive\n"},
+ {EXIT=>1}],
+ ['fmt-err-2', '--format "hello"',
+ {ERR=>"$prog: format 'hello' has no % directive\n"},
+ {EXIT=>1}],
+ ['fmt-err-3', '--format "hello%"',
+ {ERR=>"$prog: format 'hello%' ends in %\n"},
+ {EXIT=>1}],
+ ['fmt-err-4', '--format "%d"',
+ {ERR=>"$prog: invalid format '%d', " .
+ "directive must be %[0]['][-][N][.][N]f\n"},
+ {EXIT=>1}],
+ ['fmt-err-5', '--format "% -43 f"',
+ {ERR=>"$prog: invalid format '% -43 f', " .
+ "directive must be %[0]['][-][N][.][N]f\n"},
+ {EXIT=>1}],
+ ['fmt-err-6', '--format "%f %f"',
+ {ERR=>"$prog: format '%f %f' has too many % directives\n"},
+ {EXIT=>1}],
+ ['fmt-err-7', '--format "%'.$limits->{LONG_OFLOW}.'f"',
+ {ERR=>"$prog: invalid format '%$limits->{LONG_OFLOW}f'".
+ " (width overflow)\n"},
+ {EXIT=>1}],
+ ['fmt-err-9', '--format "%f" --grouping',
+ {ERR=>"$prog: --grouping cannot be combined with --format\n"},
+ {EXIT=>1}],
+ ['fmt-err-10', '--format "%\'f" --to=si',
+ {ERR=>"$prog: grouping cannot be combined with --to\n"},
+ {EXIT=>1}],
+ ['fmt-err-11', '--debug --format "%\'f" 5000', {OUT=>"5000"},
+ {ERR=>"$prog: grouping has no effect in this locale\n"}],
+
+ ## Format string - check some corner cases
+ ['fmt-1', '--format "%% %f" 5000', {OUT=>"%%5000"}],
+ ['fmt-2', '--format "%f %%" 5000', {OUT=>"5000 %%"}],
+
+ ['fmt-3', '--format "--%f--" 5000000', {OUT=>"--5000000--"}],
+ ['fmt-4', '--format "--%f--" --to=si 5000000', {OUT=>"--5.0M--"}],
+
+ ['fmt-5', '--format "--%10f--" --to=si 5000000',{OUT=>"-- 5.0M--"}],
+ ['fmt-6', '--format "--%-10f--" --to=si 5000000',{OUT=>"--5.0M --"}],
+ ['fmt-7', '--format "--%10f--" 5000000',{OUT=>"-- 5000000--"}],
+ ['fmt-8', '--format "--%-10f--" 5000000',{OUT=>"--5000000 --"}],
+
+ # too-short width
+ ['fmt-9', '--format "--%5f--" 5000000',{OUT=>"--5000000--"}],
+
+ # Format + Suffix
+ ['fmt-10', '--format "--%10f--" --suffix Foo 50', {OUT=>"-- 50Foo--"}],
+ ['fmt-11', '--format "--%-10f--" --suffix Foo 50',{OUT=>"--50Foo --"}],
+
+ # Grouping in C locale - no grouping effect
+ ['fmt-12', '--format "%\'f" 50000',{OUT=>"50000"}],
+ ['fmt-13', '--format "%\'10f" 50000', {OUT=>" 50000"}],
+ ['fmt-14', '--format "%\'-10f" 50000',{OUT=>"50000 "}],
+
+ # Very large format strings
+ ['fmt-15', '--format "--%100000f--" --to=si 4200',
+ {OUT=>"--" . " " x 99996 . "4.2K--" }],
+
+ # --format padding overrides --padding
+ ['fmt-16', '--format="%6f" --padding=66 1234',{OUT=>" 1234"}],
+
+ # zero padding
+ ['fmt-17', '--format="%06f" 1234',{OUT=>"001234"}],
+ # also support spaces (which are ignored as spacing is handled separately)
+ ['fmt-18', '--format="%0 6f" 1234',{OUT=>"001234"}],
+ # handle generic padding in combination
+ ['fmt-22', '--format="%06f" --padding=7 1234',{OUT=>" 001234"}],
+ ['fmt-23', '--format="%06f" --padding=-7 1234',{OUT=>"001234 "}],
+
+
+ ## Check all errors again, this time with --invalid=fail
+ ## Input will be printed without conversion,
+ ## and exit code will be 2
+ ['ign-err-1', '--invalid=fail 4J',
+ {ERR => "$prog: invalid suffix in input: '4J'\n"},
+ {OUT => "4J\n"},
+ {EXIT => 2}],
+ ['ign-err-2', '--invalid=fail 4M',
+ {ERR => "$prog: rejecting suffix " .
+ "in input: '4M' (consider using --from)\n"},
+ {OUT => "4M\n"},
+ {EXIT => 2}],
+ ['ign-err-3', '--invalid=fail --from=si 4MJ',
+ {ERR => "$prog: invalid suffix in input '4MJ': 'J'\n"},
+ {OUT => "4MJ\n"},
+ {EXIT => 2}],
+ ['ign-err-4', '--invalid=fail --suffix=Foo --to=si 7000FooF',
+ {ERR => "$prog: invalid suffix in input: '7000FooF'\n"},
+ {OUT => "7000FooF\n"},
+ {EXIT => 2}],
+ ['ign-err-5','--invalid=fail --field 3 --from=auto "Hello 40M World 90G"',
+ {ERR => "$prog: invalid number: 'World'\n"},
+ {OUT => "Hello 40M World 90G\n"},
+ {EXIT => 2}],
+ ['ign-err-7', '--invalid=fail --from=si "foo"',
+ {ERR => "$prog: invalid number: 'foo'\n"},
+ {OUT => "foo\n"},
+ {EXIT=> 2}],
+ ['ign-err-8', '--invalid=fail 12M',
+ {ERR => "$prog: rejecting suffix " .
+ "in input: '12M' (consider using --from)\n"},
+ {OUT => "12M\n"},
+ {EXIT => 2}],
+ ['ign-err-9', '--invalid=fail --from=iec-i 12M',
+ {ERR => "$prog: missing 'i' suffix in input: " .
+ "'12M' (e.g Ki/Mi/Gi)\n"},
+ {OUT => "12M\n"},
+ {EXIT=>2}],
+
+ ## Ignore Errors with multiple conversions
+ ['ign-err-m1', '--invalid=ignore --to=si 1000 2000 bad 3000',
+ {OUT => "1.0K\n2.0K\nbad\n3.0K"},
+ {EXIT => 0}],
+ ['ign-err-m1.1', '--invalid=ignore --to=si',
+ {IN_PIPE => "1000\n2000\nbad\n3000\n"},
+ {OUT => "1.0K\n2.0K\nbad\n3.0K"},
+ {EXIT => 0}],
+ ['ign-err-m1.3', '--invalid=fail --debug --to=si 1000 2000 3000',
+ {OUT => "1.0K\n2.0K\n3.0K"},
+ {EXIT => 0}],
+ ['ign-err-m2', '--invalid=fail --to=si 1000 Foo 3000',
+ {OUT => "1.0K\nFoo\n3.0K\n"},
+ {ERR => "$prog: invalid number: 'Foo'\n"},
+ {EXIT => 2}],
+ ['ign-err-m2.1', '--invalid=warn --to=si',
+ {IN_PIPE => "1000\nFoo\n3000\n"},
+ {OUT => "1.0K\nFoo\n3.0K"},
+ {ERR => "$prog: invalid number: 'Foo'\n"},
+ {EXIT => 0}],
+
+ # --debug will trigger a final warning at EOF
+ ['ign-err-m2.2', '--invalid=fail --debug --to=si 1000 Foo 3000',
+ {OUT => "1.0K\nFoo\n3.0K\n"},
+ {ERR => "$prog: invalid number: 'Foo'\n" .
+ "$prog: failed to convert some of the input numbers\n"},
+ {EXIT => 2}],
+
+ ['ign-err-m3', '--invalid=fail --field 2 --from=si --to=iec',
+ {IN_PIPE => "A 1K x\nB 2M y\nC 3G z\n"},
+ {OUT => "A 1000 x\nB 2.0M y\nC 2.8G z"},
+ {EXIT => 0}],
+ # invalid input on one of the fields
+ ['ign-err-m3.1', '--invalid=fail --field 2 --from=si --to=iec',
+ {IN_PIPE => "A 1K x\nB Foo y\nC 3G z\n"},
+ {OUT => "A 1000 x\nB Foo y\nC 2.8G z\n"},
+ {ERR => "$prog: invalid number: 'Foo'\n"},
+ {EXIT => 2}],
+ );
+
+# test null-terminated lines
+my @NullDelim_Tests =
+ (
+ # Input from STDIN
+ ['z1', '-z --to=iec',
+ {IN_PIPE => "1025\x002048\x00"}, {OUT=>"1.1K\x002.0K\x00"}],
+
+ # Input from the commandline - terminated by NULL vs NL
+ ['z3', ' --to=iec 1024', {OUT=>"1.0K\n"}],
+ ['z2', '-z --to=iec 1024', {OUT=>"1.0K\x00"}],
+
+ # Input from STDIN, with fields
+ ['z4', '-z --field=3 --to=si',
+ {IN_PIPE => "A B 1001 C\x00" .
+ "D E 2002 F\x00"},
+ {OUT => "A B 1.1K C\x00" .
+ "D E 2.1K F\x00"}],
+
+ # Input from STDIN, with fields and embedded NL
+ ['z5', '-z --field=3 --to=si',
+ {IN_PIPE => "A\nB 1001 C\x00" .
+ "D E\n2002 F\x00"},
+ {OUT => "A B 1.1K C\x00" .
+ "D E 2.1K F\x00"}],
+ );
+
+my @Limit_Tests =
+ (
+ # Large Values
+ ['large-1','1000000000000000', {OUT=>"1000000000000000"}],
+ # 18 digits is OK
+ ['large-2','1000000000000000000', {OUT=>"1000000000000000000"}],
+ # 19 digits is too much (without output scaling)
+ ['large-3','10000000000000000000',
+ {ERR => "$prog: value too large to be printed: '1e+19' " .
+ "(consider using --to)\n"},
+ {EXIT=>2}],
+ ['large-4','1000000000000000000.0',
+ {ERR => "$prog: value/precision too large to be printed: " .
+ "'1e+18/1' (consider using --to)\n"},
+ {EXIT=>2}],
+
+
+ # Test input:
+ # Up to 33 digits is OK.
+ ['large-3.1', '--to=si 1', {OUT=> "1"}],
+ ['large-3.2', '--to=si 10', {OUT=> "10"}],
+ ['large-3.3', '--to=si 100', {OUT=> "100"}],
+ ['large-3.4', '--to=si 1000', {OUT=>"1.0K"}],
+ ['large-3.5', '--to=si 10000', {OUT=> "10K"}],
+ ['large-3.6', '--to=si 100000', {OUT=>"100K"}],
+ ['large-3.7', '--to=si 1000000', {OUT=>"1.0M"}],
+ ['large-3.8', '--to=si 10000000', {OUT=> "10M"}],
+ ['large-3.9', '--to=si 100000000', {OUT=>"100M"}],
+ ['large-3.10','--to=si 1000000000', {OUT=>"1.0G"}],
+ ['large-3.11','--to=si 10000000000', {OUT=> "10G"}],
+ ['large-3.12','--to=si 100000000000', {OUT=>"100G"}],
+ ['large-3.13','--to=si 1000000000000', {OUT=>"1.0T"}],
+ ['large-3.14','--to=si 10000000000000', {OUT=> "10T"}],
+ ['large-3.15','--to=si 100000000000000', {OUT=>"100T"}],
+ ['large-3.16','--to=si 1000000000000000', {OUT=>"1.0P"}],
+ ['large-3.17','--to=si 10000000000000000', {OUT=> "10P"}],
+ ['large-3.18','--to=si 100000000000000000', {OUT=>"100P"}],
+ ['large-3.19','--to=si 1000000000000000000', {OUT=>"1.0E"}],
+ ['large-3.20','--to=si 10000000000000000000', {OUT=> "10E"}],
+ ['large-3.21','--to=si 210000000000000000000', {OUT=>"210E"}],
+ ['large-3.22','--to=si 3210000000000000000000', {OUT=>"3.3Z"}],
+ ['large-3.23','--to=si 43210000000000000000000', {OUT=> "44Z"}],
+ ['large-3.24','--to=si 543210000000000000000000', {OUT=>"544Z"}],
+ ['large-3.25','--to=si 6543210000000000000000000', {OUT=>"6.6Y"}],
+ ['large-3.26','--to=si 76543210000000000000000000', {OUT=> "77Y"}],
+ ['large-3.27','--to=si 876543210000000000000000000', {OUT=>"877Y"}],
+ ['large-3.28','--to=si 9876543210000000000000000000', {OUT=>"9.9R"}],
+ ['large-3.29','--to=si 19876543210000000000000000000', {OUT=> "20R"}],
+ ['large-3.30','--to=si 219876543210000000000000000000', {OUT=>"220R"}],
+ ['large-3.31','--to=si 3219876543210000000000000000000', {OUT=>"3.3Q"}],
+ ['large-3.32','--to=si 43219876543210000000000000000000', {OUT=> "44Q"}],
+ ['large-3.33','--to=si 543219876543210000000000000000000', {OUT=>"544Q"}],
+
+ # More than 33 digits is not OK
+ ['large-3.34','--to=si 6543219876543210000000000000000000',
+ {ERR => "$prog: value too large to be converted: " .
+ "'6543219876543210000000000000000000'\n"},
+ {EXIT => 2}],
+
+ # Test Output
+ ['large-4.1', '--from=si 9.7M', {OUT=>"9700000"}],
+ ['large-4.2', '--from=si 10M', {OUT =>"10000000"}],
+ ['large-4.3', '--from=si 200M', {OUT =>"200000000"}],
+ ['large-4.4', '--from=si 3G', {OUT =>"3000000000"}],
+ ['large-4.5', '--from=si 40G', {OUT =>"40000000000"}],
+ ['large-4.6', '--from=si 500G', {OUT =>"500000000000"}],
+ ['large-4.7', '--from=si 6T', {OUT =>"6000000000000"}],
+ ['large-4.8', '--from=si 70T', {OUT =>"70000000000000"}],
+ ['large-4.9', '--from=si 800T', {OUT =>"800000000000000"}],
+ ['large-4.10','--from=si 9P', {OUT =>"9000000000000000"}],
+ ['large-4.11','--from=si 10P', {OUT =>"10000000000000000"}],
+ ['large-4.12','--from=si 200P', {OUT =>"200000000000000000"}],
+ ['large-4.13','--from=si 3E', {OUT =>"3000000000000000000"}],
+
+ # More than 18 digits of output without scaling - no good.
+ ['large-4.14','--from=si 40E',
+ {ERR => "$prog: value too large to be printed: '4e+19' " .
+ "(consider using --to)\n"},
+ {EXIT => 2}],
+ ['large-4.15','--from=si 500E',
+ {ERR => "$prog: value too large to be printed: '5e+20' " .
+ "(consider using --to)\n"},
+ {EXIT => 2}],
+ ['large-4.16','--from=si 6Z',
+ {ERR => "$prog: value too large to be printed: '6e+21' " .
+ "(consider using --to)\n"},
+ {EXIT => 2}],
+ ['large-4.17','--from=si 70Z',
+ {ERR => "$prog: value too large to be printed: '7e+22' " .
+ "(consider using --to)\n"},
+ {EXIT => 2}],
+ ['large-4.18','--from=si 800Z',
+ {ERR => "$prog: value too large to be printed: '8e+23' " .
+ "(consider using --to)\n"},
+ {EXIT => 2}],
+ ['large-4.19','--from=si 9Y',
+ {ERR => "$prog: value too large to be printed: '9e+24' " .
+ "(consider using --to)\n"},
+ {EXIT => 2}],
+ ['large-4.20','--from=si 10Y',
+ {ERR => "$prog: value too large to be printed: '1e+25' " .
+ "(consider using --to)\n"},
+ {EXIT => 2}],
+ ['large-4.21','--from=si 200Y',
+ {ERR => "$prog: value too large to be printed: '2e+26' " .
+ "(consider using --to)\n"},
+ {EXIT => 2}],
+
+ ['large-5.1','--to=si 1000000000000000000', {OUT=>"1.0E"}],
+ ['large-5','--from=si --to=si 2E', {OUT=>"2.0E"}],
+ ['large-6','--from=si --to=si 3.4Z', {OUT=>"3.4Z"}],
+ ['large-7','--from=si --to=si 80Y', {OUT=>"80Y"}],
+ ['large-8','--from=si --to=si 9000Z', {OUT=>"9.0Y"}],
+
+ ['large-10','--from=si --to=si 999Q', {OUT=>"999Q"}],
+ ['large-11','--from=si --to=iec 999Q', {OUT=>"789Q"}],
+ ['large-12','--from=si --round=down --to=iec 999Q', {OUT=>"788Q"}],
+
+ # units can also affect the output
+ ['large-13','--from=si --from-unit=1000000 9P',
+ {ERR => "$prog: value too large to be printed: '9e+21' " .
+ "(consider using --to)\n"},
+ {EXIT => 2}],
+ ['large-13.1','--from=si --from-unit=1000000 --to=si 9P', {OUT=>"9.0Z"}],
+
+ # Numbers>999Q are never acceptable, regardless of scaling
+ ['large-14','--from=si --to=si 999Q', {OUT=>"999Q"}],
+ ['large-14.1','--from=si --to=si 1000Q',
+ {ERR => "$prog: value too large to be printed: '1e+33' " .
+ "(cannot handle values > 999Q)\n"},
+ {EXIT => 2}],
+ ['large-14.2','--from=si --to=si --from-unit=10000 1Q',
+ {ERR => "$prog: value too large to be printed: '1e+34' " .
+ "(cannot handle values > 999Q)\n"},
+ {EXIT => 2}],
+
+ # intmax_t overflow when rounding caused this to fail before 8.24
+ ['large-15',$limits->{INTMAX_OFLOW}, {OUT=>$limits->{INTMAX_OFLOW}}],
+ ['large-16','9.300000000000000000', {OUT=>'9.300000000000000000'}],
+
+ # INTEGRAL_OVERFLOW
+ ['strtod-3', '--from=si "1234567890123456789012345678901234567890'.
+ '1234567890123456789012345678901234567890"',
+ {ERR=>"$prog: value too large to be converted: '" .
+ "1234567890123456789012345678901234567890" .
+ "1234567890123456789012345678901234567890'\n",
+ },
+ {EXIT=> 2}],
+
+ # FRACTION_OVERFLOW
+ ['strtod-7', '--from=si "12.1234567890123456789012345678901234567890'.
+ '1234567890123456789012345678901234567890"',
+ {ERR=>"$prog: value too large to be converted: '" .
+ "12.1234567890123456789012345678901234567890" .
+ "1234567890123456789012345678901234567890'\n",
+ },
+ {EXIT=> 2}],
+
+ ['debug-4', '--to=si --debug 12345678901234567890',
+ {OUT=>"13E"},
+ {ERR=>"$prog: large input value '12345678901234567890':" .
+ " possible precision loss\n"}],
+ ['debug-5', '--to=si --from=si --debug 1.12345678901234567890Y',
+ {OUT=>"1.2Y"},
+ {ERR=>"$prog: large input value '1.12345678901234567890Y':" .
+ " possible precision loss\n"}],
+
+ ['ign-err-10','--invalid=fail 10000000000000000000',
+ {ERR => "$prog: value too large to be printed: '1e+19' " .
+ "(consider using --to)\n"},
+ {OUT => "10000000000000000000\n"},
+ {EXIT=>2}],
+ ['ign-err-11','--invalid=fail --to=si 6543219876543210000000000000000000',
+ {ERR => "$prog: value too large to be converted: " .
+ "'6543219876543210000000000000000000'\n"},
+ {OUT => "6543219876543210000000000000000000\n"},
+ {EXIT => 2}],
+ );
+# Restrict these tests to systems with LDBL_DIG == 18
+(system "$prog ---debug 1 2>&1|grep 'MAX_UNSCALED_DIGITS: 18' > /dev/null") == 0
+ and push @Tests, @Limit_Tests;
+
+my @Locale_Tests =
+ (
+ # Locale that supports grouping, but without '--grouping' parameter
+ ['lcl-grp-1', '--from=si 7M', {OUT=>"7000000"},
+ {ENV=>"LC_ALL=$locale"}],
+
+ # Locale with grouping
+ ['lcl-grp-2', '--from=si --grouping 7M', {OUT=>"7 000 000"},
+ {ENV=>"LC_ALL=$locale"}],
+
+ # Locale with grouping and debug - no debug warning message
+ ['lcl-grp-3', '--from=si --debug --grouping 7M', {OUT=>"7 000 000"},
+ {ENV=>"LC_ALL=$locale"}],
+
+ # Input with locale'd decimal-point
+ ['lcl-stdtod-1', '--from=si 12,2K', {OUT=>"12200"},
+ {ENV=>"LC_ALL=$locale"}],
+
+ ['lcl-dbl-to-human-1', '--to=si 1100', {OUT=>"1,1K"},
+ {ENV=>"LC_ALL=$locale"}],
+
+ # Format + Grouping
+ ['lcl-fmt-1', '--format "%\'f" 50000',{OUT=>"50 000"},
+ {ENV=>"LC_ALL=$locale"}],
+ ['lcl-fmt-2', '--format "--%\'10f--" 50000', {OUT=>"-- 50 000--"},
+ {ENV=>"LC_ALL=$locale"}],
+ ['lcl-fmt-3', '--format "--%\'-10f--" 50000',{OUT=>"--50 000 --"},
+ {ENV=>"LC_ALL=$locale"}],
+ ['lcl-fmt-4', '--format "--%-10f--" --to=si 5000000',
+ {OUT=>"--5,0M --"},
+ {ENV=>"LC_ALL=$locale"}],
+ # handle zero/grouping in combination
+ ['lcl-fmt-5', '--format="%\'06f" 1234',{OUT=>"01 234"},
+ {ENV=>"LC_ALL=$locale"}],
+ ['lcl-fmt-6', '--format="%0\'6f" 1234',{OUT=>"01 234"},
+ {ENV=>"LC_ALL=$locale"}],
+ ['lcl-fmt-7', '--format="%0\'\'6f" 1234',{OUT=>"01 234"},
+ {ENV=>"LC_ALL=$locale"}],
+
+ );
+if ($locale ne 'C')
+ {
+ # Reset locale to 'C' if LOCALE_FR_UTF8 doesn't output as expected
+ # as determined by the separate printf program.
+ open(LOC_NUM, "env LC_ALL=$locale printf \"%'d\" 1234|")
+ or die "Can't fork command: $!";
+ my $loc_num = <LOC_NUM>;
+ close(LOC_NUM) || die "Failed to read grouped number from printf";
+ if ($loc_num ne '1 234')
+ {
+ warn "skipping locale grouping tests as 1234 groups like $loc_num\n";
+ $locale = 'C';
+ }
+ }
+push @Tests, @Locale_Tests if $locale ne 'C';
+
+## Check all valid/invalid suffixes
+foreach my $suf ( 'A' .. 'Z', 'a' .. 'z' ) {
+ if ( $suf =~ /^[KMGTPEZYRQ]$/ )
+ {
+ push @Tests, ["auto-suf-si-$suf","--from=si --to=si 1$suf",
+ {OUT=>"1.0$suf"}];
+ push @Tests, ["auto-suf-iec-$suf","--from=iec --to=iec 1$suf",
+ {OUT=>"1.0$suf"}];
+ push @Tests, ["auto-suf-auto-$suf","--from=auto --to=iec 1${suf}i",
+ {OUT=>"1.0$suf"}];
+ push @Tests, ["auto-suf-iec-to-ieci-$suf","--from=iec --to=iec-i 1${suf}",
+ {OUT=>"1.0${suf}i"}];
+ push @Tests, ["auto-suf-ieci-to-iec-$suf",
+ "--from=iec-i --to=iec 1${suf}i",{OUT=>"1.0${suf}"}];
+ }
+ else
+ {
+ push @Tests, ["auto-suf-si-$suf","--from=si --to=si 1$suf",
+ {ERR=>"$prog: invalid suffix in input: '1${suf}'\n"},
+ {EXIT=>2}];
+ }
+}
+
+# Prepend the command line argument and append a newline to end
+# of each expected 'OUT' string.
+my $t;
+
+Test:
+foreach $t (@Tests)
+ {
+ # Don't fiddle with expected OUT string if there's a nonzero exit status.
+ foreach my $e (@$t)
+ {
+ ref $e eq 'HASH' && exists $e->{EXIT} && $e->{EXIT}
+ and next Test;
+ }
+
+ foreach my $e (@$t)
+ {
+ ref $e eq 'HASH' && exists $e->{OUT}
+ and $e->{OUT} .= "\n"
+ }
+ }
+
+# Add test for null-terminated lines (after adjusting the OUT string, above).
+push @Tests, @NullDelim_Tests;
+
+my $save_temps = $ENV{SAVE_TEMPS};
+my $verbose = $ENV{VERBOSE};
+
+my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;
diff --git a/tests/misc/paste.pl b/tests/misc/paste.pl
new file mode 100755
index 0000000..e14ef1c
--- /dev/null
+++ b/tests/misc/paste.pl
@@ -0,0 +1,74 @@
+#!/usr/bin/perl
+# Test paste.
+
+# Copyright (C) 2003-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use strict;
+
+(my $program_name = $0) =~ s|.*/||;
+
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+my $prog = 'paste';
+my $msg = "$prog: delimiter list ends with an unescaped backslash: ";
+
+my @Tests =
+ (
+ # Ensure that paste properly handles files lacking a final newline.
+ ['no-nl-1', {IN=>"a"}, {IN=>"b"}, {OUT=>"a\tb\n"}],
+ ['no-nl-2', {IN=>"a\n"}, {IN=>"b"}, {OUT=>"a\tb\n"}],
+ ['no-nl-3', {IN=>"a"}, {IN=>"b\n"}, {OUT=>"a\tb\n"}],
+ ['no-nl-4', {IN=>"a\n"}, {IN=>"b\n"}, {OUT=>"a\tb\n"}],
+
+ ['zno-nl-1', '-z', {IN=>"a"}, {IN=>"b"}, {OUT=>"a\tb\0"}],
+ ['zno-nl-2', '-z', {IN=>"a\0"}, {IN=>"b"}, {OUT=>"a\tb\0"}],
+ ['zno-nl-3', '-z', {IN=>"a"}, {IN=>"b\0"}, {OUT=>"a\tb\0"}],
+ ['zno-nl-4', '-z', {IN=>"a\0"}, {IN=>"b\0"}, {OUT=>"a\tb\0"}],
+
+ # Same as above, but with a two lines in each input file and
+ # the addition of the -d option to make SPACE be the output delimiter.
+ ['no-nla1', '-d" "', {IN=>"1\na"}, {IN=>"2\nb"}, {OUT=>"1 2\na b\n"}],
+ ['no-nla2', '-d" "', {IN=>"1\na\n"}, {IN=>"2\nb"}, {OUT=>"1 2\na b\n"}],
+ ['no-nla3', '-d" "', {IN=>"1\na"}, {IN=>"2\nb\n"}, {OUT=>"1 2\na b\n"}],
+ ['no-nla4', '-d" "', {IN=>"1\na\n"}, {IN=>"2\nb\n"}, {OUT=>"1 2\na b\n"}],
+
+ ['zno-nla1', '-zd" "', {IN=>"1\0a"}, {IN=>"2\0b"}, {OUT=>"1 2\0a b\0"}],
+ ['zno-nla2', '-zd" "', {IN=>"1\0a\0"}, {IN=>"2\0b"}, {OUT=>"1 2\0a b\0"}],
+ ['zno-nla3', '-zd" "', {IN=>"1\0a"}, {IN=>"2\0b\0"}, {OUT=>"1 2\0a b\0"}],
+ ['zno-nla4', '-zd" "', {IN=>"1\0a\0"}, {IN=>"2\0b\0"}, {OUT=>"1 2\0a b\0"}],
+
+ # Specifying a delimiter with a trailing backslash would overrun a
+ # malloc'd buffer.
+ ['delim-bs1', q!-d'\'!, {IN=>{'a'x50=>''}}, {EXIT => 1},
+ # We print a single backslash into the expected output
+ {ERR => $msg . q!\\! . "\n"} ],
+
+ # Prior to coreutils-5.1.2, this sort of abuse would make paste
+ # scribble on command-line arguments. With paste from coreutils-5.1.0,
+ # this example would mangle the first file name argument, if it contains
+ # accepted backslash-escapes:
+ # $ paste -d\\ '123\b\b\b.....@' 2>&1 |cat -A
+ # paste: 23^H^H^H.....@...@: No such file or directory$
+ ['delim-bs2', q!-d'\'!, {IN=>{'123\b\b\b.....@'=>''}}, {EXIT => 1},
+ {ERR => $msg . q!\\! . "\n"} ],
+ );
+
+my $save_temps = $ENV{DEBUG};
+my $verbose = $ENV{VERBOSE};
+
+my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;
diff --git a/tests/misc/pathchk.sh b/tests/misc/pathchk.sh
new file mode 100755
index 0000000..b0892ac
--- /dev/null
+++ b/tests/misc/pathchk.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+# pathchk tests
+
+# Copyright (C) 2002-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ pathchk
+skip_if_root_
+
+touch file || framework_failure_
+
+
+# This should exit nonzero. Before 2.0.13, it gave a diagnostic,
+# but exited successfully.
+returns_ 1 pathchk file/x > /dev/null 2>&1 || fail=1
+
+# This should exit nonzero. Through 5.3.0 it exited with status zero.
+returns_ 1 pathchk -p '' > /dev/null 2>&1 || fail=1
+
+# This tests the new -P option.
+returns_ 1 pathchk -P '' > /dev/null 2>&1 || fail=1
+returns_ 1 pathchk -P -- - > /dev/null 2>&1 || fail=1
+returns_ 1 pathchk -p -P x/- > /dev/null 2>&1 || fail=1
+
+Exit $fail
diff --git a/tests/misc/printenv.sh b/tests/misc/printenv.sh
new file mode 100755
index 0000000..708f92b
--- /dev/null
+++ b/tests/misc/printenv.sh
@@ -0,0 +1,83 @@
+#!/bin/sh
+# Verify behavior of printenv.
+
+# Copyright (C) 2009-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ printenv
+
+# Without arguments, printenv behaves like env. Some shells provide
+# printenv as a builtin, so we must invoke it via "env".
+# But beware of $_, set by many shells to the last command run.
+# Also, filter out LD_PRELOAD, which is set when running under valgrind.
+# Note the apparently redundant "env env": this is to ensure to get
+# env's output the same way as that of printenv and works around a bug
+# on aarch64 at least where libc's execvp reverses the order of the
+# output.
+env -- env | grep -Ev '^(_|LD_PRELOAD)=' > exp || framework_failure_
+env -- printenv | grep -Ev '^(_|LD_PRELOAD)=' > out || fail=1
+compare exp out || fail=1
+
+# POSIX is clear that environ may, but need not be, sorted.
+# Environment variable values may contain newlines, which cannot be
+# observed by merely inspecting output from printenv.
+if env -- printenv | grep '^ENV_TEST' >/dev/null ; then
+ skip_ "environment has potential interference from ENV_TEST*"
+fi
+
+# Printing a single variable's value.
+returns_ 1 env -- printenv ENV_TEST > out || fail=1
+compare /dev/null out || fail=1
+echo a > exp || framework_failure_
+ENV_TEST=a env -- printenv ENV_TEST > out || fail=1
+compare exp out || fail=1
+
+# Printing multiple variables. Order follows command line.
+ENV_TEST1=a ENV_TEST2=b env -- printenv ENV_TEST2 ENV_TEST1 ENV_TEST2 > out \
+ || fail=1
+ENV_TEST1=a ENV_TEST2=b env -- printenv ENV_TEST1 ENV_TEST2 >> out || fail=1
+cat <<EOF > exp || framework_failure_
+b
+a
+b
+a
+b
+EOF
+compare exp out || fail=1
+
+# Exit status reflects missing variable, but remaining arguments processed.
+export ENV_TEST1=a
+returns_ 1 env -- printenv ENV_TEST2 ENV_TEST1 > out || fail=1
+returns_ 1 env -- printenv ENV_TEST1 ENV_TEST2 >> out || fail=1
+unset ENV_TEST1
+cat <<EOF > exp || framework_failure_
+a
+a
+EOF
+compare exp out || fail=1
+
+# Non-standard environment variable name. Shells won't create it, but
+# env can, and printenv must be able to deal with it.
+echo b > exp || framework_failure_
+env -- -a=b printenv -- -a > out || fail=1
+compare exp out || fail=1
+
+# Silently reject invalid env-var names.
+# Bug present through coreutils 8.0.
+returns_ 1 env a=b=c printenv a=b > out || fail=1
+compare /dev/null out || fail=1
+
+Exit $fail
diff --git a/tests/misc/read-errors.sh b/tests/misc/read-errors.sh
new file mode 100755
index 0000000..7f395bb
--- /dev/null
+++ b/tests/misc/read-errors.sh
@@ -0,0 +1,95 @@
+#!/bin/sh
+# Make sure all of these programs diagnose read errors
+
+# Copyright (C) 2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+
+! cat . >/dev/null 2>&1 || skip_ "Need unreadable directories"
+
+echo "\
+basenc --base32 .
+basenc -d --base64 .
+cat .
+cksum -a blake2b .
+cksum -a bsd .
+cksum -a crc .
+cksum -a md5 .
+cksum -a sha1 .
+cksum -a sha224 .
+cksum -a sha256 .
+cksum -a sha384 .
+cksum -a sha512 .
+cksum -a sm3 .
+cksum -a sysv .
+comm . .
+csplit . 1
+cut -c1 .
+cut -f1 .
+date -f .
+dd if=.
+dircolors .
+expand .
+factor < .
+fmt .
+fold .
+head -n1 .
+head -n-1 .
+head -c1 .
+head -c-1 .
+join . .
+nl .
+numfmt < .
+od .
+paste .
+pr .
+ptx .
+shuf -r .
+shuf -n1 .
+sort .
+split -l1 .
+split -b1 .
+split -C1 .
+split -n1 .
+split -nl/1 .
+split -nr/1 .
+tac .
+tail -n1 .
+tail -c1 .
+tail -n+1 .
+tail -c+1 .
+tee < .
+tr 1 1 < .
+tsort .
+unexpand .
+uniq .
+uniq -c .
+wc .
+wc -c .
+wc -l .
+" |
+sort -k 1b,1 > all_readers || framework_failure_
+
+printf '%s\n' $built_programs |
+sort -k 1b,1 > built_programs || framework_failure_
+
+join all_readers built_programs > built_readers || framework_failure_
+
+while read reader; do
+ eval $reader >/dev/null && { fail=1; echo "$reader: exited with 0" >&2; }
+done < built_readers
+
+Exit $fail
diff --git a/tests/misc/realpath.sh b/tests/misc/realpath.sh
new file mode 100755
index 0000000..251ea19
--- /dev/null
+++ b/tests/misc/realpath.sh
@@ -0,0 +1,111 @@
+#!/bin/sh
+# Validate realpath operation
+
+# Copyright (C) 2011-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ realpath
+
+stat_single=$(stat -c %d:%i /) || framework_failure_
+stat_double=$(stat -c %d:%i //) || framework_failure_
+double_slash=//
+if test x"$stat_single" = x"$stat_double"; then
+ double_slash=/
+fi
+nl='
+'
+
+test -d /dev || framework_failure_
+
+# Setup dir, file, symlink structure
+
+mkdir -p dir1/dir2 || framework_failure_
+ln -s dir1/dir2 ldir2 || framework_failure_
+touch dir1/f dir1/dir2/f || framework_failure_
+ln -s / one || framework_failure_
+ln -s // two || framework_failure_
+ln -s /// three || framework_failure_
+
+# Basic operation
+realpath -Pqz . >/dev/null || fail=1
+# Operand is required
+returns_ 1 realpath >/dev/null || fail=1
+returns_ 1 realpath --relative-base . --relative-to . || fail=1
+returns_ 1 realpath --relative-base . || fail=1
+
+# -e --relative-* require directories
+returns_ 1 realpath -e --relative-to=dir1/f --relative-base=. . || fail=1
+realpath -e --relative-to=dir1/ --relative-base=. . || fail=1
+
+# Note NUL params are unconditionally rejected by canonicalize_filename_mode
+returns_ 1 realpath -m '' || fail=1
+returns_ 1 realpath --relative-base= --relative-to=. . || fail=1
+
+# symlink resolution
+this=$(realpath .)
+test "$(realpath ldir2/..)" = "$this/dir1" || fail=1
+test "$(realpath -L ldir2/..)" = "$this" || fail=1
+test "$(realpath -s ldir2)" = "$this/ldir2" || fail=1
+
+# relative string handling
+test $(realpath -m --relative-to=prefix prefixed/1) = '../prefixed/1' || fail=1
+test $(realpath -m --relative-to=prefixed prefix/1) = '../prefix/1' || fail=1
+test $(realpath -m --relative-to=prefixed prefixed/1) = '1' || fail=1
+
+# Ensure no redundant trailing '/' present, as was the case in v8.15
+test $(realpath -sm --relative-to=/usr /) = '..' || fail=1
+# Ensure no redundant leading '../' present, as was the case in v8.15
+test $(realpath -sm --relative-to=/ /usr) = 'usr' || fail=1
+
+# Ensure --relative-base works
+out=$(realpath -sm --relative-base=/usr --relative-to=/usr /tmp /usr) || fail=1
+test "$out" = "/tmp$nl." || fail=1
+out=$(realpath -sm --relative-base=/ --relative-to=/ / /usr) || fail=1
+test "$out" = ".${nl}usr" || fail=1
+# --relative-to defaults to the value of --relative-base
+out=$(realpath -sm --relative-base=/usr /tmp /usr) || fail=1
+test "$out" = "/tmp$nl." || fail=1
+out=$(realpath -sm --relative-base=/ / /usr) || fail=1
+test "$out" = ".${nl}usr" || fail=1
+# For now, --relative-base must be a prefix of --relative-to, or all output
+# will be absolute (compare to MacOS 'relpath -d dir start end').
+out=$(realpath -sm --relative-base=/usr/local --relative-to=/usr \
+ /usr /usr/local) || fail=1
+test "$out" = "/usr${nl}/usr/local" || fail=1
+
+# Ensure // is handled correctly.
+test "$(realpath / // ///)" = "/$nl$double_slash$nl/" || fail=1
+test "$(realpath one two three)" = "/$nl$double_slash$nl/" || fail=1
+out=$(realpath -sm --relative-to=/ / // /dev //dev) || fail=1
+if test $double_slash = //; then
+ test "$out" = ".$nl//${nl}dev$nl//dev" || fail=1
+else
+ test "$out" = ".$nl.${nl}dev${nl}dev" || fail=1
+fi
+out=$(realpath -sm --relative-to=// / // /dev //dev) || fail=1
+if test $double_slash = //; then
+ test "$out" = "/$nl.$nl/dev${nl}dev" || fail=1
+else
+ test "$out" = ".$nl.${nl}dev${nl}dev" || fail=1
+fi
+out=$(realpath --relative-base=/ --relative-to=// / //) || fail=1
+if test $double_slash = //; then
+ test "$out" = "/$nl//" || fail=1
+else
+ test "$out" = ".$nl." || fail=1
+fi
+
+Exit $fail
diff --git a/tests/misc/selinux.sh b/tests/misc/selinux.sh
new file mode 100755
index 0000000..35ad5f8
--- /dev/null
+++ b/tests/misc/selinux.sh
@@ -0,0 +1,65 @@
+#!/bin/sh
+# Test SELinux-related options.
+
+# Copyright (C) 2007-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ chcon cp ls mv stat
+
+require_root_
+require_selinux_
+skip_if_mcstransd_is_running_
+
+# Create a regular file, dir, fifo.
+touch f || framework_failure_
+mkdir d s1 s2 || framework_failure_
+mkfifo_or_skip_ p
+
+
+# special context that works both with and without mcstransd
+ctx='root:object_r:tmp_t'
+mls_enabled_ && ctx="$ctx:s0"
+
+chcon $ctx f d p || skip_ "Failed to set context: $ctx"
+
+# inspect that context with both ls -Z and stat.
+for i in d f p; do
+ c=$(ls -dogZ $i|cut -d' ' -f3); test x$c = x$ctx || fail=1
+ c=$(stat --printf %C $i); test x$c = x$ctx || fail=1
+done
+
+# ensure that ls -l output includes the ".".
+c=$(ls -l f|cut -c11); test "$c" = . || fail=1
+
+# Copy with an invalid context and ensure it fails
+# Note this may succeed when root and selinux is in permissive mode
+if test "$(getenforce)" = Enforcing; then
+ returns_ 1 cp --context='invalid-selinux-context' f f.cp || fail=1
+fi
+
+# Copy each to a new directory and ensure that context is preserved.
+cp -r --preserve=all d f p s1 || fail=1
+for i in d f p; do
+ c=$(stat --printf %C s1/$i); test x$c = x$ctx || fail=1
+done
+
+# Now, move each to a new directory and ensure that context is preserved.
+mv d f p s2 || fail=1
+for i in d f p; do
+ c=$(stat --printf %C s2/$i); test x$c = x$ctx || fail=1
+done
+
+Exit $fail
diff --git a/tests/misc/sleep.sh b/tests/misc/sleep.sh
new file mode 100755
index 0000000..9082853
--- /dev/null
+++ b/tests/misc/sleep.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+# Validate sleep parameters
+
+# Copyright (C) 2016-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ sleep printf
+getlimits_
+
+# invalid timeouts
+returns_ 1 timeout 10 sleep invalid || fail=1
+returns_ 1 timeout 10 sleep -- -1 || fail=1
+returns_ 1 timeout 10 sleep 42D || fail=1
+returns_ 1 timeout 10 sleep 42d 42day || fail=1
+returns_ 1 timeout 10 sleep nan || fail=1
+returns_ 1 timeout 10 sleep '' || fail=1
+returns_ 1 timeout 10 sleep || fail=1
+
+# subsecond actual sleep
+timeout 10 sleep 0.001 || fail=1
+timeout 10 sleep 0x.002p1 || fail=1
+
+# Using small timeouts for larger sleeps is racy,
+# but false positives should be avoided on most systems
+returns_ 124 timeout 0.1 sleep 1d 2h 3m 4s || fail=1
+returns_ 124 timeout 0.1 sleep inf || fail=1
+returns_ 124 timeout 0.1 sleep $LDBL_MAX || fail=1
+
+# Test locale decimal handling for printf, sleep, timeout
+: ${LOCALE_FR_UTF8=none}
+if test "$LOCALE_FR_UTF8" != "none"; then
+ f=$LOCALE_FR_UTF8
+ locale_decimal=$(LC_ALL=$f env printf '%0.3f' 0.001) || fail=1
+ locale_decimal=$(LC_ALL=$f env printf '%0.3f' "$locale_decimal") || fail=1
+ case "$locale_decimal" in
+ 0?001)
+ LC_ALL=$f timeout 1$locale_decimal sleep "$locale_decimal" || fail=1 ;;
+ *) fail=1 ;;
+ esac
+fi
+
+Exit $fail
diff --git a/tests/misc/stdbuf.sh b/tests/misc/stdbuf.sh
new file mode 100755
index 0000000..c4369f7
--- /dev/null
+++ b/tests/misc/stdbuf.sh
@@ -0,0 +1,125 @@
+#!/bin/sh
+# Exercise stdbuf functionality
+
+# Copyright (C) 2009-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ stdbuf env
+
+getlimits_
+
+# stdbuf fails when the absolute top build dir name contains e.g.,
+# space, TAB, NL
+lf='
+'
+case $abs_top_builddir in
+ *[\\\"\#\$\&\'\`$lf\ \ ]*)
+ skip_ "unsafe absolute build directory name: $abs_top_builddir";;
+esac
+
+# Use a fifo rather than a pipe in the tests below
+# so that the producer (uniq) will wait until the
+# consumer (dd) opens the fifo therefore increasing
+# the chance that dd will read the data from each
+# write separately.
+mkfifo_or_skip_ fifo
+
+
+# Verify input parameter checking
+stdbuf -o1 true || fail=1 # verify size syntax
+stdbuf -oK true || fail=1 # verify size syntax
+stdbuf -o0 true || fail=1 # verify unbuffered syntax
+stdbuf -oL true || fail=1 # verify line buffered syntax
+
+# Capital 'L' required
+# Internal error is a particular status
+returns_ 125 stdbuf -ol true || fail=1
+
+returns_ 125 stdbuf -o$SIZE_OFLOW true || fail=1 # size too large
+returns_ 125 stdbuf -iL true || fail=1 # line buffering stdin disallowed
+returns_ 125 stdbuf true || fail=1 # a buffering mode must be specified
+stdbuf -i0 -o0 -e0 true || fail=1 #check all files
+returns_ 126 env . && { returns_ 126 stdbuf -o1 . || fail=1; } # invalid command
+returns_ 127 stdbuf -o1 no_such || fail=1 # no such command
+
+# Terminate any background processes
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+# Ensure line buffering stdout takes effect
+stdbuf_linebuffer()
+{
+ local delay="$1"
+
+ printf '1\n' > exp
+ > out || framework_failure_
+ dd count=1 if=fifo > out 2> err & pid=$!
+ (printf '1\n'; sleep $delay; printf '2\n') | stdbuf -oL uniq > fifo
+ wait $pid
+ compare exp out
+}
+
+retry_delay_ stdbuf_linebuffer .1 6 || fail=1
+
+stdbuf_unbuffer()
+{
+ local delay="$1"
+
+ # Ensure un buffering stdout takes effect
+ printf '1\n' > exp
+ > out || framework_failure_
+ dd count=1 if=fifo > out 2> err & pid=$!
+ (printf '1\n'; sleep $delay; printf '2\n') | stdbuf -o0 uniq > fifo
+ wait $pid
+ compare exp out
+}
+
+retry_delay_ stdbuf_unbuffer .1 6 || fail=1
+
+# Ensure un buffering stdin takes effect
+# The following works for me, but is racy. I.e., we're depending
+# on dd to run and close the fifo before the second write by uniq.
+# If we add a sleep, then we're just testing -oL
+ # printf '3\n' > exp
+ # dd count=1 if=fifo > /dev/null 2> err &
+ # printf '1\n\2\n3\n' | (stdbuf -i0 -oL uniq > fifo; cat) > out
+ # wait # for dd to complete
+ # compare exp out || fail=1
+# One could remove the need for dd (used to close the fifo to get uniq to quit
+# early), if head -n1 read stdin char by char. Note uniq | head -c2 doesn't
+# suffice due to the buffering implicit in the pipe. sed currently does read
+# stdin char by char, so we can test with 'sed 1q'. However I'm wary about
+# adding this dependency on a program outside of coreutils.
+ # printf '2\n' > exp
+ # printf '1\n2\n' | (stdbuf -i0 sed 1q >/dev/null; cat) > out
+ # compare exp out || fail=1
+
+# Ensure block buffering stdout takes effect
+# We don't currently test block buffering failures as
+# this doesn't work on GLIBC-2.7 or GLIBC-2.9 at least.
+ # stdbuf_blockbuffer()
+ # {
+ # local delay="$1"
+ #
+ # printf '1\n2\n' > exp
+ # dd count=1 if=fifo > out 2> err &
+ # (printf '1\n'; sleep $delay; printf '2\n') | stdbuf -o4 uniq > fifo
+ # wait # for dd to complete
+ # compare exp out
+ # }
+ #
+ # retry_delay_ stdbuf_blockbuffer .1 6 || fail=1
+
+Exit $fail
diff --git a/tests/misc/sync.sh b/tests/misc/sync.sh
new file mode 100755
index 0000000..daedf41
--- /dev/null
+++ b/tests/misc/sync.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+# Test various sync(1) operations
+
+# Copyright (C) 2015-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ sync
+
+touch file || framework_failure_
+
+# fdatasync+syncfs is nonsensical
+returns_ 1 sync --data --file-system || fail=1
+
+# fdatasync needs an operand
+returns_ 1 sync -d || fail=1
+
+# Test syncing of file (fsync) (little side effects)
+sync file || fail=1
+
+# Test syncing of write-only file - which failed since adding argument
+# support to sync in coreutils-8.24.
+chmod 0200 file || framework_failure_
+sync file || fail=1
+
+# Ensure multiple args are processed and diagnosed
+returns_ 1 sync file nofile || fail=1
+
+# Ensure inaccessible dirs give an appropriate error
+mkdir norw || framework_failure_
+chmod 0 norw || framework_failure_
+if ! test -r norw; then
+ returns_ 1 sync norw 2>errt || fail=1
+ # AIX gives "Is a directory"
+ sed 's/Is a directory/Permission denied/' <errt >err || framework_failure_
+ printf "sync: error opening 'norw': Permission denied\n" >exp
+ compare exp err || fail=1
+fi
+
+if test "$fail" != '1'; then
+ # Ensure a fifo doesn't block
+ mkfifo_or_skip_ fifo
+ returns_ 124 timeout 10 sync fifo && fail=1
+fi
+
+Exit $fail
diff --git a/tests/misc/tee.sh b/tests/misc/tee.sh
new file mode 100755
index 0000000..3daf610
--- /dev/null
+++ b/tests/misc/tee.sh
@@ -0,0 +1,145 @@
+#!/bin/sh
+# test for basic tee functionality.
+
+# Copyright (C) 2005-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ tee
+
+echo line >sample || framework_failure_
+
+# POSIX says: "Processing of at least 13 file operands shall be supported."
+for n in 0 1 2 12 13; do
+ files=$(seq $n)
+ rm -f $files
+ tee $files <sample >out || fail=1
+ for f in out $files; do
+ compare sample $f || fail=1
+ done
+done
+
+# Ensure tee treats '-' as the name of a file, as mandated by POSIX.
+# Between v5.3.0 and v8.23, a '-' argument caused tee to send another
+# copy of input to standard output.
+tee - <sample >out 2>err || fail=1
+compare sample ./- || fail=1
+compare sample out || fail=1
+compare /dev/null err || fail=1
+
+# Ensure tee exits early if no more writable outputs
+if test -w /dev/full && test -c /dev/full; then
+ yes | returns_ 1 timeout 10 tee /dev/full 2>err >/dev/full || fail=1
+ # Ensure an error for each of the 2 outputs
+ # (and no redundant errors for stdout).
+ test $(wc -l < err) = 2 || { cat err; fail=1; }
+
+
+ # Ensure we continue with outputs that are OK
+ seq 10000 > multi_read || framework_failure_
+
+ returns_ 1 tee /dev/full out2 2>err >out1 <multi_read || fail=1
+ cmp multi_read out1 || fail=1
+ cmp multi_read out2 || fail=1
+ # Ensure an error for failing output
+ test $(wc -l < err) = 1 || { cat err; fail=1; }
+
+ returns_ 1 tee out1 out2 2>err >/dev/full <multi_read || fail=1
+ cmp multi_read out1 || fail=1
+ cmp multi_read out2 || fail=1
+ # Ensure an error for failing output
+ test $(wc -l < err) = 1 || { cat err; fail=1; }
+fi
+
+case $host_triplet in
+ *aix*) echo 'avoiding due to no way to detect closed outputs on AIX' ;;
+ *)
+# Test iopoll-powered early exit for closed pipes
+tee_exited() { sleep $1; test -f tee.exited; }
+# Currently this functionality is most useful with
+# intermittent input from a terminal, but here we
+# use an input pipe that doesn't write anything
+# but will exit as soon as tee does, or it times out
+retry_delay_ tee_exited .1 7 | # 12.7s (Must be > following timeout)
+{ timeout 10 tee -p 2>err && touch tee.exited; } | :
+test $(wc -l < err) = 0 || { cat err; fail=1; }
+test -f tee.exited || fail=1 ;;
+esac
+
+# Test with unwritable files
+if ! uid_is_privileged_; then # root does not get EPERM.
+ touch file.ro || framework_failure_
+ chmod a-w file.ro || framework_failure_
+ returns_ 1 tee -p </dev/null file.ro || fail=1
+fi
+
+mkfifo_or_skip_ fifo
+
+# Ensure tee handles nonblocking output correctly
+# Terminate any background processes
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+read_fifo_delayed() {
+ { sleep .1; timeout 10 dd of=/dev/null status=none; } <fifo
+}
+read_fifo_delayed & pid=$!
+dd count=20 bs=100K if=/dev/zero status=none |
+{
+ dd count=0 oflag=nonblock status=none
+ tee || { cleanup_; touch tee.fail; }
+} >fifo
+test -f tee.fail && fail=1 || cleanup_
+
+# Ensure tee honors --output-error modes
+read_fifo() { timeout 10 dd count=1 if=fifo of=/dev/null status=none & }
+
+# Determine platform sigpipe exit status
+read_fifo
+yes >fifo
+pipe_status=$?
+
+# Default operation is to continue on output errors but exit silently on SIGPIPE
+read_fifo
+yes | returns_ $pipe_status timeout 10 tee ./e/noent 2>err >fifo || fail=1
+test $(wc -l < err) = 1 || { cat err; fail=1; }
+
+# With -p, SIGPIPE is suppressed, exit 0 for EPIPE when all outputs finished
+read_fifo
+yes | timeout 10 tee -p 2>err >fifo || fail=1
+test $(wc -l < err) = 0 || { cat err; fail=1; }
+
+# With --output-error=warn, exit 1 for EPIPE when all outputs finished
+read_fifo
+yes | returns_ 1 timeout 10 tee --output-error=warn 2>err >fifo || fail=1
+test $(wc -l < err) = 1 || { cat err; fail=1; }
+
+# With --output-error=exit, exit 1 immediately for EPIPE
+read_fifo
+yes | returns_ 1 timeout 10 tee --output-error=exit /dev/null 2>err >fifo \
+ || fail=1
+test $(wc -l < err) = 1 || { cat err; fail=1; }
+
+# With --output-error=exit, exit 1 immediately on output error
+read_fifo
+yes | returns_ 1 timeout 10 tee --output-error=exit ./e/noent 2>err >fifo \
+ || fail=1
+test $(wc -l < err) = 1 || { cat err; fail=1; }
+
+# With --output-error=exit-nopipe, exit 0 for EPIPE
+read_fifo
+yes | timeout 10 tee --output-error=exit-nopipe 2>err >fifo || fail=1
+test $(wc -l < err) = 0 || { cat err; fail=1; }
+
+wait
+Exit $fail
diff --git a/tests/misc/time-style.sh b/tests/misc/time-style.sh
new file mode 100755
index 0000000..3433836
--- /dev/null
+++ b/tests/misc/time-style.sh
@@ -0,0 +1,119 @@
+#!/bin/sh
+# Test --time-style in programs like 'ls'.
+
+# Copyright (C) 2016-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ du
+print_ver_ ls
+print_ver_ pr
+
+echo hello >a || framework_failure_
+
+# The tests assume this is an old timestamp in northern hemisphere summer.
+TZ=UTC0 touch -d '1970-07-08 09:10:11' a || framework_failure_
+
+for tz in UTC0 PST8 PST8PDT,M3.2.0,M11.1.0 XXXYYY-12:30; do
+ for style in full-iso long-iso iso locale '+%Y-%m-%d %H:%M:%S %z (%Z)' \
+ +%%b%b%%b%b; do
+ test "$style" = locale ||
+ TZ=$tz LC_ALL=C du --time --time-style="$style" a >>duout 2>>err || fail=1
+ TZ=$tz LC_ALL=C ls -no --time-style="$style" a >>lsout 2>>err || fail=1
+ case $style in
+ (+*) TZ=$tz LC_ALL=C pr -D"$style" a >>prout 2>>err || fail=1 ;;
+ esac
+ done
+done
+
+sed 's/[^ ]* //' duout >dued || framework_failure_
+sed 's/[^ ]* *[^ ]* *[^ ]* *[^ ]* *//' lsout >lsed || framework_failure_
+sed '/^$/d' prout >pred || framework_failure_
+
+cat <<\EOF > duexp || framework_failure_
+1970-07-08 09:10:11.000000000 +0000 a
+1970-07-08 09:10 a
+1970-07-08 a
+1970-07-08 09:10:11 +0000 (UTC) a
+%bJul%bJul a
+1970-07-08 01:10:11.000000000 -0800 a
+1970-07-08 01:10 a
+1970-07-08 a
+1970-07-08 01:10:11 -0800 (PST) a
+%bJul%bJul a
+1970-07-08 02:10:11.000000000 -0700 a
+1970-07-08 02:10 a
+1970-07-08 a
+1970-07-08 02:10:11 -0700 (PDT) a
+%bJul%bJul a
+1970-07-08 21:40:11.000000000 +1230 a
+1970-07-08 21:40 a
+1970-07-08 a
+1970-07-08 21:40:11 +1230 (XXXYYY) a
+%bJul%bJul a
+EOF
+
+cat <<\EOF > lsexp || framework_failure_
+1970-07-08 09:10:11.000000000 +0000 a
+1970-07-08 09:10 a
+1970-07-08 a
+Jul 8 1970 a
+1970-07-08 09:10:11 +0000 (UTC) a
+%bJul%bJul a
+1970-07-08 01:10:11.000000000 -0800 a
+1970-07-08 01:10 a
+1970-07-08 a
+Jul 8 1970 a
+1970-07-08 01:10:11 -0800 (PST) a
+%bJul%bJul a
+1970-07-08 02:10:11.000000000 -0700 a
+1970-07-08 02:10 a
+1970-07-08 a
+Jul 8 1970 a
+1970-07-08 02:10:11 -0700 (PDT) a
+%bJul%bJul a
+1970-07-08 21:40:11.000000000 +1230 a
+1970-07-08 21:40 a
+1970-07-08 a
+Jul 8 1970 a
+1970-07-08 21:40:11 +1230 (XXXYYY) a
+%bJul%bJul a
+EOF
+
+cat <<\EOF > prexp || framework_failure_
++1970-07-08 09:10:11 +0000 (UTC) a Page 1
+hello
++%bJul%bJul a Page 1
+hello
++1970-07-08 01:10:11 -0800 (PST) a Page 1
+hello
++%bJul%bJul a Page 1
+hello
++1970-07-08 02:10:11 -0700 (PDT) a Page 1
+hello
++%bJul%bJul a Page 1
+hello
++1970-07-08 21:40:11 +1230 (XXXYYY) a Page 1
+hello
++%bJul%bJul a Page 1
+hello
+EOF
+
+compare duexp dued || fail=1
+compare lsexp lsed || fail=1
+compare prexp pred || fail=1
+compare /dev/null err || fail=1
+
+Exit $fail
diff --git a/tests/misc/tsort.pl b/tests/misc/tsort.pl
new file mode 100755
index 0000000..ca3aa3b
--- /dev/null
+++ b/tests/misc/tsort.pl
@@ -0,0 +1,66 @@
+#!/usr/bin/perl
+# Test "tsort".
+
+# Copyright (C) 1999-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use strict;
+
+(my $program_name = $0) =~ s|.*/||;
+
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+my @Tests =
+ (
+ ['cycle-1', {IN => {f => "t b\nt s\ns t\n"}}, {OUT => "s\nt\nb\n"},
+ {EXIT => 1},
+ {ERR => "tsort: f: input contains a loop:\ntsort: s\ntsort: t\n"} ],
+ ['cycle-2', {IN => {f => "t x\nt s\ns t\n"}}, {OUT => "s\nt\nx\n"},
+ {EXIT => 1},
+ {ERR => "tsort: f: input contains a loop:\ntsort: s\ntsort: t\n"} ],
+
+ ['posix-1', {IN => "a b c c d e\ng g\nf g e f\nh h\n"},
+ {OUT => "a\nc\nd\nh\nb\ne\nf\ng\n"}],
+ ['posix-2', {IN => "b a\nd c\nz h x h r h\n"},
+ {OUT => "b\nd\nr\nx\nz\na\nc\nh\n"}],
+
+ ['linear-1', {IN => "a b b c c d d e e f f g\n"},
+ {OUT => "a\nb\nc\nd\ne\nf\ng\n"}],
+
+ ['tree-1', {IN => "a b b c c d d e e f f g\nc x x y y z\n"},
+ {OUT => "a\nb\nc\nx\nd\ny\ne\nz\nf\ng\n"}],
+ ['tree-2', {IN => "a b b c c d d e e f f g\nc x x y y z\nf r r s s t\n"},
+ {OUT => "a\nb\nc\nx\nd\ny\ne\nz\nf\nr\ng\ns\nt\n"}],
+
+ # Before coreutils-5.0.1, given an odd number of input tokens,
+ # tsort would accept that and treat the input as if an additional
+ # copy of the final token were appended.
+ ['odd', {IN => "a\n"},
+ {EXIT => 1},
+ {ERR => "tsort: odd.1: input contains an odd number of tokens\n"}],
+
+ ['only-one', {IN => {f => ""}}, {IN => {g => ""}},
+ {EXIT => 1},
+ {ERR => "tsort: extra operand 'g'\n"
+ . "Try 'tsort --help' for more information.\n"}],
+ );
+
+my $save_temps = $ENV{DEBUG};
+my $verbose = $ENV{VERBOSE};
+
+my $prog = 'tsort';
+my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;
diff --git a/tests/misc/unexpand.pl b/tests/misc/unexpand.pl
new file mode 100755
index 0000000..d78a1bc
--- /dev/null
+++ b/tests/misc/unexpand.pl
@@ -0,0 +1,135 @@
+#!/usr/bin/perl
+# Test "unexpand".
+
+# Copyright (C) 2000-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use strict;
+
+my $limits = getlimits ();
+
+(my $program_name = $0) =~ s|.*/||;
+
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+my $prog = 'unexpand';
+
+my @Tests =
+ (
+ ['a1', {IN=> ' 'x 1 ."y\n"}, {OUT=> ' 'x 1 ."y\n"}],
+ ['a2', {IN=> ' 'x 2 ."y\n"}, {OUT=> ' 'x 2 ."y\n"}],
+ ['a3', {IN=> ' 'x 3 ."y\n"}, {OUT=> ' 'x 3 ."y\n"}],
+ ['a4', {IN=> ' 'x 4 ."y\n"}, {OUT=> ' 'x 4 ."y\n"}],
+ ['a5', {IN=> ' 'x 5 ."y\n"}, {OUT=> ' 'x 5 ."y\n"}],
+ ['a6', {IN=> ' 'x 6 ."y\n"}, {OUT=> ' 'x 6 ."y\n"}],
+ ['a7', {IN=> ' 'x 7 ."y\n"}, {OUT=> ' 'x 7 ."y\n"}],
+ ['a8', {IN=> ' 'x 8 ."y\n"}, {OUT=> "\ty\n"}],
+
+ ['aa-1', '-a', {IN=> 'w'.' 'x 1 ."y\n"}, {OUT=> 'w'.' 'x 1 ."y\n"}],
+ ['aa-2', '-a', {IN=> 'w'.' 'x 2 ."y\n"}, {OUT=> 'w'.' 'x 2 ."y\n"}],
+ ['aa-3', '-a', {IN=> 'w'.' 'x 3 ."y\n"}, {OUT=> 'w'.' 'x 3 ."y\n"}],
+ ['aa-4', '-a', {IN=> 'w'.' 'x 4 ."y\n"}, {OUT=> 'w'.' 'x 4 ."y\n"}],
+ ['aa-5', '-a', {IN=> 'w'.' 'x 5 ."y\n"}, {OUT=> 'w'.' 'x 5 ."y\n"}],
+ ['aa-6', '-a', {IN=> 'w'.' 'x 6 ."y\n"}, {OUT=> 'w'.' 'x 6 ."y\n"}],
+ ['aa-7', '-a', {IN=> 'w'.' 'x 7 ."y\n"}, {OUT=> "w\ty\n"}],
+ ['aa-8', '-a', {IN=> 'w'.' 'x 8 ."y\n"}, {OUT=> "w\t y\n"}],
+
+ ['b-1', '-t', '2,4', {IN=> " ."}, {OUT=>"\t\t ."}],
+ # These would infloop prior to textutils-2.0d.
+
+ ['infloop-1', '-t', '1,2', {IN=> " \t\t .\n"}, {OUT=>"\t\t\t .\n"}],
+ ['infloop-2', '-t', '4,5', {IN=> ' 'x4 . "\t\t \n"}, {OUT=>"\t\t\t \n"}],
+ ['infloop-3', '-t', '2,3', {IN=> "x \t\t \n"}, {OUT=>"x\t\t\t \n"}],
+ ['infloop-4', '-t', '1,2', {IN=> " \t\t \n"}, {OUT=>"\t\t\t \n"}],
+ ['c-1', '-t', '1,2', {IN=> "x\t\t .\n"}, {OUT=>"x\t\t .\n"}],
+
+ # -t implies -a
+ # Feature addition (--first-only) prompted by a report from Jie Xu.
+ ['tabs-1', qw(-t 3), {IN=> " a b\n"}, {OUT=>"\ta\tb\n"}],
+ ['tabs-2', qw(-t 3 --first-only), {IN=> " a b\n"}, {OUT=>"\ta b\n"}],
+
+ # blanks
+ ['blanks-1', qw(-t 1), {IN=> " b c d\n"}, {OUT=> "\tb\t\tc\t\t\td\n"}],
+ ['blanks-2', qw(-t 1), {IN=> "a \n"}, {OUT=> "a \n"}],
+ ['blanks-3', qw(-t 1), {IN=> "a \n"}, {OUT=> "a\t\t\n"}],
+ ['blanks-4', qw(-t 1), {IN=> "a \n"}, {OUT=> "a\t\t\t\n"}],
+ ['blanks-5', qw(-t 1), {IN=> "a "}, {OUT=> "a "}],
+ ['blanks-6', qw(-t 1), {IN=> "a "}, {OUT=> "a\t\t"}],
+ ['blanks-7', qw(-t 1), {IN=> "a "}, {OUT=> "a\t\t\t"}],
+ ['blanks-8', qw(-t 1), {IN=> " a a a\n"}, {OUT=> "\ta a\t\ta\n"}],
+ ['blanks-9', qw(-t 2), {IN=> " a a a\n"}, {OUT=> "\t a\ta\t a\n"}],
+ ['blanks-10', '-t', '3,4', {IN=> "0 2 4 6\t8\n"}, {OUT=> "0 2 4 6\t8\n"}],
+ ['blanks-11', '-t', '3,4', {IN=> " 4\n"}, {OUT=> "\t\t4\n"}],
+ ['blanks-12', '-t', '3,4', {IN=> "01 4\n"}, {OUT=> "01\t\t4\n"}],
+ ['blanks-13', '-t', '3,4', {IN=> "0 4\n"}, {OUT=> "0\t\t4\n"}],
+
+ # POSIX says spaces should only follow tabs. Also a single
+ # trailing space is not converted to a tab, when before
+ # a field starting with non blanks
+ ['posix-1', '-a', {IN=> "1234567 \t1\n"}, {OUT=>"1234567\t\t1\n"}],
+ ['posix-2', '-a', {IN=> "1234567 \t1\n"}, {OUT=>"1234567\t\t1\n"}],
+ ['posix-3', '-a', {IN=> "1234567 \t1\n"}, {OUT=>"1234567\t\t1\n"}],
+ ['posix-4', '-a', {IN=> "1234567\t1\n"}, {OUT=>"1234567\t1\n"}],
+ ['posix-5', '-a', {IN=> "1234567 1\n"}, {OUT=>"1234567\t 1\n"}],
+ ['posix-6', '-a', {IN=> "1234567 1\n"}, {OUT=>"1234567 1\n"}],
+
+ # It is debatable whether this test should require an environment
+ # setting of e.g., _POSIX2_VERSION=1.
+ ['obs-ovflo', "-$limits->{UINTMAX_OFLOW}", {IN=>''}, {OUT=>''},
+ {EXIT => 1}, {ERR => "$prog: tab stop value is too large\n"}],
+
+
+ # Test input with backspaces '\b' ('bs1' is the baseline, without \b)
+ # Note: If users report errors in these tests, copy&pasting results from
+ # their terminate output might be confusing due to '\b' overriding
+ # characters. For details see '\b' tests in 'expand.pl'.
+ ['bs1', '-a -t4', {IN=>"aa c\n"}, {OUT=>"aa\tc\n"}],
+ ['bs2', '-a -t4', {IN=>"aa\b c\n"}, {OUT=>"aa\b c\n"}],
+ ['bs3', '-a -t4', {IN=>"aa\b c\n"}, {OUT=>"aa\b\tc\n"}],
+ ['bs4', '-a -t3', {IN=>"aa\b c\n"}, {OUT=>"aa\b\tc\n"}],
+
+ # Undocumented feature:
+ # treat "unexpand -7" as "unexpand --first-only --tabs 7" ,
+ # and "unexpand -90" as "unexpand --first-only --tabs 90",
+ ['u1', '-a -3', {IN=>"a b c"}, {OUT=>"a\tb\tc"}],
+ ['u2', '-a -4,9', {IN=>"a b c"}, {OUT=>"a\tb\tc"}],
+ ['u3', '-a -11', {IN=>"a b"}, {OUT=>"a\tb"}],
+ # Test all digits (for full code coverage)
+ ['u4', '-a -2,6', {IN=>"a b c"}, {OUT=>"a b\tc"}],
+ ['u5', '-a -7', {IN=>"a b"}, {OUT=>"a\tb"}],
+ ['u6', '-a -8', {IN=>"a b"}, {OUT=>"a\tb"}],
+ # This syntax is handled internally as "-3, -9"
+ ['u7', '-a -3,9', {IN=>"a b c"}, {OUT=>"a\tb\tc"}],
+ # Default (without -a) is --first-only:
+ ['u8', '-3', {IN=>" a b"}, {OUT=>"\ta b"}],
+
+ # Arguably this should minimize translation as is done on Solaris.
+ # I.e., not modify the input. But since the result is equivalent,
+ # and to be consistent in output with older versions, we output
+ # a '\t' rather than a space for the second tab position.
+ # For more detailed comparison with other implementations see:
+ # https://lists.gnu.org/r/coreutils/2016-06/msg00015.html
+ # https://lists.gnu.org/r/coreutils/2016-07/msg00011.html
+ ['ts1', '-t8,9', {IN=>"x\t \t y\n"}, {OUT=>"x\t\t\t y\n"}],
+ # There is no ambiguity here. This should always be the output.
+ ['ts2', '-t5,8', {IN=>"x\t \t y\n"}, {OUT=>"x\t\t y\n"}],
+ );
+
+my $save_temps = $ENV{DEBUG};
+my $verbose = $ENV{VERBOSE};
+
+my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;
diff --git a/tests/misc/usage_vs_getopt.sh b/tests/misc/usage_vs_getopt.sh
new file mode 100755
index 0000000..f4f1213
--- /dev/null
+++ b/tests/misc/usage_vs_getopt.sh
@@ -0,0 +1,96 @@
+#!/bin/sh
+# Verify that all options mentioned in usage are recognized by getopt.
+
+# Copyright (C) 2017-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+
+checkprg () {
+ prg="$1"
+
+ # Get stderr output for unrecognized options for later use as a pattern.
+ # Also consider the expected exit status of each program.
+ rcexp=1
+ case "$prg" in
+ dir | ls | printenv | sort | tty | vdir ) rcexp=2 ;;
+ env | chroot | nice | nohup | runcon | stdbuf | timeout ) rcexp=125 ;;
+ esac
+ # Write the pattern for a long, unknown option into a pattern file.
+ o='thisoptiondoesnotexist'
+ returns_ $rcexp $prg --$o >/dev/null 2> err || fail=1
+ grep -F "$o" err || framework_failure_
+ sed -n "1s/--$o/OPT/p" < err > pat || framework_failure_
+
+ # Append the pattern for a short unknown option.
+ returns_ $rcexp $prg -/ >/dev/null 2> err || fail=1
+ grep " '*/'*" err || framework_failure_
+ # The following only handles the common case that has single quotes,
+ # as it simplifies to identify missing options only on common systems.
+ sed -n "1s/'\/'/'OPT'/p" < err >> pat || framework_failure_
+
+ # Get output for --help.
+ $prg --help > help || fail=1
+
+ # Extract all options mention in the above --help output.
+ nl="
+ "
+ sed -n -e '/--version/q' \
+ -e 's/^ \{2,6\}-/-/; s/ .*//; s/[=[].*//; s/, /\'"$nl"'/g; s/^-/-/p' help \
+ > opts || framework_failure_
+ cat opts # for debugging purposes
+
+ # Test all options mentioned in usage (but --version).
+ while read opt; do
+ test "x$opt" = 'x--help' \
+ && continue
+ # Append --help to be on the safe side: the option under test either
+ # requires a further argument, or --help triggers usage(); so $prg should
+ # exit without performing its regular operation.
+ # Add a 2nd --help for the 'env -u --help' case.
+ $prg "$opt" --help --help </dev/null >out 2>err1
+ rc=$?
+ # In the --help case, i.e., when the option under test has been accepted,
+ # the exit code should be Zero.
+ if [ $rc = 0 ]; then
+ compare help out || fail=1
+ else
+ # Else $prg should have complained about a missing argument.
+ # Catch false positives.
+ case "$prg/$opt" in
+ 'pr/-COLUMN') continue;;
+ esac
+ # Replace $opt in stderr output by the neutral placeholder.
+ # Handle both long and short option error messages.
+ sed -e "s/$opt/OPT/" -e "s/'.'/'OPT'/" < err1 > err || framework_failure_
+ # Fail if the stderr output matches the above provoked error.
+ grep -Ff pat err && { fail=1; cat err1; }
+ fi
+ done < opts
+}
+
+for prg in $built_programs; do
+ case "$prg" in
+ # Skip utilities entirely which have special option parsing.
+ '[' | expr | stty )
+ continue;;
+ # Wrap some utilities known by the shell by env.
+ echo | false | kill | printf | pwd | sleep | test | true )
+ prg="env $prg";;
+ esac
+ checkprg $prg
+done
+
+Exit $fail
diff --git a/tests/misc/write-errors.sh b/tests/misc/write-errors.sh
new file mode 100755
index 0000000..4f17028
--- /dev/null
+++ b/tests/misc/write-errors.sh
@@ -0,0 +1,65 @@
+#!/bin/sh
+# Make sure all of these programs promptly diagnose write errors.
+
+# Copyright (C) 2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ timeout
+
+if ! test -w /dev/full || ! test -c /dev/full; then
+ skip_ '/dev/full is required'
+fi
+
+# Writers that may output data indefinitely
+# First word in command line is checked against built programs
+echo "\
+cat /dev/zero
+comm -z /dev/zero /dev/zero
+cut -z -c1- /dev/zero
+cut -z -f1- /dev/zero
+dd if=/dev/zero
+expand /dev/zero
+factor --version; yes 1 | factor
+# TODO: fmt /dev/zero
+# TODO: fold -b /dev/zero
+head -z -n-1 /dev/zero
+join -a 1 -z /dev/zero /dev/null
+# TODO: nl --version; yes | nl
+# TODO: numfmt --version; yes 1 | numfmt
+od -v /dev/zero
+paste /dev/zero
+# TODO: pr /dev/zero
+seq inf
+tail -n+1 -z /dev/zero
+tee < /dev/zero
+tr . . < /dev/zero
+unexpand /dev/zero
+uniq -z -D /dev/zero
+yes
+" |
+sort -k 1b,1 > all_writers || framework_failure_
+
+printf '%s\n' $built_programs |
+sort -k 1b,1 > built_programs || framework_failure_
+
+join all_writers built_programs > built_writers || framework_failure_
+
+while read writer; do
+ timeout 10 $SHELL -c "$writer > /dev/full"
+ test $? = 124 && { fail=1; echo "$writer: failed to exit" >&2; }
+done < built_writers
+
+Exit $fail
diff --git a/tests/misc/xattr.sh b/tests/misc/xattr.sh
new file mode 100755
index 0000000..1416578
--- /dev/null
+++ b/tests/misc/xattr.sh
@@ -0,0 +1,130 @@
+#!/bin/sh
+# Ensure that cp --preserve=xattr, cp --preserve=all and mv preserve extended
+# attributes and install does not preserve extended attributes.
+# cp -a should preserve xattr, error diagnostics should not be displayed
+
+# Copyright (C) 2009-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ cp mv ginstall
+
+# Skip this test if cp was built without xattr support:
+touch src dest || framework_failure_
+cp --preserve=xattr -n src dest \
+ || skip_ "coreutils built without xattr support"
+
+# this code was taken from test mv/backup-is-src
+cleanup_() { rm -rf "$other_partition_tmpdir"; }
+. "$abs_srcdir/tests/other-fs-tmpdir"
+b_other="$other_partition_tmpdir/b"
+rm -f "$b_other" || framework_failure_
+
+# testing xattr name-value pair
+xattr_name="user.foo"
+xattr_value="bar"
+xattr_pair="$xattr_name=\"$xattr_value\""
+
+# create new file and check its xattrs
+touch a || framework_failure_
+getfattr -d a >out_a || skip_ "failed to get xattr of file"
+grep -F "$xattr_pair" out_a && framework_failure_
+
+# try to set user xattr on file
+setfattr -n "$xattr_name" -v "$xattr_value" a >out_a \
+ || skip_ "failed to set xattr of file"
+getfattr -d a >out_a || skip_ "failed to get xattr of file"
+grep -F "$xattr_pair" out_a \
+ || skip_ "failed to set xattr of file"
+
+
+# cp should not preserve xattr by default
+cp a b || fail=1
+getfattr -d b >out_b || skip_ "failed to get xattr of file"
+grep -F "$xattr_pair" out_b && fail=1
+
+# test if --preserve=xattr option works
+cp --preserve=xattr a b || fail=1
+getfattr -d b >out_b || skip_ "failed to get xattr of file"
+grep -F "$xattr_pair" out_b || fail=1
+
+# test if --preserve=all option works
+cp --preserve=all a c || fail=1
+getfattr -d c >out_c || skip_ "failed to get xattr of file"
+grep -F "$xattr_pair" out_c || fail=1
+
+# cp's -a option must produce no diagnostics.
+cp -a a d 2>err && { compare /dev/null err || fail=1; }
+getfattr -d d >out_d || skip_ "failed to get xattr of file"
+grep -F "$xattr_pair" out_d || fail=1
+
+# test if --preserve=xattr works even for files without write access
+chmod a-w a || framework_failure_
+rm -f e
+cp --preserve=xattr a e || fail=1
+getfattr -d e >out_e || skip_ "failed to get xattr of file"
+grep -F "$xattr_pair" out_e || fail=1
+
+# Ensure that permission bits are preserved, too.
+src_perm=$(stat --format=%a a)
+dst_perm=$(stat --format=%a e)
+test "$dst_perm" = "$src_perm" || fail=1
+
+chmod u+w a || framework_failure_
+
+rm b || framework_failure_
+
+# install should never preserve xattr
+ginstall a b || fail=1
+getfattr -d b >out_b || skip_ "failed to get xattr of file"
+grep -F "$xattr_pair" out_b && fail=1
+
+# mv should preserve xattr when renaming within a file system.
+# This is implicitly done by rename () and doesn't need explicit
+# xattr support in mv.
+mv a b || fail=1
+getfattr -d b >out_b || skip_ "failed to get xattr of file"
+grep -F "$xattr_pair" out_b || cat >&2 <<EOF
+=================================================================
+$0: WARNING!!!
+rename () does not preserve extended attributes
+=================================================================
+EOF
+
+# try to set user xattr on file on other partition
+test_mv=1
+touch "$b_other" || framework_failure_
+setfattr -n "$xattr_name" -v "$xattr_value" "$b_other" >out_a \
+ || test_mv=0
+getfattr -d "$b_other" >out_b || test_mv=0
+grep -F "$xattr_pair" out_b || test_mv=0
+rm -f "$b_other" || framework_failure_
+
+if test $test_mv -eq 1; then
+ # mv should preserve xattr when copying content from one partition to another
+ mv b "$b_other" || fail=1
+ getfattr -d "$b_other" >out_b ||
+ skip_ "failed to get xattr of file"
+ grep -F "$xattr_pair" out_b || fail=1
+else
+ cat >&2 <<EOF
+=================================================================
+$0: WARNING!!!
+failed to set xattr of file $b_other
+=================================================================
+EOF
+fi
+
+Exit $fail
diff --git a/tests/misc/xstrtol.pl b/tests/misc/xstrtol.pl
new file mode 100755
index 0000000..dfdaee0
--- /dev/null
+++ b/tests/misc/xstrtol.pl
@@ -0,0 +1,49 @@
+#!/usr/bin/perl
+# exercise xstrtol's diagnostics via pr
+
+# Copyright (C) 2007-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use strict;
+
+(my $ME = $0) =~ s|.*/||;
+
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+my $prog = 'pr';
+my $too_big = '9' x 81; # Big enough to overflow a 256-bit integer.
+my @Tests =
+ (
+ # test-name, [option, option, ...] {OUT=>"expected-output"}
+
+ ['inval-suffix', "--pages=${too_big}h", {EXIT => 1},
+ {ERR=>"$prog: invalid suffix in --pages argument '${too_big}h'\n"}],
+
+ ['too-big', "--pages=$too_big", {EXIT => 1},
+ {ERR=>"$prog: --pages argument '$too_big' too large\n"}],
+
+ ['simply-inval', "--pages=x", {EXIT => 1},
+ {ERR=>"$prog: invalid --pages argument 'x'\n"}],
+
+ ['inv-pg-range', "--pages=9x", {EXIT => 1},
+ {ERR=>"$prog: invalid page range '9x'\n"}],
+ );
+
+my $save_temps = $ENV{DEBUG};
+my $verbose = $ENV{VERBOSE};
+
+my $fail = run_tests ($ME, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;
diff --git a/tests/misc/yes.sh b/tests/misc/yes.sh
new file mode 100755
index 0000000..9cd07d4
--- /dev/null
+++ b/tests/misc/yes.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+# Validate yes buffer handling
+
+# Copyright (C) 2015-2023 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ yes
+
+# Check basic operation
+test "$(yes | head -n1)" = 'y' || fail=1
+
+# Check various single item sizes, with the most important
+# size being BUFSIZ used for the local buffer to yes(1).
+# Note a \n is added, so actual sizes required internally
+# are 1 more than the size used here.
+for size in 1 1999 4095 4096 8191 8192 16383 16384; do
+ printf "%${size}s\n" '' > out.1
+ yes "$(printf %${size}s '')" | head -n2 | uniq > out.2
+ compare out.1 out.2 || fail=1
+done
+
+# Check the many small items case,
+# both fitting and overflowing the internal buffer.
+# First check that 4000 arguments supported.
+if test 4000 -eq $(sh -c 'echo $#' 0 $(seq 4000)); then
+ for i in 100 4000; do
+ seq $i | paste -s -d ' ' | sed p > out.1
+ yes $(seq $i) | head -n2 > out.2
+ compare out.1 out.2 || fail=1
+ done
+fi
+
+# Check a single appropriate diagnostic is output on write error
+if test -w /dev/full && test -c /dev/full; then
+ # The single output diagnostic expected,
+ # (without the possibly varying :strerror(ENOSPC) suffix).
+ printf '%s\n' "yes: standard output" > exp
+
+ for size in 1 16384; do
+ returns_ 1 yes "$(printf %${size}s '')" >/dev/full 2>errt || fail=1
+ sed 's/\(yes:.*\):.*/\1/' errt > err
+ compare exp err || fail=1
+ done
+fi
+
+Exit $fail