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