Logo AND Algorithmique Numérique Distribuée

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