Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Remove old trace files and add a picture for distrib.
[simgrid.git] / buildtools / Cmake / tesh.pl
1 #! /usr/bin/perl
2 eval 'exec perl -S $0 ${1+"$@"}'
3     if $running_under_some_shell;
4
5 =encoding UTF-8
6
7 =head1 NAME
8
9 tesh -- testing shell
10
11 =head1 SYNOPSIS
12
13 B<tesh> [I<options>] I<tesh_file>
14
15 =cut
16
17 my $path = $0;
18 $path =~ s|[^/]*$||;
19 push @INC,$path;
20
21 use Pod::Usage qw(pod2usage);
22 use Getopt::Long qw(GetOptions);
23 use strict;
24 use Term::ANSIColor;
25 use IPC::Open3;
26
27
28 # make sure we received a tesh file
29 scalar @ARGV > 0 || pod2usage(-exitval => 1);
30
31 #Add current directory to path
32 $ENV{PATH} = "$ENV{PATH}:.";
33
34
35 ##
36 ## Command line option handling
37 ##
38
39 # option handling helper subs
40 sub cd_cmd {
41     my $directory=$_[1];
42     if (-e $directory && -d $directory) {
43         chdir("$directory");
44         print "[Tesh/INFO] change directory to $directory\n";
45     } elsif (-e $directory) {
46         die "[Tesh/CRITICAL] Cannot change directory to '$directory': it is not a directory\n";
47     } else {
48         die "[Tesh/CRITICAL] Cannot change directory to '$directory': no such directory\n";
49     }
50 }
51
52 sub setenv_cmd {
53     if ($_[1] =~ /^(.*)=(.*)$/) {
54         my($var,$ctn)=($1,$2);
55         $ENV{$var} = $ctn;
56         print "[Tesh/INFO] setenv $var=$ctn\n";
57     } else { 
58         die "[Tesh/CRITICAL] Malformed argument to setenv: expected 'name=value' but got '$_[1]'\n";
59     }
60 }
61
62 # Main option parsing sub
63 my $tesh_file;
64 sub get_options {
65     # remove the tesh file from the ARGV used
66     my @ARGV = @_;
67     $tesh_file = pop @ARGV;
68
69     # temporary arrays for GetOption
70     my @verbose = ();
71     my @cfg;
72     my $log; # ignored
73
74     my %opt = (
75         "help"    => 0,
76         "debug"   => 0,
77         "verbose" => 0
78         );
79
80     Getopt::Long::config('bundling', 'no_getopt_compat', 'no_auto_abbrev');
81     
82     GetOptions(
83         'help|h'     => \$opt{'help'},
84
85         'verbose|v'  => \@verbose,
86         'debug|d'    => \$opt{"debug"},
87
88         'cd=s'       => \&cd_cmd,
89         'setenv=s'   => \&setenv_cmd,
90         'cfg=s'      => \@cfg,
91         'log=s'      => \$log,
92         );
93
94     $opt{'verbose'} = scalar @verbose;
95     foreach (@cfg) {
96         $opt{'cfg'} .= " --cfg=$_";
97     }
98     return %opt;
99 }
100
101 my %opts = get_options(@ARGV);
102
103 ##
104 ## File parsing
105 ##
106 my($sort)=0;
107 my($nb_arg)=0;
108 my($timeout)=0;
109 my($old_buffer);
110 my($linebis);
111 my($SIGABRT)=0;
112 my($no_output_ignore)=1;
113 my($verbose)=0;
114 my($return)=-1;
115 my($pid);
116 my($result);
117 my($result_err);
118 my($forked);
119 my($config)="";
120
121 my($tesh_command)=0;
122 my(@buffer_tesh)=();
123
124 eval {
125     use POSIX;
126     sub exit_status {
127         my $status = shift;
128         if (POSIX::WIFEXITED($status)) {
129             return "returned code ".POSIX::WEXITSTATUS($status);
130         } elsif (POSIX::WIFSIGNALED($status)) {
131             return "got signal ".$SIG{POSIX::WTERMSIG($status)};
132         }
133         return "Unparsable status. Is the process stopped?";
134     }
135 };
136 if ($@) { # no POSIX available?
137     warn "POSIX not usable to parse the return value of forked child: $@\n";
138     sub exit_status {
139         return "returned code 0";
140     }
141 }
142
143 sub exec_cmd { 
144     my %cmd = %{$_[0]};
145     if ($opts{'debug'}) {
146         print "IN BEGIN\n";
147         map {print "  $_"} @{$cmd{'in'}};
148         print "IN END\n";
149         print "OUT BEGIN\n";
150         map {print "  $_"} @{$cmd{'out'}};
151         print "OUT END\n";
152         print "CMD: $cmd{'cmd'}\n";
153     }
154
155     # cleanup the command line
156     $cmd{'cmd'} =~ s/\${EXEEXT:=}//g;
157     $cmd{'cmd'} =~ s|^\./||g;
158 #    $cmd{'cmd'} =~ s|tesh|tesh.pl|g;
159     $cmd{'cmd'} =~ s/\(%i:%P@%h\)/\\\(%i:%P@%h\\\)/g;
160     $cmd{'cmd'} .= " $opts{'cfg'}" if (defined($opts{'cfg'}) && length($opts{'cfg'}));
161
162     print "[$cmd{'file'}:$cmd{'line'}] $cmd{'cmd'}\n";
163
164     ###
165     # exec the command line
166     ###
167     $pid = open3(\*IN, \*OUT, \*OUT, $cmd{'cmd'} );
168
169     # if timeout specified, fork and kill executing child at the end of timeout
170     if ($timeout){
171         $forked = fork();
172         die "fork() failed: $!" unless defined $forked;
173         if ( $forked == 0 ) { # child
174             sleep $timeout;
175             kill(9, $pid);
176             exit;
177         }
178     }
179
180     # push all provided input to executing child
181     map { print IN "$_\n" } $cmd{'in'};
182     close IN;
183
184     # pop all output from executing child
185     my @got;
186     while(defined(my $got=<OUT>)) {
187         $got =~ s/\r//g;
188         $got =~ s/^( )*//g;
189         chomp $got;
190         push @got, "$got";
191     }   
192     close OUT;
193     
194     # Cleanup the executing child, and kill the timeouter brother on need
195     $cmd{'return'} = 0 unless defined($cmd{'return'});
196     my $wantret = "returned code ".(defined($cmd{'return'})? $cmd{'return'} : 0);
197     waitpid ($pid, 0);
198     my $gotret = exit_status($?);
199     if($gotret ne $wantret) {
200         my $msg = "Test suite `$cmd{'file'}': NOK (<$cmd{'file'}:$cmd{'line'}> $gotret)\n".
201             "Output of <$cmd{'file'}:$cmd{'line'}> so far:\n";      
202         map {$msg .=  "|| $_\n"} @got;
203         print STDERR "$msg";
204         exit(1);
205     }
206     if($timeout){kill(9, $forked);$timeout=0;}
207     $timeout = 0;
208             
209     ###
210     # Check the result of execution 
211     ###
212     my $diff = build_diff(\@{$cmd{'out'}}, \@got);
213     if (length $diff) {
214         print color("red")."[TESH/CRITICAL$$] Output mismatch\n";
215         map { print "[TESH/CRITICAL] $_\n" } split(/\n/,$diff);
216         print color("reset");
217         die "Tesh failed\n";
218     }
219 }
220
221 sub mkfile_cmd {
222     my %cmd = %{$_[0]};
223     my $file = $cmd{'arg'};
224     print "[Tesh/INFO] mkfile $file\n";
225
226     die "[TESH/CRITICAL] no input provided to mkfile\n" unless defined($cmd{'in'}) && scalar @{$cmd{'in'}};
227     unlink($file);
228     open(FILE,">$file") or die "[Tesh/CRITICAL] Unable to create file $file: $!\n";
229     print FILE join("\n", @{$cmd{'in'}});
230     print FILE "\n" if (scalar @{$cmd{'in'}} > 0);
231     close(FILE);
232 }
233
234 # parse tesh file
235 print "Test suite $tesh_file\n";
236 open TESH_FILE, $tesh_file or die "[Tesh/CRITICAL] Unable to open $tesh_file $!\n";
237
238
239 my %cmd; # everything about the next command to run
240 my $line_num=0;
241 LINE: while (defined(my $line=<TESH_FILE>)) {
242     $line_num++;
243     chomp $line;
244     print "[TESH/debug] $line_num: $line\n" if $opts{'debug'};
245
246     # deal with line continuations
247     while ($line =~ /^(.*?)\\$/) {
248         my $next=<TESH_FILE>;
249         die "[TESH/CRITICAL] Continued line at end of file\n"
250             unless defined($next);
251         chomp $next;
252         print "[TESH/debug] $line_num: $next\n" if $opts{'debug'};
253         $line = $1.$next;
254     }
255
256     # Push delayed commands on empty lines
257     unless ($line =~ m/^(..)(.*)$/) {
258         if (defined($cmd{'cmd'})) {
259             exec_cmd(\%cmd);
260             %cmd = ();
261         }
262         next LINE;
263     }   
264  
265     my ($cmd,$arg) = ($1,$2);
266     $arg =~ s/\r//g;
267
268     # handle the commands
269     if ($cmd =~ /^#/) { #comment
270     } elsif ($cmd eq '> '){     #expected result line
271         print "[TESH/debug] push expected result\n" if $opts{'debug'};
272         push @{$cmd{'out'}}, $arg;
273
274     } elsif ($cmd eq '< ') {    # provided input
275         print "[TESH/debug] push provided input\n" if $opts{'debug'};
276         push @{$cmd{'in'}}, $arg;
277
278     } elsif ($cmd eq 'p ') {    # comment
279         print "[Tesh/INFO] $arg\n";
280
281     } elsif ($cmd eq '$ ') {  # Command
282         # if we have something buffered, run it now
283         if (defined($cmd{'cmd'})) {
284             exec_cmd(\%cmd);
285             %cmd = ();
286         }
287         if ($arg =~ /^ *mkfile /){      # "mkfile" command line
288             die "[TESH/CRITICAL] Output expected from mkfile command!\n" if scalar @{cmd{'out'}};
289
290             $cmd{'arg'} = $arg;
291             $cmd{'arg'} =~ s/ *mkfile //;
292             mkfile_cmd(\%cmd);
293             %cmd = ();
294
295         } elsif ($arg =~ /^ *cd /) {
296             die "[TESH/CRITICAL] Input provided to cd command!\n" if scalar @{cmd{'in'}};
297             die "[TESH/CRITICAL] Output expected from cd command!\n" if scalar @{cmd{'out'}};
298
299             $arg =~ s/^ *cd //;
300             cd_cmd("",$arg);
301             %cmd = ();
302
303         } else { # regular command
304             $cmd{'cmd'} = $arg;
305             $cmd{'file'} = $tesh_file;
306             $cmd{'line'} = $line_num;
307         }
308     }
309     elsif($cmd eq '& '){        # parallel command line
310         $cmd{'background'} = 1;
311         $cmd{'cmd'} = $arg;
312     }   
313     elsif($line =~ /^! output sort/){   #output sort
314         $cmd{'sort'} = 1;
315     }
316     elsif($line =~ /^! output ignore/){ #output ignore
317         $cmd{'output ignore'} = 1;
318     }
319     elsif($line =~ /^! expect signal SIGABRT$/) {#expect signal SIGABRT
320         $cmd{'expect'} = "SIGABRT";
321     }
322     elsif($line =~ /^! expect return/){ #expect return
323         $line =~ s/^! expect return //g;
324         $line =~ s/\r//g;
325         $cmd{'return'} = $line;
326     }
327     elsif($line =~ /^! setenv/){        #setenv
328         $line =~ s/^! setenv //g;
329         $line =~ s/\r//g;
330         setenv_cmd($line);
331     }
332     elsif($line =~ /^! include/){       #output sort
333         print color("red"), "[Tesh/CRITICAL] need include";
334         print color("reset"), "\n";
335         die;
336     }
337     elsif($line =~ /^! timeout/){       #timeout
338         $line =~ s/^! timeout //;
339         $line =~ s/\r//g;
340         $cmd{'timeout'} = $line;
341     } else {
342         die "[TESH/CRITICAL] parse error: $line\n";
343     }
344 }
345
346 # Deal with last command
347 if (defined($cmd{'cmd'})) {
348     exec_cmd(\%cmd);
349     %cmd = ();
350 }
351
352 #my (@a,@b);
353 #push @a,"bl1";   push @b,"bl1";
354 #push @a,"bl2";   push @b,"bl2";
355 #push @a,"bl3";   push @b,"bl3";
356 #push @a,"bl4";   push @b,"bl4";
357 #push @a,"bl5";   push @b,"bl5";
358 #push @a,"bl6";   push @b,"bl6";
359 #push @a,"bl7";   push @b,"bl7";
360 ##push @a,"Perl";  push @b,"ruby";
361 #push @a,"END1";   push @b,"END1";
362 #push @a,"END2";   push @b,"END2";
363 #push @a,"END3";   push @b,"END3";
364 #push @a,"END4";   push @b,"END4";
365 #push @a,"END5";   push @b,"END5";
366 #push @a,"END6";   push @b,"END6";
367 #push @a,"END7";   push @b,"END7";
368 #print "Identical:\n". build_diff(\@a,\@b);
369
370 #@a = (); @b =();
371 #push @a,"AZE"; push @b,"EZA";
372 #print "Different:\n".build_diff(\@a,\@b);
373
374 use Diff qw(diff); # postpone a bit to have time to change INC
375
376 sub build_diff {
377     my $res;
378     my $diff = Diff->new(@_);
379     
380     $diff->Base( 1 );   # Return line numbers, not indices
381     my $chunk_count = $diff->Next(-1); # Compute the amount of chuncks
382     return ""     if ($chunk_count == 1 && $diff->Same());
383     $diff->Reset();
384     while(  $diff->Next()  ) {
385         my @same = $diff->Same();
386         if ($diff->Same() ) {
387             if ($diff->Next(0) > 1) { # not first chunk: print 2 first lines
388                 $res .= '  '.$same[0]."\n" ;
389                 $res .= '  '.$same[1]."\n" if (scalar @same>1);
390             }   
391             $res .= "...\n"  if (scalar @same>2);
392 #       $res .= $diff->Next(0)."/$chunk_count\n";
393             if ($diff->Next(0) < $chunk_count) { # not last chunk: print 2 last lines
394                 $res .= '  '.$same[scalar @same -2]."\n" if (scalar @same>1);
395                 $res .= '  '.$same[scalar @same -1]."\n";
396             } 
397         } 
398         next if  $diff->Same();
399         map { $res .= "- $_\n" } $diff->Items(1);
400         map { $res .= "+ $_\n" } $diff->Items(2);
401     }
402     return $res;
403 }
404
405