summaryrefslogtreecommitdiff
path: root/source4/script/tests/selftest.pl
diff options
context:
space:
mode:
Diffstat (limited to 'source4/script/tests/selftest.pl')
-rwxr-xr-xsource4/script/tests/selftest.pl528
1 files changed, 373 insertions, 155 deletions
diff --git a/source4/script/tests/selftest.pl b/source4/script/tests/selftest.pl
index b14333e0b5..6c2ea6d0b1 100755
--- a/source4/script/tests/selftest.pl
+++ b/source4/script/tests/selftest.pl
@@ -2,6 +2,107 @@
# Bootstrap Samba and run a number of tests against it.
# Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@samba.org>
# Published under the GNU GPL, v3 or later.
+
+=pod
+
+=head1 NAME
+
+selftest - Samba test runner
+
+=head1 SYNOPSIS
+
+selftest --help
+
+selftest [--srcdir=DIR] [--builddir=DIR] [--target=samba4|samba3|win] [--socket-wrapper] [--quick] [--one] [--prefix=prefix] [--immediate] [TESTS]
+
+=head1 DESCRIPTION
+
+A simple test runner. TESTS is a regular expression with tests to run.
+
+=head1 OPTIONS
+
+=over 4
+
+=item I<--help>
+
+Show list of available options.
+
+=item I<--srcdir=DIR>
+
+Source directory.
+
+=item I<--builddir=DIR>
+
+Build directory.
+
+=item I<--prefix=DIR>
+
+Change directory to run tests in. Default is 'st'.
+
+=item I<--immediate>
+
+Show errors as soon as they happen rather than at the end of the test run.
+
+=item I<--target samba4|samba3|win>
+
+Specify test target against which to run. Default is 'samba4'.
+
+=item I<--quick>
+
+Run only a limited number of tests. Intended to run in about 30 seconds on
+moderately recent systems.
+
+=item I<--socket-wrapper>
+
+Use socket wrapper library for communication with server. Only works
+when the server is running locally.
+
+Will prevent TCP and UDP ports being opened on the local host but
+(transparently) redirects these calls to use unix domain sockets.
+
+=item I<--expected-failures>
+
+Specify a file containing a list of tests that are expected to fail. Failures for
+these tests will be counted as successes, successes will be counted as failures.
+
+The format for the file is, one entry per line:
+
+TESTSUITE-NAME/TEST-NAME
+
+=item I<--one>
+
+Abort as soon as one test fails.
+
+=back
+
+=head1 ENVIRONMENT
+
+=over 4
+
+=item I<SMBD_VALGRIND>
+
+=item I<TORTURE_MAXTIME>
+
+=item I<VALGRIND>
+
+=item I<TEST_LDAP>
+
+=item I<TLS_ENABLED>
+
+=item I<srcdir>
+
+=back
+
+=head1 LICENSE
+
+selftest is licensed under the GNU General Public License L<http://www.gnu.org/licenses/gpl.html>.
+
+=head1 AUTHOR
+
+Jelmer Vernooij
+
+=cut
+
use strict;
use warnings;
@@ -10,86 +111,163 @@ use File::Spec;
use Getopt::Long;
use POSIX;
use Cwd;
+use lib "$RealBin";
+use Samba4;
+use SocketWrapper;
-sub slapd_start($$) {
- my ($conf, $uri) = @_;
- my $oldpath = $ENV{PATH};
- $ENV{PATH} = "/usr/local/sbin:/usr/sbin:/sbin:$ENV{PATH}";
- # running slapd in the background means it stays in the same process group, so it can be
- # killed by timelimit
- system("slapd -d0 -f $conf -h $uri &");
- $ENV{PATH} = $oldpath;
- return $? >> 8;
-}
+my $opt_help = 0;
+my $opt_target = "samba4";
+my $opt_quick = 0;
+my $opt_socket_wrapper = 0;
+my $opt_socket_wrapper_pcap = undef;
+my $opt_one = 0;
+my $opt_immediate = 0;
+my $opt_expected_failures = undef;
+my $opt_verbose = 0;
-sub smbd_check_or_start($$$$$$)
+my $srcdir = ".";
+my $builddir = ".";
+my $prefix = "st";
+
+my $suitesfailed = [];
+my $start = time();
+my @expected_failures = ();
+
+my $statistics = {
+ SUITES_FAIL => 0,
+ SUITES_OK => 0,
+
+ TESTS_UNEXPECTED_OK => 0,
+ TESTS_EXPECTED_OK => 0,
+ TESTS_UNEXPECTED_FAIL => 0,
+ TESTS_EXPECTED_FAIL => 0,
+ TESTS_ERROR => 0
+};
+
+sub expecting_failure($)
{
- my ($bindir, $test_fifo, $test_log, $socket_wrapper_dir, $max_time, $conffile) = @_;
- return 0 if ( -p $test_fifo );
+ my $fullname = shift;
- if (defined($socket_wrapper_dir)) {
- if ( -d $socket_wrapper_dir ) {
- unlink <$socket_wrapper_dir/*>;
- } else {
- mkdir($socket_wrapper_dir);
- }
+ foreach (@expected_failures) {
+ return 1 if $fullname =~ /^$_$/;
}
- unlink($test_fifo);
- system("mkfifo $test_fifo");
-
- unlink($test_log);
-
- my $valgrind = "";
- if (defined($ENV{SMBD_VALGRIND})) {
- $valgrind = $ENV{SMBD_VALGRIND};
- }
-
- print "STARTING SMBD...";
- my $pid = fork();
- if ($pid == 0) {
- my $ret = system("$valgrind $bindir/smbd --maximum-runtime=$max_time -s $conffile -M single -i --leak-report-full < $test_fifo > $test_log");
- open LOG, ">>$test_log";
- if ($? == -1) {
- print LOG "Unable to start smbd: $ret: $!\n";
- print "Unable to start smbd: $ret: $!\n";
- exit 1;
- }
- unlink($test_fifo);
- unlink(<$socket_wrapper_dir/*>) if (defined($socket_wrapper_dir) and -d $socket_wrapper_dir);
- my $exit = $? >> 8;
- if ( $ret == 0 ) {
- print "smbd exits with status $exit\n";
- print LOG "smbd exits with status $exit\n";
- } elsif ( $ret & 127 ) {
- print "smbd got signal ".($ret & 127)." and exits with $exit!\n";
- print LOG "smbd got signal".($ret & 127). " and exits with $exit!\n";
- } else {
- $ret = $? >> 8;
- print "smbd failed with status $exit!\n";
- print LOG "smbd failed with status $exit!\n";
+ return 0;
+}
+
+sub run_test_buildfarm($$$$)
+{
+ my ($name, $cmd, $i, $suitestotal) = @_;
+ print "--==--==--==--==--==--==--==--==--==--==--\n";
+ print "Running test $name (level 0 stdout)\n";
+ print "--==--==--==--==--==--==--==--==--==--==--\n";
+ system("date");
+
+ my $expected_ret = 1;
+ my $open_tests = {};
+ open(RESULT, "$cmd|");
+ while (<RESULT>) {
+ print;
+ if (/^test: (.+)\n/) {
+ $open_tests->{$1} = 1;
+ } elsif (/^(success|failure|skip|error): (.*?)( \[)?\n/) {
+ my $result = $1;
+ if ($1 eq "success") {
+ delete $open_tests->{$2};
+ if (expecting_failure("$name/$2")) {
+ $statistics->{TESTS_UNEXPECTED_OK}++;
+ } else {
+ $statistics->{TESTS_EXPECTED_OK}++;
+ }
+ } elsif ($1 eq "failure") {
+ delete $open_tests->{$2};
+ if (expecting_failure("$name/$2")) {
+ $statistics->{TESTS_EXPECTED_FAIL}++;
+ $expected_ret = 0;
+ } else {
+ $statistics->{TESTS_UNEXPECTED_FAIL}++;
+ }
+ } elsif ($1 eq "skip") {
+ delete $open_tests->{$2};
+ } elsif ($1 eq "error") {
+ $statistics->{TESTS_ERROR}++;
+ delete $open_tests->{$2};
+ }
}
- close(LOG);
- exit $exit;
}
- print "DONE\n";
+ print "COMMAND: $cmd\n";
+ foreach (keys %$open_tests) {
+ print "$_ was started but never finished!\n";
+ $statistics->{TESTS_ERROR}++;
+ }
+ my $ret = close(RESULT);
- return $pid;
+ print "==========================================\n";
+ if ($ret == $expected_ret) {
+ print "TEST PASSED: $name\n";
+ } else {
+ print "TEST FAILED: $name (status $ret)\n";
+ }
+ print "==========================================\n";
}
-sub teststatus($$) {
- my ($name, $failed) = @_;
-
- print "TEST STATUS: $failed failures\n";
- if ($failed > 0) {
-print <<EOF
-************************
-*** TESTSUITE FAILED ***
-************************
-EOF
-;
+my $test_output = {};
+sub run_test_plain($$$$)
+{
+ my ($name, $cmd, $i, $totalsuites) = @_;
+ my $err = "";
+ if ($#$suitesfailed+1 > 0) { $err = ", ".($#$suitesfailed+1)." errors"; }
+ printf "[$i/$totalsuites in " . (time() - $start)."s$err] $name\n";
+ open(RESULT, "$cmd 2>&1|");
+ my $expected_ret = 1;
+ my $open_tests = {};
+ $test_output->{$name} = "";
+ while (<RESULT>) {
+ $test_output->{$name}.=$_;
+ print if ($opt_verbose);
+ if (/^test: (.+)\n/) {
+ $open_tests->{$1} = 1;
+ } elsif (/^(success|failure|skip|error): (.*?)( \[)?\n/) {
+ my $result = $1;
+ if ($1 eq "success") {
+ delete $open_tests->{$2};
+ if (expecting_failure("$name/$2")) {
+ $statistics->{TESTS_UNEXPECTED_OK}++;
+ } else {
+ $statistics->{TESTS_EXPECTED_OK}++;
+ }
+ } elsif ($1 eq "failure") {
+ delete $open_tests->{$2};
+ if (expecting_failure("$name/$2")) {
+ $statistics->{TESTS_EXPECTED_FAIL}++;
+ $expected_ret = 0;
+ } else {
+ $statistics->{TESTS_UNEXPECTED_FAIL}++;
+ }
+ } elsif ($1 eq "skip") {
+ delete $open_tests->{$2};
+ } elsif ($1 eq "error") {
+ $statistics->{TESTS_ERROR}++;
+ delete $open_tests->{$2};
+ }
+ }
+ }
+ $test_output->{$name}.="COMMAND: $cmd\n";
+ foreach (keys %$open_tests) {
+ $test_output->{$name}.="$_ was started but never finished!\n";
+ $statistics->{TESTS_ERROR}++;
+ }
+ my $ret = close(RESULT);
+ if ($ret != $expected_ret and ($opt_immediate or $opt_one) and not $opt_verbose) {
+ print "$test_output->{$name}\n";
+ }
+ if ($ret != $expected_ret) {
+ push(@$suitesfailed, $name);
+ $statistics->{SUITES_FAIL}++;
+ exit(1) if ($opt_one);
+ } else {
+ $statistics->{SUITES_OK}++;
}
- exit $failed;
}
sub ShowHelp()
@@ -97,51 +275,60 @@ sub ShowHelp()
print "Samba test runner
Copyright (C) Jelmer Vernooij <jelmer\@samba.org>
-Usage: $Script PREFIX
+Usage: $Script [OPTIONS] PREFIX
Generic options:
--help this help page
+
+Paths:
+ --prefix=DIR prefix to run tests in [st]
+ --srcdir=DIR source directory [.]
+ --builddir=DIR output directory [.]
+
+Target Specific:
--target=samba4|samba3|win Samba version to target
+ --socket-wrapper-pcap=FILE save traffic to pcap file
--socket-wrapper enable socket wrapper
+ --expected-failures=FILE specify list of tests that is guaranteed to fail
+
+Behaviour:
--quick run quick overall test
--one abort when the first test fails
+ --immediate print test output for failed tests during run
+ --verbose be verbose
";
exit(0);
}
-my $opt_help = 0;
-my $opt_target = "samba4";
-my $opt_quick = 0;
-my $opt_socket_wrapper = 0;
-my $opt_one = 0;
-
my $result = GetOptions (
'help|h|?' => \$opt_help,
- 'target' => \$opt_target,
+ 'target=s' => \$opt_target,
+ 'prefix=s' => \$prefix,
'socket-wrapper' => \$opt_socket_wrapper,
+ 'socket-wrapper-pcap=s' => \$opt_socket_wrapper_pcap,
'quick' => \$opt_quick,
- 'one' => \$opt_one
+ 'one' => \$opt_one,
+ 'immediate' => \$opt_immediate,
+ 'expected-failures=s' => \$opt_expected_failures,
+ 'srcdir=s' => \$srcdir,
+ 'builddir=s' => \$builddir,
+ 'verbose' => \$opt_verbose
);
-if (not $result) {
- exit(1);
-}
+exit(1) if (not $result);
ShowHelp() if ($opt_help);
-ShowHelp() if ($#ARGV < 0);
-my $prefix = shift;
+my $tests = shift;
my $torture_maxtime = $ENV{TORTURE_MAXTIME};
unless (defined($torture_maxtime)) {
$torture_maxtime = 1200;
}
-# disable rpc validation when using valgrind - its way too slow
-my $valgrind = $ENV{VALGRIND};
-my $validate = undef;
-unless (defined($valgrind)) {
- $validate = "validate";
+# quick hack to disable rpc validation when using valgrind - its way too slow
+unless (defined($ENV{VALGRIND})) {
+ $ENV{VALIDATE} = "validate";
}
my $old_pwd = "$RealBin/../..";
@@ -150,10 +337,6 @@ my $ldap = (defined($ENV{TEST_LDAP}) and ($ENV{TEST_LDAP} eq "yes"))?1:0;
$prefix =~ s+//+/+;
$ENV{PREFIX} = $prefix;
-my $srcdir = "$RealBin/../..";
-if (defined($ENV{srcdir})) {
- $srcdir = $ENV{srcdir};
-}
$ENV{SRCDIR} = $srcdir;
my $bindir = "$srcdir/bin";
@@ -161,6 +344,8 @@ my $setupdir = "$srcdir/setup";
my $testsdir = "$srcdir/script/tests";
my $tls_enabled = not $opt_quick;
+my $from_build_farm = (defined($ENV{RUN_FROM_BUILD_FARM}) and
+ ($ENV{RUN_FROM_BUILD_FARM} eq "yes"));
$ENV{TLS_ENABLED} = ($tls_enabled?"yes":"no");
$ENV{LD_LDB_MODULE_PATH} = "$old_pwd/bin/modules/ldb";
@@ -173,14 +358,12 @@ if (defined($ENV{LD_LIBRARY_PATH})) {
$ENV{PKG_CONFIG_PATH} = "$old_pwd/bin/pkgconfig:$ENV{PKG_CONFIG_PATH}";
$ENV{PATH} = "$old_pwd/bin:$ENV{PATH}";
+my @torture_options = ();
+
+my $testenv_vars = {};
+
if ($opt_target eq "samba4") {
- print "PROVISIONING...";
- open(IN, "$RealBin/mktestsetup.sh $prefix|") or die("Unable to setup");
- while (<IN>) {
- next unless (/^([A-Z_]+)=(.*)$/);
- $ENV{$1} = $2;
- }
- close(IN);
+ $testenv_vars = Samba4::provision($prefix);
} elsif ($opt_target eq "win") {
die ("Windows tests will not run without root privileges.")
if (`whoami` ne "root");
@@ -196,77 +379,82 @@ if ($opt_target eq "samba4") {
die ("$ENV{WINTESTCONF} could not be read.") if (! -r $ENV{WINTESTCONF});
$ENV{WINTEST_DIR}="$ENV{SRCDIR}/script/tests/win";
+} elsif ($opt_target eq "none") {
} else {
die("unknown target `$opt_target'");
}
-my $socket_wrapper_dir = undef;
+foreach (keys %$testenv_vars) { $ENV{$_} = $testenv_vars->{$_}; }
+
+if ($opt_socket_wrapper_pcap) {
+ $ENV{SOCKET_WRAPPER_PCAP_FILE} = $opt_socket_wrapper_pcap;
+ # Socket wrapper pcap implies socket wrapper
+ $opt_socket_wrapper = 1;
+}
-if ( $opt_socket_wrapper)
+my $socket_wrapper_dir;
+if ($opt_socket_wrapper)
{
- $socket_wrapper_dir = "$prefix/w";
- $ENV{SOCKET_WRAPPER_DIR} = $socket_wrapper_dir;
- print "SOCKET_WRAPPER_DIR=$ENV{SOCKET_WRAPPER_DIR}\n";
-} else {
- print "NOT USING SOCKET_WRAPPER\n";
+ $socket_wrapper_dir = SocketWrapper::setup_dir("$prefix/w");
+ print "SOCKET_WRAPPER_DIR=$socket_wrapper_dir\n";
}
# Start slapd before smbd
if ($ldap) {
- slapd_start($ENV{SLAPD_CONF}, $ENV{LDAPI_ESCAPE}) or die("couldn't start slapd");
+ Samba4::slapd_start($ENV{SLAPD_CONF}, $ENV{LDAPI_ESCAPE}) or die("couldn't start slapd");
+
print "LDAP PROVISIONING...";
- system("$bindir/smbscript $setupdir/provision $ENV{PROVISION_OPTIONS} --ldap-backend=$ENV{LDAPI}") or
- die("LDAP PROVISIONING failed: $bindir/smbscript $setupdir/provision $ENV{PROVISION_OPTIONS} --ldap-backend=$ENV{LDAPI}");
+ Samba4::provision_ldap($bindir, $setupdir);
# LDAP is slow
$torture_maxtime *= 2;
}
+if (defined($opt_expected_failures)) {
+ open(KNOWN, "<$opt_expected_failures") or die("unable to read known failures file: $!");
+ while (<KNOWN>) {
+ chomp;
+ s/([ \t]+)\#(.*)$//;
+ push (@expected_failures, $_); }
+ close(KNOWN);
+}
+
my $test_fifo = "$prefix/smbd_test.fifo";
$ENV{SMBD_TEST_FIFO} = $test_fifo;
$ENV{SMBD_TEST_LOG} = "$prefix/smbd_test.log";
-$ENV{SOCKET_WRAPPER_DEFAULT_IFACE} = 1;
+SocketWrapper::set_default_iface(1);
my $max_time = 5400;
if (defined($ENV{SMBD_MAX_TIME})) {
$max_time = $ENV{SMBD_MAX_TIME};
}
-smbd_check_or_start($bindir, $test_fifo, $ENV{SMBD_TEST_LOG}, $socket_wrapper_dir, $max_time, $ENV{CONFFILE});
+Samba4::smbd_check_or_start($bindir, $test_fifo, $ENV{SMBD_TEST_LOG},
+ $socket_wrapper_dir, $max_time, $ENV{CONFFILE});
+
+SocketWrapper::set_default_iface(6);
-$ENV{SOCKET_WRAPPER_DEFAULT_IFACE} = 6;
-$ENV{TORTURE_INTERFACES} = '127.0.0.6/8,127.0.0.7/8,127.0.0.8/8,127.0.0.9/8,127.0.0.10/8,127.0.0.11/8';
+my $interfaces = join(',', ("127.0.0.6/8",
+ "127.0.0.7/8",
+ "127.0.0.8/8",
+ "127.0.0.9/8",
+ "127.0.0.10/8",
+ "127.0.0.11/8"));
-my @torture_options = ("--option=interfaces=$ENV{TORTURE_INTERFACES} $ENV{CONFIGURATION}");
+push (@torture_options, "--option=interfaces=$interfaces");
+push (@torture_options, $ENV{CONFIGURATION});
# ensure any one smbtorture call doesn't run too long
push (@torture_options, "--maximum-runtime=$torture_maxtime");
push (@torture_options, "--target=$opt_target");
-push (@torture_options, "--option=torture:progress=no")
- if (defined($ENV{RUN_FROM_BUILD_FARM}) and $ENV{RUN_FROM_BUILD_FARM} eq "yes");
+push (@torture_options, "--option=torture:progress=no") if ($from_build_farm);
+push (@torture_options, "--format=subunit");
+push (@torture_options, "--option=torture:quick=yes") if ($opt_quick);
$ENV{TORTURE_OPTIONS} = join(' ', @torture_options);
print "OPTIONS $ENV{TORTURE_OPTIONS}\n";
-my $start = time();
-
open(DATA, ">$test_fifo");
-# give time for nbt server to register its names
-print "delaying for nbt name registration\n";
-sleep(4);
-
-# This will return quickly when things are up, but be slow if we need to wait for (eg) SSL init
-system("bin/nmblookup $ENV{CONFIGURATION} $ENV{SERVER}");
-system("bin/nmblookup $ENV{CONFIGURATION} -U $ENV{SERVER} $ENV{SERVER}");
-system("bin/nmblookup $ENV{CONFIGURATION} $ENV{SERVER}");
-system("bin/nmblookup $ENV{CONFIGURATION} -U $ENV{SERVER} $ENV{NETBIOSNAME}");
-system("bin/nmblookup $ENV{CONFIGURATION} $ENV{NETBIOSNAME}");
-system("bin/nmblookup $ENV{CONFIGURATION} -U $ENV{SERVER} $ENV{NETBIOSNAME}");
-
-# start off with 0 failures
-$ENV{failed} = 0;
-my $totalfailed = 0;
-
my @todo = ();
if ($opt_target eq "win") {
@@ -283,27 +471,35 @@ if ($opt_target eq "win") {
$name =~ s/\n//g;
my $cmdline = <IN>;
$cmdline =~ s/\n//g;
- push (@todo, [$name, $cmdline]);
+ push (@todo, [$name, $cmdline])
+ if (not defined($tests) or $name =~ /$tests/);
} else {
print;
}
}
- close(IN);
+ close(IN) or die("Error creating recipe");
}
-my $total = $#todo + 1;
+Samba4::wait_for_start();
+
+# start off with 0 failures
+$ENV{failed} = 0;
+
+my $suitestotal = $#todo + 1;
my $i = 0;
$| = 1;
+delete $ENV{DOMAIN};
+
foreach (@todo) {
- $i = $i + 1;
- my $err = "";
- if ($totalfailed > 0) { $err = ", $totalfailed errors"; }
- printf "[$i/$total in " . (time() - $start)."s$err] $$_[0]\n";
- my $ret = system("$$_[1] >/dev/null 2>/dev/null");
- if ($ret != 0) {
- $totalfailed++;
- exit(1) if ($opt_one);
+ $i++;
+ my $cmd = $$_[1];
+ $cmd =~ s/([\(\)])/\\$1/g;
+ my $name = $$_[0];
+ if ($from_build_farm) {
+ run_test_buildfarm($name, $cmd, $i, $suitestotal);
+ } else {
+ run_test_plain($name, $cmd, $i, $suitestotal);
}
}
@@ -311,6 +507,8 @@ print "\n";
close(DATA);
+sleep(2);
+
my $failed = $? >> 8;
if (-f "$ENV{PIDDIR}/smbd.pid" ) {
@@ -319,15 +517,37 @@ if (-f "$ENV{PIDDIR}/smbd.pid" ) {
close(IN);
}
-if ($ldap) {
- open(IN, "<$ENV{PIDDIR}/slapd.pid") or die("unable to open slapd pid file");
- kill 9, <IN>;
- close(IN);
-}
+Samba4::slapd_stop() if ($ldap);
-my $end=time();
-print "DURATION: " . ($end-$start). " seconds\n";
-print "$totalfailed failures\n";
+my $end = time();
+my $duration = ($end-$start);
+my $numfailed = $#$suitesfailed+1;
+if ($numfailed == 0) {
+ my $ok = $statistics->{TESTS_EXPECTED_OK} + $statistics->{TESTS_EXPECTED_FAIL};
+ print "ALL OK ($ok tests in $statistics->{SUITES_OK} testsuites)\n";
+} else {
+
+ unless ($from_build_farm) {
+ if (not $opt_immediate and not $opt_verbose) {
+ foreach (@$suitesfailed) {
+ print "===============================================================================\n";
+ print "FAIL: $_\n";
+ print $test_output->{$_};
+ print "\n";
+ }
+ }
+
+ print "FAILED ($statistics->{TESTS_UNEXPECTED_FAIL} failures and $statistics->{TESTS_ERROR} errors in $statistics->{SUITES_FAIL} testsuites)\n";
+ } else {
+ print <<EOF
+************************
+*** TESTSUITE FAILED ***
+************************
+EOF
+;
+ }
+}
+print "DURATION: $duration seconds\n";
# if there were any valgrind failures, show them
foreach (<$prefix/valgrind.log*>) {
@@ -340,6 +560,4 @@ foreach (<$prefix/valgrind.log*>) {
}
}
-teststatus($Script, $failed);
-
-exit $failed;
+exit $numfailed;