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