Logo AND Algorithmique Numérique Distribuée

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