Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
f9f54d660833a8fb3cca41322ea74bfbdd24b4d7
[simgrid.git] / buildtools / Cmake / Scripts / 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($timeout)=0;
19 my($time_to_wait)=0;
20 my $path = $0;
21 my $OS;
22 my $enable_coverage=0;
23 my $tesh_file;
24 my $tesh_name;
25 my $error=0;
26 my $exitcode=0;
27 my @bg_cmds;
28
29 $path =~ s|[^/]*$||;
30 push @INC,$path;
31
32 use Getopt::Long qw(GetOptions);
33 use strict;
34 use Term::ANSIColor;
35 use IPC::Open3;
36 use IO::File;
37
38 if($^O eq "linux"){
39     $OS = "UNIX";
40 }
41 else{
42     $OS = "WIN";
43     $ENV{"PRINTF_EXPONENT_DIGITS"} = "2"; 
44 }
45
46 #Add current directory to path
47 $ENV{PATH} = "$ENV{PATH}:.";
48
49 ##
50 ## Command line option handling
51 ##
52
53 # option handling helper subs
54 sub cd_cmd {
55   my $directory=$_[1];
56   my $failure=1;
57   if (-e $directory && -d $directory) {
58     chdir("$directory");
59     print "[Tesh/INFO] change directory to $directory\n";
60   $failure=0;
61   } elsif (-e $directory) {
62     print "Cannot change directory to '$directory': it is not a directory\n";
63   } else {
64     print "Chdir to $directory failed: No such file or directory\n";
65   }
66   if($failure==1){
67   $error=1;
68   $exitcode=4;
69   print "Test suite `$tesh_file': NOK (system error)\n";
70   exit 4;
71   }
72 }
73
74 sub setenv_cmd {
75   my($var,$ctn);
76   if ($_[0] =~ /^(.*)=(.*)$/) {
77     ($var,$ctn)=($1,$2);
78   }elsif ($_[1] =~ /^(.*)=(.*)$/) {
79     ($var,$ctn)=($1,$2);
80   } else { 
81       die "[Tesh/CRITICAL] Malformed argument to setenv: expected 'name=value' but got '$_[1]'\n";
82   }
83     
84     if($var =~ /bindir/){
85         print "[Tesh/INFO] setenv $var=$ctn\n";
86         $bindir = $ctn;
87     }
88     else
89     {
90         if($var =~ /srcdir/){
91             $srcdir = $ctn;
92         }
93         else{
94             $ENV{$var} = $ctn;
95             print "[Tesh/INFO] setenv $var=$ctn\n";
96         }
97     }    
98 }
99
100 # Main option parsing sub
101
102 sub get_options {
103   # remove the tesh file from the ARGV used
104   my @ARGV = @_;
105   $tesh_file = pop @ARGV;
106
107   # temporary arrays for GetOption
108   my @verbose = ();
109   my @cfg;
110   my $log; # ignored
111
112
113   my %opt = (
114     "help"  => 0,
115     "debug"   => 0,
116     "verbose" => 0
117     );
118
119   Getopt::Long::config('bundling', 'no_getopt_compat', 'no_auto_abbrev');
120   
121   GetOptions(
122     'help|h'   => \$opt{'help'},
123
124     'verbose|v'  => \@verbose,
125     'debug|d'  => \$opt{"debug"},
126
127     'cd=s'     => \&cd_cmd,
128     'timeout=s'  => \$opt{'timeout'},    
129     'setenv=s'   => \&setenv_cmd,
130     'cfg=s'    => \@cfg,
131     'log=s'    => \$log,
132     'enable-coverage+'  => \$enable_coverage,    
133     );
134
135   if($enable_coverage){
136     print "Enable coverage\n";
137   }
138
139   unless($tesh_file=~/(.*)\.tesh/){
140     $tesh_file="(stdin)";
141     $tesh_name="(stdin)";
142     print "Test suite from stdin\n";
143   }else{
144     $tesh_name=$1;
145     print "Test suite `$tesh_name'\n";
146   }
147
148   $opt{'verbose'} = scalar @verbose;
149   foreach (@cfg) {
150     $opt{'cfg'} .= " --cfg=$_";
151   }
152   return %opt;
153 }
154
155 my %opts = get_options(@ARGV);
156
157 ##
158 ## File parsing
159 ##
160 my($nb_arg)=0;
161 my($old_buffer);
162 my($linebis);
163 my($SIGABRT)=0;
164 my($verbose)=0;
165 my($return)=-1;
166 my($pid);
167 my($result);
168 my($result_err);
169 my($forked);
170 my($config)="";
171 my($tesh_command)=0;
172 my(@buffer_tesh)=();
173
174 #eval {
175   use POSIX;
176
177   sub exit_status {
178     my $status = shift;
179     if (POSIX::WIFEXITED($status)) {
180       $exitcode=POSIX::WEXITSTATUS($status)+40;
181       return "returned code ".POSIX::WEXITSTATUS($status);
182     } elsif (POSIX::WIFSIGNALED($status)) {
183       my $code;
184       if (POSIX::WTERMSIG($status) == SIGINT){$code="SIGINT"; }
185       elsif  (POSIX::WTERMSIG($status) == SIGTERM) {$code="SIGTERM"; }
186       elsif  (POSIX::WTERMSIG($status) == SIGKILL) {$code= "SIGKILL"; }
187       elsif  (POSIX::WTERMSIG($status) == SIGABRT) {$code="SIGABRT"; }
188       elsif  (POSIX::WTERMSIG($status) == SIGSEGV) {$code="SIGSEGV" ;}
189       $exitcode=POSIX::WTERMSIG($status)+4;
190       return "got signal $code";
191     }
192     return "Unparsable status. Is the process stopped?";
193   }
194 #};
195 #if ($@) { # no POSIX available?
196 #  warn "POSIX not usable to parse the return value of forked child: $@\n";
197 #  sub exit_status {
198 #    return "returned code -1 $@ ";
199 #  }
200 #}
201
202 sub exec_cmd { 
203   my %cmd = %{$_[0]};
204   if ($opts{'debug'}) {
205     print "IN BEGIN\n";
206     map {print "  $_"} @{$cmd{'in'}};
207     print "IN END\n";
208     print "OUT BEGIN\n";
209     map {print "  $_"} @{$cmd{'out'}};
210     print "OUT END\n";
211     print "CMD: $cmd{'cmd'}\n";
212   }
213
214   # cleanup the command line
215   if($OS eq "WIN"){
216         $cmd{'cmd'} =~ s/\${EXEEXT:=}/.exe/g;
217         $cmd{'cmd'} =~ s/\${EXEEXT}/.exe/g;
218         $cmd{'cmd'} =~ s/\$EXEEXT/.exe/g;
219     }
220     else{
221         $cmd{'cmd'} =~ s/\${EXEEXT:=}//g;
222     }
223   $cmd{'cmd'} =~ s/\${bindir:=}/$bindir/g;
224   $cmd{'cmd'} =~ s/\${srcdir:=}/$srcdir/g;
225   $cmd{'cmd'} =~ s/\${bindir:=.}/$bindir/g;
226   $cmd{'cmd'} =~ s/\${srcdir:=.}/$srcdir/g;
227   $cmd{'cmd'} =~ s/\${bindir}/$bindir/g;
228   $cmd{'cmd'} =~ s/\${srcdir}/$srcdir/g;
229 # $cmd{'cmd'} =~ s|^\./||g;
230 #  $cmd{'cmd'} =~ s|tesh|tesh.pl|g;
231   $cmd{'cmd'} =~ s/\(%i:%P@%h\)/\\\(%i:%P@%h\\\)/g;
232   $cmd{'cmd'} .= " $opts{'cfg'}" if (defined($opts{'cfg'}) && length($opts{'cfg'}));
233
234   print "[$tesh_name:$cmd{'line'}] $cmd{'cmd'}\n" ;
235
236   ###
237   # exec the command line
238   ###  $line =~ s/\r//g;
239
240   $cmd{'got'} = IO::File->new_tmpfile;
241   $cmd{'got'}->autoflush(1);
242   local *E = $cmd{'got'}; 
243   $cmd{'pid'} = open3(\*CHILD_IN,  ">&E",  ">&E", $cmd{'cmd'} );
244
245   # push all provided input to executing child
246   map { print CHILD_IN "$_\n"; }  @{$cmd{'in'}};
247   close CHILD_IN;
248
249   # if timeout specified, fork and kill executing child at the end of timeout
250   if (defined($cmd{'timeout'}) or defined($opts{'timeout'})){
251     $time_to_wait= defined($cmd{'timeout'}) ? $cmd{'timeout'} : $opts{'timeout'};
252     $forked = fork();
253     $timeout=-1;
254     die "fork() failed: $!" unless defined $forked;
255     if ( $forked == 0 ) { # child
256       sleep $time_to_wait;
257       kill(SIGKILL, $cmd{'pid'});
258       exit $time_to_wait;
259     }
260   }
261
262   
263   # Cleanup the executing child, and kill the timeouter brother on need
264   $cmd{'return'} = 0 unless defined($cmd{'return'});
265   if($cmd{'background'} != 1){
266     waitpid ($cmd{'pid'}, 0);
267     $cmd{'gotret'} = exit_status($?);
268     parse_out(\%cmd);
269   }else{
270     # & commands, which will be handled at the end
271     push @bg_cmds, \%cmd;
272     # no timeout for background commands
273     if($forked){
274        kill(SIGKILL, $forked);
275        $timeout=0;
276        $forked=0;
277     }
278   }
279 }
280
281
282 sub parse_out { 
283   my %cmd = %{$_[0]};
284   my $gotret=$cmd{'gotret'};
285
286   my $wantret;
287
288   if(defined($cmd{'expect'}) and ($cmd{'expect'} ne "")){
289     $wantret = "got signal $cmd{'expect'}";
290   }else{
291     $wantret = "returned code ".(defined($cmd{'return'})? $cmd{'return'} : 0);
292   }
293
294   local *got = $cmd{'got'};
295   seek(got,0,0);
296   # pop all output from executing child
297   my @got;
298   while(defined(my $got=<got>)) {
299     $got =~ s/\r//g;
300     chomp $got;
301     if (!($enable_coverage and $got=~ /^profiling:/)){
302       push @got, $got;
303     }
304   }    
305
306   if ($cmd{'sort'}){   
307     sub mysort{
308     $a cmp $b
309     }
310     use sort qw(defaults _quicksort); # force quicksort
311     @got = sort mysort @got;
312     #also resort the other one, as perl sort is not the same as the C one used to generate teshes
313     if(defined($cmd{'out'})){
314       @{$cmd{'out'}}=sort mysort @{$cmd{'out'}};
315     }
316   }
317
318   #Did we timeout ? If yes, handle it. If not, kill the forked process.
319
320   if($timeout==-1 and $gotret eq "got signal SIGKILL"){
321     $gotret="return code 0";
322     $timeout=1;
323     $gotret= "timeout after $time_to_wait sec";
324     $error=1;
325     $exitcode=3;
326     print STDERR "<$cmd{'file'}:$cmd{'line'}> timeouted. Kill the process.\n";
327   }else{
328     $timeout=0;  
329   }
330   if($gotret ne $wantret) {
331     $error=1;
332     my $msg = "Test suite `$cmd{'file'}': NOK (<$cmd{'file'}:$cmd{'line'}> $gotret)\n";
333     if ($timeout!=1) {
334         $msg=$msg."Output of <$cmd{'file'}:$cmd{'line'}> so far:\n";    
335     }
336     map {$msg .=  "|| $_\n"} @got;
337     if(!@got) {
338         if($timeout==1){
339         print STDERR "<$cmd{'file'}:$cmd{'line'}> No output before timeout\n";
340         }else{
341         $msg .= "||\n";
342         }
343     }
344     $timeout = 0;
345     print STDERR "$msg";
346   }
347
348       
349   ###
350   # Check the result of execution 
351   ###
352   my $diff;
353   if (!defined($cmd{'output ignore'})){
354     $diff = build_diff(\@{$cmd{'out'}}, \@got);
355   }else{
356   print "(ignoring the output of <$cmd{'file'}:$cmd{'line'}> as requested)\n"
357   }
358   if (length $diff) {
359     print "Output of <$cmd{'file'}:$cmd{'line'}> mismatch:\n";
360     map { print "$_\n" } split(/\n/,$diff);
361
362     print "Test suite `$cmd{'file'}': NOK (<$cmd{'file'}:$cmd{'line'}> output mismatch)\n";
363     $error=1;
364     $exitcode=2;
365   }
366 }
367
368 sub mkfile_cmd {
369   my %cmd = %{$_[0]};
370   my $file = $cmd{'arg'};
371   print "[Tesh/INFO] mkfile $file\n";
372
373   unlink($file);
374   open(FILE,">$file") or die "[Tesh/CRITICAL] Unable to create file $file: $!\n";
375   print FILE join("\n", @{$cmd{'in'}});
376   print FILE "\n" if (scalar @{$cmd{'in'}} > 0);
377   close(FILE);
378 }
379
380 # parse tesh file
381 #my $teshfile=$tesh_file;
382 #$teshfile=~ s{\.[^.]+$}{};
383
384 unless($tesh_file eq "(stdin)"){
385   open TESH_FILE, $tesh_file or die "[Tesh/CRITICAL] Unable to open $tesh_file $!\n";
386 }
387
388 my %cmd; # everything about the next command to run
389 my $line_num=0;
390 my $finished =0;
391 LINE: while (not $finished and not $error) {
392   my $line;
393
394
395   if ($tesh_file ne "(stdin)" and !defined($line=<TESH_FILE>)){
396     $finished=1;
397     next LINE;
398   }elsif ($tesh_file eq "(stdin)" and !defined($line=<>)){
399     $finished=1;
400     next LINE;
401   }
402
403
404   $line_num++;
405   chomp $line;
406   $line =~ s/\r//g;
407   print "[TESH/debug] $line_num: $line\n" if $opts{'debug'};
408   my $next;
409   # deal with line continuations
410   while ($line =~ /^(.*?)\\$/) {
411     $next=<TESH_FILE>;
412     die "[TESH/CRITICAL] Continued line at end of file\n"
413       unless defined($next);
414     $line_num++;
415     chomp $next;
416     print "[TESH/debug] $line_num: $next\n" if $opts{'debug'};
417     $line = $1.$next;
418   }
419
420   # Push delayed commands on empty lines
421   unless ($line =~ m/^(.)(.*)$/) {
422     if (defined($cmd{'cmd'}))  {
423       exec_cmd(\%cmd);
424       %cmd = ();
425     }
426     next LINE;
427   }     
428  
429   my ($cmd,$arg) = ($1,$2);
430   $arg =~ s/^ //g;
431   $arg =~ s/\r//g;
432   $arg =~ s/\\\\/\\/g;
433   # handle the commands
434   if ($cmd =~ /^#/) {    #comment
435   } elsif ($cmd eq '>'){    #expected result line
436     print "[TESH/debug] push expected result\n" if $opts{'debug'};
437     push @{$cmd{'out'}}, $arg;
438
439   } elsif ($cmd eq '<') {    # provided input
440     print "[TESH/debug] push provided input\n" if $opts{'debug'};
441     push @{$cmd{'in'}}, $arg;
442
443   } elsif ($cmd eq 'p') {    # comment
444     print "[$tesh_name:$line_num] $arg\n";
445
446   } elsif ($cmd eq '$') {  # Command
447     # if we have something buffered, run it now
448     if (defined($cmd{'cmd'})) {
449       exec_cmd(\%cmd);
450       %cmd = ();
451     }
452     if ($arg =~ /^\s*mkfile /){      # "mkfile" command line
453       die "[TESH/CRITICAL] Output expected from mkfile command!\n" if scalar @{cmd{'out'}};
454
455       $cmd{'arg'} = $arg;
456       $cmd{'arg'} =~ s/\s*mkfile //;
457       mkfile_cmd(\%cmd);
458       %cmd = ();
459
460     } elsif ($arg =~ /^\s*cd /) {
461       die "[TESH/CRITICAL] Input provided to cd command!\n" if scalar @{cmd{'in'}};
462       die "[TESH/CRITICAL] Output expected from cd command!\n" if scalar @{cmd{'out'}};
463
464       $arg =~ s/^ *cd //;
465       cd_cmd("",$arg);
466       %cmd = ();
467
468     } else { # regular command
469       $cmd{'cmd'} = $arg;
470       $cmd{'file'} = $tesh_file;
471       $cmd{'line'} = $line_num;
472     }
473   }
474   elsif($cmd eq '&'){      # parallel command line
475
476     if (defined($cmd{'cmd'})) {
477       exec_cmd(\%cmd);
478       %cmd = ();
479     }
480     $cmd{'background'} = 1;
481     $cmd{'cmd'} = $arg;
482     $cmd{'file'} = $tesh_file;
483     $cmd{'line'} = $line_num;
484   }    
485   elsif($line =~ /^!\s*output sort/){    #output sort
486     if (defined($cmd{'cmd'})) {
487       exec_cmd(\%cmd);
488       %cmd = ();
489     }
490     $cmd{'sort'} = 1;
491   }
492   elsif($line =~ /^!\s*output ignore/){    #output ignore
493     if (defined($cmd{'cmd'})) {
494       exec_cmd(\%cmd);
495       %cmd = ();
496     }
497     $cmd{'output ignore'} = 1;
498   }
499   elsif($line =~ /^!\s*expect signal (\w*)/) {#expect signal SIGABRT
500     if (defined($cmd{'cmd'})) {
501       exec_cmd(\%cmd);
502       %cmd = ();
503     }
504 print "hey\n";
505     $cmd{'expect'} = "$1";
506   }
507   elsif($line =~ /^!\s*expect return/){    #expect return
508     if (defined($cmd{'cmd'})) {
509       exec_cmd(\%cmd);
510       %cmd = ();
511     }
512     $line =~ s/^! expect return //g;
513     $line =~ s/\r//g;
514     $cmd{'return'} = $line;
515   }
516   elsif($line =~ /^!\s*setenv/){    #setenv
517     if (defined($cmd{'cmd'})) {
518       exec_cmd(\%cmd);
519       %cmd = ();
520     }
521     $line =~ s/^! setenv //g;
522     $line =~ s/\r//g;
523     setenv_cmd($line);
524   }
525   elsif($line =~ /^!\s*include/){    #output sort
526     if (defined($cmd{'cmd'})) {
527       exec_cmd(\%cmd);
528       %cmd = ();
529     }
530     print color("red"), "[Tesh/CRITICAL] need include";
531     print color("reset"), "\n";
532     die;
533   }
534   elsif($line =~ /^!\s*timeout/){    #timeout
535     if (defined($cmd{'cmd'})) {
536       exec_cmd(\%cmd);
537       %cmd = ();
538     }
539     $line =~ s/^! timeout //;
540     $line =~ s/\r//g;
541     $cmd{'timeout'} = $line;
542   } else {
543     die "[TESH/CRITICAL] parse error: $line\n";
544   }
545   if($forked){
546    kill(SIGKILL, $forked);
547    $timeout=0;
548   }
549
550 }
551
552
553
554 # Deal with last command
555 if (defined($cmd{'cmd'})) {
556   exec_cmd(\%cmd);
557   %cmd = ();
558 }
559
560
561 if($forked){
562    kill(SIGKILL, $forked);
563    $timeout=0;
564 }
565
566 foreach(@bg_cmds){
567   my %test=%{$_};
568   waitpid ($test{'pid'}, 0);
569   $test{'gotret'} = exit_status($?);
570   parse_out(\%test);
571 }
572
573 @bg_cmds=();
574
575
576 if($error !=0){
577     exit $exitcode;
578 }elsif($tesh_file eq "(stdin)"){
579     print "Test suite from stdin OK\n";
580 }else{
581     print "Test suite `$tesh_name' OK\n";
582 }
583
584 #my (@a,@b);
585 #push @a,"bl1";   push @b,"bl1";
586 #push @a,"bl2";   push @b,"bl2";
587 #push @a,"bl3";   push @b,"bl3";
588 #push @a,"bl4";   push @b,"bl4";
589 #push @a,"bl5";   push @b,"bl5";
590 #push @a,"bl6";   push @b,"bl6";
591 #push @a,"bl7";   push @b,"bl7";
592 ##push @a,"Perl";  push @b,"ruby";
593 #push @a,"END1";   push @b,"END1";
594 #push @a,"END2";   push @b,"END2";
595 #push @a,"END3";   push @b,"END3";
596 #push @a,"END4";   push @b,"END4";
597 #push @a,"END5";   push @b,"END5";
598 #push @a,"END6";   push @b,"END6";
599 #push @a,"END7";   push @b,"END7";
600 #print "Identical:\n". build_diff(\@a,\@b);
601
602 #@a = (); @b =();
603 #push @a,"AZE"; push @b,"EZA";
604 #print "Different:\n".build_diff(\@a,\@b);
605
606 use lib "@CMAKE_BINARY_DIR@/bin" ;
607
608 use Diff qw(diff); # postpone a bit to have time to change INC
609
610 sub build_diff {
611   my $res;
612   my $diff = Diff->new(@_);
613   
614   $diff->Base( 1 );   # Return line numbers, not indices
615   my $chunk_count = $diff->Next(-1); # Compute the amount of chuncks
616   return ""   if ($chunk_count == 1 && $diff->Same());
617   $diff->Reset();
618   while(  $diff->Next()  ) {
619     my @same = $diff->Same();
620     if ($diff->Same() ) {
621       if ($diff->Next(0) > 1) { # not first chunk: print 2 first lines
622         $res .= '  '.$same[0]."\n" ;
623         $res .= '  '.$same[1]."\n" if (scalar @same>1);
624       }     
625       $res .= "...\n"  if (scalar @same>2);
626 #    $res .= $diff->Next(0)."/$chunk_count\n";
627       if ($diff->Next(0) < $chunk_count) { # not last chunk: print 2 last lines
628         $res .= '  '.$same[scalar @same -2]."\n" if (scalar @same>1);
629         $res .= '  '.$same[scalar @same -1]."\n";
630       } 
631     } 
632     next if  $diff->Same();
633     map { $res .= "- $_\n" } $diff->Items(1);
634     map { $res .= "+ $_\n" } $diff->Items(2);
635   }
636   return $res;
637 }
638
639