Logo AND Algorithmique Numérique Distribuée

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