Logo AND Algorithmique Numérique Distribuée

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