1#!/usr/local/bin/perl 2# 3# CDDL HEADER START 4# 5# The contents of this file are subject to the terms of the 6# Common Development and Distribution License (the "License"). 7# You may not use this file except in compliance with the License. 8# 9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10# or http://www.opensolaris.org/os/licensing. 11# See the License for the specific language governing permissions 12# and limitations under the License. 13# 14# When distributing Covered Code, include this CDDL HEADER in each 15# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16# If applicable, add the following below this CDDL HEADER, with the 17# fields enclosed by brackets "[]" replaced with your own identifying 18# information: Portions Copyright [yyyy] [name of copyright owner] 19# 20# CDDL HEADER END 21# 22 23# 24# Copyright 2008 Sun Microsystems, Inc. All rights reserved. 25# Use is subject to license terms. 26# 27 28require 5.8.4; 29 30use File::Find; 31use File::Basename; 32use Getopt::Std; 33use Cwd; 34use Cwd 'abs_path'; 35 36$PNAME = $0; 37$PNAME =~ s:.*/::; 38$OPTSTR = 'abd:fghi:jlnqsx:'; 39$USAGE = "Usage: $PNAME [-abfghjlnqs] [-d dir] [-i isa] " 40 . "[-x opt[=arg]] [file | dir ...]\n"; 41($MACH = `uname -p`) =~ s/\W*\n//; 42($PLATFORM = `uname -i`) =~ s/\W*\n//; 43 44@dtrace_argv = (); 45 46$ksh_path = '/usr/local/bin/ksh'; 47 48@files = (); 49%exceptions = (); 50%results = (); 51$errs = 0; 52 53# 54# If no test files are specified on the command-line, execute a find on "." 55# and append any tst.*.d, tst.*.ksh, err.*.d or drp.*.d files found within 56# the directory tree. 57# 58sub wanted 59{ 60 push(@files, $File::Find::name) 61 if ($_ =~ /^(tst|err|drp)\..+\.(d|ksh)$/ && -f "$_"); 62} 63 64sub dirname { 65 my($s) = @_; 66 my($i); 67 68 $s = substr($s, 0, $i) if (($i = rindex($s, '/')) != -1); 69 return $i == -1 ? '.' : $i == 0 ? '/' : $s; 70} 71 72sub usage 73{ 74 print $USAGE; 75 print "\t -a execute test suite using anonymous enablings\n"; 76 print "\t -b execute bad ioctl test program\n"; 77 print "\t -d specify directory for test results files and cores\n"; 78 print "\t -g enable libumem debugging when running tests\n"; 79 print "\t -f force bypassed tests to run\n"; 80 print "\t -h display verbose usage message\n"; 81 print "\t -i specify ISA to test instead of isaexec(3C) default\n"; 82 print "\t -j execute test suite using jdtrace (Java API) only\n"; 83 print "\t -l save log file of results and PIDs used by tests\n"; 84 print "\t -n execute test suite using dtrace(1m) only\n"; 85 print "\t -q set quiet mode (only report errors and summary)\n"; 86 print "\t -s save results files even for tests that pass\n"; 87 print "\t -x pass corresponding -x argument to dtrace(1M)\n"; 88 exit(2); 89} 90 91sub errmsg 92{ 93 my($msg) = @_; 94 95 print STDERR $msg; 96 print LOG $msg if ($opt_l); 97 $errs++; 98} 99 100sub fail 101{ 102 my(@parms) = @_; 103 my($msg) = $parms[0]; 104 my($errfile) = $parms[1]; 105 my($n) = 0; 106 my($dest) = basename($file); 107 108 while (-d "$opt_d/failure.$n") { 109 $n++; 110 } 111 112 unless (mkdir "$opt_d/failure.$n") { 113 warn "ERROR: failed to make directory $opt_d/failure.$n: $!\n"; 114 exit(125); 115 } 116 117 open(README, ">$opt_d/failure.$n/README"); 118 print README "ERROR: " . $file . " " . $msg; 119 120 if (scalar @parms > 1) { 121 print README "; see $errfile\n"; 122 } else { 123 if (-f "$opt_d/$pid.core") { 124 print README "; see $pid.core\n"; 125 } else { 126 print README "\n"; 127 } 128 } 129 130 close(README); 131 132 if (-f "$opt_d/$pid.out") { 133 rename("$opt_d/$pid.out", "$opt_d/failure.$n/$pid.out"); 134 link("$file.out", "$opt_d/failure.$n/$dest.out"); 135 } 136 137 if (-f "$opt_d/$pid.err") { 138 rename("$opt_d/$pid.err", "$opt_d/failure.$n/$pid.err"); 139 link("$file.err", "$opt_d/failure.$n/$dest.err"); 140 } 141 142 if (-f "$opt_d/$pid.core") { 143 rename("$opt_d/$pid.core", "$opt_d/failure.$n/$pid.core"); 144 } 145 146 link("$file", "$opt_d/failure.$n/$dest"); 147 148 $msg = "ERROR: " . $dest . " " . $msg; 149 150 if (scalar @parms > 1) { 151 $msg = $msg . "; see $errfile in failure.$n\n"; 152 } else { 153 $msg = $msg . "; details in failure.$n\n"; 154 } 155 156 errmsg($msg); 157} 158 159sub logmsg 160{ 161 my($msg) = @_; 162 163 print STDOUT $msg unless ($opt_q); 164 print LOG $msg if ($opt_l); 165} 166 167# Trim leading and trailing whitespace 168sub trim { 169 my($s) = @_; 170 171 $s =~ s/^\s*//; 172 $s =~ s/\s*$//; 173 return $s; 174} 175 176# Load exception set of skipped tests from the file at the given 177# pathname. The test names are assumed to be paths relative to $dt_tst, 178# for example: common/aggs/tst.neglquant.d, and specify tests to be 179# skipped. 180sub load_exceptions { 181 my($listfile) = @_; 182 my($line) = ""; 183 184 %exceptions = (); 185 if (length($listfile) > 0) { 186 exit(123) unless open(STDIN, "<$listfile"); 187 while (<STDIN>) { 188 chomp; 189 $line = $_; 190 # line is non-empty and not a comment 191 if ((length($line) > 0) && ($line =~ /^\s*[^\s#]/ )) { 192 $exceptions{trim($line)} = 1; 193 } 194 } 195 } 196} 197 198# Return 1 if the test is found in the exception set, 0 otherwise. 199sub is_exception { 200 my($file) = @_; 201 my($i) = -1; 202 203 if (scalar(keys(%exceptions)) == 0) { 204 return 0; 205 } 206 207 # hash absolute pathname after $dt_tst/ 208 $file = abs_path($file); 209 $i = index($file, $dt_tst); 210 if ($i == 0) { 211 $file = substr($file, length($dt_tst) + 1); 212 return $exceptions{$file}; 213 } 214 return 0; 215} 216 217# 218# Iterate over the set of test files specified on the command-line or by a find 219# on "$defdir/common", "$defdir/$MACH" and "$defdir/$PLATFORM" and execute each 220# one. If the test file is executable, we fork and exec it. If the test is a 221# .ksh file, we run it with $ksh_path. Otherwise we run dtrace -s on it. If 222# the file is named tst.* we assume it should return exit status 0. If the 223# file is named err.* we assume it should return exit status 1. If the file is 224# named err.D_[A-Z0-9]+[.*].d we use dtrace -xerrtags and examine stderr to 225# ensure that a matching error tag was produced. If the file is named 226# drp.[A-Z0-9]+[.*].d we use dtrace -xdroptags and examine stderr to ensure 227# that a matching drop tag was produced. If any *.out or *.err files are found 228# we perform output comparisons. 229# 230# run_tests takes two arguments: The first is the pathname of the dtrace 231# command to invoke when running the tests. The second is the pathname 232# of a file (may be the empty string) listing tests that ought to be 233# skipped (skipped tests are listed as paths relative to $dt_tst, for 234# example: common/aggs/tst.neglquant.d). 235# 236sub run_tests { 237 my($dtrace, $exceptions_path) = @_; 238 my($passed) = 0; 239 my($bypassed) = 0; 240 my($failed) = $errs; 241 my($total) = 0; 242 243 die "$PNAME: $dtrace not found\n" unless (-x "$dtrace"); 244 logmsg($dtrace . "\n"); 245 246 load_exceptions($exceptions_path); 247 248 foreach $file (sort @files) { 249 $file =~ m:.*/((.*)\.(\w+)):; 250 $name = $1; 251 $base = $2; 252 $ext = $3; 253 254 $dir = dirname($file); 255 $isksh = 0; 256 $tag = 0; 257 $droptag = 0; 258 259 if ($name =~ /^tst\./) { 260 $isksh = ($ext eq 'ksh'); 261 $status = 0; 262 } elsif ($name =~ /^err\.(D_[A-Z0-9_]+)\./) { 263 $status = 1; 264 $tag = $1; 265 } elsif ($name =~ /^err\./) { 266 $status = 1; 267 } elsif ($name =~ /^drp\.([A-Z0-9_]+)\./) { 268 $status = 0; 269 $droptag = $1; 270 } else { 271 errmsg("ERROR: $file is not a valid test file name\n"); 272 next; 273 } 274 275 $fullname = "$dir/$name"; 276 $exe = "$dir/$base.exe"; 277 $exe_pid = -1; 278 279 if ($opt_a && ($status != 0 || $tag != 0 || $droptag != 0 || 280 -x $exe || $isksh || -x $fullname)) { 281 $bypassed++; 282 next; 283 } 284 285 if (!$opt_f && is_exception("$dir/$name")) { 286 $bypassed++; 287 next; 288 } 289 290 if (!$isksh && -x $exe) { 291 if (($exe_pid = fork()) == -1) { 292 errmsg( 293 "ERROR: failed to fork to run $exe: $!\n"); 294 next; 295 } 296 297 if ($exe_pid == 0) { 298 open(STDIN, '</dev/null'); 299 300 exec($exe); 301 302 warn "ERROR: failed to exec $exe: $!\n"; 303 } 304 } 305 306 logmsg("testing $file ... "); 307 308 if (($pid = fork()) == -1) { 309 errmsg("ERROR: failed to fork to run test $file: $!\n"); 310 next; 311 } 312 313 if ($pid == 0) { 314 open(STDIN, '</dev/null'); 315 exit(125) unless open(STDOUT, ">$opt_d/$$.out"); 316 exit(125) unless open(STDERR, ">$opt_d/$$.err"); 317 318 unless (chdir($dir)) { 319 warn "ERROR: failed to chdir for $file: $!\n"; 320 exit(126); 321 } 322 323 push(@dtrace_argv, '-xerrtags') if ($tag); 324 push(@dtrace_argv, '-xdroptags') if ($droptag); 325 push(@dtrace_argv, $exe_pid) if ($exe_pid != -1); 326 327 if ($isksh) { 328 exit(123) unless open(STDIN, "<$name"); 329 exec("$ksh_path /dev/stdin $dtrace"); 330 } elsif (-x $name) { 331 warn "ERROR: $name is executable\n"; 332 exit(1); 333 } else { 334 if ($tag == 0 && $status == $0 && $opt_a) { 335 push(@dtrace_argv, '-A'); 336 } 337 338 push(@dtrace_argv, '-C'); 339 push(@dtrace_argv, '-s'); 340 push(@dtrace_argv, $name); 341 exec($dtrace, @dtrace_argv); 342 } 343 344 warn "ERROR: failed to exec for $file: $!\n"; 345 exit(127); 346 } 347 348 if (waitpid($pid, 0) == -1) { 349 errmsg("ERROR: timed out waiting for $file\n"); 350 kill(9, $exe_pid) if ($exe_pid != -1); 351 kill(9, $pid); 352 next; 353 } 354 355 kill(9, $exe_pid) if ($exe_pid != -1); 356 357 if ($tag == 0 && $status == $0 && $opt_a) { 358 # 359 # We can chuck the earler output. 360 # 361 unlink($pid . '.out'); 362 unlink($pid . '.err'); 363 364 # 365 # This is an anonymous enabling. We need to get 366 # the module unloaded. 367 # 368 system("dtrace -ae 1> /dev/null 2> /dev/null"); 369 system("svcadm disable -s " . 370 "svc:/network/nfs/mapid:default"); 371 system("modunload -i 0 ; modunload -i 0 ; " . 372 "modunload -i 0"); 373 if (!system("modinfo | grep dtrace")) { 374 warn "ERROR: couldn't unload dtrace\n"; 375 system("svcadm enable " . 376 "-s svc:/network/nfs/mapid:default"); 377 exit(124); 378 } 379 380 # 381 # DTrace is gone. Now update_drv(1M), and rip 382 # everything out again. 383 # 384 system("update_drv dtrace"); 385 system("dtrace -ae 1> /dev/null 2> /dev/null"); 386 system("modunload -i 0 ; modunload -i 0 ; " . 387 "modunload -i 0"); 388 if (!system("modinfo | grep dtrace")) { 389 warn "ERROR: couldn't unload dtrace\n"; 390 system("svcadm enable " . 391 "-s svc:/network/nfs/mapid:default"); 392 exit(124); 393 } 394 395 # 396 # Now bring DTrace back in. 397 # 398 system("sync ; sync"); 399 system("dtrace -l -n bogusprobe 1> /dev/null " . 400 "2> /dev/null"); 401 system("svcadm enable -s " . 402 "svc:/network/nfs/mapid:default"); 403 404 # 405 # That should have caused DTrace to reload with 406 # the new configuration file. Now we can try to 407 # snag our anonymous state. 408 # 409 if (($pid = fork()) == -1) { 410 errmsg("ERROR: failed to fork to run " . 411 "test $file: $!\n"); 412 next; 413 } 414 415 if ($pid == 0) { 416 open(STDIN, '</dev/null'); 417 exit(125) unless open(STDOUT, ">$opt_d/$$.out"); 418 exit(125) unless open(STDERR, ">$opt_d/$$.err"); 419 420 push(@dtrace_argv, '-a'); 421 422 unless (chdir($dir)) { 423 warn "ERROR: failed to chdir " . 424 "for $file: $!\n"; 425 exit(126); 426 } 427 428 exec($dtrace, @dtrace_argv); 429 warn "ERROR: failed to exec for $file: $!\n"; 430 exit(127); 431 } 432 433 if (waitpid($pid, 0) == -1) { 434 errmsg("ERROR: timed out waiting for $file\n"); 435 kill(9, $pid); 436 next; 437 } 438 } 439 440 logmsg("[$pid]\n"); 441 $wstat = $?; 442 $wifexited = ($wstat & 0xFF) == 0; 443 $wexitstat = ($wstat >> 8) & 0xFF; 444 $wtermsig = ($wstat & 0x7F); 445 446 if (!$wifexited) { 447 fail("died from signal $wtermsig"); 448 next; 449 } 450 451 if ($wexitstat == 125) { 452 die "$PNAME: failed to create output file in $opt_d " . 453 "(cd elsewhere or use -d)\n"; 454 } 455 456 if ($wexitstat != $status) { 457 fail("returned $wexitstat instead of $status"); 458 next; 459 } 460 461 if (-f "$file.out" && 462 system("cmp -s $file.out $opt_d/$pid.out") != 0) { 463 fail("stdout mismatch", "$pid.out"); 464 next; 465 } 466 467 if (-f "$file.err" && 468 system("cmp -s $file.err $opt_d/$pid.err") != 0) { 469 fail("stderr mismatch: see $pid.err"); 470 next; 471 } 472 473 if ($tag) { 474 open(TSTERR, "<$opt_d/$pid.err"); 475 $tsterr = <TSTERR>; 476 close(TSTERR); 477 478 unless ($tsterr =~ /: \[$tag\] line \d+:/) { 479 fail("errtag mismatch: see $pid.err"); 480 next; 481 } 482 } 483 484 if ($droptag) { 485 $found = 0; 486 open(TSTERR, "<$opt_d/$pid.err"); 487 488 while (<TSTERR>) { 489 if (/\[$droptag\] /) { 490 $found = 1; 491 last; 492 } 493 } 494 495 close (TSTERR); 496 497 unless ($found) { 498 fail("droptag mismatch: see $pid.err"); 499 next; 500 } 501 } 502 503 unless ($opt_s) { 504 unlink($pid . '.out'); 505 unlink($pid . '.err'); 506 } 507 } 508 509 if ($opt_a) { 510 # 511 # If we're running with anonymous enablings, we need to 512 # restore the .conf file. 513 # 514 system("dtrace -A 1> /dev/null 2> /dev/null"); 515 system("dtrace -ae 1> /dev/null 2> /dev/null"); 516 system("modunload -i 0 ; modunload -i 0 ; modunload -i 0"); 517 system("update_drv dtrace"); 518 } 519 520 $total = scalar(@files); 521 $failed = $errs - $failed; 522 $passed = ($total - $failed - $bypassed); 523 $results{$dtrace} = { 524 "passed" => $passed, 525 "bypassed" => $bypassed, 526 "failed" => $failed, 527 "total" => $total 528 }; 529} 530 531die $USAGE unless (getopts($OPTSTR)); 532usage() if ($opt_h); 533 534foreach $arg (@ARGV) { 535 if (-f $arg) { 536 push(@files, $arg); 537 } elsif (-d $arg) { 538 find(\&wanted, $arg); 539 } else { 540 die "$PNAME: $arg is not a valid file or directory\n"; 541 } 542} 543 544$dt_tst = '/opt/SUNWdtrt/tst'; 545$dt_bin = '/opt/SUNWdtrt/bin'; 546$defdir = -d $dt_tst ? $dt_tst : '.'; 547$bindir = -d $dt_bin ? $dt_bin : '.'; 548 549find(\&wanted, "$defdir/common") if (scalar(@ARGV) == 0); 550find(\&wanted, "$defdir/$MACH") if (scalar(@ARGV) == 0); 551find(\&wanted, "$defdir/$PLATFORM") if (scalar(@ARGV) == 0); 552die $USAGE if (scalar(@files) == 0); 553 554$dtrace_path = '/usr/sbin/dtrace'; 555$jdtrace_path = "$bindir/jdtrace"; 556 557%exception_lists = ("$jdtrace_path" => "$bindir/exception.lst"); 558 559if ($opt_j || $opt_n || $opt_i) { 560 @dtrace_cmds = (); 561 push(@dtrace_cmds, $dtrace_path) if ($opt_n); 562 push(@dtrace_cmds, $jdtrace_path) if ($opt_j); 563 push(@dtrace_cmds, "/usr/sbin/$opt_i/dtrace") if ($opt_i); 564} else { 565 @dtrace_cmds = ($dtrace_path, $jdtrace_path); 566} 567 568if ($opt_d) { 569 die "$PNAME: -d arg must be absolute path\n" unless ($opt_d =~ /^\//); 570 die "$PNAME: -d arg $opt_d is not a directory\n" unless (-d "$opt_d"); 571 system("coreadm -p $opt_d/%p.core"); 572} else { 573 my $dir = getcwd; 574 system("coreadm -p $dir/%p.core"); 575 $opt_d = '.'; 576} 577 578if ($opt_x) { 579 push(@dtrace_argv, '-x'); 580 push(@dtrace_argv, $opt_x); 581} 582 583die "$PNAME: failed to open $PNAME.$$.log: $!\n" 584 unless (!$opt_l || open(LOG, ">$PNAME.$$.log")); 585 586$ENV{'DTRACE_DEBUG_REGSET'} = 'true'; 587 588if ($opt_g) { 589 $ENV{'UMEM_DEBUG'} = 'default,verbose'; 590 $ENV{'UMEM_LOGGING'} = 'fail,contents'; 591 $ENV{'LD_PRELOAD'} = 'libumem.so'; 592} 593 594# 595# Ensure that $PATH contains a cc(1) so that we can execute the 596# test programs that require compilation of C code. 597# 598#$ENV{'PATH'} = $ENV{'PATH'} . ':/ws/onnv-tools/SUNWspro/SS11/bin'; 599 600if ($opt_b) { 601 logmsg("badioctl'ing ... "); 602 603 if (($badioctl = fork()) == -1) { 604 errmsg("ERROR: failed to fork to run badioctl: $!\n"); 605 next; 606 } 607 608 if ($badioctl == 0) { 609 open(STDIN, '</dev/null'); 610 exit(125) unless open(STDOUT, ">$opt_d/$$.out"); 611 exit(125) unless open(STDERR, ">$opt_d/$$.err"); 612 613 exec($bindir . "/badioctl"); 614 warn "ERROR: failed to exec badioctl: $!\n"; 615 exit(127); 616 } 617 618 619 logmsg("[$badioctl]\n"); 620 621 # 622 # If we're going to be bad, we're just going to iterate over each 623 # test file. 624 # 625 foreach $file (sort @files) { 626 ($name = $file) =~ s:.*/::; 627 $dir = dirname($file); 628 629 if (!($name =~ /^tst\./ && $name =~ /\.d$/)) { 630 next; 631 } 632 633 logmsg("baddof'ing $file ... "); 634 635 if (($pid = fork()) == -1) { 636 errmsg("ERROR: failed to fork to run baddof: $!\n"); 637 next; 638 } 639 640 if ($pid == 0) { 641 open(STDIN, '</dev/null'); 642 exit(125) unless open(STDOUT, ">$opt_d/$$.out"); 643 exit(125) unless open(STDERR, ">$opt_d/$$.err"); 644 645 unless (chdir($dir)) { 646 warn "ERROR: failed to chdir for $file: $!\n"; 647 exit(126); 648 } 649 650 exec($bindir . "/baddof", $name); 651 652 warn "ERROR: failed to exec for $file: $!\n"; 653 exit(127); 654 } 655 656 sleep 60; 657 kill(9, $pid); 658 waitpid($pid, 0); 659 660 logmsg("[$pid]\n"); 661 662 unless ($opt_s) { 663 unlink($pid . '.out'); 664 unlink($pid . '.err'); 665 } 666 } 667 668 kill(9, $badioctl); 669 waitpid($badioctl, 0); 670 671 unless ($opt_s) { 672 unlink($badioctl . '.out'); 673 unlink($badioctl . '.err'); 674 } 675 676 exit(0); 677} 678 679# 680# Run all the tests specified on the command-line (the entire test suite 681# by default) once for each dtrace command tested, skipping any tests 682# not valid for that command. 683# 684foreach $dtrace_cmd (@dtrace_cmds) { 685 run_tests($dtrace_cmd, $exception_lists{$dtrace_cmd}); 686} 687 688$opt_q = 0; # force final summary to appear regardless of -q option 689 690logmsg("\n==== TEST RESULTS ====\n"); 691foreach $key (keys %results) { 692 my $passed = $results{$key}{"passed"}; 693 my $bypassed = $results{$key}{"bypassed"}; 694 my $failed = $results{$key}{"failed"}; 695 my $total = $results{$key}{"total"}; 696 697 logmsg("\n mode: " . $key . "\n"); 698 logmsg(" passed: " . $passed . "\n"); 699 if ($bypassed) { 700 logmsg(" bypassed: " . $bypassed . "\n"); 701 } 702 logmsg(" failed: " . $failed . "\n"); 703 logmsg(" total: " . $total . "\n"); 704} 705 706exit($errs != 0); 707