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 2006 Sun Microsystems, Inc.  All rights reserved.
25# Use is subject to license terms.
26#
27# ident	"%Z%%M%	%I%	%E% SMI"
28
29require 5.6.1;
30
31use File::Find;
32use File::Basename;
33use Getopt::Std;
34use Cwd;
35use Cwd 'abs_path';
36
37$PNAME = $0;
38$PNAME =~ s:.*/::;
39$OPTSTR = 'abd:ghi:lqsux:';
40$USAGE = "Usage: $PNAME [-abghlqsu] [-d dir] [-i isa] "
41    . "[-x opt[=arg]] [file | dir ...]\n";
42($MACH = `uname -p`) =~ s/\W*\n//;
43
44$dtrace_path = '/usr/sbin/dtrace';
45@dtrace_argv = ();
46
47$ksh_path = '/usr/bin/ksh';
48
49@files = ();
50%exceptions = ();
51$errs = 0;
52$bypassed = 0;
53
54#
55# If no test files are specified on the command-line, execute a find on "."
56# and append any tst.*.d, tst.*.ksh, err.*.d or drp.*.d files found within
57# the directory tree.
58#
59sub wanted
60{
61	push(@files, $File::Find::name)
62	    if ($_ =~ /^(tst|err|drp)\..+\.(d|ksh)$/ && -f "$_");
63}
64
65sub dirname {
66	my($s) = @_;
67	my($i);
68
69	$s = substr($s, 0, $i) if (($i = rindex($s, '/')) != -1);
70	return $i == -1 ? '.' : $i == 0 ? '/' : $s;
71}
72
73sub usage
74{
75	print $USAGE;
76	print "\t -a  execute test suite using anonymous enablings\n";
77	print "\t -b  execute bad ioctl test program\n";
78	print "\t -d  specify directory for test results files and cores\n";
79	print "\t -g  enable libumem debugging when running tests\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 -l  save log file of results and PIDs used by tests\n";
83	print "\t -q  set quiet mode (only report errors and summary)\n";
84	print "\t -s  save results files even for tests that pass\n";
85	print "\t -x  pass corresponding -x argument to dtrace(1M)\n";
86	print "\n\tUse \"-i java\" to run tests using the ";
87	print "Java DTrace API.\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# Loads exception set of skipped tests
177sub load_exceptions {
178	my($listfile) = @_;
179	my($line) = "";
180
181	exit(123) unless open(STDIN, "<$listfile");
182	while (<STDIN>) {
183		chomp;
184		$line = $_;
185		# line is non-empty and not a comment
186		if ((length($line) > 0) && ($line =~ /^\s*[^\s#]/ )) {
187			$exceptions{trim($line)} = 1;
188		}
189	}
190	return 0;
191}
192
193# Return 1 if file name found in exception set, 0 otherwise
194sub is_exception {
195	my($file) = @_;
196	my($i) = -1;
197
198	# hash absolute pathname after $dt_tst/
199	$file = abs_path($file);
200	$i = index($file, $dt_tst);
201	if ($i == 0) {
202		$file = substr($file, length($dt_tst) + 1);
203		return $exceptions{$file};
204	}
205	return 0;
206}
207
208die $USAGE unless (getopts($OPTSTR));
209usage() if ($opt_h);
210
211foreach $arg (@ARGV) {
212	if (-f $arg) {
213		push(@files, $arg);
214	} elsif (-d $arg) {
215		find(\&wanted, $arg);
216	} else {
217		die "$PNAME: $arg is not a valid file or directory\n";
218	}
219}
220
221$dt_tst = '/opt/SUNWdtrt/tst';
222$dt_bin = '/opt/SUNWdtrt/bin';
223$defdir = -d $dt_tst ? $dt_tst : '.';
224$bindir = -d $dt_bin ? $dt_bin : '.';
225
226find(\&wanted, "$defdir/common") if (scalar(@ARGV) == 0);
227find(\&wanted, "$defdir/$MACH") if (scalar(@ARGV) == 0);
228die $USAGE if (scalar(@files) == 0);
229
230if ($opt_d) {
231	die "$PNAME: -d arg must be absolute path\n" unless ($opt_d =~ /^\//);
232	die "$PNAME: -d arg $opt_d is not a directory\n" unless (-d "$opt_d");
233	system("coreadm -p $opt_d/%p.core");
234} else {
235	my $dir = getcwd;
236	system("coreadm -p $dir/%p.core");
237	$opt_d = '.';
238}
239
240if ($opt_i) {
241	if ($opt_i eq "java") {
242		$dtrace_path = $bindir . "/jdtrace";
243		die "$PNAME: jdtrace not found\n"
244		    unless (-x "$dtrace_path");
245		load_exceptions($bindir . "/exception.lst");
246	} else {
247		$dtrace_path = "/usr/sbin/$opt_i/dtrace";
248		die "$PNAME: dtrace(1M) for ISA $opt_i not found\n"
249		    unless (-x "$dtrace_path");
250	}
251}
252
253if ($opt_x) {
254	push(@dtrace_argv, '-x');
255	push(@dtrace_argv, $opt_x);
256}
257
258die "$PNAME: failed to open $PNAME.$$.log: $!\n"
259    unless (!$opt_l || open(LOG, ">$PNAME.$$.log"));
260
261if ($opt_g) {
262	$ENV{'UMEM_DEBUG'} = 'default,verbose';
263	$ENV{'UMEM_LOGGING'} = 'fail,contents';
264	$ENV{'LD_PRELOAD'} = 'libumem.so';
265}
266
267#
268# Ensure that $PATH contains a cc(1) so that we can execute the
269# test programs that require compilation of C code.
270#
271$ENV{'PATH'} = $ENV{'PATH'} . ':/ws/onnv-tools/SUNWspro/SS11/bin';
272
273if ($opt_b) {
274	logmsg("badioctl'ing ... ");
275
276	if (($badioctl = fork()) == -1) {
277		errmsg("ERROR: failed to fork to run badioctl: $!\n");
278		next;
279	}
280
281	if ($badioctl == 0) {
282		open(STDIN, '</dev/null');
283		exit(125) unless open(STDOUT, ">$opt_d/$$.out");
284		exit(125) unless open(STDERR, ">$opt_d/$$.err");
285
286		exec($bindir . "/badioctl");
287		warn "ERROR: failed to exec badioctl: $!\n";
288		exit(127);
289	}
290
291
292	logmsg("[$badioctl]\n");
293
294	#
295	# If we're going to be bad, we're just going to iterate over each
296	# test file.
297	#
298	foreach $file (sort @files) {
299		($name = $file) =~ s:.*/::;
300		$dir = dirname($file);
301
302		if (!($name =~ /^tst\./ && $name =~ /\.d$/)) {
303			next;
304		}
305
306		logmsg("baddof'ing $file ... ");
307
308		if (($pid = fork()) == -1) {
309			errmsg("ERROR: failed to fork to run baddof: $!\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			exec($bindir . "/baddof", $name);
324
325			warn "ERROR: failed to exec for $file: $!\n";
326			exit(127);
327		}
328
329		sleep 60;
330		kill(9, $pid);
331		waitpid($pid, 0);
332
333		logmsg("[$pid]\n");
334
335		unless ($opt_s) {
336			unlink($pid . '.out');
337			unlink($pid . '.err');
338		}
339	}
340
341	kill(9, $badioctl);
342	waitpid($badioctl, 0);
343
344	unless ($opt_s) {
345		unlink($badioctl . '.out');
346		unlink($badioctl . '.err');
347	}
348
349	exit(0);
350}
351
352if ($opt_u) {
353	logmsg "spawning module unloading process... ";
354
355	$unloader = fork;
356
357	if ($unloader != 0 && !defined $unloader) {
358		#
359		# Couldn't fork for some reason.
360		#
361		die "couldn't fork: $!\n";
362	}
363
364	if ($unloader == 0) {
365		#
366		# We're in the child.  Go modunload krazy.
367		#
368		for (;;) {
369			system("modunload -i 0");
370		}
371	} else {
372		logmsg "[$unloader]\n";
373
374		$SIG{INT} = sub {
375			kill 9, $unloader;
376			exit($errs != 0);
377		};
378	}
379}
380
381#
382# Iterate over the set of test files specified on the command-line or located
383# by a find on "." and execute each one.  If the test file is executable, we
384# assume it is a #! script and run it.  Otherwise we run dtrace -s on it.
385# If the file is named tst.* we assume it should return exit status 0.
386# If the file is named err.* we assume it should return exit status 1.
387# If the file is named err.D_[A-Z0-9]+[.*].d we use dtrace -xerrtags and
388# examine stderr to ensure that a matching error tag was produced.
389# If the file is named drp.[A-Z0-9]+[.*].d we use dtrace -xdroptags and
390# examine stderr to ensure that a matching drop tag was produced.
391# If any *.out or *.err files are found we perform output comparisons.
392#
393foreach $file (sort @files) {
394	$file =~ m:.*/((.*)\.(\w+)):;
395	$name = $1;
396	$base = $2;
397	$ext = $3;
398
399	$dir = dirname($file);
400	$isksh = 0;
401	$tag = 0;
402	$droptag = 0;
403
404	if ($name =~ /^tst\./) {
405		$isksh = ($ext eq 'ksh');
406		$status = 0;
407	} elsif ($name =~ /^err\.(D_[A-Z0-9_]+)\./) {
408		$status = 1;
409		$tag = $1;
410	} elsif ($name =~ /^err\./) {
411		$status = 1;
412	} elsif ($name =~ /^drp\.([A-Z0-9_]+)\./) {
413		$status = 0;
414		$droptag = $1;
415	} else {
416		errmsg("ERROR: $file is not a valid test file name\n");
417		next;
418	}
419
420	$fullname = "$dir/$name";
421	$exe = "$dir/$base.exe";
422	$exe_pid = -1;
423
424	if ($opt_a && ($status != 0 || $tag != 0 || $droptag != 0 ||
425	    -x $exe || $isksh || -x $fullname)) {
426		$bypassed++;
427		next;
428	}
429
430	if ($opt_i eq "java") {
431		if (is_exception("$dir/$name")) {
432			$bypassed++;
433			next;
434		}
435	}
436
437	if (!$isksh && -x $exe) {
438		if (($exe_pid = fork()) == -1) {
439			errmsg("ERROR: failed to fork to run $exe: $!\n");
440			next;
441		}
442
443		if ($exe_pid == 0) {
444			open(STDIN, '</dev/null');
445
446			exec($exe);
447
448			warn "ERROR: failed to exec $exe: $!\n";
449		}
450	}
451
452	logmsg("testing $file ... ");
453
454	if (($pid = fork()) == -1) {
455		errmsg("ERROR: failed to fork to run test $file: $!\n");
456		next;
457	}
458
459	if ($pid == 0) {
460		open(STDIN, '</dev/null');
461		exit(125) unless open(STDOUT, ">$opt_d/$$.out");
462		exit(125) unless open(STDERR, ">$opt_d/$$.err");
463
464		unless (chdir($dir)) {
465			warn "ERROR: failed to chdir for $file: $!\n";
466			exit(126);
467		}
468
469		push(@dtrace_argv, '-xerrtags') if ($tag);
470		push(@dtrace_argv, '-xdroptags') if ($droptag);
471		push(@dtrace_argv, $exe_pid) if ($exe_pid != -1);
472
473		if ($isksh) {
474			exit(123) unless open(STDIN, "<$name");
475			exec("$ksh_path /dev/stdin $dtrace_path");
476		} elsif (-x $name) {
477		        warn "ERROR: $name is executable\n";
478			exit(1);
479		} else {
480			if ($tag == 0 && $status == $0 && $opt_a) {
481				push(@dtrace_argv, '-A');
482			}
483
484			push(@dtrace_argv, '-C');
485			push(@dtrace_argv, '-s');
486			push(@dtrace_argv, $name);
487			exec($dtrace_path, @dtrace_argv);
488		}
489
490		warn "ERROR: failed to exec for $file: $!\n";
491		exit(127);
492	}
493
494	if (waitpid($pid, 0) == -1) {
495		errmsg("ERROR: timed out waiting for $file\n");
496		kill(9, $exe_pid) if ($exe_pid != -1);
497		kill(9, $pid);
498		next;
499	}
500
501	kill(9, $exe_pid) if ($exe_pid != -1);
502
503	if ($tag == 0 && $status == $0 && $opt_a) {
504		#
505		# We can chuck the earler output.
506		#
507		unlink($pid . '.out');
508		unlink($pid . '.err');
509
510		#
511		# This is an anonymous enabling.  We need to get the module
512		# unloaded.
513		#
514		system("dtrace -ae 1> /dev/null 2> /dev/null");
515		system("svcadm disable -s svc:/network/nfs/mapid:default");
516		system("modunload -i 0 ; modunload -i 0 ; modunload -i 0");
517		if (!system("modinfo | grep dtrace")) {
518			warn "ERROR: couldn't unload dtrace\n";
519			system("svcadm enable " .
520			    "-s svc:/network/nfs/mapid:default");
521			exit(124);
522		}
523
524		#
525		# DTrace is gone.  Now update_drv(1M), and rip everything out
526		# again.
527		#
528		system("update_drv dtrace");
529		system("dtrace -ae 1> /dev/null 2> /dev/null");
530		system("modunload -i 0 ; modunload -i 0 ; modunload -i 0");
531		if (!system("modinfo | grep dtrace")) {
532			warn "ERROR: couldn't unload dtrace\n";
533			system("svcadm enable " .
534			    "-s svc:/network/nfs/mapid:default");
535			exit(124);
536		}
537
538		#
539		# Now bring DTrace back in.
540		#
541		system("sync ; sync");
542		system("dtrace -l -n bogusprobe 1> /dev/null 2> /dev/null");
543		system("svcadm enable -s svc:/network/nfs/mapid:default");
544
545		#
546		# That should have caused DTrace to reload with the new
547		# configuration file.  Now we can try to snag our anonymous
548		# state.
549		#
550		if (($pid = fork()) == -1) {
551			errmsg("ERROR: failed to fork to run test $file: $!\n");
552			next;
553		}
554
555		if ($pid == 0) {
556			open(STDIN, '</dev/null');
557			exit(125) unless open(STDOUT, ">$opt_d/$$.out");
558			exit(125) unless open(STDERR, ">$opt_d/$$.err");
559
560			push(@dtrace_argv, '-a');
561
562			unless (chdir($dir)) {
563				warn "ERROR: failed to chdir for $file: $!\n";
564				exit(126);
565			}
566
567			exec($dtrace_path, @dtrace_argv);
568			warn "ERROR: failed to exec for $file: $!\n";
569			exit(127);
570		}
571
572		if (waitpid($pid, 0) == -1) {
573			errmsg("ERROR: timed out waiting for $file\n");
574			kill(9, $pid);
575			next;
576		}
577	}
578
579	logmsg("[$pid]\n");
580	$wstat = $?;
581	$wifexited = ($wstat & 0xFF) == 0;
582	$wexitstat = ($wstat >> 8) & 0xFF;
583	$wtermsig = ($wstat & 0x7F);
584
585	if (!$wifexited) {
586		fail("died from signal $wtermsig");
587		next;
588	}
589
590	if ($wexitstat == 125) {
591		die "$PNAME: failed to create output file in $opt_d " .
592		    "(cd elsewhere or use -d)\n";
593	}
594
595	if ($wexitstat != $status) {
596		fail("returned $wexitstat instead of $status");
597		next;
598	}
599
600	if (-f "$file.out" && system("cmp -s $file.out $opt_d/$pid.out") != 0) {
601		fail("stdout mismatch", "$pid.out");
602		next;
603	}
604
605	if (-f "$file.err" && system("cmp -s $file.err $opt_d/$pid.err") != 0) {
606		fail("stderr mismatch: see $pid.err");
607		next;
608	}
609
610	if ($tag) {
611		open(TSTERR, "<$opt_d/$pid.err");
612		$tsterr = <TSTERR>;
613		close(TSTERR);
614
615		unless ($tsterr =~ /: \[$tag\] line \d+:/) {
616			fail("errtag mismatch: see $pid.err");
617			next;
618		}
619	}
620
621	if ($droptag) {
622		$found = 0;
623		open(TSTERR, "<$opt_d/$pid.err");
624
625		while (<TSTERR>) {
626			if (/\[$droptag\] /) {
627				$found = 1;
628				last;
629			}
630		}
631
632		close (TSTERR);
633
634		unless ($found) {
635			fail("droptag mismatch: see $pid.err");
636			next;
637		}
638	}
639
640	unless ($opt_s) {
641		unlink($pid . '.out');
642		unlink($pid . '.err');
643	}
644}
645
646if ($opt_a) {
647	#
648	# If we're running with anonymous enablings, we need to restore the
649	# .conf file.
650	#
651	system("dtrace -A 1> /dev/null 2> /dev/null");
652	system("dtrace -ae 1> /dev/null 2> /dev/null");
653	system("modunload -i 0 ; modunload -i 0 ; modunload -i 0");
654	system("update_drv dtrace");
655}
656
657$opt_q = 0; # force final summary to appear regardless of -q option
658
659logmsg("\n==== TEST RESULTS ====\n");
660logmsg("   passed: " . (scalar(@files) - $errs - $bypassed) . "\n");
661
662if ($bypassed) {
663	logmsg(" bypassed: " . $bypassed . "\n");
664}
665
666logmsg("   failed: " . $errs . "\n");
667logmsg("    total: " . scalar(@files) . "\n");
668
669if ($opt_u) {
670	kill 9, $unloader;
671	waitpid $unloader, 0;
672}
673
674exit($errs != 0);
675