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