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