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