3 # Copyright (c) 2007, 2008 Andreas Gruenbacher.
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
9 # 1. Redistributions of source code must retain the above copyright
10 # notice, this list of conditions, and the following disclaimer,
11 # without modification, immediately at the beginning of the file.
12 # 2. The name of the author may not be used to endorse or promote products
13 # derived from this software without specific prior written permission.
15 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
19 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 # Alternatively, this software may be distributed under the terms of the
28 # GNU Public License ("GPL"):
30 # This program is free software: you can redistribute it and/or modify it
31 # under the terms of the GNU General Public License as published by
32 # the Free Software Foundation, either version 2 of the License, or
33 # (at your option) any later version.
35 # This program is distributed in the hope that it will be useful,
36 # but WITHOUT ANY WARRANTY; without even the implied warranty of
37 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38 # GNU General Public License for more details.
40 # You should have received a copy of the GNU General Public License
41 # along with this program. If not, see <http://www.gnu.org/licenses/>.
45 # Possible improvements:
47 # - distinguish stdout and stderr output
48 # - add environment variable like assignments
49 # - run up to a specific line
50 # - resume at a specific line
56 use POSIX qw(isatty setuid getcwd);
57 use vars qw($opt_l $opt_v);
59 no warnings qw(taint);
61 $opt_l = ~0; # a really huge number
64 my ($OK, $FAILED) = ("ok", "failed");
65 if (isatty(fileno(STDOUT))) {
66 $OK = "\033[32m" . $OK . "\033[m";
67 $FAILED = "\033[31m\033[1m" . $FAILED . "\033[m";
70 $ENV{"TUSER"} = getpwuid($>);
71 $ENV{"TGROUP"} = getgrgid($));
74 sub process_test($$$$);
76 my ($prog, $in, $out) = ([], [], []);
78 my ($tests, $failed) = (0,0);
80 my $width = ($ENV{COLUMNS} || 80) >> 1;
83 my $line = <>; $lineno++;
85 # Substitute %VAR and %{VAR} with environment variables.
86 $line =~ s[%(\w+)][$ENV{$1}]eg;
87 $line =~ s[%{(\w+)}][$ENV{$1}]eg;
90 if ($line =~ s/^\s*< ?//) {
92 } elsif ($line =~ s/^\s*> ?//) {
95 process_test($prog, $prog_line, $in, $out);
96 last if $prog_line >= $opt_l;
101 if ($line =~ s/^\s*\$ ?//) {
102 $prog = [ map { s/\\(.)/$1/g; $_ } split /(?<!\\)\s+/, $line ];
103 $prog_line = $lineno;
108 process_test($prog, $prog_line, $in, $out);
113 my $status = sprintf("%d commands (%d passed, %d failed)",
114 $tests, $tests-$failed, $failed);
115 if (isatty(fileno(STDOUT))) {
117 $status = "\033[31m\033[1m" . $status . "\033[m";
119 $status = "\033[32m" . $status . "\033[m";
123 exit $failed ? 1 : 0;
126 sub process_test($$$$) {
127 my ($prog, $prog_line, $in, $out) = @_;
129 return unless @$prog;
132 print "[$prog_line] \$ ", join(' ',
133 map { s/\s/\\$&/g; $_ } @$p), " -- ";
134 my $result = exec_test($prog, $in);
136 my $nmax = (@$out > @$result) ? @$out : @$result;
137 for (my $n=0; $n < $nmax; $n++) {
139 if (defined $out->[$n] && $out->[$n] =~ /^~ /) {
141 $out->[$n] =~ s/^~ //g;
144 if (!defined($out->[$n]) || !defined($result->[$n]) ||
145 (!$use_re && $result->[$n] ne $out->[$n]) ||
146 ( $use_re && $result->[$n] !~ /^$out->[$n]/)) {
147 push @good, ($use_re ? '!~' : '!=');
150 push @good, ($use_re ? '=~' : '==');
153 my $good = !(grep /!/, @good);
155 $failed++ unless $good;
156 print $good ? $OK : $FAILED, "\n";
157 if (!$good || $opt_v) {
158 for (my $n=0; $n < $nmax; $n++) {
159 my $l = defined($out->[$n]) ? $out->[$n] : "~";
161 my $r = defined($result->[$n]) ? $result->[$n] : "~";
163 print sprintf("%-" . ($width-3) . "s %s %s\n",
175 my ($login, $pass, $uid, $gid) = getpwnam($user)
176 or return [ "su: user $user does not exist\n" ];
178 my $fh = new FileHandle("/etc/group")
179 or return [ "opening /etc/group: $!\n" ];
182 my ($group, $passwd, $gid, $users) = split /:/;
183 foreach my $u (split /,/, $users) {
190 my $groups = join(" ", ($gid, $gid, @groups));
191 #print STDERR "[[$groups]]\n";
192 $! = 0; # reset errno
197 return [ "su: $!\n" ];
203 return [ "su: $prog->[1]: $!\n" ];
206 #print STDERR "[($>,$<)($(,$))]";
214 my $gid = getgrnam($group)
215 or return [ "sg: group $group does not exist\n" ];
216 my %groups = map { $_ eq $gid ? () : ($_ => 1) } (split /\s/, $));
218 #print STDERR "<<", join("/", keys %groups), ">>\n";
219 my $groups = join(" ", ($gid, $gid, keys %groups));
220 #print STDERR "[[$groups]]\n";
221 $! = 0; # reset errno
233 return [ "sg: $!\n" ];
235 print STDERR "[($>,$<)($(,$))]";
241 my ($prog, $in) = @_;
242 local (*IN, *IN_DUP, *IN2, *OUT_DUP, *OUT, *OUT2);
243 my $needs_shell = (join('', @$prog) =~ /[][|<>"'`\$\*\?]/);
245 if ($prog->[0] eq "umask") {
246 umask oct $prog->[1];
248 } elsif ($prog->[0] eq "cd") {
249 if (!chdir $prog->[1]) {
250 return [ "chdir: $prog->[1]: $!\n" ];
254 } elsif ($prog->[0] eq "su") {
255 return su($prog->[1]);
256 } elsif ($prog->[0] eq "sg") {
257 return sg($prog->[1]);
258 } elsif ($prog->[0] eq "export") {
259 my ($name, $value) = split /=/, $prog->[1];
260 # FIXME: need to evaluate $value, so that things like this will work:
261 # export dir=$PWD/dir
262 $ENV{$name} = $value;
264 } elsif ($prog->[0] eq "unset") {
265 delete $ENV{$prog->[1]};
270 or die "Can't create pipe for reading: $!";
271 open *IN_DUP, "<&STDIN"
274 or die "Can't duplicate pipe for reading: $!";
277 open *OUT_DUP, ">&STDOUT"
278 or die "Can't duplicate STDOUT: $!";
280 or die "Can't create pipe for writing: $!";
281 open *STDOUT, ">&OUT2"
282 or die "Can't duplicate pipe for writing: $!";
285 *STDOUT->autoflush();
291 open *STDIN, "<&IN_DUP"
292 or die "Can't duplicate STDIN: $!";
294 or die "Can't close STDIN duplicate: $!";
296 open *STDOUT, ">&OUT_DUP"
297 or die "Can't duplicate STDOUT: $!";
299 or die "Can't close STDOUT duplicate: $!";
301 foreach my $line (@$in) {
306 or die "Can't close pipe for writing: $!";
311 # remove libtool 'lt-' prefixes on prog name output
314 s#^/bin/sh: line \d+: ##;
323 or die "Can't close read end for input pipe: $!";
325 or die "Can't close write end for output pipe: $!";
327 or die "Can't close STDOUT duplicate: $!";
329 open ERR_DUP, ">&STDERR"
330 or die "Can't duplicate STDERR: $!";
331 open STDERR, ">&STDOUT"
332 or die "Can't join STDOUT and STDERR: $!";
335 exec ('/bin/sh', '-c', join(" ", @$prog));
339 print STDERR $prog->[0], ": $!\n";