#!/usr/perl5/bin/perl # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" require 5.6.1; use File::Find; use File::Basename; use Getopt::Std; use Cwd; use Cwd 'abs_path'; $PNAME = $0; $PNAME =~ s:.*/::; $OPTSTR = 'abd:ghi:lqsux:'; $USAGE = "Usage: $PNAME [-abghlqsu] [-d dir] [-i isa] " . "[-x opt[=arg]] [file | dir ...]\n"; ($MACH = `uname -p`) =~ s/\W*\n//; $dtrace_path = '/usr/sbin/dtrace'; @dtrace_argv = (); $ksh_path = '/usr/bin/ksh'; @files = (); %exceptions = (); $errs = 0; $bypassed = 0; # # If no test files are specified on the command-line, execute a find on "." # and append any tst.*.d, tst.*.ksh, err.*.d or drp.*.d files found within # the directory tree. # sub wanted { push(@files, $File::Find::name) if ($_ =~ /^(tst|err|drp)\..+\.(d|ksh)$/ && -f "$_"); } sub dirname { my($s) = @_; my($i); $s = substr($s, 0, $i) if (($i = rindex($s, '/')) != -1); return $i == -1 ? '.' : $i == 0 ? '/' : $s; } sub usage { print $USAGE; print "\t -a execute test suite using anonymous enablings\n"; print "\t -b execute bad ioctl test program\n"; print "\t -d specify directory for test results files and cores\n"; print "\t -g enable libumem debugging when running tests\n"; print "\t -h display verbose usage message\n"; print "\t -i specify ISA to test instead of isaexec(3C) default\n"; print "\t -l save log file of results and PIDs used by tests\n"; print "\t -q set quiet mode (only report errors and summary)\n"; print "\t -s save results files even for tests that pass\n"; print "\t -x pass corresponding -x argument to dtrace(1M)\n"; print "\n\tUse \"-i java\" to run tests using the "; print "Java DTrace API.\n"; exit(2); } sub errmsg { my($msg) = @_; print STDERR $msg; print LOG $msg if ($opt_l); $errs++; } sub fail { my(@parms) = @_; my($msg) = $parms[0]; my($errfile) = $parms[1]; my($n) = 0; my($dest) = basename($file); while (-d "$opt_d/failure.$n") { $n++; } unless (mkdir "$opt_d/failure.$n") { warn "ERROR: failed to make directory $opt_d/failure.$n: $!\n"; exit(125); } open(README, ">$opt_d/failure.$n/README"); print README "ERROR: " . $file . " " . $msg; if (scalar @parms > 1) { print README "; see $errfile\n"; } else { if (-f "$opt_d/$pid.core") { print README "; see $pid.core\n"; } else { print README "\n"; } } close(README); if (-f "$opt_d/$pid.out") { rename("$opt_d/$pid.out", "$opt_d/failure.$n/$pid.out"); link("$file.out", "$opt_d/failure.$n/$dest.out"); } if (-f "$opt_d/$pid.err") { rename("$opt_d/$pid.err", "$opt_d/failure.$n/$pid.err"); link("$file.err", "$opt_d/failure.$n/$dest.err"); } if (-f "$opt_d/$pid.core") { rename("$opt_d/$pid.core", "$opt_d/failure.$n/$pid.core"); } link("$file", "$opt_d/failure.$n/$dest"); $msg = "ERROR: " . $dest . " " . $msg; if (scalar @parms > 1) { $msg = $msg . "; see $errfile in failure.$n\n"; } else { $msg = $msg . "; details in failure.$n\n"; } errmsg($msg); } sub logmsg { my($msg) = @_; print STDOUT $msg unless ($opt_q); print LOG $msg if ($opt_l); } # Trim leading and trailing whitespace sub trim { my($s) = @_; $s =~ s/^\s*//; $s =~ s/\s*$//; return $s; } # Loads exception set of skipped tests sub load_exceptions { my($listfile) = @_; my($line) = ""; exit(123) unless open(STDIN, "<$listfile"); while () { chomp; $line = $_; # line is non-empty and not a comment if ((length($line) > 0) && ($line =~ /^\s*[^\s#]/ )) { $exceptions{trim($line)} = 1; } } return 0; } # Return 1 if file name found in exception set, 0 otherwise sub is_exception { my($file) = @_; my($i) = -1; # hash absolute pathname after $dt_tst/ $file = abs_path($file); $i = index($file, $dt_tst); if ($i == 0) { $file = substr($file, length($dt_tst) + 1); return $exceptions{$file}; } return 0; } die $USAGE unless (getopts($OPTSTR)); usage() if ($opt_h); foreach $arg (@ARGV) { if (-f $arg) { push(@files, $arg); } elsif (-d $arg) { find(\&wanted, $arg); } else { die "$PNAME: $arg is not a valid file or directory\n"; } } $dt_tst = '/opt/SUNWdtrt/tst'; $dt_bin = '/opt/SUNWdtrt/bin'; $defdir = -d $dt_tst ? $dt_tst : '.'; $bindir = -d $dt_bin ? $dt_bin : '.'; find(\&wanted, "$defdir/common") if (scalar(@ARGV) == 0); find(\&wanted, "$defdir/$MACH") if (scalar(@ARGV) == 0); die $USAGE if (scalar(@files) == 0); if ($opt_d) { die "$PNAME: -d arg must be absolute path\n" unless ($opt_d =~ /^\//); die "$PNAME: -d arg $opt_d is not a directory\n" unless (-d "$opt_d"); system("coreadm -p $opt_d/%p.core"); } else { my $dir = getcwd; system("coreadm -p $dir/%p.core"); $opt_d = '.'; } if ($opt_i) { if ($opt_i eq "java") { $dtrace_path = $bindir . "/jdtrace"; die "$PNAME: jdtrace not found\n" unless (-x "$dtrace_path"); load_exceptions($bindir . "/exception.lst"); } else { $dtrace_path = "/usr/sbin/$opt_i/dtrace"; die "$PNAME: dtrace(1M) for ISA $opt_i not found\n" unless (-x "$dtrace_path"); } } if ($opt_x) { push(@dtrace_argv, '-x'); push(@dtrace_argv, $opt_x); } die "$PNAME: failed to open $PNAME.$$.log: $!\n" unless (!$opt_l || open(LOG, ">$PNAME.$$.log")); if ($opt_g) { $ENV{'UMEM_DEBUG'} = 'default,verbose'; $ENV{'UMEM_LOGGING'} = 'fail,contents'; $ENV{'LD_PRELOAD'} = 'libumem.so'; } # # Ensure that $PATH contains a cc(1) so that we can execute the # test programs that require compilation of C code. # $ENV{'PATH'} = $ENV{'PATH'} . ':/ws/onnv-tools/SUNWspro/SS11/bin'; if ($opt_b) { logmsg("badioctl'ing ... "); if (($badioctl = fork()) == -1) { errmsg("ERROR: failed to fork to run badioctl: $!\n"); next; } if ($badioctl == 0) { open(STDIN, '$opt_d/$$.out"); exit(125) unless open(STDERR, ">$opt_d/$$.err"); exec($bindir . "/badioctl"); warn "ERROR: failed to exec badioctl: $!\n"; exit(127); } logmsg("[$badioctl]\n"); # # If we're going to be bad, we're just going to iterate over each # test file. # foreach $file (sort @files) { ($name = $file) =~ s:.*/::; $dir = dirname($file); if (!($name =~ /^tst\./ && $name =~ /\.d$/)) { next; } logmsg("baddof'ing $file ... "); if (($pid = fork()) == -1) { errmsg("ERROR: failed to fork to run baddof: $!\n"); next; } if ($pid == 0) { open(STDIN, '$opt_d/$$.out"); exit(125) unless open(STDERR, ">$opt_d/$$.err"); unless (chdir($dir)) { warn "ERROR: failed to chdir for $file: $!\n"; exit(126); } exec($bindir . "/baddof", $name); warn "ERROR: failed to exec for $file: $!\n"; exit(127); } sleep 60; kill(9, $pid); waitpid($pid, 0); logmsg("[$pid]\n"); unless ($opt_s) { unlink($pid . '.out'); unlink($pid . '.err'); } } kill(9, $badioctl); waitpid($badioctl, 0); unless ($opt_s) { unlink($badioctl . '.out'); unlink($badioctl . '.err'); } exit(0); } if ($opt_u) { logmsg "spawning module unloading process... "; $unloader = fork; if ($unloader != 0 && !defined $unloader) { # # Couldn't fork for some reason. # die "couldn't fork: $!\n"; } if ($unloader == 0) { # # We're in the child. Go modunload krazy. # for (;;) { system("modunload -i 0"); } } else { logmsg "[$unloader]\n"; $SIG{INT} = sub { kill 9, $unloader; exit($errs != 0); }; } } # # Iterate over the set of test files specified on the command-line or located # by a find on "." and execute each one. If the test file is executable, we # assume it is a #! script and run it. Otherwise we run dtrace -s on it. # If the file is named tst.* we assume it should return exit status 0. # If the file is named err.* we assume it should return exit status 1. # If the file is named err.D_[A-Z0-9]+[.*].d we use dtrace -xerrtags and # examine stderr to ensure that a matching error tag was produced. # If the file is named drp.[A-Z0-9]+[.*].d we use dtrace -xdroptags and # examine stderr to ensure that a matching drop tag was produced. # If any *.out or *.err files are found we perform output comparisons. # foreach $file (sort @files) { $file =~ m:.*/((.*)\.(\w+)):; $name = $1; $base = $2; $ext = $3; $dir = dirname($file); $isksh = 0; $tag = 0; $droptag = 0; if ($name =~ /^tst\./) { $isksh = ($ext eq 'ksh'); $status = 0; } elsif ($name =~ /^err\.(D_[A-Z0-9_]+)\./) { $status = 1; $tag = $1; } elsif ($name =~ /^err\./) { $status = 1; } elsif ($name =~ /^drp\.([A-Z0-9_]+)\./) { $status = 0; $droptag = $1; } else { errmsg("ERROR: $file is not a valid test file name\n"); next; } $fullname = "$dir/$name"; $exe = "$dir/$base.exe"; $exe_pid = -1; if ($opt_a && ($status != 0 || $tag != 0 || $droptag != 0 || -x $exe || $isksh || -x $fullname)) { $bypassed++; next; } if ($opt_i eq "java") { if (is_exception("$dir/$name")) { $bypassed++; next; } } if (!$isksh && -x $exe) { if (($exe_pid = fork()) == -1) { errmsg("ERROR: failed to fork to run $exe: $!\n"); next; } if ($exe_pid == 0) { open(STDIN, '$opt_d/$$.out"); exit(125) unless open(STDERR, ">$opt_d/$$.err"); unless (chdir($dir)) { warn "ERROR: failed to chdir for $file: $!\n"; exit(126); } push(@dtrace_argv, '-xerrtags') if ($tag); push(@dtrace_argv, '-xdroptags') if ($droptag); push(@dtrace_argv, $exe_pid) if ($exe_pid != -1); if ($isksh) { exit(123) unless open(STDIN, "<$name"); exec("$ksh_path /dev/stdin $dtrace_path"); } elsif (-x $name) { warn "ERROR: $name is executable\n"; exit(1); } else { if ($tag == 0 && $status == $0 && $opt_a) { push(@dtrace_argv, '-A'); } push(@dtrace_argv, '-C'); push(@dtrace_argv, '-s'); push(@dtrace_argv, $name); exec($dtrace_path, @dtrace_argv); } warn "ERROR: failed to exec for $file: $!\n"; exit(127); } if (waitpid($pid, 0) == -1) { errmsg("ERROR: timed out waiting for $file\n"); kill(9, $exe_pid) if ($exe_pid != -1); kill(9, $pid); next; } kill(9, $exe_pid) if ($exe_pid != -1); if ($tag == 0 && $status == $0 && $opt_a) { # # We can chuck the earler output. # unlink($pid . '.out'); unlink($pid . '.err'); # # This is an anonymous enabling. We need to get the module # unloaded. # system("dtrace -ae 1> /dev/null 2> /dev/null"); system("svcadm disable -s svc:/network/nfs/mapid:default"); system("modunload -i 0 ; modunload -i 0 ; modunload -i 0"); if (!system("modinfo | grep dtrace")) { warn "ERROR: couldn't unload dtrace\n"; system("svcadm enable " . "-s svc:/network/nfs/mapid:default"); exit(124); } # # DTrace is gone. Now update_drv(1M), and rip everything out # again. # system("update_drv dtrace"); system("dtrace -ae 1> /dev/null 2> /dev/null"); system("modunload -i 0 ; modunload -i 0 ; modunload -i 0"); if (!system("modinfo | grep dtrace")) { warn "ERROR: couldn't unload dtrace\n"; system("svcadm enable " . "-s svc:/network/nfs/mapid:default"); exit(124); } # # Now bring DTrace back in. # system("sync ; sync"); system("dtrace -l -n bogusprobe 1> /dev/null 2> /dev/null"); system("svcadm enable -s svc:/network/nfs/mapid:default"); # # That should have caused DTrace to reload with the new # configuration file. Now we can try to snag our anonymous # state. # if (($pid = fork()) == -1) { errmsg("ERROR: failed to fork to run test $file: $!\n"); next; } if ($pid == 0) { open(STDIN, '$opt_d/$$.out"); exit(125) unless open(STDERR, ">$opt_d/$$.err"); push(@dtrace_argv, '-a'); unless (chdir($dir)) { warn "ERROR: failed to chdir for $file: $!\n"; exit(126); } exec($dtrace_path, @dtrace_argv); warn "ERROR: failed to exec for $file: $!\n"; exit(127); } if (waitpid($pid, 0) == -1) { errmsg("ERROR: timed out waiting for $file\n"); kill(9, $pid); next; } } logmsg("[$pid]\n"); $wstat = $?; $wifexited = ($wstat & 0xFF) == 0; $wexitstat = ($wstat >> 8) & 0xFF; $wtermsig = ($wstat & 0x7F); if (!$wifexited) { fail("died from signal $wtermsig"); next; } if ($wexitstat == 125) { die "$PNAME: failed to create output file in $opt_d " . "(cd elsewhere or use -d)\n"; } if ($wexitstat != $status) { fail("returned $wexitstat instead of $status"); next; } if (-f "$file.out" && system("cmp -s $file.out $opt_d/$pid.out") != 0) { fail("stdout mismatch", "$pid.out"); next; } if (-f "$file.err" && system("cmp -s $file.err $opt_d/$pid.err") != 0) { fail("stderr mismatch: see $pid.err"); next; } if ($tag) { open(TSTERR, "<$opt_d/$pid.err"); $tsterr = ; close(TSTERR); unless ($tsterr =~ /: \[$tag\] line \d+:/) { fail("errtag mismatch: see $pid.err"); next; } } if ($droptag) { $found = 0; open(TSTERR, "<$opt_d/$pid.err"); while () { if (/\[$droptag\] /) { $found = 1; last; } } close (TSTERR); unless ($found) { fail("droptag mismatch: see $pid.err"); next; } } unless ($opt_s) { unlink($pid . '.out'); unlink($pid . '.err'); } } if ($opt_a) { # # If we're running with anonymous enablings, we need to restore the # .conf file. # system("dtrace -A 1> /dev/null 2> /dev/null"); system("dtrace -ae 1> /dev/null 2> /dev/null"); system("modunload -i 0 ; modunload -i 0 ; modunload -i 0"); system("update_drv dtrace"); } $opt_q = 0; # force final summary to appear regardless of -q option logmsg("\n==== TEST RESULTS ====\n"); logmsg(" passed: " . (scalar(@files) - $errs - $bypassed) . "\n"); if ($bypassed) { logmsg(" bypassed: " . $bypassed . "\n"); } logmsg(" failed: " . $errs . "\n"); logmsg(" total: " . scalar(@files) . "\n"); if ($opt_u) { kill 9, $unloader; waitpid $unloader, 0; } exit($errs != 0);