Home | History | Annotate | Line # | Download | only in tests
      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;
    517