From adc257481c543b978018c0e0cf640fc2cb3f61eb Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 24 Apr 2008 13:02:53 +0200 Subject: Add support for running in parallel mysql-test/lib/My/Options.pm: Allow $VAR as option mysql-test/lib/My/SafeProcess.pm: Remove printouts mysql-test/lib/My/Test.pm: New BitKeeper file ``mysql-test/lib/My/Test.pm'' --- mysql-test/lib/My/Options.pm | 3 + mysql-test/lib/My/SafeProcess.pm | 6 +- mysql-test/lib/My/Test.pm | 122 +++++++++ mysql-test/lib/mtr_cases.pm | 103 ++----- mysql-test/lib/mtr_report.pm | 126 ++++++++- mysql-test/mysql-test-run.pl | 576 ++++++++++++++++++++++++++++----------- 6 files changed, 678 insertions(+), 258 deletions(-) create mode 100644 mysql-test/lib/My/Test.pm diff --git a/mysql-test/lib/My/Options.pm b/mysql-test/lib/My/Options.pm index f0fc4ba75b5..40f05c41d1c 100644 --- a/mysql-test/lib/My/Options.pm +++ b/mysql-test/lib/My/Options.pm @@ -61,6 +61,9 @@ sub _split_option { elsif ($option=~ /^--(.*)$/){ return ($1, undef) } + elsif ($option=~ /^\$(.*)$/){ # $VAR + return ($1, undef) + } elsif ($option=~ /^(.*)=(.*)$/){ return ($1, $2) } diff --git a/mysql-test/lib/My/SafeProcess.pm b/mysql-test/lib/My/SafeProcess.pm index 4d703d73b9a..eccc0fcdeed 100644 --- a/mysql-test/lib/My/SafeProcess.pm +++ b/mysql-test/lib/My/SafeProcess.pm @@ -65,7 +65,7 @@ END { # Kill any children still running for my $proc (values %running){ if ( $proc->is_child($$) ){ - print "Killing: $proc\n"; + #print "Killing: $proc\n"; $proc->kill(); } } @@ -461,8 +461,8 @@ sub wait_one { return 1; } - warn "wait_one: expected pid $pid but got $retpid" - unless( $retpid == $pid ); + #warn "wait_one: expected pid $pid but got $retpid" + # unless( $retpid == $pid ); $self->_collect(); return 0; diff --git a/mysql-test/lib/My/Test.pm b/mysql-test/lib/My/Test.pm new file mode 100644 index 00000000000..890ae76b282 --- /dev/null +++ b/mysql-test/lib/My/Test.pm @@ -0,0 +1,122 @@ +# -*- cperl -*- + + +# +# One test +# +package My::Test; + +use strict; +use warnings; +use Carp; + + +sub new { + my $class= shift; + my $self= bless { + @_, + }, $class; + return $self; +} + + +# +# Return a unique key that can be used to +# identify this test in a hash +# +sub key { + my ($self)= @_; + my $key= $self->{name}; + $key.= "+".$self->{combination} if $self->{combination}; + return $key; +} + + +sub _encode { + my ($value)= @_; + $value =~ s/([|\\\x{0a}\x{0d}])/sprintf('\%02X', ord($1))/eg; + return $value; +} + +sub _decode { + my ($value)= @_; + $value =~ s/\\([0-9a-fA-F]{2})/chr(hex($1))/ge; + return $value; +} + +sub is_failed { + my ($self)= @_; + my $result= $self->{result}; + croak "'is_failed' can't be called until test has been run!" + unless defined $result; + + return ($result eq 'MTR_RES_FAILED'); +} + + +sub write_test { + my ($test, $sock, $header)= @_; + + print $sock $header, "\n"; + while ((my ($key, $value)) = each(%$test)) { + print $sock $key, "= "; + if (ref $value eq "ARRAY") { + print $sock "[", _encode(join(", ", @$value)), "]"; + } else { + print $sock _encode($value); + } + print $sock "\n"; + } + print $sock "\n"; +} + + +sub read_test { + my ($sock)= @_; + my $test= My::Test->new(); + # Read the : separated key value pairs until a + # single newline on it's own line + my $line; + while (defined($line= <$sock>)) { + # List is terminated by newline on it's own + if ($line eq "\n") { + # Correctly terminated reply + # print "Got newline\n"; + last; + } + chomp($line); + + # Split key/value on the first "=" + my ($key, $value)= split("= ", $line, 2); + + if ($value =~ /^\[(.*)\]/){ + my @values= split(", ", _decode($1)); + push(@{$test->{$key}}, @values); + } + else + { + $test->{$key}= _decode($value); + } + } + return $test; +} + + +sub print_test { + my ($self)= @_; + + print "[", $self->{name}, "]", "\n"; + while ((my ($key, $value)) = each(%$self)) { + print " ", $key, "= "; + if (ref $value eq "ARRAY") { + print "[", join(", ", @$value), "]"; + } else { + print $value; + } + print "\n"; + } + print "\n"; +} + + +1; diff --git a/mysql-test/lib/mtr_cases.pm b/mysql-test/lib/mtr_cases.pm index 0f8329f2f31..4e10066c176 100644 --- a/mysql-test/lib/mtr_cases.pm +++ b/mysql-test/lib/mtr_cases.pm @@ -40,7 +40,7 @@ our $default_storage_engine; our $opt_with_ndbcluster_only; our $defaults_file; our $defaults_extra_file; -our $reorder; +our $reorder= 1; sub collect_option { my ($opt, $value)= @_; @@ -55,6 +55,7 @@ use File::Basename; use IO::File(); use My::Config; use My::Platform; +use My::Test; use My::Find; require "mtr_misc.pl"; @@ -135,52 +136,16 @@ sub collect_test_cases ($$) { { my @criteria = (); - # Look for tests that must be run in a defined order - that is - # defined by test having the same name except for the ending digit + # + # Append the criteria for sorting, in order of importance. + # + push(@criteria, "ndb=" . ($tinfo->{'ndb_test'} ? "A" : "B")); + # Group test with equal options together. + # Ending with "~" makes empty sort later than filled + my $opts= $tinfo->{'master_opt'} ? $tinfo->{'master_opt'} : []; + push(@criteria, join("!", sort @{$opts}) . "~"); - # Put variables into hash - my $test_name= $tinfo->{'name'}; - my $depend_on_test_name; - if ( $test_name =~ /^([\D]+)([0-9]{1})$/ ) - { - my $base_name= $1; - my $idx= $2; - mtr_verbose("$test_name => $base_name idx=$idx"); - if ( $idx > 1 ) - { - $idx-= 1; - $base_name= "$base_name$idx"; - mtr_verbose("New basename $base_name"); - } - - foreach my $tinfo2 (@$cases) - { - if ( $tinfo2->{'name'} eq $base_name ) - { - mtr_verbose("found dependent test $tinfo2->{'name'}"); - $depend_on_test_name=$base_name; - } - } - } - - if ( defined $depend_on_test_name ) - { - mtr_verbose("Giving $test_name same critera as $depend_on_test_name"); - $sort_criteria{$test_name} = $sort_criteria{$depend_on_test_name}; - } - else - { - # - # Append the criteria for sorting, in order of importance. - # - push(@criteria, "ndb=" . ($tinfo->{'ndb_test'} ? "1" : "0")); - # Group test with equal options together. - # Ending with "~" makes empty sort later than filled - my $opts= $tinfo->{'master_opt'} ? $tinfo->{'master_opt'} : []; - push(@criteria, join("!", sort @{$opts}) . "~"); - - $sort_criteria{$test_name} = join(" ", @criteria); - } + $sort_criteria{$tinfo->{name}} = join(" ", @criteria); } @$cases = sort { @@ -454,7 +419,7 @@ sub collect_one_suite($) } # Copy test options - my $new_test= {}; + my $new_test= My::Test->new(); while (my ($key, $value) = each(%$test)) { if (ref $value eq "ARRAY") { push(@{$new_test->{$key}}, @$value); @@ -682,13 +647,16 @@ sub collect_one_test_case { # ---------------------------------------------------------------------- # Set defaults # ---------------------------------------------------------------------- - my $tinfo= {}; - $tinfo->{'name'}= $suitename . ".$tname"; - $tinfo->{'path'}= "$testdir/$filename"; + my $tinfo= My::Test->new + ( + name => "$suitename.$tname", + path => "$testdir/$filename", - # TODO allow nonexistsing result file - # in that case .test must issue "exit" otherwise test should fail by default - $tinfo->{'result_file'}= "$resdir/$tname.result"; + # TODO allow nonexistsing result file + # in that case .test must issue "exit" otherwise test + # should fail by default + result_file => "$resdir/$tname.result", + ); # ---------------------------------------------------------------------- # Skip some tests but include in list, just mark them as skipped @@ -1034,19 +1002,6 @@ sub unspace { } - -sub envsubst { - my $string= shift; - - if ( ! defined $ENV{$string} ) - { - mtr_error(".opt file references '$string' which is not set"); - } - - return $ENV{$string}; -} - - sub opts_from_file ($) { my $file= shift; @@ -1083,10 +1038,6 @@ sub opts_from_file ($) { or $arg =~ s/^([^\'\"]*)\"(.*)\"([^\'\"]*)$/$1$2$3/; $arg =~ s/\\\\/\\/g; - # Expand environment variables - $arg =~ s/\$\{(\w+)\}/envsubst($1)/ge; - $arg =~ s/\$(\w+)/envsubst($1)/ge; - # Do not pass empty string since my_getopt is not capable to handle it. if (length($arg)) { push(@args, $arg); @@ -1102,17 +1053,7 @@ sub print_testcases { print "=" x 60, "\n"; foreach my $test (@cases){ - print "[", $test->{name}, "]", "\n"; - while ((my ($key, $value)) = each(%$test)) { - print " ", $key, "= "; - if (ref $value eq "ARRAY") { - print "[", join(", ", @$value), "]"; - } else { - print $value; - } - print "\n"; - } - print "\n"; + $test->print_test(); } print "=" x 60, "\n"; } diff --git a/mysql-test/lib/mtr_report.pm b/mysql-test/lib/mtr_report.pm index a3c0f26c8fd..f749168c64a 100644 --- a/mysql-test/lib/mtr_report.pm +++ b/mysql-test/lib/mtr_report.pm @@ -27,7 +27,7 @@ our @EXPORT= qw(report_option mtr_print_line mtr_print_thick_line mtr_warning mtr_error mtr_debug mtr_verbose mtr_verbose_restart mtr_report_test_passed mtr_report_test_failed mtr_report_test_skipped - mtr_report_stats); + mtr_report_stats mtr_report_test); use mtr_match; require "mtr_io.pl"; @@ -35,6 +35,10 @@ require "mtr_io.pl"; my $tot_real_time= 0; our $timestamp= 0; +our $name; +our $verbose; +our $verbose_restart= 0; + sub report_option { my ($opt, $value)= @_; @@ -43,6 +47,8 @@ sub report_option { $opt =~ s/-/_/; no strict 'refs'; ${$opt}= $value; + + #print $name, " setting $opt to ", (defined $value? $value : "undef") ,"\n"; } sub SHOW_SUITE_NAME() { return 1; }; @@ -51,6 +57,8 @@ sub _mtr_report_test_name ($) { my $tinfo= shift; my $tname= $tinfo->{name}; + return unless defined $verbose; + # Remove suite part of name $tname =~ s/.*\.// unless SHOW_SUITE_NAME; @@ -58,7 +66,7 @@ sub _mtr_report_test_name ($) { $tname.= " '$tinfo->{combination}'" if defined $tinfo->{combination}; - print _timestamp(); + print $name, _timestamp(); printf "%-30s ", $tname; } @@ -100,12 +108,19 @@ sub mtr_report_test_passed ($$) { $timer= mtr_fromfile("$::opt_vardir/log/timer"); $tot_real_time += ($timer/1000); $timer= sprintf "%12s", $timer; + $tinfo->{timer}= $timer; } # Set as passed unless already set if ( not defined $tinfo->{'result'} ){ $tinfo->{'result'}= 'MTR_RES_PASSED'; } mtr_report("[ pass ] $timer"); + + # Show any problems check-testcase found + if ( defined $tinfo->{'check'} ) + { + mtr_report($tinfo->{'check'}); + } } @@ -143,9 +158,7 @@ sub mtr_report_test_failed ($$) { { # Test failure was detected by test tool and its report # about what failed has been saved to file. Display the report. - print "\n"; - mtr_printfile($logfile); - print "\n"; + $tinfo->{logfile}= mtr_fromfile($logfile); } else { @@ -156,6 +169,83 @@ sub mtr_report_test_failed ($$) { } +sub mtr_report_test ($) { + my ($tinfo)= @_; + _mtr_report_test_name($tinfo); + + if ($tinfo->{'result'} eq 'MTR_RES_FAILED'){ + + #my $test_failures= $tinfo->{'failures'} || 0; + #$tinfo->{'failures'}= $test_failures + 1; + if ( defined $tinfo->{'warnings'} ) + { + mtr_report("[ fail ] Found warnings in server log file!"); + mtr_report($tinfo->{'warnings'}); + return; + } + if ( defined $tinfo->{'timeout'} ) + { + mtr_report("[ fail ] timeout"); + return; + } + else + { + mtr_report("[ fail ]"); + } + + if ( $tinfo->{'comment'} ) + { + # The test failure has been detected by mysql-test-run.pl + # when starting the servers or due to other error, the reason for + # failing the test is saved in "comment" + mtr_report("\nERROR: $tinfo->{'comment'}"); + } + elsif ( $tinfo->{logfile} ) + { + # Test failure was detected by test tool and its report + # about what failed has been saved to file. Display the report. + mtr_report("\n"); + mtr_report($tinfo->{logfile}, "\n"); + + } + else + { + # Neither this script or the test tool has recorded info + # about why the test has failed. Should be debugged. + mtr_report("\nUnexpected termination, probably when starting mysqld");; + } + } + elsif ($tinfo->{'result'} eq 'MTR_RES_SKIPPED') + { + if ( $tinfo->{'disable'} ) + { + mtr_report("[ disabled ] $tinfo->{'comment'}"); + } + elsif ( $tinfo->{'comment'} ) + { + if ( $tinfo->{skip_detected_by_test} ) + { + mtr_report("[ skip ]. $tinfo->{'comment'}"); + } + else + { + mtr_report("[ skip ] $tinfo->{'comment'}"); + } + } + else + { + mtr_report("[ skip ]"); + } + } + elsif ($tinfo->{'result'} eq 'MTR_RES_PASSED') + { + my $timer= $tinfo->{timer} || ""; + mtr_report("[ pass ] $timer"); + } + +} + + sub mtr_report_stats ($) { my $tests= shift; @@ -342,35 +432,42 @@ sub _timestamp { # Print message to screen sub mtr_report (@) { - print join(" ", @_), "\n"; + if (defined $verbose) + { + print join(" ", @_), "\n"; + } } # Print warning to screen sub mtr_warning (@) { - print STDERR _timestamp(), "mysql-test-run: WARNING: ", join(" ", @_), "\n"; + print STDERR $name, _timestamp(), + "mysql-test-run: WARNING: ", join(" ", @_), "\n"; } # Print error to screen and then exit sub mtr_error (@) { - print STDERR _timestamp(), "mysql-test-run: *** ERROR: ", join(" ", @_), "\n"; + print STDERR $name, _timestamp(), + "mysql-test-run: *** ERROR: ", join(" ", @_), "\n"; exit(1); } sub mtr_debug (@) { - if ( $::opt_verbose > 1 ) + if ( $verbose > 2 ) { - print STDERR _timestamp(), "####: ", join(" ", @_), "\n"; + print STDERR $name, + _timestamp(), "####: ", join(" ", @_), "\n"; } } sub mtr_verbose (@) { - if ( $::opt_verbose ) + if ( $verbose ) { - print STDERR _timestamp(), "> ",join(" ", @_),"\n"; + print STDERR $name, _timestamp(), + "> ",join(" ", @_),"\n"; } } @@ -378,9 +475,10 @@ sub mtr_verbose (@) { sub mtr_verbose_restart (@) { my ($server, @args)= @_; my $proc= $server->{proc}; - if ( $::opt_verbose_restart ) + if ( $verbose_restart ) { - print STDERR _timestamp(), "> Restart $proc - ",join(" ", @args),"\n"; + print STDERR $name,_timestamp(), + "> Restart $proc - ",join(" ", @args),"\n"; } } diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index cf23d63ee13..3b252b9dde3 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -51,6 +51,8 @@ use My::Find; use mtr_cases; use mtr_report; use mtr_match; +use IO::Socket::INET; +use IO::Select; require "lib/mtr_process.pl"; require "lib/mtr_io.pl"; @@ -88,7 +90,6 @@ my $DEFAULT_SUITES= "main,binlog,federated,rpl,rpl_ndb,ndb"; my $opt_suites; our $opt_verbose= 0; # Verbose output, enable with --verbose -our $opt_verbose_restart= 0; # Verbose output for restarts our $exe_mysql; our $exe_mysqladmin; @@ -141,7 +142,7 @@ my $config; # The currently running config my $current_config_name; # The currently running config file template my $opt_baseport; -my $opt_mtr_build_thread= $ENV{'MTR_BUILD_THREAD'} || "auto"; +my $opt_build_thread= $ENV{'MTR_BUILD_THREAD'} || "auto"; my $opt_record; my $opt_report_features; @@ -161,6 +162,8 @@ my $opt_repeat= 1; my $opt_retry= 3; my $opt_retry_failure= 2; +my $opt_parallel= 1; + my $opt_strace_client; our $opt_timer= 1; @@ -203,25 +206,46 @@ main(); sub main { - command_line_setup(); + # This is needed for test log evaluation in "gen-build-status-page" + # in all cases where the calling tool does not log the commands + # directly before it executes them, like "make test-force-pl" in RPM builds. + mtr_report("Logging: $0 ", join(" ", @ARGV)); - mtr_report("Checking supported features..."); + Getopt::Long::Configure("pass_through"); + GetOptions('parallel=i' => \$opt_parallel) or usage("Can't read options"); - check_ndbcluster_support(\%mysqld_variables); - check_ssl_support(\%mysqld_variables); - check_debug_support(\%mysqld_variables); + # Create server socket on any free port + my $server = new IO::Socket::INET + ( + LocalAddr => 'localhost', + Proto => 'tcp', + Listen => $opt_parallel, + ); + mtr_error("Could not create testcase server port: $!") unless $server; + my $server_port = $server->sockport(); + mtr_report("Using server port $server_port"); + + # Create child processes + my %children; + for my $child_num (1..$opt_parallel){ + my $child_pid= My::SafeProcess::Base::_safe_fork(); + if ($child_pid == 0){ + $server= undef; # Close the server port in child + run_worker($server_port, $child_num); + exit(1); + } - executable_setup(); + mtr_report("Started worker, pid: $child_pid"); + $children{$child_pid}= 1; + } - environment_setup(); + command_line_setup(0); - if ( $opt_gcov ) - { + if ( $opt_gcov ) { gcov_prepare(); } - if (!$opt_suites) - { + if (!$opt_suites) { $opt_suites= $DEFAULT_SUITES; # Check for any extra suites to enable based on the path name @@ -235,10 +259,9 @@ sub main { "mysql-6.0-ndb" => "ndb_team", ); - foreach my $dir ( reverse splitdir($basedir) ) - { + foreach my $dir ( reverse splitdir($basedir) ) { my $extra_suite= $extra_suites{$dir}; - if (defined $extra_suite){ + if (defined $extra_suite) { mtr_report("Found extra suite: $extra_suite"); $opt_suites= "$extra_suite,$opt_suites"; last; @@ -249,26 +272,289 @@ sub main { mtr_report("Collecting tests..."); my $tests= collect_test_cases($opt_suites, \@opt_cases); - initialize_servers(); - if ( $opt_report_features ) { # Put "report features" as the first test to run - my $tinfo = {}; - $tinfo->{'name'} = 'report_features'; - $tinfo->{'result_file'} = undef; # Prints result - $tinfo->{'path'} = 'include/report-features.test'; - $tinfo->{'master_opt'} = []; - $tinfo->{'slave_opt'} = []; + my $tinfo = My::Test->new + ( + name => 'report_features', + result_file => undef, # Prints result + path => 'include/report-features.test'. + master_opt => [], + slave_opt => [], + ); unshift(@$tests, $tinfo); } + initialize_servers(); + + mtr_report(); + mtr_print_thick_line(); + mtr_print_header(); + + my $completed= run_test_server($server, $tests, $opt_parallel); + + # Send Ctrl-C to any children still running + kill("INT", keys(%children)); + + # Wait for childs to exit + foreach my $pid (keys %children) + { + my $ret_pid= waitpid($pid, 0); + if ($ret_pid != $pid){ + mtr_report("Unknown process $ret_pid exited"); + } + else { + delete $children{$ret_pid}; + } + } + + mtr_verbose("Server exit\n"); + + mtr_print_line(); + + mtr_report_stats($completed); + + exit(0); +} + + +sub run_test_server { + my ($server, $tests, $childs) = @_; + + # Scheduler variables + my $max_ndb= $opt_parallel / 2; + $max_ndb = 4 if $max_ndb > 4; + $max_ndb = 1 if $max_ndb < 1; + my $num_ndb_tests= 0; + + my $completed= []; + my %running; + my $result; + + my $s= IO::Select->new(); + $s->add($server); + while (1) { + my @ready = $s->can_read(1); # Wake up once every second + foreach my $sock (@ready) { + if ($sock == $server) { + # New client connected + my $child= $sock->accept(); + mtr_verbose("Client connected"); + $s->add($child); + print $child "HELLO\n"; + } + else { + my $line= <$sock>; + if (!defined $line) { + # Client disconnected + mtr_verbose("Child closed socket"); + $s->remove($sock); + if (--$childs == 0){ + return $completed; + } + next; + } + chomp($line); + + if ($line eq 'TESTRESULT'){ + $result= My::Test::read_test($sock); + # $result->print_test(); + + # Report test status + mtr_report_test($result); + + if ($result->is_failed() and !$opt_force){ + # Test has failed, force is off + push(@$completed, $result); + return $completed; + } + + # Retry test run after test failure + my $retries= $result->{retries} || 1; + my $test_has_failed= $result->{failures} || 0; + if ($test_has_failed and $retries < $opt_retry){ + # Test should be run one more time unless it has failed + # too many times already + my $failures= $result->{failures}; + if ($opt_retry > 1 and $failures >= $opt_retry_failure){ + mtr_report("Test has failed $failures times,", + "no more retries!\n"); + } + else { + mtr_report("\nRetrying test, attempt($retries/$opt_retry)...\n"); + $result->{retries}= $retries+1; + $result->write_test($sock, 'TESTCASE'); + next; + } + } + + # Repeat test $opt_repeat number of times + my $repeat= $result->{repeat} || 1; + if ($repeat < $opt_repeat) + { + $result->{retries}= 0; + $result->{failures}= 0; + + $result->{repeat}= $repeat+1; + $result->write_test($sock, 'TESTCASE'); + next; + } + + # Remove from list of running + mtr_error("'", $result->{name},"' is not known to be running") + unless delete $running{$result->key()}; + + # Update scheduler variables + $num_ndb_tests-- if ($result->{ndb_test}); + + # Save result in completed list + push(@$completed, $result); + + } + elsif ($line eq 'START'){ + ; # Send first test + } + else { + mtr_error("Unknown response: '$line' from client"); + } + + # Find next test to schedule + # - Try to use same configuration as worker used last time + # - Limit number of parallel ndb tests + + my $next; + my $second_best; + for(my $i= 0; $i <= $#$tests; $i++) + { + my $t= $tests->[$i]; + + if (run_testcase_check_skip_test($t)){ + # Move the test to completed list + #mtr_report("skip - Moving test $i to completed"); + push(@$completed, splice(@$tests, $i, 1)); + redo; # Start over again + } + + # Limit number of parallell NDB tests + if ($t->{ndb_test} and $num_ndb_tests >= $max_ndb){ + #mtr_report("Skipping, num ndb is already at max, $num_ndb_tests"); + next; + } + + # Prefer same configuration + if (defined $result and + $result->{template_path} eq $t->{template_path}) + { + #mtr_report("Test uses same config => good match"); + # Test uses same config => good match + $next= splice(@$tests, $i, 1); + last; + } + + # Second best choice is the first that does not fulfill + # any of the above conditions + if (!defined $second_best){ + #mtr_report("Setting second_best to $i"); + $second_best= $i; + } + } + + # Use second best choice if no other test has been found + if (!$next and defined $second_best){ + #mtr_report("Take second best choice $second_best"); + mtr_error("Internal error, second best too large") + if $second_best > $#$tests; + $next= splice(@$tests, $second_best, 1); + } + + if ($next) { + #$next->print_test(); + $next->write_test($sock, 'TESTCASE'); + $running{$next->key()}= $next; + $num_ndb_tests++ if ($next->{ndb_test}); + } + else { + # No more test, tell child to exit + #mtr_report("Saying BYE to child"); + print $sock "BYE\n"; + } + } + } + } +} + + +sub run_worker ($) { + my ($server_port, $thread_num)= @_; + + $SIG{INT}= sub { exit(1); }; + + report_option('name',"worker[$thread_num]"); + + # Connect to server + my $server = new IO::Socket::INET + ( + PeerAddr => 'localhost', + PeerPort => $server_port, + Proto => 'tcp' + ); + mtr_error("Could not connect to server at port $server_port: $!") + unless $server; + + # Read hello from server which it will send when shared + # resources have been setup + my $hello= <$server>; + + command_line_setup($thread_num); + + if ( $opt_gcov ) + { + gcov_prepare(); + } + + setup_vardir(); + mysql_install_db($thread_num); + if ( using_extern() ) { create_config_file_for_extern(%opts_extern); } - run_tests($tests); + # Ask server for first test + print $server "START\n"; - exit(0); + while(my $line= <$server>){ + chomp($line); + if ($line eq 'TESTCASE'){ + my $test= My::Test::read_test($server); + #$test->print_test(); + run_testcase($test); + #$test->{result}= 'MTR_RES_PASSED'; + # Send it back, now with results set + #$test->print_test(); + $test->write_test($server, 'TESTRESULT'); + } + elsif ($line eq 'BYE'){ + mtr_report("Server said BYE"); + exit(0); + } + else { + mtr_error("Could not understand server, '$line'"); + } + } + + stop_all_servers(); + + if ( $opt_gcov ) + { + gcov_collect(); # collect coverage information + } + + if ( $opt_gcov ) + { + gcov_collect(); # collect coverage information + } + + exit(1); } @@ -278,14 +564,11 @@ sub ignore_option { } sub command_line_setup { + my ($thread_num)= @_; + my $opt_comment; my $opt_usage; - # This is needed for test log evaluation in "gen-build-status-page" - # in all cases where the calling tool does not log the commands - # directly before it executes them, like "make test-force-pl" in RPM builds. - mtr_report("Logging: $0 ", join(" ", @ARGV)); - # Read the command line options # Note: Keep list, and the order, in sync with usage at end of this file Getopt::Long::Configure("pass_through"); @@ -325,7 +608,7 @@ sub command_line_setup { 'skip-im' => \&ignore_option, # Specify ports - 'build-thread|mtr-build-thread=i' => \$opt_mtr_build_thread, + 'build-thread|mtr-build-thread=i' => \$opt_build_thread, # Test case authoring 'record' => \$opt_record, @@ -383,10 +666,10 @@ sub command_line_setup { 'report-features' => \$opt_report_features, 'comment=s' => \$opt_comment, 'fast' => \$opt_fast, - 'reorder' => \&collect_option, + 'reorder!' => \&collect_option, 'enable-disabled' => \&collect_option, 'verbose+' => \$opt_verbose, - 'verbose-restart' => \$opt_verbose_restart, + 'verbose-restart' => \&report_option, 'sleep=i' => \$opt_sleep, 'start-dirty' => \$opt_start_dirty, 'start' => \$opt_start, @@ -408,9 +691,21 @@ sub command_line_setup { usage("") if $opt_usage; # -------------------------------------------------------------------------- - # Check mtr_build_thread and calculate baseport + # Setup verbosity # -------------------------------------------------------------------------- - set_mtr_build_thread_ports($opt_mtr_build_thread); + if ($thread_num == 0){ + # The server should by default have verbose on + report_option('verbose', $opt_verbose ? $opt_verbose : 0); + } else { + # Worker should by default have verbose off + report_option('verbose', $opt_verbose ? $opt_verbose : undef); + } + + # -------------------------------------------------------------------------- + # Check build_thread and calculate baseport + # Use auto build thread in all but first worker + # -------------------------------------------------------------------------- + set_build_thread_ports($thread_num > 1 ? 'auto' : $opt_build_thread); if ( -d "../sql" ) { @@ -537,8 +832,9 @@ sub command_line_setup { # -------------------------------------------------------------------------- # Check if we should speed up tests by trying to run on tmpfs + # - Dont check in workers # -------------------------------------------------------------------------- - if ( defined $opt_mem ) + if ( defined $opt_mem and $thread_num == 0) { mtr_error("Can't use --mem and --vardir at the same time ") if $opt_vardir; @@ -554,7 +850,7 @@ sub command_line_setup { { if ( -d $fs ) { - my $template= "var_${opt_mtr_build_thread}_XXXX"; + my $template= "var_${opt_build_thread}_XXXX"; $opt_mem= tempdir( $template, DIR => $fs, CLEANUP => 0); last; } @@ -569,21 +865,11 @@ sub command_line_setup { { $opt_vardir= $default_vardir; } - elsif ( $mysql_version_id < 50000 and - $opt_vardir ne $default_vardir) - { - # Version 4.1 and --vardir was specified - # Only supported as a symlink from var/ - # by setting up $opt_mem that symlink will be created - if ( ! IS_WINDOWS ) - { - # Only platforms that have native symlinks can use the vardir trick - $opt_mem= $opt_vardir; - mtr_report("Using 4.1 vardir trick"); - } - $opt_vardir= $default_vardir; - } + # If more than one parallel run, use a subdir of the selected var + if ($thread_num && $opt_parallel > 1) { + $opt_vardir.= "/".$thread_num; + } $path_vardir_trace= $opt_vardir; # Chop off any "c:", DBUG likes a unix path ex: c:/src/... => /src/... @@ -738,6 +1024,16 @@ sub command_line_setup { $path_testlog= "$opt_vardir/log/mysqltest.log"; $path_current_testlog= "$opt_vardir/log/current_test"; + mtr_report("Checking supported features..."); + + check_ndbcluster_support(\%mysqld_variables); + check_ssl_support(\%mysqld_variables); + check_debug_support(\%mysqld_variables); + + executable_setup(); + + environment_setup(); + } @@ -756,19 +1052,20 @@ sub command_line_setup { # http://www.ncftp.com/ncftpd/doc/misc/ephemeral_ports.html # But a fairly safe range seems to be 5001 - 32767 # -sub set_mtr_build_thread_ports($) { - my $mtr_build_thread= shift || 0; +sub set_build_thread_ports($) { + my $build_thread= shift || 0; - if ( lc($mtr_build_thread) eq 'auto' ) { - print "Requesting build thread... "; - $mtr_build_thread= + if ( lc($build_thread) eq 'auto' ) { + mtr_report("Requesting build thread... "); + $build_thread= mtr_require_unique_id_and_wait("/tmp/mysql-test-ports", 200, 299); - print "got ".$mtr_build_thread."\n"; + mtr_report(" - got $build_thread"); } - $ENV{MTR_BUILD_THREAD}= $mtr_build_thread; + $ENV{MTR_BUILD_THREAD}= $build_thread; + $opt_build_thread= $build_thread; # Calculate baseport - $opt_baseport= $mtr_build_thread * 10 + 10000; + $opt_baseport= $build_thread * 10 + 10000; if ( $opt_baseport < 5001 or $opt_baseport + 9 >= 32767 ) { mtr_error("MTR_BUILD_THREAD number results in a port", @@ -776,7 +1073,7 @@ sub set_mtr_build_thread_ports($) { "($opt_baseport - $opt_baseport + 9)"); } - mtr_report("Using MR_BUILD_THREAD $mtr_build_thread,", + mtr_report("Using MTR_BUILD_THREAD $build_thread,", "with reserved ports $opt_baseport..".($opt_baseport+9)); } @@ -1383,7 +1680,7 @@ sub remove_stale_vardir () { # Create var and the directories needed in var # sub setup_vardir() { - mtr_report("Creating var directory..."); + mtr_report("Creating var directory '$opt_vardir'..."); if ( $opt_vardir eq $default_vardir ) { @@ -1773,76 +2070,6 @@ sub ndbcluster_start ($) { } -# -# Run the collected tests -# -my $suite_timeout_proc; -sub run_tests { - my ($tests)= @_; - - mtr_report(); - mtr_print_thick_line(); - mtr_print_header(); - - $suite_timeout_proc= My::SafeProcess->timer($opt_suite_timeout* 60); - foreach my $tinfo ( @$tests ) - { - if (run_testcase_check_skip_test($tinfo)) - { - next; - } - - for my $repeat (1..$opt_repeat){ - - if (run_testcase($tinfo)) - { - # Testcase failed, enter retry mode - my $retries= 1; - while ($retries < $opt_retry){ - mtr_report("\nRetrying, attempt($retries/$opt_retry)...\n"); - - if (run_testcase($tinfo) <= 0) - { - # Testcase suceeded - - my $test_has_failed= $tinfo->{failures} || 0; - if (!$test_has_failed){ - last; - } - } - else - { - # Testcase failed - - # Limit number of test failures - my $failures= $tinfo->{failures}; - if ($opt_retry > 1 and $failures >= $opt_retry_failure){ - mtr_report("Test has failed $failures times, no more retries!\n"); - last; - } - } - $retries++; - } - } - } - } - # Kill the test suite timer - $suite_timeout_proc->kill(); - - mtr_print_line(); - - stop_all_servers(); - - if ( $opt_gcov ) - { - gcov_collect(); # collect coverage information - } - - mtr_report_stats($tests); - -} - - sub create_config_file_for_extern { my %opts= ( @@ -1990,7 +2217,7 @@ sub initialize_servers { remove_stale_vardir(); setup_vardir(); - mysql_install_db(); + mysql_install_db(0); } } check_running_as_root(); @@ -2044,6 +2271,7 @@ sub sql_to_bootstrap { sub mysql_install_db { + my ($thread_num)= @_; my $data_dir= "$opt_vardir/install.db"; mtr_report("Installing system database..."); @@ -2080,6 +2308,8 @@ sub mysql_install_db { # ---------------------------------------------------------------------- $ENV{'MYSQLD_BOOTSTRAP_CMD'}= "$exe_mysqld_bootstrap " . join(" ", @$args); + return if $thread_num > 0; # Only generate MYSQLD_BOOTSTRAP_CMD in workers + # ---------------------------------------------------------------------- # Create the bootstrap.sql file # ---------------------------------------------------------------------- @@ -2387,7 +2617,7 @@ sub run_testcase ($) { # ---------------------------------------------------------------------- if ( $opt_start or $opt_start_dirty ) { - $suite_timeout_proc->kill(); +# MASV $suite_timeout_proc->kill(); mtr_report("\nStarted", started(all_servers())); mtr_report("Waiting for server(s) to exit..."); my $proc= My::SafeProcess->wait_any(); @@ -2538,11 +2768,12 @@ sub run_testcase ($) { # ---------------------------------------------------- # Check if test suite timer expired # ---------------------------------------------------- - if ( $proc eq $suite_timeout_proc ) - { - mtr_report("Test suite timeout! Terminating..."); - exit(1); - } +# MASV +# if ( $proc eq $suite_timeout_proc ) +# { +# mtr_report("Test suite timeout! Terminating..."); +# exit(1); +# } mtr_error("Unhandled process $proc exited"); } @@ -2617,7 +2848,7 @@ sub check_warnings ($) { my $res= 0; # Clear previous warnings - $tinfo->{warnings}= undef; + delete($tinfo->{warnings}); # Parallell( mysqlds(), run_check_warning, check_warning_failed); foreach my $mysqld ( mysqlds() ) @@ -2727,18 +2958,14 @@ sub report_failure_and_restart ($) { my $tinfo= shift; mtr_report_test_failed($tinfo, $path_current_testlog); - print "\n"; - if ( $opt_force ) - { - # Stop all servers that are known to be running - stop_all_servers(); - after_test_failure($tinfo->{'name'}); - mtr_report("Resuming tests...\n"); - return; - } - mtr_error("Test '$tinfo->{'name'}' failed.", - "To continue, re-run with '--force'"); + # Stop all servers that are known to be running + stop_all_servers(); + + # Collect and clean files + after_test_failure($tinfo->{'name'}); + + mtr_report("Resuming tests...\n"); } @@ -3118,12 +3345,32 @@ sub started { return grep(defined $_, map($_->{proc}, @_)); } sub stopped { return grep(!defined $_, map($_->{proc}, @_)); } +sub envsubst { + my $string= shift; + + if ( ! defined $ENV{$string} ) + { + mtr_error(".opt file references '$string' which is not set"); + } + + return $ENV{$string}; +} + + sub get_extra_opts { my ($mysqld, $tinfo)= @_; - return + my $opts= $mysqld->option("#!use-slave-opt") ? $tinfo->{slave_opt} : $tinfo->{master_opt}; + + # Expand environment variables + foreach my $opt ( @$opts ) + { + $opt =~ s/\$\{(\w+)\}/envsubst($1)/ge; + $opt =~ s/\$(\w+)/envsubst($1)/ge; + } + return $opts; } @@ -3227,7 +3474,12 @@ sub start_servers($) { } # Copy datadir from installed system db - copytree("$opt_vardir/install.db", $datadir) + for my $path ( "$opt_vardir", "$opt_vardir/..") { + my $install_db= "$path/install.db"; + copytree($install_db, $datadir) + if -d $install_db; + } + mtr_error("Failed to copy system db to '$datadir'") unless -d $datadir; # Write start of testcase to log file @@ -3332,16 +3584,20 @@ sub run_check_testcase ($$$) { if ( $mode eq "after" and $res == 1 ) { - mtr_report("\nThe check of testcase '$tname' failed, this is the\n", - "diff between before and after:\n"); - # Test failed, display the report mysqltest has created - mtr_printfile($errfile); + # Test failed, grab the report mysqltest has created + my $report= mtr_grab_file($errfile); + $tinfo->{check}.= + "\nThe check of testcase '$tname' failed, this is the\n". + "diff between before and after:\n"; + $tinfo->{check}.= $report; + } elsif ( $res ) { - mtr_report("\nCould not execute 'check-testcase' $mode testcase '$tname':"); - mtr_printfile($errfile); - mtr_report(); + my $report= mtr_grab_file($errfile); + $tinfo->{'check'}.= + "\nCould not execute 'check-testcase' $mode testcase '$tname':\n"; + $tinfo->{check}.= $report; } return $res; } -- cgit v1.2.1