xref: /illumos-gate/usr/src/cmd/dtrace/test/cmd/scripts/dtest.pl (revision 2b6e762c557496a41438c0b105d604f60c593682)
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;
35
36$PNAME = $0;
37$PNAME =~ s:.*/::;
38$USAGE = "Usage: $PNAME [-abghlqsu] [-d dir] [-i isa] "
39    . "[-x opt[=arg]] [file | dir ...]\n";
40($MACH = `uname -p`) =~ s/\W*\n//;
41
42$dtrace_path = '/usr/sbin/dtrace';
43@dtrace_argv = ();
44
45$ksh_path = '/usr/bin/ksh';
46
47@files = ();
48$errs = 0;
49$bypassed = 0;
50
51#
52# If no test files are specified on the command-line, execute a find on "."
53# and append any tst.*.d, tst.*.ksh, err.*.d or drp.*.d files found within
54# the directory tree.
55#
56sub wanted
57{
58	push(@files, $File::Find::name)
59	    if ($_ =~ /^(tst|err|drp)\..+\.(d|ksh)$/ && -f "$_");
60}
61
62sub dirname {
63	my($s) = @_;
64	my($i);
65
66	$s = substr($s, 0, $i) if (($i = rindex($s, '/')) != -1);
67	return $i == -1 ? '.' : $i == 0 ? '/' : $s;
68}
69
70sub usage
71{
72	print $USAGE;
73	print "\t -a  execute test suite using anonymous enablings\n";
74	print "\t -b  execute bad ioctl test program\n";
75	print "\t -d  specify directory for test results files and cores\n";
76	print "\t -g  enable libumem debugging when running tests\n";
77	print "\t -h  display verbose usage message\n";
78	print "\t -i  specify ISA to test instead of isaexec(3C) default\n";
79	print "\t -l  save log file of results and PIDs used by tests\n";
80	print "\t -q  set quiet mode (only report errors and summary)\n";
81	print "\t -s  save results files even for tests that pass\n";
82	print "\t -x  pass corresponding -x argument to dtrace(1M)\n";
83	exit(2);
84}
85
86sub errmsg
87{
88	my($msg) = @_;
89
90	print STDERR $msg;
91	print LOG $msg if ($opt_l);
92	$errs++;
93}
94
95sub fail
96{
97	my(@parms) = @_;
98	my($msg) = $parms[0];
99	my($errfile) = $parms[1];
100	my($n) = 0;
101	my($dest) = basename($file);
102
103	while (-d "$opt_d/failure.$n") {
104		$n++;
105	}
106
107	unless (mkdir "$opt_d/failure.$n") {
108		warn "ERROR: failed to make directory $opt_d/failure.$n: $!\n";
109		exit(125);
110	}
111
112	open(README, ">$opt_d/failure.$n/README");
113	print README "ERROR: " . $file . " " . $msg;
114
115	if (scalar @parms > 1) {
116		print README "; see $errfile\n";
117	} else {
118		if (-f "$opt_d/$pid.core") {
119			print README "; see $pid.core\n";
120		} else {
121			print README "\n";
122		}
123	}
124
125	close(README);
126
127	if (-f "$opt_d/$pid.out") {
128		rename("$opt_d/$pid.out", "$opt_d/failure.$n/$pid.out");
129		link("$file.out", "$opt_d/failure.$n/$dest.out");
130	}
131
132	if (-f "$opt_d/$pid.err") {
133		rename("$opt_d/$pid.err", "$opt_d/failure.$n/$pid.err");
134		link("$file.err", "$opt_d/failure.$n/$dest.err");
135	}
136
137	if (-f "$opt_d/$pid.core") {
138		rename("$opt_d/$pid.core", "$opt_d/failure.$n/$pid.core");
139	}
140
141	link("$file", "$opt_d/failure.$n/$dest");
142
143	$msg = "ERROR: " . $dest . " " . $msg;
144
145	if (scalar @parms > 1) {
146		$msg = $msg . "; see $errfile in failure.$n\n";
147	} else {
148		$msg = $msg . "; details in failure.$n\n";
149	}
150
151	errmsg($msg);
152}
153
154sub logmsg
155{
156	my($msg) = @_;
157
158	print STDOUT $msg unless ($opt_q);
159	print LOG $msg if ($opt_l);
160}
161
162die $USAGE unless (getopts('abd:ghi:lqsux:'));
163usage() if ($opt_h);
164
165foreach $arg (@ARGV) {
166	if (-f $arg) {
167		push(@files, $arg);
168	} elsif (-d $arg) {
169		find(\&wanted, $arg);
170	} else {
171		die "$PNAME: $arg is not a valid file or directory\n";
172	}
173}
174
175$defdir = -d '/opt/SUNWdtrt/tst' ? '/opt/SUNWdtrt/tst' : '.';
176$bindir = -d '/opt/SUNWdtrt/bin' ? '/opt/SUNWdtrt/bin' : '.';
177
178find(\&wanted, "$defdir/common") if (scalar(@ARGV) == 0);
179find(\&wanted, "$defdir/$MACH") if (scalar(@ARGV) == 0);
180die $USAGE if (scalar(@files) == 0);
181
182if ($opt_d) {
183	die "$PNAME: -d arg must be absolute path\n" unless ($opt_d =~ /^\//);
184	die "$PNAME: -d arg $opt_d is not a directory\n" unless (-d "$opt_d");
185	system("coreadm -p $opt_d/%p.core");
186} else {
187	my $dir = getcwd;
188	system("coreadm -p $dir/%p.core");
189	$opt_d = '.';
190}
191
192if ($opt_i) {
193	$dtrace_path = "/usr/sbin/$opt_i/dtrace";
194	die "$PNAME: dtrace(1M) for ISA $opt_i not found\n"
195	    unless (-x "$dtrace_path");
196}
197
198if ($opt_x) {
199	push(@dtrace_argv, '-x');
200	push(@dtrace_argv, $opt_x);
201}
202
203die "$PNAME: failed to open $PNAME.$$.log: $!\n"
204    unless (!$opt_l || open(LOG, ">$PNAME.$$.log"));
205
206if ($opt_g) {
207	$ENV{'UMEM_DEBUG'} = 'default,verbose';
208	$ENV{'UMEM_LOGGING'} = 'fail,contents';
209	$ENV{'LD_PRELOAD'} = 'libumem.so';
210}
211
212#
213# Ensure that $PATH contains a cc(1) so that we can execute the
214# test programs that require compilation of C code.
215#
216$ENV{'PATH'} = $ENV{'PATH'} . ':/ws/onnv-tools/SUNWspro/SS11/bin';
217
218if ($opt_b) {
219	logmsg("badioctl'ing ... ");
220
221	if (($badioctl = fork()) == -1) {
222		errmsg("ERROR: failed to fork to run badioctl: $!\n");
223		next;
224	}
225
226	if ($badioctl == 0) {
227		open(STDIN, '</dev/null');
228		exit(125) unless open(STDOUT, ">$opt_d/$$.out");
229		exit(125) unless open(STDERR, ">$opt_d/$$.err");
230
231		exec($bindir . "/badioctl");
232		warn "ERROR: failed to exec badioctl: $!\n";
233		exit(127);
234	}
235
236
237	logmsg("[$badioctl]\n");
238
239	#
240	# If we're going to be bad, we're just going to iterate over each
241	# test file.
242	#
243	foreach $file (sort @files) {
244		($name = $file) =~ s:.*/::;
245		$dir = dirname($file);
246
247		if (!($name =~ /^tst\./ && $name =~ /\.d$/)) {
248			next;
249		}
250
251		logmsg("baddof'ing $file ... ");
252
253		if (($pid = fork()) == -1) {
254			errmsg("ERROR: failed to fork to run baddof: $!\n");
255			next;
256		}
257
258		if ($pid == 0) {
259			open(STDIN, '</dev/null');
260			exit(125) unless open(STDOUT, ">$opt_d/$$.out");
261			exit(125) unless open(STDERR, ">$opt_d/$$.err");
262
263			unless (chdir($dir)) {
264				warn "ERROR: failed to chdir for $file: $!\n";
265				exit(126);
266			}
267
268			exec($bindir . "/baddof", $name);
269
270			warn "ERROR: failed to exec for $file: $!\n";
271			exit(127);
272		}
273
274		sleep 60;
275		kill(9, $pid);
276		waitpid($pid, 0);
277
278		logmsg("[$pid]\n");
279
280		unless ($opt_s) {
281			unlink($pid . '.out');
282			unlink($pid . '.err');
283		}
284	}
285
286	kill(9, $badioctl);
287	waitpid($badioctl, 0);
288
289	unless ($opt_s) {
290		unlink($badioctl . '.out');
291		unlink($badioctl . '.err');
292	}
293
294	exit(0);
295}
296
297if ($opt_u) {
298	logmsg "spawning module unloading process... ";
299
300	$unloader = fork;
301
302	if ($unloader != 0 && !defined $unloader) {
303		#
304		# Couldn't fork for some reason.
305		#
306		die "couldn't fork: $!\n";
307	}
308
309	if ($unloader == 0) {
310		#
311		# We're in the child.  Go modunload krazy.
312		#
313		for (;;) {
314			system("modunload -i 0");
315		}
316	} else {
317		logmsg "[$unloader]\n";
318
319		$SIG{INT} = sub {
320			kill 9, $unloader;
321			exit($errs != 0);
322		};
323	}
324}
325
326#
327# Iterate over the set of test files specified on the command-line or located
328# by a find on "." and execute each one.  If the test file is executable, we
329# assume it is a #! script and run it.  Otherwise we run dtrace -s on it.
330# If the file is named tst.* we assume it should return exit status 0.
331# If the file is named err.* we assume it should return exit status 1.
332# If the file is named err.D_[A-Z0-9]+[.*].d we use dtrace -xerrtags and
333# examine stderr to ensure that a matching error tag was produced.
334# If the file is named drp.[A-Z0-9]+[.*].d we use dtrace -xdroptags and
335# examine stderr to ensure that a matching drop tag was produced.
336# If any *.out or *.err files are found we perform output comparisons.
337#
338foreach $file (sort @files) {
339	$file =~ m:.*/((.*)\.(\w+)):;
340	$name = $1;
341	$base = $2;
342	$ext = $3;
343
344	$dir = dirname($file);
345	$isksh = 0;
346	$tag = 0;
347	$droptag = 0;
348
349	if ($name =~ /^tst\./) {
350		$isksh = ($ext eq 'ksh');
351		$status = 0;
352	} elsif ($name =~ /^err\.(D_[A-Z0-9_]+)\./) {
353		$status = 1;
354		$tag = $1;
355	} elsif ($name =~ /^err\./) {
356		$status = 1;
357	} elsif ($name =~ /^drp\.([A-Z0-9_]+)\./) {
358		$status = 0;
359		$droptag = $1;
360	} else {
361		errmsg("ERROR: $file is not a valid test file name\n");
362		next;
363	}
364
365	$fullname = "$dir/$name";
366	$exe = "$dir/$base.exe";
367	$exe_pid = -1;
368
369	if ($opt_a && ($status != 0 || $tag != 0 || $droptag != 0 ||
370	    -x $exe || $isksh || -x $fullname)) {
371		$bypassed++;
372		next;
373	}
374
375	if (!$isksh && -x $exe) {
376		if (($exe_pid = fork()) == -1) {
377			errmsg("ERROR: failed to fork to run $exe: $!\n");
378			next;
379		}
380
381		if ($exe_pid == 0) {
382			open(STDIN, '</dev/null');
383
384			exec($exe);
385
386			warn "ERROR: failed to exec $exe: $!\n";
387		}
388	}
389
390	logmsg("testing $file ... ");
391
392	if (($pid = fork()) == -1) {
393		errmsg("ERROR: failed to fork to run test $file: $!\n");
394		next;
395	}
396
397	if ($pid == 0) {
398		open(STDIN, '</dev/null');
399		exit(125) unless open(STDOUT, ">$opt_d/$$.out");
400		exit(125) unless open(STDERR, ">$opt_d/$$.err");
401
402		unless (chdir($dir)) {
403			warn "ERROR: failed to chdir for $file: $!\n";
404			exit(126);
405		}
406
407		push(@dtrace_argv, '-xerrtags') if ($tag);
408		push(@dtrace_argv, '-xdroptags') if ($droptag);
409		push(@dtrace_argv, $exe_pid) if ($exe_pid != -1);
410
411		if ($isksh) {
412			exit(123) unless open(STDIN, "<$name");
413			exec($ksh_path);
414		} elsif (-x $name) {
415		        warn "ERROR: $name is executable\n";
416			exit(1);
417		} else {
418			if ($tag == 0 && $status == $0 && $opt_a) {
419				push(@dtrace_argv, '-A');
420			}
421
422			push(@dtrace_argv, '-C');
423			push(@dtrace_argv, '-s');
424			push(@dtrace_argv, $name);
425			exec($dtrace_path, @dtrace_argv);
426		}
427
428		warn "ERROR: failed to exec for $file: $!\n";
429		exit(127);
430	}
431
432	if (waitpid($pid, 0) == -1) {
433		errmsg("ERROR: timed out waiting for $file\n");
434		kill(9, $exe_pid) if ($exe_pid != -1);
435		kill(9, $pid);
436		next;
437	}
438
439	kill(9, $exe_pid) if ($exe_pid != -1);
440
441	if ($tag == 0 && $status == $0 && $opt_a) {
442		#
443		# We can chuck the earler output.
444		#
445		unlink($pid . '.out');
446		unlink($pid . '.err');
447
448		#
449		# This is an anonymous enabling.  We need to get the module
450		# unloaded.
451		#
452		system("dtrace -ae 1> /dev/null 2> /dev/null");
453		system("svcadm disable -s svc:/network/nfs/mapid:default");
454		system("modunload -i 0 ; modunload -i 0 ; modunload -i 0");
455		if (!system("modinfo | grep dtrace")) {
456			warn "ERROR: couldn't unload dtrace\n";
457			system("svcadm enable " .
458			    "-s svc:/network/nfs/mapid:default");
459			exit(124);
460		}
461
462		#
463		# DTrace is gone.  Now update_drv(1M), and rip everything out
464		# again.
465		#
466		system("update_drv dtrace");
467		system("dtrace -ae 1> /dev/null 2> /dev/null");
468		system("modunload -i 0 ; modunload -i 0 ; modunload -i 0");
469		if (!system("modinfo | grep dtrace")) {
470			warn "ERROR: couldn't unload dtrace\n";
471			system("svcadm enable " .
472			    "-s svc:/network/nfs/mapid:default");
473			exit(124);
474		}
475
476		#
477		# Now bring DTrace back in.
478		#
479		system("sync ; sync");
480		system("dtrace -l -n bogusprobe 1> /dev/null 2> /dev/null");
481		system("svcadm enable -s svc:/network/nfs/mapid:default");
482
483		#
484		# That should have caused DTrace to reload with the new
485		# configuration file.  Now we can try to snag our anonymous
486		# state.
487		#
488		if (($pid = fork()) == -1) {
489			errmsg("ERROR: failed to fork to run test $file: $!\n");
490			next;
491		}
492
493		if ($pid == 0) {
494			open(STDIN, '</dev/null');
495			exit(125) unless open(STDOUT, ">$opt_d/$$.out");
496			exit(125) unless open(STDERR, ">$opt_d/$$.err");
497
498			push(@dtrace_argv, '-a');
499
500			unless (chdir($dir)) {
501				warn "ERROR: failed to chdir for $file: $!\n";
502				exit(126);
503			}
504
505			exec($dtrace_path, @dtrace_argv);
506			warn "ERROR: failed to exec for $file: $!\n";
507			exit(127);
508		}
509
510		if (waitpid($pid, 0) == -1) {
511			errmsg("ERROR: timed out waiting for $file\n");
512			kill(9, $pid);
513			next;
514		}
515	}
516
517	logmsg("[$pid]\n");
518	$wstat = $?;
519	$wifexited = ($wstat & 0xFF) == 0;
520	$wexitstat = ($wstat >> 8) & 0xFF;
521	$wtermsig = ($wstat & 0x7F);
522
523	if (!$wifexited) {
524		fail("died from signal $wtermsig");
525		next;
526	}
527
528	if ($wexitstat == 125) {
529		die "$PNAME: failed to create output file in $opt_d " .
530		    "(cd elsewhere or use -d)\n";
531	}
532
533	if ($wexitstat != $status) {
534		fail("returned $wexitstat instead of $status");
535		next;
536	}
537
538	if (-f "$file.out" && system("cmp -s $file.out $opt_d/$pid.out") != 0) {
539		fail("stdout mismatch", "$pid.out");
540		next;
541	}
542
543	if (-f "$file.err" && system("cmp -s $file.err $opt_d/$pid.err") != 0) {
544		fail("stderr mismatch: see $pid.err");
545		next;
546	}
547
548	if ($tag) {
549		open(TSTERR, "<$opt_d/$pid.err");
550		$tsterr = <TSTERR>;
551		close(TSTERR);
552
553		unless ($tsterr =~ /: \[$tag\] line \d+:/) {
554			fail("errtag mismatch: see $pid.err");
555			next;
556		}
557	}
558
559	if ($droptag) {
560		$found = 0;
561		open(TSTERR, "<$opt_d/$pid.err");
562
563		while (<TSTERR>) {
564			if (/\[$droptag\] /) {
565				$found = 1;
566				last;
567			}
568		}
569
570		close (TSTERR);
571
572		unless ($found) {
573			fail("droptag mismatch: see $pid.err");
574			next;
575		}
576	}
577
578	unless ($opt_s) {
579		unlink($pid . '.out');
580		unlink($pid . '.err');
581	}
582}
583
584if ($opt_a) {
585	#
586	# If we're running with anonymous enablings, we need to restore the
587	# .conf file.
588	#
589	system("dtrace -A 1> /dev/null 2> /dev/null");
590	system("dtrace -ae 1> /dev/null 2> /dev/null");
591	system("modunload -i 0 ; modunload -i 0 ; modunload -i 0");
592	system("update_drv dtrace");
593}
594
595$opt_q = 0; # force final summary to appear regardless of -q option
596
597logmsg("\n==== TEST RESULTS ====\n");
598logmsg("   passed: " . (scalar(@files) - $errs - $bypassed) . "\n");
599
600if ($bypassed) {
601	logmsg(" bypassed: " . $bypassed . "\n");
602}
603
604logmsg("   failed: " . $errs . "\n");
605logmsg("    total: " . scalar(@files) . "\n");
606
607if ($opt_u) {
608	kill 9, $unloader;
609	waitpid $unloader, 0;
610}
611
612exit($errs != 0);
613