4 # Possible improvements:
6 # - distinguish stdout and stderr output
7 # - add environment variable like assignments
8 # - run up to a specific line
9 # - resume at a specific line
15 use POSIX qw(isatty setuid);
18 no warnings qw(taint);
22 my ($OK, $FAILED) = ("ok", "failed");
23 if (isatty(fileno(STDOUT))) {
24 $OK = "\033[32m" . $OK . "\033[m";
25 $FAILED = "\033[31m\033[1m" . $FAILED . "\033[m";
29 sub process_test($$$$);
31 my ($prog, $in, $out) = ([], [], []);
34 my ($tests, $failed) = (0,0);
37 if (!defined($ARGV[0])) {
38 print "No test file specified\n";
42 open($testfile, $ARGV[0]) or die "Can't open file $ARGV[0]: $!";
44 my $line = <$testfile>; $line_number++;
45 if (defined $line && $line !~ /\%s/) {
46 # Substitute %VAR and %{VAR} with environment variables.
47 $line =~ s[%(?:(\w+)|\{(\w+)\})][$ENV{"$1$2"}]eg;
50 if ($line =~ s/^\s*< ?//) {
52 } elsif ($line =~ s/^\s*> ?//) {
55 process_test($prog, $prog_line, $in, $out);
60 if ($line =~ s/^\s*\$ ?//) {
61 $line =~ s/\s+#.*//; # remove comments here...
62 $prog = [ map { s/\\(.)/$1/g; $_ } split /(?<!\\)\s+/, $line ];
63 $prog_line = $line_number;
68 process_test($prog, $prog_line, $in, $out);
74 my $status = sprintf("%d commands (%d passed, %d failed)",
75 $tests, $tests-$failed, $failed);
76 if (isatty(fileno(STDOUT))) {
78 $status = "\033[31m\033[1m" . $status . "\033[m";
80 $status = "\033[32m" . $status . "\033[m";
87 sub process_test($$$$) {
88 my ($prog, $prog_line, $in, $out) = @_;
93 print "[$prog_line] \$ ", join(' ',
94 map { s/\s/\\$&/g; $_ } @$p), " -- ";
95 my $result = exec_test($prog, $in);
97 my $nmax = (@$out > @$result) ? @$out : @$result;
98 for (my $n=0; $n < $nmax; $n++) {
99 if (!defined($out->[$n]) || !defined($result->[$n]) ||
100 ($result->[$n] !~ m/\Q$out->[$n]\E/)) {
105 $failed++ unless $good;
106 print $good ? $OK : $FAILED, "\n";
108 for (my $n=0; $n < $nmax; $n++) {
109 my $l = defined($out->[$n]) ? $out->[$n] : "~";
111 my $r = defined($result->[$n]) ? $result->[$n] : "~";
113 print sprintf("%-37s %s %-39s\n", $l, $l eq $r ? "|" : "?", $r);
116 print join('', @$result);
126 my ($login, $pass, $uid, $gid) = getpwnam($user)
127 or return [ "su: user $user does not exist\n" ];
129 my $fh = new FileHandle("/etc/group")
130 or return [ "opening /etc/group: $!\n" ];
133 my ($group, $passwd, $gid, $users) = split /:/;
134 foreach my $u (split /,/, $users) {
141 my $groups = join(" ", ($gid, $gid, @groups));
142 #print STDERR "[[$groups]]\n";
143 $! = 0; # reset errno
148 return [ "su: $!\n" ];
154 return [ "su: $prog->[1]: $!\n" ];
157 #print STDERR "[($>,$<)($(,$))]";
165 my $gid = getgrnam($group)
166 or return [ "sg: group $group does not exist\n" ];
167 my %groups = map { $_ eq $gid ? () : ($_ => 1) } (split /\s/, $));
169 #print STDERR "<<", join("/", keys %groups), ">>\n";
170 my $groups = join(" ", ($gid, $gid, keys %groups));
171 #print STDERR "[[$groups]]\n";
172 $! = 0; # reset errno
184 return [ "sg: $!\n" ];
186 print STDERR "[($>,$<)($(,$))]";
192 my ($prog, $in) = @_;
193 local (*IN, *IN_DUP, *IN2, *OUT_DUP, *OUT, *OUT2);
194 my $needs_shell = (join('', @$prog) =~ /[][|<>"'`\$\*\?]/);
196 if ($prog->[0] eq "umask") {
197 umask oct $prog->[1];
199 } elsif ($prog->[0] eq "cd") {
200 if (!chdir $prog->[1]) {
201 return [ "chdir: $prog->[1]: $!\n" ];
204 } elsif ($prog->[0] eq "su") {
205 return su($prog->[1]);
206 } elsif ($prog->[0] eq "sg") {
207 return sg($prog->[1]);
211 or die "Can't create pipe for reading: $!";
212 open *IN_DUP, "<&STDIN"
215 or die "Can't duplicate pipe for reading: $!";
218 open *OUT_DUP, ">&STDOUT"
219 or die "Can't duplicate STDOUT: $!";
221 or die "Can't create pipe for writing: $!";
222 open *STDOUT, ">&OUT2"
223 or die "Can't duplicate pipe for writing: $!";
226 *STDOUT->autoflush();
232 open *STDIN, "<&IN_DUP"
233 or die "Can't duplicate STDIN: $!";
235 or die "Can't close STDIN duplicate: $!";
237 open *STDOUT, ">&OUT_DUP"
238 or die "Can't duplicate STDOUT: $!";
240 or die "Can't close STDOUT duplicate: $!";
242 foreach my $line (@$in) {
247 or die "Can't close pipe for writing: $!";
253 s#^/bin/sh: line \d+: ##;
254 s#^/bin/sh: ##; # temporarily added by ericm
263 or die "Can't close read end for input pipe: $!";
265 or die "Can't close write end for output pipe: $!";
267 or die "Can't close STDOUT duplicate: $!";
269 open ERR_DUP, ">&STDERR"
270 or die "Can't duplicate STDERR: $!";
271 open STDERR, ">&STDOUT"
272 or die "Can't join STDOUT and STDERR: $!";
275 exec ('/bin/sh', '-c', join(" ", @$prog));
279 print STDERR $prog->[0], ": $!\n";