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