#!/usr/bin/env perl # # Were we told where to find tcpdump? # if (!($TCPDUMP = $ENV{TCPDUMP_BIN})) { # # No. Use the appropriate path. # if ($^O eq 'MSWin32') { # # XXX - assume, for now, a Visual Studio debug build, so that # tcpdump is in the Debug subdirectory. # $TCPDUMP = "Debug\\tcpdump" } else { $TCPDUMP = "./tcpdump" } } # # Make true and false work as Booleans. # use constant true => 1; use constant false => 0; use File::Basename; use POSIX qw( WEXITSTATUS WIFEXITED); use Cwd qw(abs_path getcwd); use File::Path qw(mkpath); # mkpath works with ancient perl, as well as newer perl use File::Spec; use Data::Dumper; # for debugging. # these are created in the directory where we are run, which might be # a build directory. my $newdir = "tests/NEW"; my $diffdir= "tests/DIFF"; mkpath($newdir); mkpath($diffdir); my $origdir = getcwd(); my $srcdir = $ENV{'srcdir'} || "."; # Default to unified diff and allow to fall back to basic diff if necessary. my $diff_flags = defined $ENV{'DIFF_FLAGS'} ? $ENV{'DIFF_FLAGS'} : '-u'; # # Force UTC, so time stamps are printed in a standard time zone, and # tests don't have to be run in the time zone in which the output # file was generated. # $ENV{'TZ'}='GMT0'; # # Get the tests directory from $0. # my $testsdir = dirname($0); # # Convert it to an absolute path, so it works even after we do a cd. # $testsdir = abs_path($testsdir); print "Running tests from ${testsdir}\n"; print "with ${TCPDUMP}, version:\n"; system "${TCPDUMP} --version"; unshift(@INC, $testsdir); $passedcount = 0; $failedcount = 0; # my $failureoutput=$origdir . "/tests/failure-outputs.txt"; # truncate the output file open(FAILUREOUTPUT, ">" . $failureoutput); close(FAILUREOUTPUT); $confighhash = undef; sub showfile { local($path) = @_; # # XXX - just do this directly in Perl? # if ($^O eq 'MSWin32') { my $winpath = File::Spec->canonpath($path); system "type $winpath"; } else { system "cat $path"; } } sub runtest { local($name, $input, $output, $options) = @_; my $r; $outputbase = basename($output); my $coredump = false; my $status = 0; my $linecount = 0; my $rawstderrlog = "tests/NEW/${outputbase}.raw.stderr"; my $stderrlog = "tests/NEW/${outputbase}.stderr"; my $diffstat = 0; my $errdiffstat = 0; # we used to do this as a nice pipeline, but the problem is that $r fails to # to be set properly if the tcpdump core dumps. # # Furthermore, on Windows, fc can't read the standard input, so we # can't do it as a pipeline in any case. $r = system "$TCPDUMP -# -n -r $input $options >tests/NEW/${outputbase} 2>${rawstderrlog}"; if($r != 0) { # # Something other than "tcpdump opened the file, read it, and # dissected all the packets". What happened? # # We write out an exit status after whatever the subprocess # wrote out, so it shows up when we diff the expected output # with it. # open(OUTPUT, ">>"."tests/NEW/$outputbase") || die "fail to open $outputbase\n"; if($r == -1) { # failed to start due to error. $status = $!; printf OUTPUT "FAILED TO RUN: status: %d\n", $status; } else { if ($^O eq 'MSWin32' or $^O eq 'msys') { # # On Windows, the return value of system is the lower 8 # bits of the exit status of the process, shifted left # 8 bits. # # If the process crashed, rather than exiting, the # exit status will be one of the EXCEPTION_ values # listed in the documentation for the GetExceptionCode() # macro. # # Those are defined as STATUS_ values, which should have # 0xC in the topmost 4 bits (being fatal error # statuses); some of them have a value that fits in # the lower 8 bits. We could, I guess, assume that # any value that 1) isn't returned by tcpdump and 2) # corresponds to the lower 8 bits of a STATUS_ value # used as an EXCEPTION_ value indicates that tcpdump # exited with that exception. # # However, as we're running tcpdump with system, which # runs the command through cmd.exe, and as cmd.exe # doesn't map the command's exit code to its own exit # code in any straightforward manner, we can't get # that information in any case, so there's no point # in trying to interpret it in that fashion. # $status = $r >> 8; } else { # # On UN*Xes, the return status is a POSIX as filled in # by wait() or waitpid(). # # POSIX offers some calls for analyzing it, such as # WIFSIGNALED() to test whether it indicates that the # process was terminated by a signal, WTERMSIG() to # get the signal number from it, WIFEXITED() to test # whether it indicates that the process exited normally, # and WEXITSTATUS() to get the exit status from it. # # POSIX doesn't standardize core dumps, so the POSIX # calls can't test whether a core dump occurred. # However, all the UN*Xes we are likely to encounter # follow Research UNIX in this regard, with the exit # status containing either 0 or a signal number in # the lower 7 bits, with 0 meaning "exited rather # than being terminated by a signal", the "core dumped" # flag in the 0x80 bit, and, if the signal number is # 0, the exit status in the next 8 bits up. # # This should be cleaned up to use the POSIX calls # from the Perl library - and to define an additional # WCOREDUMP() call to test the "core dumped" bit and # use that. # # But note also that, as we're running tcpdump with # system, which runs the command through a shell, if # tcpdump crashes, we'll only know that if the shell # maps the signal indication and uses that as its # exit status. # # The good news is that the Bourne shell, and compatible # shells, have traditionally done that. If the process # for which the shell reports the exit status terminates # with a signal, it adds 128 to the signal number and # returns that as its exit status. (This is why the # "this is now working right" behavior described in a # comment below is occurring.) # # As tcpdump itself never returns with an exit status # >= 128, we can try checking for an exit status with # the 0x80 bit set and, if we have one, get the signal # number from the lower 7 bits of the exit status. We # can't get the "core dumped" indication from the # shell's exit status; all we can do is check whether # there's a core file. # if( $r & 128 ) { $coredump = $r & 127; } if( WIFEXITED($r)) { $status = WEXITSTATUS($r); } } if($coredump || $status) { printf OUTPUT "EXIT CODE %08x: dump:%d code: %d\n", $r, $coredump, $status; } else { printf OUTPUT "EXIT CODE %08x\n", $r; } $r = 0; } close(OUTPUT); } if($r == 0) { # # Compare tcpdump's output with what we think it should be. # If tcpdump failed to produce output, we've produced our own # "output" above, with the exit status. # if ($^O eq 'MSWin32') { my $winoutput = File::Spec->canonpath($output); $r = system "fc /lb1000 /t /1 $winoutput tests\\NEW\\$outputbase >tests\\DIFF\\$outputbase.diff"; $diffstat = $r >> 8; } else { $r = system "diff $diff_flags $output tests/NEW/$outputbase >tests/DIFF/$outputbase.diff"; $diffstat = WEXITSTATUS($r); } } # process the standard error file, sanitize "reading from" line, # and count lines $linecount = 0; open(ERRORRAW, "<" . $rawstderrlog); open(ERROROUT, ">" . $stderrlog); while() { next if /^$/; # blank lines are boring if(/^(reading from file )(.*)(,.*)$/) { my $filename = basename($2); print ERROROUT "${1}${filename}${3}\n"; next; } print ERROROUT; $linecount++; } close(ERROROUT); close(ERRORRAW); if ( -f "$output.stderr" ) { # # Compare the standard error with what we think it should be. # if ($^O eq 'MSWin32') { my $winoutput = File::Spec->canonpath($output); my $canonstderrlog = File::Spec->canonpath($stderrlog); $nr = system "fc /lb1000 /t /1 $winoutput.stderr $canonstderrlog >tests\DIFF\$outputbase.stderr.diff"; $errdiffstat = $nr >> 8; } else { $nr = system "diff $output.stderr $stderrlog >tests/DIFF/$outputbase.stderr.diff"; $errdiffstat = WEXITSTATUS($nr); } if($r == 0) { $r = $nr; } } if($r == 0) { if($linecount == 0 && $status == 0) { unlink($stderrlog); } else { $errdiffstat = 1; } } #print sprintf("END: %08x\n", $r); if($r == 0) { if($linecount == 0) { printf " %-40s: passed\n", $name; } else { printf " %-40s: passed with error messages:\n", $name; showfile($stderrlog); } unlink "tests/DIFF/$outputbase.diff"; return 0; } # must have failed! printf " %-40s: TEST FAILED(exit core=%d/diffstat=%d,%d/r=%d)", $name, $coredump, $diffstat, $errdiffstat, $r; open FOUT, '>>tests/failure-outputs.txt'; printf FOUT "\nFailed test: $name\n\n"; close FOUT; if(-f "tests/DIFF/$outputbase.diff") { # # XXX - just do this directly in Perl? # if ($^O eq 'MSWin32') { system "type tests\\DIFF\\$outputbase.diff >> tests\\failure-outputs.txt"; } else { system "cat tests/DIFF/$outputbase.diff >> tests/failure-outputs.txt"; } } if($r == -1) { print " (failed to execute: $!)\n"; return(30); } # this is not working right, $r == 0x8b00 when there is a core dump. # clearly, we need some platform specific perl magic to take this apart, so look for "core" # too. # In particular, on Solaris 10 SPARC an alignment problem results in SIGILL, # a core dump and $r set to 0x00008a00 ($? == 138 in the shell). if($r & 127 || -f "core") { my $with = ($r & 128) ? 'with' : 'without'; if(-f "core") { $with = "with"; } printf " (terminated with signal %u, %s coredump)", ($r & 127), $with; if($linecount == 0) { print "\n"; } else { print " with error messages:\n"; showfile($stderrlog); } return(($r & 128) ? 10 : 20); } if($linecount == 0) { print "\n"; } else { print " with error messages:\n"; showfile($stderrlog); } return(5); } sub loadconfighash { if(defined($confighhash)) { return $confighhash; } $main::confighhash = {}; # this could be loaded once perhaps. open(CONFIG_H, "config.h") || die "Can not open config.h: $!\n"; while() { chomp; if(/^\#define (.*) 1/) { #print "Setting $1\n"; $main::confighhash->{$1} = 1; } } close(CONFIG_H); #print Dumper($main::confighhash); # also run tcpdump --fp-type to get the type of floating-point # arithmetic we're doing, setting a HAVE_{fptype} key based # on the value it prints open(FPTYPE_PIPE, "$TCPDUMP --fp-type |") or die("piping tcpdump --fp-type failed\n"); my $fptype_val = ; close(FPTYPE_PIPE); my $have_fptype; if($fptype_val == "9877.895") { $have_fptype = "HAVE_FPTYPE1"; } else { $have_fptype = "HAVE_FPTYPE2"; } $main::confighhash->{$have_fptype} = 1; # and check whether this is OpenBSD, as one test fails in OpenBSD # due to the sad hellscape of low-numbered DLT_ values, due to # 12 meaning "OpenBSD loopback" rather than "raw IP" on OpenBSD if($^O eq "openbsd") { $main::confighhash->{"IS_OPENBSD"} = 1; } return $main::confighhash; } sub runOneComplexTest { local($testconfig) = @_; my $output = $testconfig->{output}; my $input = $testconfig->{input}; my $name = $testconfig->{name}; my $options= $testconfig->{args}; my $foundit = 1; my $unfoundit=1; my $configset = $testconfig->{config_set}; my $configunset = $testconfig->{config_unset}; my $ch = loadconfighash(); #print Dumper($ch); if(defined($configset)) { $foundit = ($ch->{$configset} == 1); } if(defined($configunset)) { $unfoundit=($ch->{$configunset} != 1); } if(!$foundit) { printf " %-40s: skipped (%s not set)\n", $name, $configset; return 0; } if(!$unfoundit) { printf " %-40s: skipped (%s set)\n", $name, $configunset; return 0; } #use Data::Dumper; #print Dumper($testconfig); # EXPAND any occurrences of @TESTDIR@ to $testsdir $options =~ s/\@TESTDIR\@/$testsdir/; my $result = runtest($name, $testsdir . "/" . $input, $testsdir . "/" . $output, $options); if($result == 0) { $passedcount++; } else { $failedcount++; } } # *.tests files are PERL hash definitions. They should create an array of hashes # one per test, and place it into the variable @testlist. sub runComplexTests { my @files = glob( $testsdir . '/*.tests' ); foreach $file (@files) { my @testlist = undef; my $definitions; print "FILE: ${file}\n"; open(FILE, "<".$file) || die "can not open $file: $!"; { local $/ = undef; $definitions = ; } close(FILE); #print "STUFF: ${definitions}\n"; eval $definitions; if(defined($testlist)) { #use Data::Dumper; #print Dumper($testlist); foreach $test (@$testlist) { runOneComplexTest($test); } } else { warn "File: ${file} could not be loaded as PERL: $!"; } } } sub runSimpleTests { local($only)=@_; open(TESTLIST, "<" . "${testsdir}/TESTLIST") || die "no ${testsdir}/TESTFILE: $!\n"; while() { next if /^\#/; next if /^$/; unlink("core"); ($name, $input, $output, @options) = split; #print "processing ${only} vs ${name}\n"; next if(defined($only) && $only ne $name); my $options = join(" ", @options); #print "@{options} becomes ${options}\n"; my $hash = { name => $name, input=> $input, output=>$output, args => $options }; runOneComplexTest($hash); } } if(scalar(@ARGV) == 0) { runSimpleTests(); runComplexTests(); } else { runSimpleTests($ARGV[0]); } # exit with number of failing tests. print "------------------------------------------------\n"; printf("%4u tests failed\n",$failedcount); printf("%4u tests passed\n",$passedcount); showfile(${failureoutput}); exit $failedcount;