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