]> The Tcpdump Group git mirrors - tcpdump/blob - tests/TESTrun
Adapt TESTrun constants for older Perl. [skip ci]
[tcpdump] / tests / TESTrun
1 #!/usr/bin/env perl
2
3 #
4 # Were we told where to find tcpdump?
5 #
6 if (!($TCPDUMP = $ENV{TCPDUMP_BIN})) {
7 #
8 # No. Use the appropriate path.
9 #
10 if ($^O eq 'MSWin32') {
11 #
12 # XXX - assume, for now, a Visual Studio debug build, so that
13 # tcpdump is in the Debug subdirectory.
14 #
15 $TCPDUMP = "Debug\\tcpdump"
16 } else {
17 $TCPDUMP = "./tcpdump"
18 }
19 }
20
21 #
22 # Make true and false work as Booleans.
23 #
24 use constant true => 1;
25 use constant false => 0;
26
27 use File::Basename;
28 use POSIX qw( WEXITSTATUS WIFEXITED);
29 use Cwd qw(abs_path getcwd);
30 use File::Path qw(mkpath); # mkpath works with ancient perl, as well as newer perl
31 use File::Spec;
32 use Data::Dumper; # for debugging.
33
34 # these are created in the directory where we are run, which might be
35 # a build directory.
36 my $newdir = "tests/NEW";
37 my $diffdir= "tests/DIFF";
38 mkpath($newdir);
39 mkpath($diffdir);
40 my $origdir = getcwd();
41 my $srcdir = $ENV{'srcdir'} || ".";
42
43 #
44 # Force UTC, so time stamps are printed in a standard time zone, and
45 # tests don't have to be run in the time zone in which the output
46 # file was generated.
47 #
48 $ENV{'TZ'}='GMT0';
49
50 #
51 # Get the tests directory from $0.
52 #
53 my $testsdir = dirname($0);
54
55 #
56 # Convert it to an absolute path, so it works even after we do a cd.
57 #
58 $testsdir = abs_path($testsdir);
59 print "Running tests from ${testsdir}\n";
60 print "with ${TCPDUMP}, version:\n";
61 system "${TCPDUMP} --version";
62
63 unshift(@INC, $testsdir);
64
65 $passedcount = 0;
66 $failedcount = 0;
67 #
68 my $failureoutput=$origdir . "/tests/failure-outputs.txt";
69
70 # truncate the output file
71 open(FAILUREOUTPUT, ">" . $failureoutput);
72 close(FAILUREOUTPUT);
73
74 $confighhash = undef;
75
76 sub showfile {
77 local($path) = @_;
78
79 #
80 # XXX - just do this directly in Perl?
81 #
82 if ($^O eq 'MSWin32') {
83 my $winpath = File::Spec->canonpath($path);
84 system "type $winpath";
85 } else {
86 system "cat $path";
87 }
88 }
89
90 sub runtest {
91 local($name, $input, $output, $options) = @_;
92 my $r;
93
94 $outputbase = basename($output);
95 my $coredump = false;
96 my $status = 0;
97 my $linecount = 0;
98 my $rawstderrlog = "tests/NEW/${outputbase}.raw.stderr";
99 my $stderrlog = "tests/NEW/${outputbase}.stderr";
100 my $diffstat = 0;
101 my $errdiffstat = 0;
102
103 # we used to do this as a nice pipeline, but the problem is that $r fails to
104 # to be set properly if the tcpdump core dumps.
105 #
106 # Furthermore, on Windows, fc can't read the standard input, so we
107 # can't do it as a pipeline in any case.
108 $r = system "$TCPDUMP -# -n -r $input $options >tests/NEW/${outputbase} 2>${rawstderrlog}";
109 if($r != 0) {
110 #
111 # Something other than "tcpdump opened the file, read it, and
112 # dissected all the packets". What happened?
113 #
114 # We write out an exit status after whatever the subprocess
115 # wrote out, so it shows up when we diff the expected output
116 # with it.
117 #
118 open(OUTPUT, ">>"."tests/NEW/$outputbase") || die "fail to open $outputbase\n";
119 if($r == -1) {
120 # failed to start due to error.
121 $status = $!;
122 printf OUTPUT "FAILED TO RUN: status: %d\n", $status;
123 } else {
124 if ($^O eq 'MSWin32') {
125 #
126 # On Windows, the return value of system is the lower 8
127 # bits of the exit status of the process, shifted left
128 # 8 bits.
129 #
130 # If the process crashed, rather than exiting, the
131 # exit status will be one of the EXCEPTION_ values
132 # listed in the documentation for the GetExceptionCode()
133 # macro.
134 #
135 # Those are defined as STATUS_ values, which should have
136 # 0xC in the topmost 4 bits (being fatal error
137 # statuses); some of them have a value that fits in
138 # the lower 8 bits. We could, I guess, assume that
139 # any value that 1) isn't returned by tcpdump and 2)
140 # corresponds to the lower 8 bits of a STATUS_ value
141 # used as an EXCEPTION_ value indicates that tcpdump
142 # exited with that exception.
143 #
144 # However, as we're running tcpdump with system, which
145 # runs the command through cmd.exe, and as cmd.exe
146 # doesn't map the command's exit code to its own exit
147 # code in any straightforward manner, we can't get
148 # that information in any case, so there's no point
149 # in trying to interpret it in that fashion.
150 #
151 $status = $r >> 8;
152 } else {
153 #
154 # On UN*Xes, the return status is a POSIX as filled in
155 # by wait() or waitpid().
156 #
157 # POSIX offers some calls for analyzing it, such as
158 # WIFSIGNALED() to test whether it indicates that the
159 # process was terminated by a signal, WTERMSIG() to
160 # get the signal number from it, WIFEXITED() to test
161 # whether it indicates that the process exited normally,
162 # and WEXITSTATUS() to get the exit status from it.
163 #
164 # POSIX doesn't standardize core dumps, so the POSIX
165 # calls can't test whether a core dump occurred.
166 # However, all the UN*Xes we are likely to encounter
167 # follow Research UNIX in this regard, with the exit
168 # status containing either 0 or a signal number in
169 # the lower 7 bits, with 0 meaning "exited rather
170 # than being terminated by a signal", the "core dumped"
171 # flag in the 0x80 bit, and, if the signal number is
172 # 0, the exit status in the next 8 bits up.
173 #
174 # This should be cleaned up to use the POSIX calls
175 # from the Perl library - and to define an additional
176 # WCOREDUMP() call to test the "core dumped" bit and
177 # use that.
178 #
179 # But note also that, as we're running tcpdump with
180 # system, which runs the command through a shell, if
181 # tcpdump crashes, we'll only know that if the shell
182 # maps the signal indication and uses that as its
183 # exit status.
184 #
185 # The good news is that the Bourne shell, and compatible
186 # shells, have traditionally done that. If the process
187 # for which the shell reports the exit status terminates
188 # with a signal, it adds 128 to the signal number and
189 # returns that as its exit status. (This is why the
190 # "this is now working right" behavior described in a
191 # comment below is occurring.)
192 #
193 # As tcpdump itself never returns with an exit status
194 # >= 128, we can try checking for an exit status with
195 # the 0x80 bit set and, if we have one, get the signal
196 # number from the lower 7 bits of the exit status. We
197 # can't get the "core dumped" indication from the
198 # shell's exit status; all we can do is check whether
199 # there's a core file.
200 #
201 if( $r & 128 ) {
202 $coredump = $r & 127;
203 }
204 if( WIFEXITED($r)) {
205 $status = WEXITSTATUS($r);
206 }
207 }
208
209 if($coredump || $status) {
210 printf OUTPUT "EXIT CODE %08x: dump:%d code: %d\n", $r, $coredump, $status;
211 } else {
212 printf OUTPUT "EXIT CODE %08x\n", $r;
213 }
214 $r = 0;
215 }
216 close(OUTPUT);
217 }
218 if($r == 0) {
219 #
220 # Compare tcpdump's output with what we think it should be.
221 # If tcpdump failed to produce output, we've produced our own
222 # "output" above, with the exit status.
223 #
224 if ($^O eq 'MSWin32') {
225 my $winoutput = File::Spec->canonpath($output);
226 $r = system "fc /lb1000 /t /1 $winoutput tests\\NEW\\$outputbase >tests\\DIFF\\$outputbase.diff";
227 $diffstat = $r >> 8;
228 } else {
229 $r = system "diff $output tests/NEW/$outputbase >tests/DIFF/$outputbase.diff";
230 $diffstat = WEXITSTATUS($r);
231 }
232 }
233
234 # process the standard error file, sanitize "reading from" line,
235 # and count lines
236 $linecount = 0;
237 open(ERRORRAW, "<" . $rawstderrlog);
238 open(ERROROUT, ">" . $stderrlog);
239 while(<ERRORRAW>) {
240 next if /^$/; # blank lines are boring
241 if(/^(reading from file )(.*)(,.*)$/) {
242 my $filename = basename($2);
243 print ERROROUT "${1}${filename}${3}\n";
244 next;
245 }
246 print ERROROUT;
247 $linecount++;
248 }
249 close(ERROROUT);
250 close(ERRORRAW);
251
252 if ( -f "$output.stderr" ) {
253 #
254 # Compare the standard error with what we think it should be.
255 #
256 if ($^O eq 'MSWin32') {
257 my $winoutput = File::Spec->canonpath($output);
258 my $canonstderrlog = File::Spec->canonpath($stderrlog);
259 $nr = system "fc /lb1000 /t /1 $winoutput.stderr $canonstderrlog >tests\DIFF\$outputbase.stderr.diff";
260 $errdiffstat = $nr >> 8;
261 } else {
262 $nr = system "diff $output.stderr $stderrlog >tests/DIFF/$outputbase.stderr.diff";
263 $errdiffstat = WEXITSTATUS($nr);
264 }
265 if($r == 0) {
266 $r = $nr;
267 }
268 }
269
270 if($r == 0) {
271 if($linecount == 0 && $status == 0) {
272 unlink($stderrlog);
273 } else {
274 $errdiffstat = 1;
275 }
276 }
277
278 #print sprintf("END: %08x\n", $r);
279
280 if($r == 0) {
281 if($linecount == 0) {
282 printf " %-40s: passed\n", $name;
283 } else {
284 printf " %-40s: passed with error messages:\n", $name;
285 showfile($stderrlog);
286 }
287 unlink "tests/DIFF/$outputbase.diff";
288 return 0;
289 }
290 # must have failed!
291 printf " %-40s: TEST FAILED(exit core=%d/diffstat=%d,%d/r=%d)", $name, $coredump, $diffstat, $errdiffstat, $r;
292 open FOUT, '>>tests/failure-outputs.txt';
293 printf FOUT "\nFailed test: $name\n\n";
294 close FOUT;
295 if(-f "tests/DIFF/$outputbase.diff") {
296 #
297 # XXX - just do this directly in Perl?
298 #
299 if ($^O eq 'MSWin32') {
300 system "type tests\\DIFF\\$outputbase.diff >> tests\\failure-outputs.txt";
301 } else {
302 system "cat tests/DIFF/$outputbase.diff >> tests/failure-outputs.txt";
303 }
304 }
305
306 if($r == -1) {
307 print " (failed to execute: $!)\n";
308 return(30);
309 }
310
311 # this is not working right, $r == 0x8b00 when there is a core dump.
312 # clearly, we need some platform specific perl magic to take this apart, so look for "core"
313 # too.
314 # In particular, on Solaris 10 SPARC an alignment problem results in SIGILL,
315 # a core dump and $r set to 0x00008a00 ($? == 138 in the shell).
316 if($r & 127 || -f "core") {
317 my $with = ($r & 128) ? 'with' : 'without';
318 if(-f "core") {
319 $with = "with";
320 }
321 printf " (terminated with signal %u, %s coredump)", ($r & 127), $with;
322 if($linecount == 0) {
323 print "\n";
324 } else {
325 print " with error messages:\n";
326 showfile($stderrlog);
327 }
328 return(($r & 128) ? 10 : 20);
329 }
330 if($linecount == 0) {
331 print "\n";
332 } else {
333 print " with error messages:\n";
334 showfile($stderrlog);
335 }
336 return(5);
337 }
338
339 sub loadconfighash {
340 if(defined($confighhash)) {
341 return $confighhash;
342 }
343
344 $main::confighhash = {};
345
346 # this could be loaded once perhaps.
347 open(CONFIG_H, "config.h") || die "Can not open config.h: $!\n";
348 while(<CONFIG_H>) {
349 chomp;
350 if(/^\#define (.*) 1/) {
351 #print "Setting $1\n";
352 $main::confighhash->{$1} = 1;
353 }
354 }
355 close(CONFIG_H);
356 #print Dumper($main::confighhash);
357
358 # also run tcpdump --fp-type to get the type of floating-point
359 # arithmetic we're doing, setting a HAVE_{fptype} key based
360 # on the value it prints
361 open(FPTYPE_PIPE, "$TCPDUMP --fp-type |") or die("piping tcpdump --fp-type failed\n");
362 my $fptype_val = <FPTYPE_PIPE>;
363 close(FPTYPE_PIPE);
364 my $have_fptype;
365 if($fptype_val == "9877.895") {
366 $have_fptype = "HAVE_FPTYPE1";
367 } else {
368 $have_fptype = "HAVE_FPTYPE2";
369 }
370 $main::confighhash->{$have_fptype} = 1;
371
372 return $main::confighhash;
373 }
374
375
376 sub runOneComplexTest {
377 local($testconfig) = @_;
378
379 my $output = $testconfig->{output};
380 my $input = $testconfig->{input};
381 my $name = $testconfig->{name};
382 my $options= $testconfig->{args};
383 my $foundit = 1;
384 my $unfoundit=1;
385
386 my $configset = $testconfig->{config_set};
387 my $configunset = $testconfig->{config_unset};
388 my $ch = loadconfighash();
389 #print Dumper($ch);
390
391 if(defined($configset)) {
392 $foundit = ($ch->{$configset} == 1);
393 }
394 if(defined($configunset)) {
395 $unfoundit=($ch->{$configunset} != 1);
396 }
397
398 if(!$foundit) {
399 printf " %-40s: skipped (%s not set)\n", $name, $configset;
400 return 0;
401 }
402
403 if(!$unfoundit) {
404 printf " %-40s: skipped (%s set)\n", $name, $configunset;
405 return 0;
406 }
407
408 #use Data::Dumper;
409 #print Dumper($testconfig);
410
411 # EXPAND any occurrences of @TESTDIR@ to $testsdir
412 $options =~ s/\@TESTDIR\@/$testsdir/;
413
414 my $result = runtest($name,
415 $testsdir . "/" . $input,
416 $testsdir . "/" . $output,
417 $options);
418
419 if($result == 0) {
420 $passedcount++;
421 } else {
422 $failedcount++;
423 }
424 }
425
426 # *.tests files are PERL hash definitions. They should create an array of hashes
427 # one per test, and place it into the variable @testlist.
428 sub runComplexTests {
429 my @files = glob( $testsdir . '/*.tests' );
430 foreach $file (@files) {
431 my @testlist = undef;
432 my $definitions;
433 print "FILE: ${file}\n";
434 open(FILE, "<".$file) || die "can not open $file: $!";
435 {
436 local $/ = undef;
437 $definitions = <FILE>;
438 }
439 close(FILE);
440 #print "STUFF: ${definitions}\n";
441 eval $definitions;
442 if(defined($testlist)) {
443 #use Data::Dumper;
444 #print Dumper($testlist);
445 foreach $test (@$testlist) {
446 runOneComplexTest($test);
447 }
448 } else {
449 warn "File: ${file} could not be loaded as PERL: $!";
450 }
451 }
452 }
453
454 sub runSimpleTests {
455
456 local($only)=@_;
457
458 open(TESTLIST, "<" . "${testsdir}/TESTLIST") || die "no ${testsdir}/TESTFILE: $!\n";
459 while(<TESTLIST>) {
460 next if /^\#/;
461 next if /^$/;
462
463 unlink("core");
464 ($name, $input, $output, @options) = split;
465 #print "processing ${only} vs ${name}\n";
466 next if(defined($only) && $only ne $name);
467
468 my $options = join(" ", @options);
469 #print "@{options} becomes ${options}\n";
470
471 my $hash = { name => $name,
472 input=> $input,
473 output=>$output,
474 args => $options };
475
476 runOneComplexTest($hash);
477 }
478 }
479
480 if(scalar(@ARGV) == 0) {
481 runSimpleTests();
482 runComplexTests();
483 } else {
484 runSimpleTests($ARGV[0]);
485 }
486
487 # exit with number of failing tests.
488 print "------------------------------------------------\n";
489 printf("%4u tests failed\n",$failedcount);
490 printf("%4u tests passed\n",$passedcount);
491
492 showfile(${failureoutput});
493 exit $failedcount;