diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:00:34 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:00:34 +0000 |
commit | 3f619478f796eddbba6e39502fe941b285dd97b1 (patch) | |
tree | e2c7b5777f728320e5b5542b6213fd3591ba51e2 /mysql-test/mariadb-test-run.pl | |
parent | Initial commit. (diff) | |
download | mariadb-3f619478f796eddbba6e39502fe941b285dd97b1.tar.xz mariadb-3f619478f796eddbba6e39502fe941b285dd97b1.zip |
Adding upstream version 1:10.11.6.upstream/1%10.11.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mysql-test/mariadb-test-run.pl')
-rwxr-xr-x | mysql-test/mariadb-test-run.pl | 6051 |
1 files changed, 6051 insertions, 0 deletions
diff --git a/mysql-test/mariadb-test-run.pl b/mysql-test/mariadb-test-run.pl new file mode 100755 index 00000000..1e868efa --- /dev/null +++ b/mysql-test/mariadb-test-run.pl @@ -0,0 +1,6051 @@ +#!/usr/bin/env perl +# -*- cperl -*- + +# Copyright (c) 2004, 2014, Oracle and/or its affiliates. +# Copyright (c) 2009, 2022, MariaDB Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA + +# +############################################################################## +# +# mysql-test-run.pl +# +# Tool used for executing a suite of .test files +# +# See the "MySQL Test framework manual" for more information +# https://mariadb.com/kb/en/library/mysqltest/ +# +# +############################################################################## + +use strict; +use warnings; + +BEGIN { + # Check that mysql-test-run.pl is started from mysql-test/ + unless ( -f "mariadb-test-run.pl" ) + { + print "**** ERROR **** ", + "You must start mysql-test-run from the mysql-test/ directory\n"; + exit(1); + } + # Check that lib exist + unless ( -d "lib/" ) + { + print "**** ERROR **** ", + "Could not find the lib/ directory \n"; + exit(1); + } + + # Check backward compatibility support + # By setting the environment variable MTR_VERSION + # it's possible to use a previous version of + # mysql-test-run.pl + my $version= $ENV{MTR_VERSION} || 2; + if ( $version == 1 ) + { + print "=======================================================\n"; + print " WARNING: Using mariadb-test-run.pl version 1! \n"; + print "=======================================================\n"; + # Should use exec() here on *nix but this appears not to work on Windows + exit(system($^X, "lib/v1/mariadb-test-run.pl", @ARGV) >> 8); + } + elsif ( $version == 2 ) + { + # This is the current version, just continue + ; + } + else + { + print "ERROR: Version $version of mariadb-test-run does not exist!\n"; + exit(1); + } +} + +use lib "lib"; + +use Cwd ; +use POSIX ":sys_wait_h"; +use Getopt::Long qw(:config bundling); +use My::File::Path; # Patched version of File::Path +use File::Basename; +use File::Copy; +use File::Find; +use File::Temp qw/tempdir/; +use File::Spec::Functions qw/splitdir rel2abs/; +use My::Platform; +use My::SafeProcess; +use My::ConfigFactory; +use My::Options; +use My::Tee; +use My::Find; +use My::SysInfo; +use My::CoreDump; +use My::Debugger; +use mtr_cases; +use mtr_report; +use mtr_match; +use mtr_unique; +use mtr_results; +use IO::Socket::INET; +use IO::Select; +use Time::HiRes qw(gettimeofday); + +sub realpath($) { (IS_WINDOWS) ? $_[0] : Cwd::realpath($_[0]) } + +require "mtr_process.pl"; +require "mtr_io.pl"; +require "mtr_gprof.pl"; +require "mtr_misc.pl"; + +my $opt_valgrind; +my $valgrind_reports= 0; + +$SIG{INT}= sub { mtr_error("Got ^C signal"); }; +$SIG{HUP}= sub { mtr_error("Hangup detected on controlling terminal"); }; + +our $mysql_version_id; +my $mysql_version_extra; +our $glob_mysql_test_dir; +our $basedir; +our $bindir; + +our $path_charsetsdir; +our $path_client_bindir; +our $path_client_libdir; +our $path_language; + +our $path_current_testlog; +our $path_testlog; + +our $default_vardir; +our $opt_vardir; # Path to use for var/ dir +our $plugindir; +our $opt_xml_report; # XML output +our $client_plugindir; +my $path_vardir_trace; # unix formatted opt_vardir for trace files +my $opt_tmpdir; # Path to use for tmp/ dir +my $opt_tmpdir_pid; + +my $opt_start; +my $opt_start_dirty; +my $opt_start_exit; +my $start_only; +my $file_wsrep_provider; +my $num_saved_cores= 0; # Number of core files saved in vardir/log/ so far. + +our @global_suppressions; + +END { + if ( defined $opt_tmpdir_pid and $opt_tmpdir_pid == $$ ) + { + if (!$opt_start_exit) + { + # Remove the tempdir this process has created + mtr_verbose("Removing tmpdir $opt_tmpdir"); + rmtree($opt_tmpdir); + } + else + { + mtr_warning("tmpdir $opt_tmpdir should be removed after the server has finished"); + } + } +} + +sub env_or_val($$) { defined $ENV{$_[0]} ? $ENV{$_[0]} : $_[1] } + +my $path_config_file; # The generated config file, var/my.cnf + +# Visual Studio produces executables in different sub-directories based on the +# configuration used to build them. To make life easier, an environment +# variable or command-line option may be specified to control which set of +# executables will be used by the test suite. +our $multiconfig = $ENV{'MTR_VS_CONFIG'}; + +my @DEFAULT_SUITES= qw( + main- + archive- + atomic- + binlog- + binlog_encryption- + client- + csv- + compat/oracle- + compat/mssql- + compat/maxdb- + encryption- + federated- + funcs_1- + funcs_2- + gcol- + handler- + heap- + innodb- + innodb_fts- + innodb_gis- + innodb_i_s- + innodb_zip- + json- + maria- + mariabackup- + multi_source- + optimizer_unfixed_bugs- + parts- + perfschema- + plugins- + roles- + rpl- + stress- + sys_vars- + sql_sequence- + unit- + vcol- + versioning- + period- + sysschema- + ); +my $opt_suites; + +our $opt_verbose= 0; # Verbose output, enable with --verbose +our $exe_patch; +our $exe_mysql; +our $exe_mysql_plugin; +our $exe_mysqladmin; +our $exe_mysqltest; +our $exe_libtool; +our $exe_mysql_embedded; +our $exe_mariadb_conv; + +our $opt_big_test= 0; +our $opt_staging_run= 0; + +our @opt_combinations; + +our @opt_extra_mysqld_opt; +our @opt_mysqld_envs; + +my $opt_stress; +my $opt_tail_lines= 20; + +my $opt_dry_run; + +my $opt_compress; +my $opt_ssl; +my $opt_skip_ssl; +my @opt_skip_test_list; +our $opt_ssl_supported; +our $opt_ps_protocol; +my $opt_sp_protocol; +my $opt_cursor_protocol; +my $opt_view_protocol; +my $opt_non_blocking_api; + +our $opt_debug; +my $debug_d= "d,*"; +my $opt_debug_common; +our $opt_debug_server; +our @opt_cases; # The test cases names in argv +our $opt_embedded_server; + +# Options used when connecting to an already running server +my %opts_extern; +sub using_extern { return (keys %opts_extern > 0);}; + +our $opt_fast= 0; +our $opt_force= 0; +our $opt_skip_not_found= 0; +our $opt_mem= $ENV{'MTR_MEM'}; +our $opt_clean_vardir= $ENV{'MTR_CLEAN_VARDIR'}; + +our $opt_gcov; +our $opt_gprof; +our %gprof_dirs; + +my $config; # The currently running config +my $current_config_name; # The currently running config file template + +our @opt_experimentals; +our $experimental_test_cases= []; + +our $baseport; +# $opt_build_thread may later be set from $opt_port_base +my $opt_build_thread= $ENV{'MTR_BUILD_THREAD'} || "auto"; +my $opt_port_base= $ENV{'MTR_PORT_BASE'} || "auto"; +my $build_thread= 0; + +my $opt_record; + +our $opt_resfile= $ENV{'MTR_RESULT_FILE'} || 0; + +my $opt_skip_core; + +our $opt_check_testcases= 1; +my $opt_mark_progress; +my $opt_max_connections; +our $opt_report_times= 0; + +my $opt_sleep; + +our $opt_retry= 1; +our $opt_retry_failure= env_or_val(MTR_RETRY_FAILURE => 2); +our $opt_testcase_timeout= $ENV{MTR_TESTCASE_TIMEOUT} || 15; # minutes +our $opt_suite_timeout = $ENV{MTR_SUITE_TIMEOUT} || 360; # minutes +our $opt_shutdown_timeout= $ENV{MTR_SHUTDOWN_TIMEOUT} || 10; # seconds +our $opt_start_timeout = $ENV{MTR_START_TIMEOUT} || 180; # seconds + +sub suite_timeout { return $opt_suite_timeout * 60; }; + +my $opt_wait_all; +my $opt_user_args; +my $opt_repeat= 1; +my $opt_reorder= 1; +my $opt_force_restart= 0; + +our $opt_user = "root"; + +my %mysqld_logs; +my $opt_debug_sync_timeout= 300; # Default timeout for WAIT_FOR actions. +my $warn_seconds = 60; + +my $rebootstrap_re= '--innodb[-_](?:page[-_]size|checksum[-_]algorithm|undo[-_]tablespaces|log[-_]group[-_]home[-_]dir|data[-_]home[-_]dir)|data[-_]file[-_]path|force_rebootstrap'; + +sub testcase_timeout ($) { return $opt_testcase_timeout * 60; } +sub check_timeout ($) { return testcase_timeout($_[0]); } + +our $opt_warnings= 1; + +our %mysqld_variables; +our @optional_plugins; + +my $source_dist= -d "../sql"; + +my $opt_max_save_core= env_or_val(MTR_MAX_SAVE_CORE => 5); +my $opt_max_save_datadir= env_or_val(MTR_MAX_SAVE_DATADIR => 20); +my $opt_max_test_fail= env_or_val(MTR_MAX_TEST_FAIL => 10); +my $opt_core_on_failure= 0; + +my $opt_parallel= $ENV{MTR_PARALLEL} || 1; +my $opt_port_group_size = $ENV{MTR_PORT_GROUP_SIZE} || 20; + +# lock file to stop tests +my $opt_stop_file= $ENV{MTR_STOP_FILE}; +# print messages when test suite is stopped (for buildbot) +my $opt_stop_keep_alive= $ENV{MTR_STOP_KEEP_ALIVE}; + +select(STDOUT); +$| = 1; # Automatically flush STDOUT + +main(); + +sub main { + $ENV{MTR_PERL}= mixed_path($^X); + + # Default, verbosity on + report_option('verbose', 0); + + # 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)); + + command_line_setup(); + + # --help will not reach here, so now it's safe to assume we have binaries + My::SafeProcess::find_bin(); + + print "vardir: $opt_vardir\n"; + initialize_servers(); + init_timers(); + + unless (IS_WINDOWS) { + binmode(STDOUT,":via(My::Tee)") or die "binmode(STDOUT, :via(My::Tee)):$!"; + binmode(STDERR,":via(My::Tee)") or die "binmode(STDERR, :via(My::Tee)):$!"; + } + + mtr_report("Checking supported features..."); + + executable_setup(); + + # --debug[-common] implies we run debug server + $opt_debug_server= 1 if $opt_debug || $opt_debug_common; + + if (using_extern()) + { + # Connect to the running mysqld and find out what it supports + collect_mysqld_features_from_running_server(); + } + else + { + # Run the mysqld to find out what features are available + collect_mysqld_features(); + } + check_ssl_support(); + check_debug_support(); + environment_setup(); + + if (!$opt_suites) { + $opt_suites= join ',', collect_default_suites(@DEFAULT_SUITES); + } + mtr_report("Using suites: $opt_suites") unless @opt_cases; + + mtr_report("Collecting tests..."); + my $tests= collect_test_cases($opt_reorder, $opt_suites, \@opt_cases, \@opt_skip_test_list); + if (@$tests == 0) { + mtr_report("No tests to run..."); + exit 0; + } + + mark_time_used('collect'); + + if (!using_extern()) + { + mysql_install_db(default_mysqld(), "$opt_vardir/install.db"); + make_readonly("$opt_vardir/install.db"); + } + if ($opt_dry_run) + { + for (@$tests) { + print $_->fullname(), "\n"; + } + exit 0; + } + + if ($opt_gcov) { + system './dgcov.pl --purge'; + } + + ####################################################################### + my $num_tests= $mtr_report::tests_total= @$tests; + if ( $opt_parallel eq "auto" ) { + # Try to find a suitable value for number of workers + if (IS_WINDOWS) + { + $opt_parallel= $ENV{NUMBER_OF_PROCESSORS} || 1; + } + else + { + my $sys_info= My::SysInfo->new(); + $opt_parallel= $sys_info->num_cpus()+int($sys_info->min_bogomips()/500)-4; + } + my $max_par= $ENV{MTR_MAX_PARALLEL} || 8; + $opt_parallel= $max_par if ($opt_parallel > $max_par); + $opt_parallel= $num_tests if ($opt_parallel > $num_tests); + $opt_parallel= 1 if ($opt_parallel < 1); + mtr_report("Using parallel: $opt_parallel"); + } + $ENV{MTR_PARALLEL} = $opt_parallel; + + if ($opt_parallel > 1 && ($opt_start_exit || $opt_stress)) { + mtr_warning("Parallel cannot be used with --start-and-exit or --stress\n" . + "Setting parallel to 1"); + $opt_parallel= 1; + } + + # 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(); + + if ($opt_resfile) { + resfile_init("$opt_vardir/mtr-results.txt"); + print_global_resfile(); + } + + # 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 + $tests= {}; # Don't need the tests list in child + + # Use subdir of var and tmp unless only one worker + if ($opt_parallel > 1) { + set_vardir("$opt_vardir/$child_num"); + $opt_tmpdir= "$opt_tmpdir/$child_num"; + } + + init_timers(); + run_worker($server_port, $child_num); + exit(1); + } + + $children{$child_pid}= 1; + } + ####################################################################### + + mtr_report(); + mtr_print_thick_line(); + mtr_print_header($opt_parallel > 1); + + mark_time_used('init'); + + my ($prefix, $fail, $completed, $extra_warnings)= + Manager::run($server, $tests, \%children); + + exit(0) if $opt_start_exit; + + # Send Ctrl-C to any children still running + kill("INT", keys(%children)); + + if (!IS_WINDOWS) { + # Wait for children to exit + foreach my $pid (keys %children) + { + my $ret_pid= waitpid($pid, 0); + if ($ret_pid == -1) { + # Child was automatically reaped. Probably not possible + # unless you $SIG{CHLD}= 'IGNORE' + mtr_warning("Child ${pid} was automatically reaped (this should never happen)"); + } elsif ($ret_pid != $pid) { + confess("Unexpected PID ${ret_pid} instead of expected ${pid}"); + } + my $exit_status= ($? >> 8); + mtr_verbose2("Child ${pid} exited with status ${exit_status}"); + delete $children{$ret_pid}; + } + } + + if ( not @$completed ) { + my $test_name= mtr_grab_file($path_testlog); + $test_name =~ s/^CURRENT_TEST:\s//; + chomp($test_name); + my $tinfo = My::Test->new(name => $test_name); + $tinfo->{result}= 'MTR_RES_FAILED'; + $tinfo->{comment}=' '; + mtr_report_test($tinfo); + mtr_error("Test suite aborted"); + } + + if ( @$completed != $num_tests){ + + # Not all tests completed, failure + mtr_report(); + mtr_report("Only ", int(@$completed), " of $num_tests completed."); + } + + if ($opt_valgrind) { + # Create minimalistic "test" for the reporting + my $tinfo = My::Test->new + ( + suite => { name => 'valgrind', }, + name => 'valgrind_report', + ); + # Set dummy worker id to align report with normal tests + $tinfo->{worker} = 0 if $opt_parallel > 1; + if ($valgrind_reports) { + $tinfo->{result}= 'MTR_RES_FAILED'; + $tinfo->{comment}= "Valgrind reported failures at shutdown, see above"; + $tinfo->{failures}= 1; + } else { + $tinfo->{result}= 'MTR_RES_PASSED'; + } + mtr_report_test($tinfo); + push @$completed, $tinfo; + ++$num_tests + } + + mtr_print_line(); + + print_total_times($opt_parallel) if $opt_report_times; + mtr_report_stats($prefix, $fail, $completed, $extra_warnings); + + if ($opt_gcov) { + mtr_report("Running dgcov"); + system "./dgcov.pl --generate > $opt_vardir/last_changes.dgcov"; + } + + if ( @$completed != $num_tests) + { + mtr_error("Not all tests completed (only ". scalar(@$completed) . + " of $num_tests)"); + } + + remove_vardir_subs() if $opt_clean_vardir; + + exit(0); +} + + +package Manager; +use POSIX ":sys_wait_h"; +use File::Basename; +use File::Find; +use IO::Socket::INET; +use IO::Select; +use mtr_report; +use My::Platform; + +my $num_saved_datadir; # Number of datadirs saved in vardir/log/ so far. +my $num_failed_test; # Number of tests failed so far +my $test_failure; # Set true if test suite failed +my $extra_warnings; # Warnings found during server shutdowns + +my $completed; +my %running; +my $result; +my $exe_mysqld; # Used as hint to CoreDump +my %names; + +sub parse_protocol($$) { + my $sock= shift; + my $line= shift; + + if ($line eq 'TESTRESULT'){ + mtr_verbose2("Got TESTRESULT from ". $names{$sock}); + $result= My::Test::read_test($sock); + + # Report test status + mtr_report_test($result); + + if ( $result->is_failed() ) { + + # Save the workers "savedir" in var/log + my $worker_savedir= $result->{savedir}; + my $worker_savename= basename($worker_savedir); + my $savedir= "$opt_vardir/log/$worker_savename"; + + # Move any core files from e.g. mysqltest + foreach my $coref (glob("core*"), glob("*.dmp")) + { + mtr_report(" - found '$coref', moving it to '$worker_savedir'"); + ::move($coref, $worker_savedir); + } + + find( + { + no_chdir => 1, + wanted => sub + { + My::CoreDump::core_wanted(\$num_saved_cores, + $opt_max_save_core, + @opt_cases == 0, + $exe_mysqld, $opt_parallel); + } + }, + $worker_savedir); + + if ($num_saved_datadir >= $opt_max_save_datadir) + { + mtr_report(" - skipping '$worker_savedir/'"); + main::rmtree($worker_savedir); + } + else + { + mtr_report(" - saving '$worker_savedir/' to '$savedir/'"); + rename($worker_savedir, $savedir); + $num_saved_datadir++; + } + main::resfile_print_test(); + $num_failed_test++ unless ($result->{retries} || + $result->{exp_fail}); + + $test_failure= 1; + if ( !$opt_force ) { + # Test has failed, force is off + push(@$completed, $result); + if ($result->{'dont_kill_server'}) + { + mtr_verbose2("${line}: saying BYE to ". $names{$sock}); + print $sock "BYE\n"; + return 2; + } + return ["Failure", 1, $completed, $extra_warnings]; + } + elsif ($opt_max_test_fail > 0 and + $num_failed_test >= $opt_max_test_fail) { + push(@$completed, $result); + mtr_report("Too many tests($num_failed_test) failed!", + "Terminating..."); + return ["Too many failed", 1, $completed, $extra_warnings]; + } + } + + main::resfile_print_test(); + # Retry test run after test failure + my $retries= $result->{retries} || 2; + 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 $tname= $result->{name}; + my $failures= $result->{failures}; + if ($opt_retry > 1 and $failures >= $opt_retry_failure){ + mtr_report("\nTest $tname has failed $failures times,", + "no more retries!\n"); + } + else { + mtr_report("\nRetrying test $tname, ". + "attempt($retries/$opt_retry)...\n"); + #saving the log file as filename.failed in case of retry + if ( $result->is_failed() ) { + my $worker_logdir= $result->{savedir}; + my $log_file_name=dirname($worker_logdir)."/".$result->{shortname}.".log"; + + if (-e $log_file_name) { + $result->{'logfile-failed'} = ::mtr_lastlinesfromfile($log_file_name, 20); + } else { + $result->{'logfile-failed'} = ""; + } + + rename $log_file_name, $log_file_name.".failed"; + } + { + local @$result{'retries', 'result'}; + delete $result->{result}; + $result->{retries}= $retries+1; + $result->write_test($sock, 'TESTCASE'); + } + push(@$completed, $result); + return 2; + } + } + + # Repeat test $opt_repeat number of times + my $repeat= $result->{repeat} || 1; + if ($repeat < $opt_repeat) + { + $result->{retries}= 0; + $result->{rep_failures}++ if $result->{failures}; + $result->{failures}= 0; + delete($result->{result}); + $result->{repeat}= $repeat+1; + $result->write_test($sock, 'TESTCASE'); + return 2; + } + + # Remove from list of running + mtr_error("'", $result->{name},"' is not known to be running") + unless delete $running{$result->key()}; + + # Save result in completed list + push(@$completed, $result); + + } # if ($line eq 'TESTRESULT') + elsif ($line=~ /^START (.*)$/){ + # Send first test + $names{$sock}= $1; + } + elsif ($line eq 'WARNINGS'){ + my $fake_test= My::Test::read_test($sock); + my $test_list= join (" ", @{$fake_test->{testnames}}); + push @$extra_warnings, $test_list; + my $report= $fake_test->{'warnings'}; + mtr_report("***Warnings generated in error logs during shutdown ". + "after running tests: $test_list\n\n$report"); + $test_failure= 1; + if ( !$opt_force ) { + # Test failure due to warnings, force is off + mtr_verbose2("Socket loop exiting 3"); + return ["Warnings in log", 1, $completed, $extra_warnings]; + } + return 1; + } + elsif ($line =~ /^SPENT/) { + main::add_total_times($line); + } + elsif ($line eq 'VALGREP' && $opt_valgrind) { + $valgrind_reports= 1; + } + else { + mtr_error("Unknown response: '$line' from client"); + } + return 0; +} + +sub run ($$$) { + my ($server, $tests, $children) = @_; + my $suite_timeout= main::start_timer(main::suite_timeout()); + $exe_mysqld= main::find_mysqld($bindir) || ""; # Used as hint to CoreDump + $num_saved_datadir= 0; # Number of datadirs saved in vardir/log/ so far. + $num_failed_test= 0; # Number of tests failed so far + $test_failure= 0; # Set true if test suite failed + $extra_warnings= []; # Warnings found during server shutdowns + $completed= []; + + my $s= IO::Select->new(); + my $childs= 0; + + $s->add($server); + while (1) { + if ($opt_stop_file) + { + if (mtr_wait_lock_file($opt_stop_file, $opt_stop_keep_alive)) + { + # We were waiting so restart timer process + my $suite_timeout= main::start_timer(main::suite_timeout()); + } + } + + main::mark_time_used('admin'); + my @ready = $s->can_read(1); # Wake up once every second + if (@ready > 0) { + mtr_verbose2("Got ". (0 + @ready). " connection(s)"); + } + main::mark_time_idle(); + my $i= 0; + sock_loop: foreach my $sock (@ready) { + ++$i; + if ($sock == $server) { + # New client connected + ++$childs; + my $child= $sock->accept(); + mtr_verbose2("Connection ${i}: Worker connected (got ${childs} childs)"); + $s->add($child); + print $child "HELLO\n"; + } + else { + my $j= 0; + $sock->blocking(0); + while (my $line= <$sock>) { + ++$j; + chomp($line); + mtr_verbose2("Connection ${i}.${j}". (exists $names{$sock} ? " from $names{$sock}" : "") .": $line"); + + $sock->blocking(1); + my $res= parse_protocol($sock, $line); + $sock->blocking(0); + if (ref $res eq 'ARRAY') { + return @$res; + } elsif ($res == 1) { + next; + } elsif ($res == 2) { + next sock_loop; + } + if (IS_WINDOWS and !IS_CYGWIN) { + # Strawberry and ActiveState don't support blocking(0), the next iteration will be blocked! + # If there is next response now in the buffer and it is TESTRESULT we are affected by MDEV-30836 and the manager will hang. + last; + } + } + $sock->blocking(1); + if ($j == 0) { + # Client disconnected + --$childs; + mtr_verbose2((exists $names{$sock} ? $names{$sock} : "Worker"). " closed socket (left ${childs} childs)"); + $s->remove($sock); + $sock->close; + next; + } + + # Find next test to schedule + # - Try to use same configuration as worker used last time + + my $next; + my $second_best; + for(my $i= 0; $i <= @$tests; $i++) + { + my $t= $tests->[$i]; + + last unless defined $t; + + if (main::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)); + + # Since the test at pos $i was taken away, next + # test will also be at $i -> redo + redo; + } + + # From secondary choices, we prefer to pick a 'long-running' test if + # possible; this helps avoid getting stuck with a few of those at the + # end of high --parallel runs, with most workers being idle. + if (!defined $second_best || + ($t->{'long_test'} && !($tests->[$second_best]{'long_test'}))){ + #mtr_report("Setting second_best to $i"); + $second_best= $i; + } + + # Smart allocation of next test within this thread. + + if ($opt_reorder and $opt_parallel > 1 and defined $result) + { + my $wid= $result->{worker}; + # Reserved for other thread, try next + next if (defined $t->{reserved} and $t->{reserved} != $wid); + if (! defined $t->{reserved}) + { + # Force-restart not relevant when comparing *next* test + $t->{criteria} =~ s/force-restart$/no-restart/; + my $criteria= $t->{criteria}; + # Reserve similar tests for this worker, but not too many + my $maxres= (@$tests - $i) / $opt_parallel + 1; + for (my $j= $i+1; $j <= $i + $maxres; $j++) + { + my $tt= $tests->[$j]; + last unless defined $tt; + last if $tt->{criteria} ne $criteria; + $tt->{reserved}= $wid; + } + } + } + + # At this point we have found next suitable test + $next= splice(@$tests, $i, 1); + last; + } # for(my $i= 0; $i <= @$tests; $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($second_best)") + if $second_best > $#$tests; + $next= splice(@$tests, $second_best, 1); + delete $next->{reserved}; + } + + if ($next) { + # We don't need this any more + delete $next->{criteria}; + $next->write_test($sock, 'TESTCASE'); + $running{$next->key()}= $next; + } + else { + # No more test, tell child to exit + mtr_verbose2("Saying BYE to ". $names{$sock}); + print $sock "BYE\n"; + } # else (!$next) + } # else ($sock != $server) + } # foreach my $sock (@ready) + + if (!IS_WINDOWS) { + foreach my $pid (keys %$children) + { + my $res= waitpid($pid, WNOHANG); + if ($res == $pid || $res == -1) { + if ($res == -1) { + # Child was automatically reaped. Probably not possible + # unless you $SIG{CHLD}= 'IGNORE' + mtr_warning("Child ${pid} was automatically reaped (this should never happen)"); + } + my $exit_status= ($? >> 8); + mtr_verbose2("Child ${pid} exited with status ${exit_status}"); + delete $children->{$pid}; + if (!%$children && $childs) { + mtr_verbose2("${childs} children didn't close socket before dying!"); + $childs= 0; + } + } elsif ($res != 0) { + confess("Unexpected result ${res} on waitpid(${pid}, WNOHANG)"); + } + } + } + + if ($childs == 0){ + return ("Completed", $test_failure, $completed, $extra_warnings); + } + + # ---------------------------------------------------- + # Check if test suite timer expired + # ---------------------------------------------------- + if ( main::has_expired($suite_timeout) ) + { + mtr_report("Test suite timeout! Terminating..."); + return ("Timeout", 1, $completed, $extra_warnings); + } + } +} + +1; + +package main; + +sub run_worker ($) { + my ($server_port, $thread_num)= @_; + + $SIG{INT}= sub { exit(1); }; + $SIG{HUP}= sub { exit(1); }; + + # 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; + + # -------------------------------------------------------------------------- + # Set worker name + # -------------------------------------------------------------------------- + report_option('name',"worker[". sprintf("%02d", $thread_num). "]"); + my $proc_title= basename($0). " ${mtr_report::name} :". $server->sockport(). " -> :${server_port}"; + $0= $proc_title; + mtr_verbose2("Running at PID $$"); + + # -------------------------------------------------------------------------- + # Set different ports per thread + # -------------------------------------------------------------------------- + set_build_thread_ports($thread_num); + + # -------------------------------------------------------------------------- + # Turn off verbosity in workers, unless explicitly specified + # -------------------------------------------------------------------------- + report_option('verbose', undef) if ($opt_verbose == 0); + + environment_setup(); + + # Read hello from server which it will send when shared + # resources have been setup + my $hello= <$server>; + + setup_vardir(); + check_running_as_root(); + + if ( using_extern() ) { + create_config_file_for_extern(%opts_extern); + } + + # Ask server for first test + print $server "START ${mtr_report::name}\n"; + + mark_time_used('init'); + + while (my $line= <$server>){ + chomp($line); + if ($line eq 'TESTCASE'){ + my $test= My::Test::read_test($server); + $0= $proc_title. " ". $test->{name}; + + # Clear comment and logfile, to avoid + # reusing them from previous test + delete($test->{'comment'}); + delete($test->{'logfile'}); + + # A sanity check. Should this happen often we need to look at it. + if (defined $test->{reserved} && $test->{reserved} != $thread_num) { + my $tres= $test->{reserved}; + mtr_warning("Test reserved for w$tres picked up by w$thread_num"); + } + $test->{worker} = $thread_num if $opt_parallel > 1; + + run_testcase($test, $server); + #$test->{result}= 'MTR_RES_PASSED'; + # Send it back, now with results set + mtr_verbose2('Writing TESTRESULT'); + $test->write_test($server, 'TESTRESULT'); + mark_time_used('restart'); + } + elsif ($line eq 'BYE'){ + mtr_verbose2("Manager said BYE"); + # We need to gracefully shut down the servers to see any + # Valgrind memory leak errors etc. since last server restart. + if ($opt_warnings) { + stop_servers(reverse all_servers()); + if(check_warnings_post_shutdown($server)) { + # Warnings appeared in log file(s) during final server shutdown. + exit(1); + } + } + else { + stop_all_servers($opt_shutdown_timeout); + } + mark_time_used('restart'); + my $valgrind_reports= 0; + if ($opt_valgrind) { + $valgrind_reports= valgrind_exit_reports(); + print $server "VALGREP\n" if $valgrind_reports; + } + if ( $opt_gprof ) { + gprof_collect (find_mysqld($bindir), keys %gprof_dirs); + } + mark_time_used('admin'); + print_times_used($server, $thread_num); + exit($valgrind_reports); + } + else { + mtr_error("Could not understand server, '$line'"); + } + } + + stop_all_servers(); + + exit(1); +} + + +# Setup any paths that are $opt_vardir related +sub set_vardir { + ($opt_vardir)= @_; + + $path_vardir_trace= $opt_vardir; + # Chop off any "c:", DBUG likes a unix path ex: c:/src/... => /src/... + $path_vardir_trace=~ s/^\w://; + + # Location of my.cnf that all clients use + $path_config_file= "$opt_vardir/my.cnf"; + + $path_testlog= "$opt_vardir/log/mysqltest.log"; + $path_current_testlog= "$opt_vardir/log/current_test"; + +} + + +sub print_global_resfile { + resfile_global("start_time", isotime $^T); + resfile_global("user_id", $<); + resfile_global("embedded-server", $opt_embedded_server ? 1 : 0); + resfile_global("ps-protocol", $opt_ps_protocol ? 1 : 0); + resfile_global("sp-protocol", $opt_sp_protocol ? 1 : 0); + resfile_global("view-protocol", $opt_view_protocol ? 1 : 0); + resfile_global("cursor-protocol", $opt_cursor_protocol ? 1 : 0); + resfile_global("ssl", $opt_ssl ? 1 : 0); + resfile_global("compress", $opt_compress ? 1 : 0); + resfile_global("parallel", $opt_parallel); + resfile_global("check-testcases", $opt_check_testcases ? 1 : 0); + resfile_global("mariadbd", \@opt_extra_mysqld_opt); + resfile_global("debug", $opt_debug ? 1 : 0); + resfile_global("gcov", $opt_gcov ? 1 : 0); + resfile_global("gprof", $opt_gprof ? 1 : 0); + resfile_global("mem", $opt_mem); + resfile_global("tmpdir", $opt_tmpdir); + resfile_global("vardir", $opt_vardir); + resfile_global("fast", $opt_fast ? 1 : 0); + resfile_global("force-restart", $opt_force_restart ? 1 : 0); + resfile_global("reorder", $opt_reorder ? 1 : 0); + resfile_global("sleep", $opt_sleep); + resfile_global("repeat", $opt_repeat); + resfile_global("user", $opt_user); + resfile_global("testcase-timeout", $opt_testcase_timeout); + resfile_global("suite-timeout", $opt_suite_timeout); + resfile_global("shutdown-timeout", $opt_shutdown_timeout ? 1 : 0); + resfile_global("warnings", $opt_warnings ? 1 : 0); + resfile_global("max-connections", $opt_max_connections); + resfile_global("product", "MariaDB"); + resfile_global("xml-report", $opt_xml_report); + # Somewhat hacky code to convert numeric version back to dot notation + my $v1= int($mysql_version_id / 10000); + my $v2= int(($mysql_version_id % 10000)/100); + my $v3= $mysql_version_id % 100; + resfile_global("version", "$v1.$v2.$v3"); +} + + + +sub command_line_setup { + my $opt_comment; + my $opt_usage; + my $opt_list_options; + + # Read the command line options + # Note: Keep list in sync with usage at end of this file + Getopt::Long::Configure("pass_through"); + my %options=( + # Control what engine/variation to run + 'embedded-server' => \$opt_embedded_server, + 'ps-protocol' => \$opt_ps_protocol, + 'sp-protocol' => \$opt_sp_protocol, + 'view-protocol' => \$opt_view_protocol, + 'cursor-protocol' => \$opt_cursor_protocol, + 'non-blocking-api' => \$opt_non_blocking_api, + 'ssl|with-openssl' => \$opt_ssl, + 'skip-ssl' => \$opt_skip_ssl, + 'compress' => \$opt_compress, + 'vs-config=s' => \$multiconfig, + + # Max number of parallel threads to use + 'parallel=s' => \$opt_parallel, + + # Config file to use as template for all tests + 'defaults-file=s' => \&collect_option, + # Extra config file to append to all generated configs + 'defaults-extra-file=s' => \&collect_option, + + # Control what test suites or cases to run + 'force+' => \$opt_force, + 'skip-not-found' => \$opt_skip_not_found, + 'suite|suites=s' => \$opt_suites, + 'skip-rpl' => \&collect_option, + 'skip-test=s' => \&collect_option, + 'do-test=s' => \&collect_option, + 'start-from=s' => \&collect_option, + 'big-test+' => \$opt_big_test, + 'combination=s' => \@opt_combinations, + 'experimental=s' => \@opt_experimentals, + 'staging-run' => \$opt_staging_run, + + # Specify ports + 'build-thread|mtr-build-thread=i' => \$opt_build_thread, + 'port-base|mtr-port-base=i' => \$opt_port_base, + 'port-group-size=s' => \$opt_port_group_size, + + # Test case authoring + 'record' => \$opt_record, + 'check-testcases!' => \$opt_check_testcases, + 'mark-progress' => \$opt_mark_progress, + + # Extra options used when starting mariadbd + 'mariadbd=s' => \@opt_extra_mysqld_opt, + 'mariadbd-env=s' => \@opt_mysqld_envs, + # mysqld is an alias for mariadbd + 'mysqld=s' => \@opt_extra_mysqld_opt, + 'mysqld-env=s' => \@opt_mysqld_envs, + + # Run test on running server + 'extern=s' => \%opts_extern, # Append to hash + + # Debugging + 'debug' => \$opt_debug, + 'debug-common' => \$opt_debug_common, + 'debug-server' => \$opt_debug_server, + 'max-save-core=i' => \$opt_max_save_core, + 'max-save-datadir=i' => \$opt_max_save_datadir, + 'max-test-fail=i' => \$opt_max_test_fail, + 'core-on-failure' => \$opt_core_on_failure, + + # Coverage, profiling etc + 'gcov' => \$opt_gcov, + 'gprof' => \$opt_gprof, + 'debug-sync-timeout=i' => \$opt_debug_sync_timeout, + + # Directories + 'tmpdir=s' => \$opt_tmpdir, + 'vardir=s' => \$opt_vardir, + 'mem' => \$opt_mem, + 'clean-vardir' => \$opt_clean_vardir, + 'client-bindir=s' => \$path_client_bindir, + 'client-libdir=s' => \$path_client_libdir, + + # Misc + 'comment=s' => \$opt_comment, + 'fast' => \$opt_fast, + 'force-restart' => \$opt_force_restart, + 'reorder!' => \$opt_reorder, + 'enable-disabled' => \&collect_option, + 'verbose|v+' => \$opt_verbose, + 'verbose-restart' => \&report_option, + 'sleep=i' => \$opt_sleep, + 'start-dirty' => \$opt_start_dirty, + 'start-and-exit' => \$opt_start_exit, + 'start' => \$opt_start, + 'user-args' => \$opt_user_args, + 'wait-all' => \$opt_wait_all, + 'print-testcases' => \&collect_option, + 'repeat=i' => \$opt_repeat, + 'retry=i' => \$opt_retry, + 'retry-failure=i' => \$opt_retry_failure, + 'timer!' => \&report_option, + 'user=s' => \$opt_user, + 'testcase-timeout=i' => \$opt_testcase_timeout, + 'suite-timeout=i' => \$opt_suite_timeout, + 'shutdown-timeout=i' => \$opt_shutdown_timeout, + 'warnings!' => \$opt_warnings, + 'timestamp' => \&report_option, + 'timediff' => \&report_option, + 'stop-file=s' => \$opt_stop_file, + 'stop-keep-alive=i' => \$opt_stop_keep_alive, + 'max-connections=i' => \$opt_max_connections, + 'report-times' => \$opt_report_times, + 'result-file' => \$opt_resfile, + 'stress=s' => \$opt_stress, + 'tail-lines=i' => \$opt_tail_lines, + 'dry-run' => \$opt_dry_run, + + 'help|h' => \$opt_usage, + # list-options is internal, not listed in help + 'list-options' => \$opt_list_options, + 'skip-test-list=s' => \@opt_skip_test_list, + 'xml-report=s' => \$opt_xml_report, + + My::Debugger::options(), + My::CoreDump::options(), + My::Platform::options() + ); + + # fix options (that take an optional argument and *only* after = sign + @ARGV = My::Debugger::fix_options(@ARGV); + GetOptions(%options) or usage("Can't read options"); + usage("") if $opt_usage; + list_options(\%options) if $opt_list_options; + + # -------------------------------------------------------------------------- + # Setup verbosity + # -------------------------------------------------------------------------- + if ($opt_verbose != 0){ + report_option('verbose', $opt_verbose); + } + + # Negative values aren't meaningful on integer options + foreach(grep(/=i$/, keys %options)) + { + if (defined ${$options{$_}} && + do { no warnings "numeric"; int ${$options{$_}} < 0}) + { + my $v= (split /=/)[0]; + die("$v doesn't accept a negative value:"); + } + } + + # Find the absolute path to the test directory + $glob_mysql_test_dir= cwd(); + if ($glob_mysql_test_dir =~ / /) + { + die("Working directory \"$glob_mysql_test_dir\" contains space\n". + "Bailing out, cannot function properly with space in path"); + } + if (IS_CYGWIN) + { + if (My::Platform::check_cygwin_subshell()) { + die("Cygwin /bin/sh subshell requires fix with --cygwin-subshell-fix=do\n"); + } + # Use mixed path format i.e c:/path/to/ + $glob_mysql_test_dir= mixed_path($glob_mysql_test_dir); + } + + # In most cases, the base directory we find everything relative to, + # is the parent directory of the "mysql-test" directory. For source + # distributions, TAR binary distributions and some other packages. + $basedir= dirname($glob_mysql_test_dir); + + # In the RPM case, binaries and libraries are installed in the + # default system locations, instead of having our own private base + # directory. And we install "/usr/share/mysql-test". Moving up one + # more directory relative to "mysql-test" gives us a usable base + # directory for RPM installs. + if ( ! $source_dist and ! -d "$basedir/bin" ) + { + $basedir= dirname($basedir); + } + # For .deb, it's like RPM, but installed in /usr/share/mysql/mysql-test. + # So move up one more directory level yet. + if ( ! $source_dist and ! -d "$basedir/bin" ) + { + $basedir= dirname($basedir); + } + + # Respect MTR_BINDIR variable, which is typically set in to the + # build directory in out-of-source builds. + $bindir=$ENV{MTR_BINDIR}||$basedir; + + fix_vs_config_dir(); + + # Look for the client binaries directory + if ($path_client_bindir) + { + # --client-bindir=path set on command line, check that the path exists + $path_client_bindir= mtr_path_exists($path_client_bindir); + } + else + { + $path_client_bindir= mtr_path_exists("$bindir/client_release", + "$bindir/client_debug", + "$bindir/client/$multiconfig", + "$bindir/client$multiconfig", + "$bindir/client", + "$bindir/bin"); + } + + # Look for language files and charsetsdir, use same share + $path_language= mtr_path_exists("$bindir/share/mariadb", + "$bindir/share/mysql", + "$bindir/sql/share", + "$bindir/share"); + my $path_share= $path_language; + $path_charsetsdir = mtr_path_exists("$basedir/share/mariadb/charsets", + "$basedir/share/mysql/charsets", + "$basedir/sql/share/charsets", + "$basedir/share/charsets"); + if ( $opt_comment ) + { + mtr_report(); + mtr_print_thick_line('#'); + mtr_report("# $opt_comment"); + mtr_print_thick_line('#'); + } + + if ( @opt_experimentals ) + { + # $^O on Windows considered not generic enough + my $plat= (IS_WINDOWS) ? 'windows' : $^O; + + # read the list of experimental test cases from the files specified on + # the command line + $experimental_test_cases = []; + foreach my $exp_file (@opt_experimentals) + { + open(FILE, "<", $exp_file) + or mtr_error("Can't read experimental file: $exp_file"); + mtr_report("Using experimental file: $exp_file"); + while(<FILE>) { + chomp; + # remove comments (# foo) at the beginning of the line, or after a + # blank at the end of the line + s/(\s+|^)#.*$//; + # If @ platform specifier given, use this entry only if it contains + # @<platform> or @!<xxx> where xxx != platform + if (/\@.*/) + { + next if (/\@!$plat/); + next unless (/\@$plat/ or /\@!/); + # Then remove @ and everything after it + s/\@.*$//; + } + # remove whitespace + s/^\s+//; + s/\s+$//; + # if nothing left, don't need to remember this line + if ( $_ eq "" ) { + next; + } + # remember what is left as the name of another test case that should be + # treated as experimental + print " - $_\n"; + push @$experimental_test_cases, $_; + } + close FILE; + } + } + + foreach my $arg ( @ARGV ) + { + if ( $arg =~ /^--skip-/ ) + { + push(@opt_extra_mysqld_opt, $arg); + } + elsif ( $arg =~ /^--$/ ) + { + # It is an effect of setting 'pass_through' in option processing + # that the lone '--' separating options from arguments survives, + # simply ignore it. + } + elsif ( $arg =~ /^-/ ) + { + usage("Invalid option \"$arg\""); + } + else + { + push(@opt_cases, $arg); + } + } + + if ( @opt_cases ) + { + # Run big tests if explicitly specified on command line + $opt_big_test= 1; + } + + # -------------------------------------------------------------------------- + # Find out type of logging that are being used + # -------------------------------------------------------------------------- + foreach my $arg ( @opt_extra_mysqld_opt ) + { + if ( $arg =~ /binlog[-_]format=(\S+)/ ) + { + # Save this for collect phase + collect_option('binlog-format', $1); + mtr_report("Using binlog format '$1'"); + } + } + + + # -------------------------------------------------------------------------- + # Find out default storage engine being used(if any) + # -------------------------------------------------------------------------- + foreach my $arg ( @opt_extra_mysqld_opt ) + { + if ( $arg =~ /default-storage-engine=(\S+)/ ) + { + # Save this for collect phase + collect_option('default-storage-engine', $1); + mtr_report("Using default engine '$1'") + } + } + + if (IS_WINDOWS and defined $opt_mem) { + mtr_report("--mem not supported on Windows, ignored"); + $opt_mem= undef; + } + + if ($opt_port_base ne "auto") + { + if (my $rem= $opt_port_base % 10) + { + mtr_warning ("Port base $opt_port_base rounded down to multiple of 10"); + $opt_port_base-= $rem; + } + $opt_build_thread= $opt_port_base / 10 - 1000; + } + + # -------------------------------------------------------------------------- + # Check if we should speed up tests by trying to run on tmpfs + # -------------------------------------------------------------------------- + if ( defined $opt_mem) + { + mtr_error("Can't use --mem and --vardir at the same time ") + if $opt_vardir; + mtr_error("Can't use --mem and --tmpdir at the same time ") + if $opt_tmpdir; + + # Search through list of locations that are known + # to be "fast disks" to find a suitable location + my @tmpfs_locations= ("/run/shm", "/dev/shm", "/tmp"); + + # Use $ENV{'MTR_MEM'} as first location to look (if defined) + unshift(@tmpfs_locations, $ENV{'MTR_MEM'}) if defined $ENV{'MTR_MEM'}; + + foreach my $fs (@tmpfs_locations) + { + if ( -d $fs && ! -l $fs && -w $fs ) + { + my $template= "var_${opt_build_thread}_XXXX"; + $opt_mem= tempdir( $template, DIR => $fs, CLEANUP => 0); + last; + } + } + } + + # -------------------------------------------------------------------------- + # Set the "var/" directory, the base for everything else + # -------------------------------------------------------------------------- + my $vardir_location= (defined $ENV{MTR_BINDIR} + ? "$ENV{MTR_BINDIR}/mysql-test" + : $glob_mysql_test_dir); + $vardir_location= realpath $vardir_location; + $default_vardir= "$vardir_location/var"; + + if ( ! $opt_vardir ) + { + $opt_vardir= $default_vardir; + } + + # We make the path absolute, as the server will do a chdir() before usage + unless ( $opt_vardir =~ m,^/, or + (IS_WINDOWS and $opt_vardir =~ m,^[a-z]:[/\\],i) ) + { + # Make absolute path, relative test dir + $opt_vardir= "$glob_mysql_test_dir/$opt_vardir"; + } + + set_vardir($opt_vardir); + + # -------------------------------------------------------------------------- + # Set the "tmp" directory + # -------------------------------------------------------------------------- + if ( ! $opt_tmpdir ) + { + $opt_tmpdir= "$opt_vardir/tmp" unless $opt_tmpdir; + + if (check_socket_path_length("$opt_tmpdir/mysql_testsocket.sock")) + { + mtr_report("Too long tmpdir path '$opt_tmpdir'", + " creating a shorter one..."); + + # Create temporary directory in standard location for temporary files + $opt_tmpdir= tempdir( TMPDIR => 1, CLEANUP => 0 ); + mtr_report(" - using tmpdir: '$opt_tmpdir'\n"); + + # Remember pid that created dir so it's removed by correct process + $opt_tmpdir_pid= $$; + } + } + $opt_tmpdir =~ s,/+$,,; # Remove ending slash if any + + # -------------------------------------------------------------------------- + # fast option + # -------------------------------------------------------------------------- + if ($opt_fast){ + $opt_shutdown_timeout= 0; # Kill processes instead of nice shutdown + } + + # -------------------------------------------------------------------------- + # Check parallel value + # -------------------------------------------------------------------------- + if ($opt_parallel ne "auto" && $opt_parallel < 1) + { + mtr_error("0 or negative parallel value makes no sense, use 'auto' or positive number"); + } + + # -------------------------------------------------------------------------- + # Record flag + # -------------------------------------------------------------------------- + if ( $opt_record and ! @opt_cases ) + { + mtr_error("Will not run in record mode without a specific test case"); + } + + if ( $opt_record ) { + # Use only one worker with --record + $opt_parallel= 1; + } + + # -------------------------------------------------------------------------- + # Embedded server flag + # -------------------------------------------------------------------------- + if ( $opt_embedded_server ) + { + $opt_skip_ssl= 1; # Turn off use of SSL + + # Turn off use of bin log + push(@opt_extra_mysqld_opt, "--skip-log-bin"); + + if ( using_extern() ) + { + mtr_error("Can't use --extern with --embedded-server"); + } + } + + # -------------------------------------------------------------------------- + # Big test and staging_run flags + # -------------------------------------------------------------------------- + if ( $opt_big_test ) + { + $ENV{'BIG_TEST'}= 1; + } + $ENV{'STAGING_RUN'}= $opt_staging_run; + + # -------------------------------------------------------------------------- + # Gcov flag + # -------------------------------------------------------------------------- + if ( ($opt_gcov or $opt_gprof) and ! $source_dist ) + { + mtr_error("Coverage test needs the source - please use source dist"); + } + + $ENV{ASAN_OPTIONS}= "abort_on_error=1:" . ($ENV{ASAN_OPTIONS} || ''); + $ENV{ASAN_OPTIONS}= "suppressions=${glob_mysql_test_dir}/asan.supp:" . + $ENV{ASAN_OPTIONS} + if -f "$glob_mysql_test_dir/asan.supp" and not IS_WINDOWS; + # The following can be useful when a test fails without any asan report + # on stderr like with openssl_1.test + # $ENV{ASAN_OPTIONS}= "log_path=${opt_vardir}/log/asan:" . $ENV{ASAN_OPTIONS}; + + # Add leak suppressions + $ENV{LSAN_OPTIONS}= "suppressions=${glob_mysql_test_dir}/lsan.supp:print_suppressions=0" + if -f "$glob_mysql_test_dir/lsan.supp" and not IS_WINDOWS; + + mtr_verbose("ASAN_OPTIONS=$ENV{ASAN_OPTIONS}"); + + # -------------------------------------------------------------------------- + # Modified behavior with --start options + # -------------------------------------------------------------------------- + if ($opt_start or $opt_start_dirty or $opt_start_exit) { + collect_option ('quick-collect', 1); + $start_only= 1; + } + if ($opt_debug) + { + $opt_testcase_timeout= 7 * 24 * 60; + $opt_suite_timeout= 7 * 24 * 60; + $opt_retry= 1; + $opt_retry_failure= 1; + } + + # -------------------------------------------------------------------------- + # Check use of user-args + # -------------------------------------------------------------------------- + + if ($opt_user_args) { + mtr_error("--user-args only valid with --start options") + unless $start_only; + mtr_error("--user-args cannot be combined with named suites or tests") + if $opt_suites || @opt_cases; + } + + # -------------------------------------------------------------------------- + # Check use of wait-all + # -------------------------------------------------------------------------- + + if ($opt_wait_all && ! $start_only) + { + mtr_error("--wait-all can only be used with --start options"); + } + + # -------------------------------------------------------------------------- + # Gather stress-test options and modify behavior + # -------------------------------------------------------------------------- + + if ($opt_stress) + { + $opt_stress=~ s/,/ /g; + $opt_user_args= 1; + mtr_error("--stress cannot be combined with named ordinary suites or tests") + if $opt_suites || @opt_cases; + $opt_suites="stress"; + @opt_cases= ("wrapper"); + $ENV{MST_OPTIONS}= $opt_stress; + } + + # -------------------------------------------------------------------------- + # Check timeout arguments + # -------------------------------------------------------------------------- + + mtr_error("Invalid value '$opt_testcase_timeout' supplied ". + "for option --testcase-timeout") + if ($opt_testcase_timeout <= 0); + mtr_error("Invalid value '$opt_suite_timeout' supplied ". + "for option --testsuite-timeout") + if ($opt_suite_timeout <= 0); + + if ($opt_debug_common) + { + $opt_debug= 1; + $debug_d= "d,query,info,error,enter,exit"; + } + + My::CoreDump::pre_setup(); +} + + +# +# To make it easier for different devs to work on the same host, +# an environment variable can be used to control all ports. A small +# number is to be used, 0 - 16 or similar. +# +# Note the MASTER_MYPORT has to be set the same in all 4.x and 5.x +# versions of this script, else a 4.0 test run might conflict with a +# 5.1 test run, even if different MTR_BUILD_THREAD is used. This means +# all port numbers might not be used in this version of the script. +# +# Also note the limitation of ports we are allowed to hand out. This +# differs between operating systems and configuration, see +# http://www.ncftp.com/ncftpd/doc/misc/ephemeral_ports.html +# But a fairly safe range seems to be 5001 - 32767 +# +sub set_build_thread_ports($) { + my $thread= shift || 0; + + if ( lc($opt_build_thread) eq 'auto' ) { + my $found_free = 0; + $build_thread = 300; # Start attempts from here + my $build_thread_upper = $build_thread + ($opt_parallel > 1500 + ? 3000 + : 2 * $opt_parallel) + 300; + while (! $found_free) + { + $build_thread= mtr_get_unique_id($build_thread, $build_thread_upper); + if ( !defined $build_thread ) { + mtr_error("Could not get a unique build thread id"); + } + $found_free= check_ports_free($build_thread); + # If not free, release and try from next number + if (! $found_free) { + mtr_release_unique_id(); + $build_thread++; + } + } + } + else + { + $build_thread = $opt_build_thread + $thread - 1; + if (! check_ports_free($build_thread)) { + # Some port was not free(which one has already been printed) + mtr_error("Some port(s) was not free") + } + } + $ENV{MTR_BUILD_THREAD}= $build_thread; + + # Calculate baseport + $baseport= $build_thread * $opt_port_group_size + 10000; + if ( $baseport < 5001 or $baseport + $opt_port_group_size >= 32767 ) + { + mtr_error("MTR_BUILD_THREAD number results in a port", + "outside 5001 - 32767", + "($baseport - $baseport + $opt_port_group_size)"); + } + + mtr_report("Using MTR_BUILD_THREAD $build_thread,", + "with reserved ports $baseport..".($baseport+($opt_port_group_size-1))); + +} + + +sub collect_mysqld_features { + # + # Execute "mysqld --no-defaults --help --verbose" to get a + # list of all features and settings + # + # --no-defaults and --skip-grant-tables are to avoid loading + # system-wide configs and plugins + # + # --datadir must exist, mysqld will chdir into it + # + my $args; + mtr_init_args(\$args); + mtr_add_arg($args, "--no-defaults"); + mtr_add_arg($args, "--datadir=."); + mtr_add_arg($args, "--basedir=%s", $basedir); + mtr_add_arg($args, "--lc-messages-dir=%s", $path_language); + mtr_add_arg($args, "--skip-grant-tables"); + mtr_add_arg($args, "--log-warnings=0"); + mtr_add_arg($args, "--log-slow-admin-statements=0"); + mtr_add_arg($args, "--log-queries-not-using-indexes=0"); + mtr_add_arg($args, "--log-slow-slave-statements=0"); + mtr_add_arg($args, "--verbose"); + mtr_add_arg($args, "--help"); + + my $exe_mysqld= find_mysqld($bindir); + my $cmd= join(" ", $exe_mysqld, @$args); + + mtr_verbose("cmd: $cmd"); + + my $list= `$cmd`; + + # to simplify the parsing, we'll merge all nicely formatted --help texts + $list =~ s/\n {22}(\S)/ $1/g; + + my @list= split '\R', $list; + + $mysql_version_id= 0; + my $exe= basename($exe_mysqld); + while (defined(my $line = shift @list)){ + if ($line =~ /\W\Q$exe\E\s+Ver\s(\d+)\.(\d+)\.(\d+)(\S*)/ ) { + $mysql_version_id= $1*10000 + $2*100 + $3; + mtr_report("MariaDB Version $1.$2.$3$4"); + last; + } + } + mtr_error("Could not find version of MariaDB") + unless $mysql_version_id > 0; + + for (@list) + { + # first part of the help - command-line options. + if (/Copyright/ .. /^-{30,}/) { + # here we want to detect all not mandatory plugins + # they are listed in the --help output as + # --archive[=name] + # Enable or disable ARCHIVE plugin. Possible values are ON, OFF, + # FORCE (don't start if the plugin fails to load), + # FORCE_PLUS_PERMANENT (like FORCE, but the plugin can not be uninstalled). + # For Innodb I_S plugins that are referenced in sys schema + # do not make them optional, to prevent diffs in tests. + push @optional_plugins, $1 + if /^ --([-a-z0-9]+)\[=name\] +Enable or disable \w+ plugin. One of: ON, OFF, FORCE/ + and $1 ne "innodb-metrics" + and $1 ne "innodb-buffer-page" + and $1 ne "innodb-lock-waits" + and $1 ne "innodb-locks" + and $1 ne "innodb-trx" + and $1 ne "gssapi"; + next; + } + + last if /^\r?$/; # then goes a list of variables, it ends with an empty line + + # Put a variable into hash + /^([\S]+)[ \t]+(.*?)\r?$/ or die "Could not parse mysqld --help: $_\n"; + $mysqld_variables{$1}= $2; + } + mtr_error("Could not find variabes list") unless %mysqld_variables; +} + + +sub collect_mysqld_features_from_running_server () +{ + my $mysql= mtr_exe_exists("$path_client_bindir/mariadb"); + + my $args; + mtr_init_args(\$args); + + mtr_add_arg($args, "--no-defaults"); + mtr_add_arg($args, "--user=%s", $opt_user); + + while (my ($option, $value)= each( %opts_extern )) { + mtr_add_arg($args, "--$option=$value"); + } + + mtr_add_arg($args, "--silent"); # Tab separated output + mtr_add_arg($args, "-e \"use mysql; SHOW VARIABLES\""); + my $cmd= "$mysql " . join(' ', @$args); + mtr_verbose("cmd: $cmd"); + + my $list = `$cmd` or + mtr_error("Could not connect to extern server using command: '$cmd'"); + foreach my $line (split('\R', $list )) + { + # Put variables into hash + if ( $line =~ /^([\S]+)[ \t]+(.*?)\r?$/ ) + { + my $name= $1; + my $value=$2; + $name =~ s/_/-/g; + # print "$name=\"$value\"\n"; + $mysqld_variables{$name}= $value; + } + } + + # Parse version + my $version_str= $mysqld_variables{'version'}; + if ( $version_str =~ /^([0-9]*)\.([0-9]*)\.([0-9]*)([^\s]*)/ ) + { + #print "Major: $1 Minor: $2 Build: $3\n"; + $mysql_version_id= $1*10000 + $2*100 + $3; + #print "mysql_version_id: $mysql_version_id\n"; + mtr_report("MariaDB Version $1.$2.$3"); + $mysql_version_extra= $4; + } + mtr_error("Could not find version of MariaDBL") unless $mysql_version_id; +} + +sub find_mysqld { + + my ($mysqld_basedir)= $ENV{MTR_BINDIR_FORCED} || $ENV{MTR_BINDIR} || @_; + + my @mysqld_names= ("mariadbd", "mysqld", "mysqld-max-nt", "mysqld-max", + "mysqld-nt"); + + if ( $opt_debug_server ){ + # Put mysqld-debug first in the list of binaries to look for + mtr_verbose("Adding mysqld-debug first in list of binaries to look for"); + unshift(@mysqld_names, "mysqld-debug"); + } + + return my_find_bin($mysqld_basedir, + ["sql", "libexec", "sbin", "bin"], + [@mysqld_names]); +} + + +sub executable_setup () { + + $exe_patch='patch' if `patch -v`; + + # Look for the client binaries + $exe_mysqladmin= mtr_exe_exists("$path_client_bindir/mariadb-admin"); + $exe_mysql= mtr_exe_exists("$path_client_bindir/mariadb"); + $exe_mysql_plugin= mtr_exe_exists("$path_client_bindir/mariadb-plugin"); + $exe_mariadb_conv= mtr_exe_exists("$path_client_bindir/mariadb-conv"); + + $exe_mysql_embedded= mtr_exe_maybe_exists("$bindir/libmysqld/examples/mysql_embedded"); + + # Look for mysqltest executable + if ( $opt_embedded_server ) + { + $exe_mysqltest= + mtr_exe_exists("$bindir/libmysqld/examples$multiconfig/mysqltest_embedded", + "$path_client_bindir/mysqltest_embedded"); + } + else + { + if ( defined $ENV{'MYSQL_TEST'} ) + { + $exe_mysqltest=$ENV{'MYSQL_TEST'}; + print "===========================================================\n"; + print "WARNING:The mysqltest binary is fetched from $exe_mysqltest\n"; + print "===========================================================\n"; + } + else + { + $exe_mysqltest= mtr_exe_exists("$path_client_bindir/mariadb-test"); + } + } + +} + + +sub client_debug_arg($$) { + my ($args, $client_name)= @_; + + # Workaround for Bug #50627: drop any debug opt + return if $client_name =~ /^mariadb-binlog/; + + if ( $opt_debug ) { + mtr_add_arg($args, + "--loose-debug=d,info,warning,warnings:t:A,%s/log/%s.trace", + $path_vardir_trace, $client_name) + } +} + + +sub client_arguments ($;$) { + my $client_name= shift; + my $group_suffix= shift; + my $client_exe= mtr_exe_exists("$path_client_bindir/$client_name"); + + my $args; + mtr_init_args(\$args); + mtr_add_arg($args, "--defaults-file=%s", $path_config_file); + if (defined($group_suffix)) { + mtr_add_arg($args, "--defaults-group-suffix=%s", $group_suffix); + client_debug_arg($args, "$client_name-$group_suffix"); + } + else + { + client_debug_arg($args, $client_name); + } + return mtr_args2str($client_exe, @$args); +} + + +sub mysqlbinlog_arguments () { + my $exe= mtr_exe_exists("$path_client_bindir/mariadb-binlog"); + + my $args; + mtr_init_args(\$args); + mtr_add_arg($args, "--defaults-file=%s", $path_config_file); + mtr_add_arg($args, "--local-load=%s", $opt_tmpdir); + client_debug_arg($args, "mysqlbinlog"); + return mtr_args2str($exe, @$args); +} + + +sub mysqlslap_arguments () { + my $exe= mtr_exe_maybe_exists("$path_client_bindir/mariadb-slap"); + if ( $exe eq "" ) { + # mysqlap was not found + + if (defined $mysql_version_id and $mysql_version_id >= 50100 ) { + mtr_error("Could not find the mariadb-slap binary"); + } + return ""; # Don't care about mariadb-slap + } + + my $args; + mtr_init_args(\$args); + mtr_add_arg($args, "--defaults-file=%s", $path_config_file); + client_debug_arg($args, "mysqlslap"); + return mtr_args2str($exe, @$args); +} + + +sub mysqldump_arguments ($) { + my($group_suffix) = @_; + my $exe= mtr_exe_exists("$path_client_bindir/mariadb-dump"); + + my $args; + mtr_init_args(\$args); + mtr_add_arg($args, "--defaults-file=%s", $path_config_file); + mtr_add_arg($args, "--defaults-group-suffix=%s", $group_suffix); + client_debug_arg($args, "mysqldump-$group_suffix"); + return mtr_args2str($exe, @$args); +} + + +sub mysql_client_test_arguments(){ + my $exe; + # mysql_client_test executable may _not_ exist + if ( $opt_embedded_server ) { + $exe= mtr_exe_maybe_exists( + "$bindir/libmysqld/examples$multiconfig/mysql_client_test_embedded", + "$bindir/bin/mysql_client_test_embedded"); + } else { + $exe= mtr_exe_maybe_exists("$bindir/tests$multiconfig/mysql_client_test", + "$bindir/bin/mysql_client_test"); + } + + my $args; + mtr_init_args(\$args); + mtr_add_arg($args, "--defaults-file=%s", $path_config_file); + mtr_add_arg($args, "--testcase"); + mtr_add_arg($args, "--vardir=$opt_vardir"); + client_debug_arg($args,"mysql_client_test"); + my $ret=mtr_args2str($exe, @$args); + return $ret; +} + +sub tool_arguments ($$) { + my($sedir, $tool_name) = @_; + my $exe= my_find_bin($bindir, + [$sedir, "bin"], + $tool_name); + + my $args; + mtr_init_args(\$args); + client_debug_arg($args, $tool_name); + return mtr_args2str($exe, @$args); +} + +# This is not used to actually start a mysqld server, just to allow test +# scripts to run the mysqld binary to test invalid server startup options. +sub mysqld_client_arguments () { + my $default_mysqld= default_mysqld(); + my $exe = find_mysqld($bindir); + my $args; + mtr_init_args(\$args); + mtr_add_arg($args, "--no-defaults"); + mtr_add_arg($args, "--basedir=%s", $basedir); + mtr_add_arg($args, "--character-sets-dir=%s", $default_mysqld->value("character-sets-dir")); + mtr_add_arg($args, "--language=%s", $default_mysqld->value("language")); + return mtr_args2str($exe, @$args); +} + + +sub have_maria_support () { + my $maria_var= $mysqld_variables{'aria-recover-options'}; + return defined $maria_var; +} + + +sub environment_setup { + + umask(022); + + $ENV{'USE_RUNNING_SERVER'}= using_extern(); + + my @ld_library_paths; + + if ($path_client_libdir) + { + # Use the --client-libdir passed on commandline + push(@ld_library_paths, "$path_client_libdir"); + } + else + { + # Setup LD_LIBRARY_PATH so the libraries from this distro/clone + # are used in favor of the system installed ones + if ( $source_dist ) + { + push(@ld_library_paths, "$basedir/libmysql/.libs/", + "$basedir/libmysql_r/.libs/", + "$basedir/zlib/.libs/"); + if ($^O eq "darwin") + { + # it is MAC OS and we have to add dynamic libraries paths + push @ld_library_paths, grep {<$_/*.dylib>} + (<$bindir/storage/*/.libs/>,<$bindir/plugin/*/.libs/>, + <$bindir/plugin/*/*/.libs/>,<$bindir/storage/*/*/.libs>); + } + } + else + { + push(@ld_library_paths, "$basedir/lib", "$basedir/lib/mysql"); + } + } + + $ENV{'LD_LIBRARY_PATH'}= join(":", @ld_library_paths, + $ENV{'LD_LIBRARY_PATH'} ? + split(':', $ENV{'LD_LIBRARY_PATH'}) : ()); + + My::Debugger::pre_setup(); + + mtr_debug("LD_LIBRARY_PATH: $ENV{'LD_LIBRARY_PATH'}"); + + $ENV{'DYLD_LIBRARY_PATH'}= join(":", @ld_library_paths, + $ENV{'DYLD_LIBRARY_PATH'} ? + split(':', $ENV{'DYLD_LIBRARY_PATH'}) : ()); + mtr_debug("DYLD_LIBRARY_PATH: $ENV{'DYLD_LIBRARY_PATH'}"); + + # The environment variable used for shared libs on AIX + $ENV{'SHLIB_PATH'}= join(":", @ld_library_paths, + $ENV{'SHLIB_PATH'} ? + split(':', $ENV{'SHLIB_PATH'}) : ()); + mtr_debug("SHLIB_PATH: $ENV{'SHLIB_PATH'}"); + + # The environment variable used for shared libs on hp-ux + $ENV{'LIBPATH'}= join(":", @ld_library_paths, + $ENV{'LIBPATH'} ? + split(':', $ENV{'LIBPATH'}) : ()); + mtr_debug("LIBPATH: $ENV{'LIBPATH'}"); + + $ENV{'UMASK'}= "0660"; # The octal *string* + $ENV{'UMASK_DIR'}= "0770"; # The octal *string* + + # + # MariaDB tests can produce output in various character sets + # (especially, ctype_xxx.test). To avoid confusing Perl + # with output which is incompatible with the current locale + # settings, we reset the current values of LC_ALL and LC_CTYPE to "C". + # For details, please see + # Bug#27636 tests fails if LC_* variables set to *_*.UTF-8 + # + $ENV{'LC_ALL'}= "C"; + $ENV{'LC_CTYPE'}= "C"; + $ENV{'LC_COLLATE'}= "C"; + + $ENV{'OPENSSL_CONF'}= $mysqld_variables{'version-ssl-library'} gt 'OpenSSL 1.1.1' + ? "$glob_mysql_test_dir/lib/openssl.cnf" : '/dev/null'; + + $ENV{'MYSQL_TEST_DIR'}= $glob_mysql_test_dir; + $ENV{'DEFAULT_MASTER_PORT'}= $mysqld_variables{'port'}; + $ENV{'MYSQL_TMP_DIR'}= $opt_tmpdir; + $ENV{'MYSQLTEST_VARDIR'}= $opt_vardir; + $ENV{'MYSQLTEST_REAL_VARDIR'}= realpath $opt_vardir; + $ENV{'MYSQL_BINDIR'}= $bindir; + $ENV{'MYSQL_SHAREDIR'}= $path_language; + $ENV{'MYSQL_CHARSETSDIR'}= $path_charsetsdir; + + if (IS_WINDOWS) + { + $ENV{'SECURE_LOAD_PATH'}= $glob_mysql_test_dir."\\std_data"; + } + else + { + $ENV{'SECURE_LOAD_PATH'}= $glob_mysql_test_dir."/std_data"; + } + + # + # Some stupid^H^H^H^H^H^Hignorant network providers set up "wildcard DNS" + # servers that return some given web server address for any lookup of a + # non-existent host name. This confuses test cases that want to test the + # behaviour when connecting to a non-existing host, so we need to be able + # to disable those tests when DNS is broken. + # + $ENV{HAVE_BROKEN_DNS}= defined(gethostbyname('invalid_hostname')); + + # ---------------------------------------------------- + # mysql clients + # ---------------------------------------------------- + $ENV{'MYSQL_CHECK'}= client_arguments("mariadb-check"); + $ENV{'MYSQL_DUMP'}= mysqldump_arguments(".1"); + $ENV{'MYSQL_DUMP_SLAVE'}= mysqldump_arguments(".2"); + $ENV{'MYSQL_SLAP'}= mysqlslap_arguments(); + $ENV{'MYSQL_IMPORT'}= client_arguments("mariadb-import"); + $ENV{'MYSQL_SHOW'}= client_arguments("mariadb-show"); + $ENV{'MYSQL_BINLOG'}= mysqlbinlog_arguments(); + $ENV{'MYSQL'}= client_arguments("mariadb"); + $ENV{'MYSQL_SLAVE'}= client_arguments("mariadb", ".2"); + $ENV{'MYSQL_UPGRADE'}= client_arguments("mariadb-upgrade"); + $ENV{'MYSQLADMIN'}= client_arguments("mariadb-admin"); + $ENV{'MYSQL_CLIENT_TEST'}= mysql_client_test_arguments(); + $ENV{'EXE_MYSQL'}= $exe_mysql; + $ENV{'MYSQL_PLUGIN'}= $exe_mysql_plugin; + $ENV{'MYSQL_EMBEDDED'}= $exe_mysql_embedded; + $ENV{'MARIADB_CONV'}= "$exe_mariadb_conv --character-sets-dir=$path_charsetsdir"; + if(IS_WINDOWS) + { + $ENV{'MYSQL_INSTALL_DB_EXE'}= mtr_exe_exists("$bindir/sql$multiconfig/mariadb-install-db", + "$bindir/bin/mariadb-install-db"); + } + + my $client_config_exe= + mtr_exe_maybe_exists( + "$bindir/libmariadb/mariadb_config$multiconfig/mariadb_config", + "$bindir/bin/mariadb_config"); + if ($client_config_exe) + { + my $tls_info= `$client_config_exe --tlsinfo`; + ($ENV{CLIENT_TLS_LIBRARY},$ENV{CLIENT_TLS_LIBRARY_VERSION})= + split(/ /, $tls_info, 2); + } + my $exe_mysqld= find_mysqld($basedir); + $ENV{'MYSQLD'}= $exe_mysqld; + my $extra_opts= join (" ", @opt_extra_mysqld_opt); + $ENV{'MYSQLD_CMD'}= "$exe_mysqld --defaults-group-suffix=.1 ". + "--defaults-file=$path_config_file $extra_opts"; + + # ---------------------------------------------------- + # bug25714 executable may _not_ exist in + # some versions, test using it should be skipped + # ---------------------------------------------------- + my $exe_bug25714= + mtr_exe_maybe_exists("$bindir/tests$multiconfig/bug25714"); + $ENV{'MYSQL_BUG25714'}= native_path($exe_bug25714); + + # ---------------------------------------------------- + # mysql_fix_privilege_tables.sql + # ---------------------------------------------------- + my $file_mysql_fix_privilege_tables= + mtr_file_exists("$bindir/scripts/mysql_fix_privilege_tables.sql", + "$bindir/share/mysql_fix_privilege_tables.sql", + "$bindir/share/mariadb/mysql_fix_privilege_tables.sql", + "$bindir/share/mysql/mysql_fix_privilege_tables.sql"); + $ENV{'MYSQL_FIX_PRIVILEGE_TABLES'}= $file_mysql_fix_privilege_tables; + + # ---------------------------------------------------- + # my_print_defaults + # ---------------------------------------------------- + my $exe_my_print_defaults= + mtr_exe_exists("$bindir/extra$multiconfig/my_print_defaults", + "$path_client_bindir/my_print_defaults"); + $ENV{'MYSQL_MY_PRINT_DEFAULTS'}= native_path($exe_my_print_defaults); + + # ---------------------------------------------------- + # myisam tools + # ---------------------------------------------------- + $ENV{'MYISAMLOG'}= tool_arguments("storage/myisam", "myisamlog", ); + $ENV{'MYISAMCHK'}= tool_arguments("storage/myisam", "myisamchk"); + $ENV{'MYISAMPACK'}= tool_arguments("storage/myisam", "myisampack"); + $ENV{'MYISAM_FTDUMP'}= tool_arguments("storage/myisam", "myisam_ftdump"); + + # ---------------------------------------------------- + # aria tools + # ---------------------------------------------------- + if (have_maria_support()) + { + $ENV{'MARIA_CHK'}= tool_arguments("storage/maria", "aria_chk"); + $ENV{'MARIA_PACK'}= tool_arguments("storage/maria", "aria_pack"); + } + + # ---------------------------------------------------- + # mysqlhotcopy + # ---------------------------------------------------- + my $mysqlhotcopy= + mtr_pl_maybe_exists("$bindir/scripts/mysqlhotcopy") || + mtr_pl_maybe_exists("$path_client_bindir/mysqlhotcopy"); + if ($mysqlhotcopy) + { + $ENV{'MYSQLHOTCOPY'}= $mysqlhotcopy; + } + + # ---------------------------------------------------- + # perror + # ---------------------------------------------------- + my $exe_perror= mtr_exe_exists("$bindir/extra$multiconfig/perror", + "$path_client_bindir/perror"); + $ENV{'MY_PERROR'}= native_path($exe_perror); + + # ---------------------------------------------------- + # mysql_tzinfo_to_sql + # ---------------------------------------------------- + my $exe_mysql_tzinfo_to_sql= mtr_exe_exists("$basedir/sql$multiconfig/mariadb-tzinfo-to-sql", + "$path_client_bindir/mariadb-tzinfo-to-sql", + "$bindir/sql$multiconfig/mariadb-tzinfo-to-sql"); + $ENV{'MYSQL_TZINFO_TO_SQL'}= native_path($exe_mysql_tzinfo_to_sql); + + # ---------------------------------------------------- + # replace + # ---------------------------------------------------- + my $exe_replace= mtr_exe_exists(vs_config_dirs('extra', 'replace'), + "$basedir/extra/replace", + "$bindir/extra$multiconfig/replace", + "$path_client_bindir/replace"); + $ENV{'REPLACE'}= native_path($exe_replace); + + # ---------------------------------------------------- + # innochecksum + # ---------------------------------------------------- + my $exe_innochecksum= + mtr_exe_maybe_exists("$bindir/extra$multiconfig/innochecksum", + "$path_client_bindir/innochecksum"); + $ENV{'INNOCHECKSUM'}= native_path($exe_innochecksum) if $exe_innochecksum; + + # ---------------------------------------------------- + # mariabackup + # ---------------------------------------------------- + my $exe_mariabackup= mtr_exe_maybe_exists( + "$bindir/extra/mariabackup$multiconfig/mariabackup", + "$path_client_bindir/mariabackup"); + + $ENV{XTRABACKUP}= native_path($exe_mariabackup) if $exe_mariabackup; + + my $exe_xbstream= mtr_exe_maybe_exists( + "$bindir/extra/mariabackup/$multiconfig/mbstream", + "$path_client_bindir/mbstream"); + $ENV{XBSTREAM}= native_path($exe_xbstream) if $exe_xbstream; + + $ENV{INNOBACKUPEX}= "$exe_mariabackup --innobackupex"; + + # Add dir of this perl to aid mysqltest in finding perl + my $perldir= dirname($^X); + my $pathsep= ":"; + $pathsep= ";" if IS_WINDOWS && ! IS_CYGWIN; + $ENV{'PATH'}= "$ENV{'PATH'}".$pathsep.$perldir; +} + + +sub remove_vardir_subs() { + foreach my $sdir ( glob("$opt_vardir/*") ) { + mtr_verbose("Removing subdir $sdir"); + rmtree($sdir); + } +} + +# +# Remove var and any directories in var/ created by previous +# tests +# +sub remove_stale_vardir () { + + mtr_report("Removing old var directory..."); + + # Safety! + mtr_error("No, don't remove the vardir when running with --extern") + if using_extern(); + + mtr_verbose("opt_vardir: $opt_vardir"); + if ( $opt_vardir eq $default_vardir ) + { + # + # Running with "var" in mysql-test dir + # + if ( -l $opt_vardir) + { + # var is a symlink + + if ( $opt_mem ) + { + # Remove the directory which the link points at + mtr_verbose("Removing " . readlink($opt_vardir)); + rmtree(readlink($opt_vardir)); + + # Remove the "var" symlink + mtr_verbose("unlink($opt_vardir)"); + unlink($opt_vardir); + } + else + { + # Some users creates a soft link in mysql-test/var to another area + # - allow it, but remove all files in it + + mtr_report(" - WARNING: Using the 'mysql-test/var' symlink"); + + # Make sure the directory where it points exist + if (! -d readlink($opt_vardir)) + { + mtr_report("The destination for symlink $opt_vardir does not exist; Removing it and creating a new var directory"); + unlink($opt_vardir); + } + remove_vardir_subs(); + } + } + else + { + # Remove the entire "var" dir + mtr_verbose("Removing $opt_vardir/"); + rmtree("$opt_vardir/"); + } + + if ( $opt_mem ) + { + # A symlink from var/ to $opt_mem will be set up + # remove the $opt_mem dir to assure the symlink + # won't point at an old directory + mtr_verbose("Removing $opt_mem"); + rmtree($opt_mem); + } + + } + else + { + # + # Running with "var" in some other place + # + + # Don't remove the var/ dir in mysql-test dir as it may be in + # use by another mysql-test-run run with --vardir + # mtr_verbose("Removing $default_vardir"); + # rmtree($default_vardir); + + # Remove the "var" dir + mtr_verbose("Removing $opt_vardir/"); + rmtree("$opt_vardir/"); + } + # Remove the "tmp" dir + mtr_verbose("Removing $opt_tmpdir/"); + rmtree("$opt_tmpdir/"); +} + +sub set_plugin_var($) { + local $_ = $_[0]; + s/\.\w+$//; + $ENV{"\U${_}_SO"} = $_[0]; +} + +# +# Create var and the directories needed in var +# +sub setup_vardir() { + mtr_report("Creating var directory '$opt_vardir'..."); + + if ( $opt_vardir eq $default_vardir ) + { + # + # Running with "var" in mysql-test dir + # + if ( -l $opt_vardir ) + { + # it's a symlink + + # Make sure the directory where it points exist + if (! -d readlink($opt_vardir)) + { + mtr_report("The destination for symlink $opt_vardir does not exist; Removing it and creating a new var directory"); + unlink($opt_vardir); + } + } + elsif ( $opt_mem ) + { + # Runinng with "var" as a link to some "memory" location, normally tmpfs + mtr_verbose("Creating $opt_mem"); + mkpath($opt_mem); + + mtr_report(" - symlinking 'var' to '$opt_mem'"); + symlink($opt_mem, $opt_vardir); + } + } + + if ( ! -d $opt_vardir ) + { + mtr_verbose("Creating $opt_vardir"); + mkpath($opt_vardir); + } + + # Ensure a proper error message if vardir couldn't be created + unless ( -d $opt_vardir and -w $opt_vardir ) + { + mtr_error("Writable 'var' directory is needed, use the " . + "'--vardir=<path>' option"); + } + + mkpath("$opt_vardir/log"); + mkpath("$opt_vardir/run"); + + # Create var/tmp and tmp - they might be different + mkpath("$opt_vardir/tmp"); + mkpath($opt_tmpdir) if ($opt_tmpdir ne "$opt_vardir/tmp"); + + # On some operating systems, there is a limit to the length of a + # UNIX domain socket's path far below PATH_MAX. + # Don't allow that to happen + if (check_socket_path_length("$opt_tmpdir/testsocket.sock")){ + mtr_error("Socket path '$opt_tmpdir' too long, it would be ", + "truncated and thus not possible to use for connection to ", + "MariaDB Server. Set a shorter with --tmpdir=<path> option"); + } + + # copy all files from std_data into var/std_data + # and make them world readable + copytree("$glob_mysql_test_dir/std_data", "$opt_vardir/std_data", "0022"); + + unless($plugindir) + { + # create a plugin dir and copy or symlink plugins into it + if ($source_dist) + { + $plugindir="$opt_vardir/plugins"; + # Source builds collect both client plugins and server plugins in the + # same directory. + $client_plugindir= $plugindir; + mkpath($plugindir); + if (IS_WINDOWS) + { + if (!$opt_embedded_server) + { + for (<$bindir/storage/*$multiconfig/*.dll>, + <$bindir/plugin/*$multiconfig/*.dll>, + <$bindir/libmariadb$multiconfig/*.dll>, + <$bindir/sql$multiconfig/*.dll>) + { + my $pname=basename($_); + copy rel2abs($_), "$plugindir/$pname"; + set_plugin_var($pname); + } + } + } + else + { + my $opt_use_copy= 1; + if (symlink "$opt_vardir/run", "$plugindir/symlink_test") + { + $opt_use_copy= 0; + unlink "$plugindir/symlink_test"; + } + + for (<$bindir/storage/*$multiconfig/*.so>, + <$bindir/plugin/*$multiconfig/*.so>, + <$bindir/libmariadb/plugins/*/*.so>, + <$bindir/libmariadb/$multiconfig/*.so>, + <$bindir/sql$multiconfig/*.so>) + { + my $pname=basename($_); + if ($opt_use_copy) + { + copy rel2abs($_), "$plugindir/$pname"; + } + else + { + symlink rel2abs($_), "$plugindir/$pname"; + } + set_plugin_var($pname); + } + } + } + else + { + # hm, what paths work for debs and for rpms ? + for (<$bindir/lib64/mysql/plugin/*.so>, + <$bindir/lib/mysql/plugin/*.so>, + <$bindir/lib64/mariadb/plugin/*.so>, + <$bindir/lib/mariadb/plugin/*.so>, + <$bindir/lib/plugin/*.so>, # bintar + <$bindir/lib/plugin/*.dll>) + { + my $pname= basename($_); + set_plugin_var($pname); + $plugindir= dirname($_) unless $plugindir; + } + + # Note: client plugins can be installed separately from server plugins, + # as is the case for Debian packaging. + for (<$bindir/lib/*/libmariadb3/plugin>) + { + $client_plugindir= $_ if <$_/*.so>; + } + $client_plugindir= $plugindir unless $client_plugindir; + } + } + + # Remove old log files + foreach my $name (glob("r/*.progress r/*.log r/*.warnings")) + { + unlink($name); + } +} + + +# +# Check if running as root +# i.e a file can be read regardless what mode we set it to +# +sub check_running_as_root () { + my $test_file= "$opt_vardir/test_running_as_root.txt"; + mtr_tofile($test_file, "MySQL"); + chmod(oct("0000"), $test_file); + + my $result=""; + if (open(FILE,"<",$test_file)) + { + $result= join('', <FILE>); + close FILE; + } + + # Some filesystems( for example CIFS) allows reading a file + # although mode was set to 0000, but in that case a stat on + # the file will not return 0000 + my $file_mode= (stat($test_file))[2] & 07777; + + mtr_verbose("result: $result, file_mode: $file_mode"); + if ($result eq "MySQL" && $file_mode == 0) + { + mtr_warning("running this script as _root_ will cause some " . + "tests to be skipped"); + $ENV{'MYSQL_TEST_ROOT'}= "1"; + } + + chmod(oct("0755"), $test_file); + unlink($test_file); +} + + +sub check_ssl_support { + if ($opt_skip_ssl) + { + mtr_report(" - skipping SSL"); + $opt_ssl_supported= 0; + $opt_ssl= 0; + return; + } + + if ( ! $mysqld_variables{'ssl'} ) + { + if ( $opt_ssl) + { + mtr_error("Couldn't find support for SSL"); + return; + } + mtr_report(" - skipping SSL, mysqld not compiled with SSL"); + $opt_ssl_supported= 0; + $opt_ssl= 0; + return; + } + mtr_report(" - SSL connections supported"); + $opt_ssl_supported= 1; +} + +sub check_debug_support { + if (defined $mysqld_variables{'debug-dbug'}) + { + mtr_report(" - binaries are debug compiled"); + } + elsif ($opt_debug_server) + { + mtr_error("Can't use --debug[-server], binary does not support it"); + } +} + + +# +# Helper function to find the correct value for the multiconfig +# if it was not set explicitly. +# +# the configuration with the most recent build dir in sql/ is selected. +# +# note: looking for all BuildLog.htm files everywhere in the tree with the +# help of File::Find would be possibly more precise, but it is also +# many times slower. Thus we are only looking at the server, client +# executables, and plugins - that is, something that can affect the test suite +# +sub fix_vs_config_dir () { + return $multiconfig="/$multiconfig" if $multiconfig; + + my $modified = 1e30; + $multiconfig=""; + + + for (<$bindir/sql/*/mysqld.exe>, + <$bindir/sql/*/mysqld> + ) { #/ + if (-M $_ < $modified) + { + $modified = -M _; + $multiconfig = basename(dirname($_)); + } + } + + mtr_report("VS config: $multiconfig"); + $multiconfig="/$multiconfig" if $multiconfig; +} + + +# +# Helper function to handle configuration-based subdirectories which Visual +# Studio uses for storing binaries. If multiconfig is set, this returns +# a path based on that setting; if not, it returns paths for the default +# /release/ and /debug/ subdirectories. +# +# $exe can be undefined, if the directory itself will be used +# +sub vs_config_dirs ($$) { + my ($path_part, $exe) = @_; + + $exe = "" if not defined $exe; + + # Don't look in these dirs when not on windows + return () unless IS_WINDOWS; + + if ($multiconfig) + { + return ("$basedir/$path_part/$multiconfig/$exe"); + } + + return ("$basedir/$path_part/release/$exe", + "$basedir/$path_part/relwithdebinfo/$exe", + "$basedir/$path_part/debug/$exe"); +} + +sub mysql_server_start($) { + my ($mysqld, $tinfo) = @_; + + if ( $mysqld->{proc} ) + { + # Already started + + # Write start of testcase to log file + mark_log($mysqld->value('log-error'), $tinfo); + + return; + } + + my $datadir= $mysqld->value('datadir'); + if (not $opt_start_dirty) + { + + my @options= ('log-bin', 'relay-log'); + foreach my $option_name ( @options ) { + next unless $mysqld->option($option_name); + + my $file_name= $mysqld->value($option_name); + next unless + defined $file_name and + -e $file_name; + + mtr_debug(" -removing '$file_name'"); + unlink($file_name) or die ("unable to remove file '$file_name'"); + } + + if (-d $datadir ) { + mtr_verbose(" - removing '$datadir'"); + rmtree($datadir); + } + } + + my $mysqld_basedir= $mysqld->value('basedir'); + my $extra_opts= get_extra_opts($mysqld, $tinfo); + + if ( $basedir eq $mysqld_basedir ) + { + if (! $opt_start_dirty) # If dirty, keep possibly grown system db + { + # Some InnoDB options are incompatible with the default bootstrap. + # If they are used, re-bootstrap + my @rebootstrap_opts; + @rebootstrap_opts = grep {/$rebootstrap_re/o} @$extra_opts if $extra_opts; + if (@rebootstrap_opts) + { + mtr_verbose("Re-bootstrap with @rebootstrap_opts"); + mysql_install_db($mysqld, undef, \@rebootstrap_opts); + } + else { + # Copy datadir from installed system db + my $path= ($opt_parallel == 1) ? "$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; + } + } + } + else + { + mysql_install_db($mysqld); # For versional testing + + mtr_error("Failed to install system db to '$datadir'") + unless -d $datadir; + + } + + # Create the servers tmpdir + my $tmpdir= $mysqld->value('tmpdir'); + mkpath($tmpdir) unless -d $tmpdir; + + # Write start of testcase to log file + mark_log($mysqld->value('log-error'), $tinfo); + + # Run <tname>-master.sh + if ($mysqld->option('#!run-master-sh') and + defined $tinfo->{master_sh} and + run_system('/bin/sh ' . $tinfo->{master_sh}) ) + { + $tinfo->{'comment'}= "Failed to execute '$tinfo->{master_sh}'"; + return 1; + } + + # Run <tname>-slave.sh + if ($mysqld->option('#!run-slave-sh') and + defined $tinfo->{slave_sh} and + run_system('/bin/sh ' . $tinfo->{slave_sh})) + { + $tinfo->{'comment'}= "Failed to execute '$tinfo->{slave_sh}'"; + return 1; + } + + if (!$opt_embedded_server) + { + mysqld_start($mysqld, $extra_opts) or + mtr_error("Failed to start mysqld ".$mysqld->name()." with command " + . $ENV{MYSQLD_LAST_CMD}); + + # Save this test case information, so next can examine it + $mysqld->{'started_tinfo'}= $tinfo; + } + + # If wsrep is on, we need to wait until the first + # server starts and bootstraps the cluster before + # starting other servers. The bootsrap server in the + # configuration should always be the first which has + # wsrep_on=ON + if (wsrep_on($mysqld) && wsrep_is_bootstrap_server($mysqld)) + { + mtr_verbose("Waiting for wsrep bootstrap server to start"); + if ($mysqld->{WAIT}->($mysqld)) + { + return 1; + } + } +} + +sub mysql_server_wait { + my ($mysqld, $tinfo) = @_; + my $expect_file= "$opt_vardir/tmp/".$mysqld->name().".expect"; + + if (!sleep_until_file_created($mysqld->value('pid-file'), $expect_file, + $opt_start_timeout, $mysqld->{'proc'}, + $warn_seconds)) + { + $tinfo->{comment}= "Failed to start ".$mysqld->name() . "\n"; + return 1; + } + + if (wsrep_on($mysqld)) + { + mtr_verbose("Waiting for wsrep server " . $mysqld->name() . " to be ready"); + if (!wait_wsrep_ready($tinfo, $mysqld)) + { + return 1; + } + } + + return 0; +} + +sub create_config_file_for_extern { + my %opts= + ( + socket => '/tmp/mysqld.sock', + port => 3306, + user => $opt_user, + password => '', + @_ + ); + + mtr_report("Creating my.cnf file for extern server..."); + my $F= IO::File->new($path_config_file, "w") + or mtr_error("Can't write to $path_config_file: $!"); + + print $F "[client]\n"; + while (my ($option, $value)= each( %opts )) { + print $F "$option= $value\n"; + mtr_report(" $option= $value"); + } + + print $F <<EOF + +# binlog reads from [client] and [mysqlbinlog] +[mysqlbinlog] +character-sets-dir= $path_charsetsdir +local-load= $opt_tmpdir + +EOF +; + + $F= undef; # Close file +} + + +# +# Kill processes left from previous runs, normally +# there should be none so make sure to warn +# if there is one +# +sub kill_leftovers ($) { + my $rundir= shift; + return unless ( -d $rundir ); + + mtr_report("Checking leftover processes..."); + + # Scan the "run" directory for process id's to kill + opendir(RUNDIR, $rundir) + or mtr_error("kill_leftovers, can't open dir \"$rundir\": $!"); + while ( my $elem= readdir(RUNDIR) ) + { + # Only read pid from files that end with .pid + if ( $elem =~ /.*[.]pid$/ ) + { + my $pidfile= "$rundir/$elem"; + next unless -f $pidfile; + my $pid= mtr_fromfile($pidfile); + unlink($pidfile); + unless ($pid=~ /^(\d+)/){ + # The pid was not a valid number + mtr_warning("Got invalid pid '$pid' from '$elem'"); + next; + } + mtr_report(" - found old pid $pid in '$elem', killing it..."); + + my $ret= kill("KILL", $pid); + if ($ret == 0) { + mtr_report(" process did not exist!"); + next; + } + + my $check_counter= 100; + while ($ret > 0 and $check_counter--) { + mtr_milli_sleep(100); + $ret= kill(0, $pid); + } + mtr_report($check_counter ? " ok!" : " failed!"); + } + else + { + mtr_warning("Found non pid file '$elem' in '$rundir'") + if -f "$rundir/$elem"; + } + } + closedir(RUNDIR); +} + +# +# Check that all the ports that are going to +# be used are free +# +sub check_ports_free ($) +{ + my $bthread= shift; + my $portbase = $bthread * $opt_port_group_size + 10000; + for ($portbase..$portbase+($opt_port_group_size-1)){ + if (mtr_ping_port($_)){ + mtr_report(" - 'localhost:$_' was not free"); + return 0; # One port was not free + } + } + + return 1; # All ports free +} + + +sub initialize_servers { + + if ( using_extern() ) + { + # Running against an already started server, if the specified + # vardir does not already exist it should be created + if ( ! -d $opt_vardir ) + { + setup_vardir(); + } + else + { + mtr_verbose("No need to create '$opt_vardir' it already exists"); + } + } + else + { + # Kill leftovers from previous run + # using any pidfiles found in var/run + kill_leftovers("$opt_vardir/run"); + + if ( ! $opt_start_dirty ) + { + remove_stale_vardir(); + setup_vardir(); + } + } +} + + +# +# Remove all newline characters expect after semicolon +# +sub sql_to_bootstrap { + my ($sql) = @_; + my @lines= split(/\R/, $sql); + my $result= "\n"; + my $delimiter= ';'; + + foreach my $line (@lines) { + + # Change current delimiter if line starts with "delimiter" + if ( $line =~ /^delimiter (.*)/ ) { + my $new= $1; + # Remove old delimiter from end of new + $new=~ s/\Q$delimiter\E$//; + $delimiter = $new; + mtr_debug("changed delimiter to $delimiter"); + # No need to add the delimiter to result + next; + } + + # Add newline if line ends with $delimiter + # and convert the current delimiter to semicolon + if ( $line =~ /\Q$delimiter\E$/ ){ + $line =~ s/\Q$delimiter\E$/;/; + $result.= "$line\n"; + mtr_debug("Added default delimiter"); + next; + } + + # Remove comments starting with -- + if ( $line =~ /^\s*--/ ) { + mtr_debug("Discarded $line"); + next; + } + + # Replace @HOSTNAME with localhost + $line=~ s/\'\@HOSTNAME\@\'/localhost/; + + # Default, just add the line without newline + # but with a space as separator + $result.= "$line "; + + } + return $result; +} + + +sub default_mysqld { + # Generate new config file from template + environment_setup(); + my $config= My::ConfigFactory->new_config + ( { + basedir => $basedir, + testdir => $glob_mysql_test_dir, + template_path => "include/default_my.cnf", + vardir => $opt_vardir, + tmpdir => $opt_tmpdir, + baseport => 0, + user => $opt_user, + password => '', + } + ); + + my $mysqld= $config->group('mysqld.1') + or mtr_error("Couldn't find mysqld.1 in default config"); + return $mysqld; +} + + +sub mysql_install_db { + my ($mysqld, $datadir, $extra_opts)= @_; + + my $install_datadir= $datadir || $mysqld->value('datadir'); + my $install_basedir= $mysqld->value('basedir'); + my $install_lang= $mysqld->value('lc-messages-dir'); + my $install_chsdir= $mysqld->value('character-sets-dir'); + + mtr_report("Installing system database..."); + + my $args; + mtr_init_args(\$args); + mtr_add_arg($args, "--no-defaults"); + mtr_add_arg($args, "--disable-getopt-prefix-matching"); + mtr_add_arg($args, "--bootstrap"); + mtr_add_arg($args, "--basedir=%s", $install_basedir); + mtr_add_arg($args, "--datadir=%s", $install_datadir); + mtr_add_arg($args, "--plugin-dir=%s", $plugindir); + mtr_add_arg($args, "--default-storage-engine=myisam"); + mtr_add_arg($args, "--loose-skip-plugin-$_") for @optional_plugins; + # starting from 10.0 bootstrap scripts require InnoDB + mtr_add_arg($args, "--loose-innodb"); + mtr_add_arg($args, "--loose-innodb-log-file-size=10M"); + mtr_add_arg($args, "--disable-sync-frm"); + mtr_add_arg($args, "--tmpdir=%s", "$opt_vardir/tmp/"); + mtr_add_arg($args, "--core-file"); + mtr_add_arg($args, "--console"); + mtr_add_arg($args, "--character-set-server=latin1"); + + if ( $opt_debug ) + { + mtr_add_arg($args, "--debug-dbug=$debug_d:t:i:A,%s/log/bootstrap.trace", + $path_vardir_trace); + } + + mtr_add_arg($args, "--lc-messages-dir=%s", $install_lang); + mtr_add_arg($args, "--character-sets-dir=%s", $install_chsdir); + + # InnoDB arguments that affect file location and sizes may + # need to be given to the bootstrap process as well as the + # server process. + foreach my $extra_opt ( @opt_extra_mysqld_opt ) { + if ($extra_opt =~ /--innodb/) { + mtr_add_arg($args, $extra_opt); + } + } + + # If DISABLE_GRANT_OPTIONS is defined when the server is compiled (e.g., + # configure --disable-grant-options), mysqld will not recognize the + # --bootstrap or --skip-grant-tables options. The user can set + # MYSQLD_BOOTSTRAP to the full path to a mysqld which does accept + # --bootstrap, to accommodate this. + my $exe_mysqld_bootstrap = + $ENV{'MYSQLD_BOOTSTRAP'} || find_mysqld($install_basedir); + + # ---------------------------------------------------------------------- + # export MYSQLD_BOOTSTRAP_CMD variable containing <path>/mysqld <args> + # ---------------------------------------------------------------------- + $ENV{'MYSQLD_BOOTSTRAP_CMD'}= "$exe_mysqld_bootstrap " . join(" ", @$args) + unless defined $ENV{'MYSQLD_BOOTSTRAP_CMD'}; + + # Extra options can come not only from the command line, but also + # from option files or combinations. We want them on a command line + # that is executed now, because otherwise the datadir might be + # incompatible with the test settings, but not on the general + # $MYSQLD_BOOTSTRAP_CMD line + foreach my $extra_opt ( @$extra_opts ) { + mtr_add_arg($args, $extra_opt); + } + + # ---------------------------------------------------------------------- + # Create the bootstrap.sql file + # ---------------------------------------------------------------------- + my $bootstrap_sql_file= "$opt_vardir/log/bootstrap.sql"; + $ENV{'MYSQL_BOOTSTRAP_SQL_FILE'}= $bootstrap_sql_file; + + if (! -e $bootstrap_sql_file) + { + My::Debugger::setup_boot_args(\$args, \$exe_mysqld_bootstrap, $bootstrap_sql_file); + + my $path_sql= my_find_file($install_basedir, + ["mysql", "sql/share", "share/mariadb", + "share/mysql", "share", "scripts"], + "mysql_system_tables.sql", + NOT_REQUIRED); + + if (-f $path_sql ) + { + my $sql_dir= dirname($path_sql); + # Use the mysql database for system tables + mtr_tofile($bootstrap_sql_file, "use mysql;\n"); + + # Add the offical mysql system tables + # for a production system + mtr_appendfile_to_file("$sql_dir/mysql_system_tables.sql", + $bootstrap_sql_file); + + my $gis_sp_path = $source_dist ? "$bindir/scripts" : $sql_dir; + mtr_appendfile_to_file("$gis_sp_path/maria_add_gis_sp_bootstrap.sql", + $bootstrap_sql_file); + + # Add the performance tables + # for a production system + mtr_appendfile_to_file("$sql_dir/mysql_performance_tables.sql", + $bootstrap_sql_file); + + # Add the mysql system tables initial data + # for a production system + mtr_appendfile_to_file("$sql_dir/mysql_system_tables_data.sql", + $bootstrap_sql_file); + + # Add test data for timezone - this is just a subset, on a real + # system these tables will be populated either by mysql_tzinfo_to_sql + # or by downloading the timezone table package from our website + mtr_appendfile_to_file("$sql_dir/mysql_test_data_timezone.sql", + $bootstrap_sql_file); + + # Fill help tables, just an empty file when running from bk repo + # but will be replaced by a real fill_help_tables.sql when + # building the source dist + mtr_appendfile_to_file("$sql_dir/fill_help_tables.sql", + $bootstrap_sql_file); + + # Append sys schema + mtr_appendfile_to_file("$gis_sp_path/mysql_sys_schema.sql", + $bootstrap_sql_file); + + mtr_tofile($bootstrap_sql_file, "CREATE DATABASE IF NOT EXISTS test CHARACTER SET latin1 COLLATE latin1_swedish_ci;\n"); + + # mysql.gtid_slave_pos was created in InnoDB, but many tests + # run without InnoDB. Alter it to Aria now + mtr_tofile($bootstrap_sql_file, "ALTER TABLE gtid_slave_pos ENGINE=Aria transactional=0;\n"); + } + else + { + # Install db from init_db.sql that exist in early 5.1 and 5.0 + # versions of MySQL + my $init_file= "$install_basedir/mysql-test/lib/init_db.sql"; + mtr_report(" - from '$init_file'"); + my $text= mtr_grab_file($init_file) or + mtr_error("Can't open '$init_file': $!"); + + mtr_tofile($bootstrap_sql_file, + sql_to_bootstrap($text)); + } + + # Remove anonymous users + mtr_tofile($bootstrap_sql_file, + "DELETE FROM mysql.global_priv where user= '';\n"); + + # Create mtr database + mtr_tofile($bootstrap_sql_file, + "CREATE DATABASE mtr CHARSET=latin1;\n"); + + # Add help tables and data for warning detection and supression + mtr_tofile($bootstrap_sql_file, + sql_to_bootstrap(mtr_grab_file("include/mtr_warnings.sql"))); + + # Add procedures for checking server is restored after testcase + mtr_tofile($bootstrap_sql_file, + sql_to_bootstrap(mtr_grab_file("include/mtr_check.sql"))); + } + + # Log bootstrap command + my $path_bootstrap_log= "$opt_vardir/log/bootstrap.log"; + mtr_tofile($path_bootstrap_log, + "$exe_mysqld_bootstrap " . join(" ", @$args) . "\n"); + + # Create directories mysql + mkpath("$install_datadir/mysql"); + + my $realtime= gettimeofday(); + if ( My::SafeProcess->run + ( + name => "bootstrap", + path => $exe_mysqld_bootstrap, + args => \$args, + input => $bootstrap_sql_file, + output => $path_bootstrap_log, + error => $path_bootstrap_log, + append => 1, + verbose => $opt_verbose, + ) != 0) + { + find( + { + no_chdir => 1, + wanted => sub + { + My::CoreDump::core_wanted(\$num_saved_cores, + $opt_max_save_core, + @opt_cases == 0, + $exe_mysqld_bootstrap, $opt_parallel); + } + }, + $install_datadir); + + my $data= mtr_grab_file($path_bootstrap_log); + mtr_error("Error executing mariadbd --bootstrap\n" . + "Could not install system database from $bootstrap_sql_file\n" . + "The $path_bootstrap_log file contains:\n$data\n"); + } + else + { + mtr_verbose("Spent " . sprintf("%.3f", (gettimeofday() - $realtime)) . " seconds in bootstrap"); + } +} + + +sub run_testcase_check_skip_test($) +{ + my ($tinfo)= @_; + + # ---------------------------------------------------------------------- + # Skip some tests silently + # ---------------------------------------------------------------------- + + my $start_from= $mtr_cases::start_from; + if ( $start_from ) + { + if ($tinfo->{'name'} eq $start_from || + $tinfo->{'shortname'} eq $start_from) + { + ## Found parting test. Run this test and all tests after this one + $mtr_cases::start_from= ""; + } + else + { + $tinfo->{'result'}= 'MTR_RES_SKIPPED'; + return 1; + } + } + + # ---------------------------------------------------------------------- + # If marked to skip, just print out and return. + # Note that a test case not marked as 'skip' can still be + # skipped later, because of the test case itself in cooperation + # with the mysqltest program tells us so. + # ---------------------------------------------------------------------- + + if ( $tinfo->{'skip'} ) + { + mtr_report_test_skipped($tinfo) unless $start_only; + return 1; + } + + return 0; +} + + +sub run_query { + my ($tinfo, $mysqld, $query)= @_; + + my $args; + mtr_init_args(\$args); + mtr_add_arg($args, "--defaults-file=%s", $path_config_file); + mtr_add_arg($args, "--defaults-group-suffix=%s", $mysqld->after('mysqld')); + + mtr_add_arg($args, "-e %s", $query); + + my $res= My::SafeProcess->run + ( + name => "run_query -> ".$mysqld->name(), + path => $exe_mysql, + args => \$args, + output => '/dev/null', + error => '/dev/null' + ); + + return $res +} + + +sub do_before_run_mysqltest($) +{ + my $tinfo= shift; + my $resfile= $tinfo->{result_file}; + return unless defined $resfile; + + # Remove old files produced by mysqltest + die "unsupported result file name $resfile, stoping" unless + $resfile =~ /^(.*?)((?:,\w+)*)\.(rdiff|result|result~)$/; + my ($base_file, $suites, $ext)= ($1, $2, $3); + # if the result file is a diff, make a proper result file + if ($ext eq 'rdiff') { + my $base_result = $tinfo->{base_result}; + my $resdir= dirname($resfile); + # we'll use a separate extension for generated result files + # to be able to distinguish them from manually created + # version-controlled results, and to ignore them in git. + my $dest = "$base_file$suites.result~"; + my @cmd = ($exe_patch); + if ($^O eq "MSWin32") { + push @cmd, '--binary'; + } + push @cmd, (qw/-r - -f -s -o/, $dest . $$, $base_result, $resfile); + if (-w $resdir) { + # don't rebuild a file if it's up to date + unless (-e $dest and -M $dest < -M $resfile + and -M $dest < -M $base_result) { + run_system(@cmd); + rename $cmd[-3], $dest or unlink $cmd[-3]; + } + } else { + $dest = $opt_tmpdir . '/' . basename($dest); + $cmd[-3] = $dest . $$; + run_system(@cmd); + rename $cmd[-3], $dest or unlink $cmd[-3]; + } + + $tinfo->{result_file} = $dest; + } + + unlink("$base_file.reject"); + unlink("$base_file.progress"); + unlink("$base_file.log"); + unlink("$base_file.warnings"); +} + + +# +# Check all server for sideffects +# +# RETURN VALUE +# 0 ok +# 1 Check failed +# >1 Fatal errro + +sub check_testcase($$) +{ + my ($tinfo, $mode)= @_; + my $tname= $tinfo->{name}; + + # Start the mysqltest processes in parallel to save time + # also makes it possible to wait for any process to exit during the check + my %started; + foreach my $mysqld ( mysqlds() ) + { + # Skip if server has been restarted with additional options + if ( defined $mysqld->{'proc'} && ! exists $mysqld->{'restart_opts'} ) + { + my $proc= start_check_testcase($tinfo, $mode, $mysqld); + $started{$proc->pid()}= $proc; + } + } + + # Return immediately if no check proceess was started + return 0 unless ( keys %started ); + + my $timeout= start_timer(check_timeout($tinfo)); + + while (1){ + my $result; + my $proc= My::SafeProcess->wait_any_timeout($timeout); + mtr_report("Got $proc"); + + if ( delete $started{$proc->pid()} ) { + + my $err_file= $proc->user_data(); + my $base_file= mtr_match_extension($err_file, "err"); # Trim extension + + # One check testcase process returned + my $res= $proc->exit_status(); + + if ( $res == 0){ + # Check completed without problem + + # Remove the .err file the check generated + unlink($err_file); + + # Remove the .result file the check generated + if ( $mode eq 'after' ){ + unlink("$base_file.result"); + } + + if ( keys(%started) == 0){ + # All checks completed + mark_time_used('check'); + return 0; + } + # Wait for next process to exit + next; + } + else + { + if ( $mode eq "after" and $res == 1 ) + { + # Test failed, grab the report mysqltest has created + my $report= mtr_grab_file($err_file); + $tinfo->{check}.= + "\nMTR's internal check of the test case '$tname' failed. +This means that the test case does not preserve the state that existed +before the test case was executed. Most likely the test case did not +do a proper clean-up. It could also be caused by the previous test run +by this thread, if the server wasn't restarted. +This is the diff of the states of the servers before and after the +test case was executed:\n"; + $tinfo->{check}.= $report; + + # Check failed, mark the test case with that info + $tinfo->{'check_testcase_failed'}= 1; + $result= 1; + } + elsif ( $res ) + { + my $report= mtr_grab_file($err_file); + $tinfo->{comment}.= + "Could not execute 'check-testcase' $mode ". + "testcase '$tname' (res: $res):\n"; + $tinfo->{comment}.= $report; + + $result= 2; + } + else + { + # Remove the .result file the check generated + unlink("$base_file.result"); + } + # Remove the .err file the check generated + unlink($err_file); + + } + } + elsif ( $proc->{timeout} ) { + $tinfo->{comment}.= "Timeout for 'check-testcase' expired after " + .check_timeout($tinfo)." seconds"; + $result= 4; + } + else { + # Unknown process returned, most likley a crash, abort everything + $tinfo->{comment}= + "The server $proc crashed while running ". + "'check testcase $mode test'". + get_log_from_proc($proc, $tinfo->{name}); + $result= 3; + } + + # Kill any check processes still running + map($_->kill(), values(%started)); + + mtr_warning("Check-testcase failed, this could also be caused by the" . + " previous test run by this worker thread") + if $result > 1 && $mode eq "before"; + mark_time_used('check'); + + return $result; + } + + mtr_error("INTERNAL_ERROR: check_testcase"); +} + + +# Start run mysqltest on one server +# +# RETURN VALUE +# 0 OK +# 1 Check failed +# +sub start_run_one ($$) { + my ($mysqld, $run)= @_; + + my $name= "$run-".$mysqld->name(); + + my $args; + mtr_init_args(\$args); + + mtr_add_arg($args, "--defaults-file=%s", $path_config_file); + mtr_add_arg($args, "--defaults-group-suffix=%s", $mysqld->after('mysqld')); + + mtr_add_arg($args, "--silent"); + mtr_add_arg($args, "--test-file=%s", "include/$run.test"); + + my $errfile= "$opt_vardir/tmp/$name.err"; + my $proc= My::SafeProcess->new + ( + name => $name, + path => $exe_mysqltest, + error => $errfile, + output => $errfile, + args => \$args, + user_data => $errfile, + verbose => $opt_verbose, + ); + mtr_verbose("Started $proc"); + return $proc; +} + + +# +# Run script on all servers, collect results +# +# RETURN VALUE +# 0 ok +# 1 Failure + +sub run_on_all($$) +{ + my ($tinfo, $run)= @_; + + # Start the mysqltest processes in parallel to save time + # also makes it possible to wait for any process to exit during the check + # and to have a timeout process + my %started; + foreach my $mysqld ( mysqlds() ) + { + if ( defined $mysqld->{'proc'} ) + { + my $proc= start_run_one($mysqld, $run); + $started{$proc->pid()}= $proc; + } + } + + # Return immediately if no check proceess was started + return 0 unless ( keys %started ); + + my $timeout= start_timer(check_timeout($tinfo)); + + while (1){ + my $result; + my $proc= My::SafeProcess->wait_any_timeout($timeout); + mtr_report("Got $proc"); + + if ( delete $started{$proc->pid()} ) { + + # One mysqltest process returned + my $err_file= $proc->user_data(); + my $res= $proc->exit_status(); + + # Append the report from .err file + $tinfo->{comment}.= " == $err_file ==\n"; + $tinfo->{comment}.= mtr_grab_file($err_file); + $tinfo->{comment}.= "\n"; + + # Remove the .err file + unlink($err_file); + + if ( keys(%started) == 0){ + # All completed + return 0; + } + + # Wait for next process to exit + next; + } + elsif ($proc->{timeout}) { + $tinfo->{comment}.= "Timeout for '$run' expired after " + .check_timeout($tinfo)." seconds"; + } + else { + # Unknown process returned, most likley a crash, abort everything + $tinfo->{comment}.= + "The server $proc crashed while running '$run'". + get_log_from_proc($proc, $tinfo->{name}); + } + + # Kill any check processes still running + map($_->kill(), values(%started)); + + return 1; + } + mtr_error("INTERNAL_ERROR: run_on_all"); +} + + +sub mark_log { + my ($log, $tinfo)= @_; + my $log_msg= "CURRENT_TEST: $tinfo->{name}\n"; + pre_write_errorlog($log, $tinfo->{name}); + mtr_tofile($log, $log_msg); +} + + +sub find_testcase_skipped_reason($) +{ + my ($tinfo)= @_; + + # Set default message + $tinfo->{'comment'}= "Detected by testcase(no log file)"; + + # Open the test log file + my $F= IO::File->new($path_current_testlog) + or return; + my $reason; + + while ( my $line= <$F> ) + { + # Look for "reason: <reason for skipping test>" + if ( $line =~ /reason: (.*)/ ) + { + $reason= $1; + } + } + + if ( ! $reason ) + { + mtr_warning("Could not find reason for skipping test in $path_current_testlog"); + $reason= "Detected by testcase(reason unknown) "; + } + $tinfo->{'comment'}= $reason; +} + + +sub find_analyze_request +{ + # Open the test log file + my $F= IO::File->new($path_current_testlog) + or return; + my $analyze; + + while ( my $line= <$F> ) + { + # Look for "reason: <reason for skipping test>" + if ( $line =~ /analyze: (.*)/ ) + { + $analyze= $1; + } + } + + return $analyze; +} + +# The test can leave a file in var/tmp/ to signal +# that all servers should be restarted +sub restart_forced_by_test($) +{ + my $file = shift; + my $restart = 0; + foreach my $mysqld ( mysqlds() ) + { + my $datadir = $mysqld->value('datadir'); + my $force_restart_file = "$datadir/mtr/$file"; + if ( -f $force_restart_file ) + { + mtr_verbose("Restart of servers forced by test"); + $restart = 1; + last; + } + } + return $restart; +} + +# Return timezone value of tinfo or default value +sub timezone { + my ($tinfo)= @_; + local $_ = $tinfo->{timezone}; + return 'DEFAULT' unless defined $_; + no warnings 'uninitialized'; + s/\$\{(\w+)\}/$ENV{$1}/ge; + s/\$(\w+)/$ENV{$1}/ge; + $_; +} + +sub mycnf_create { + my ($config) = @_; + my $res; + + foreach my $group ($config->option_groups()) { + $res .= "[$group->{name}]\n"; + + foreach my $option ($group->options()) { + $res .= $option->name(); + my $value= $option->value(); + if (defined $value) { + $res .= "=$value"; + } + $res .= "\n"; + } + $res .= "\n"; + } + $res; +} + +sub config_files($) { + my ($tinfo) = @_; + ( + 'my.cnf' => \&mycnf_create, + $tinfo->{suite}->config_files() + ); +} + +sub _like { return $config ? $config->like($_[0]) : (); } +sub mysqlds { return _like('mysqld\.'); } + +sub fix_servers($) { + my ($tinfo) = @_; + return () unless $config; + my %servers = ( + qr/mysqld\./ => { + SORT => 300, + START => \&mysql_server_start, + WAIT => \&mysql_server_wait, + }, + $tinfo->{suite}->servers() + ); + for ($config->groups()) { + while (my ($re,$prop) = each %servers) { + @$_{keys %$prop} = values %$prop if $_->{name} =~ /^$re/; + } + } +} + +sub all_servers { + return unless $config; + ( sort { $a->{SORT} <=> $b->{SORT} } + grep { defined $_->{SORT} } $config->groups() ); +} + +# Storage for changed environment variables +our %old_env; + +sub resfile_report_test ($) { + my $tinfo= shift; + + resfile_new_test(); + + resfile_test_info("name", $tinfo->{name}); + resfile_test_info("variation", $tinfo->{combination}) + if $tinfo->{combination}; + resfile_test_info("start_time", isotime time); +} + + +# +# Run a single test case +# +# RETURN VALUE +# 0 OK +# > 0 failure +# + +sub run_testcase ($$) { + my ($tinfo, $server_socket)= @_; + my $print_freq=20; + + mtr_verbose("Running test:", $tinfo->{name}); + $ENV{'MTR_TEST_NAME'} = $tinfo->{name}; + resfile_report_test($tinfo) if $opt_resfile; + + for my $key (grep { /^MTR_COMBINATION/ } keys %ENV) + { + delete $ENV{$key}; + } + + if (ref $tinfo->{combinations} eq 'ARRAY') + { + for (my $i = 0; $i < @{$tinfo->{combinations}}; ++$i ) + { + my $combination = $tinfo->{combinations}->[$i]; + # Allow only alphanumerics plus _ - + . in combination names, + # or anything beginning with -- (the latter comes from --combination) + if ($combination && $combination !~ /^\w[-\w\.\+]*$/ + && $combination !~ /^--/) + { + mtr_error("Combination '$combination' contains illegal characters"); + } + $ENV{"MTR_COMBINATION_". uc(${combination})} = 1; + } + $ENV{"MTR_COMBINATIONS"} = join(',', @{$tinfo->{combinations}}); + } + elsif (exists $tinfo->{combinations}) + { + die 'Unexpected type of $tinfo->{combinations}'; + } + + # ------------------------------------------------------- + # Init variables that can change between each test case + # ------------------------------------------------------- + my $timezone= timezone($tinfo); + if ($timezone ne 'DEFAULT') { + $ENV{'TZ'}= $timezone; + } else { + delete($ENV{'TZ'}); + } + $ENV{MTR_SUITE_DIR} = $tinfo->{suite}->{dir}; + mtr_verbose("Setting timezone: $timezone"); + + if ( ! using_extern() ) + { + my @restart= servers_need_restart($tinfo); + if ( @restart != 0) { + # Remember that we restarted for this test case (count restarts) + $tinfo->{'restarted'}= 1; + stop_servers(reverse @restart); + if ($opt_warnings) { + check_warnings_post_shutdown($server_socket); + } + } + + if ( started(all_servers()) == 0 ) + { + + # Remove old datadirs + clean_datadir() unless $opt_start_dirty; + + # Restore old ENV + while (my ($option, $value)= each( %old_env )) { + if (defined $value){ + mtr_verbose("Restoring $option to $value"); + $ENV{$option}= $value; + + } else { + mtr_verbose("Removing $option"); + delete($ENV{$option}); + } + } + %old_env= (); + + mtr_verbose("Generating my.cnf from '$tinfo->{template_path}'"); + + # Generate new config file from template + $config= My::ConfigFactory->new_config + ( { + testname => $tinfo->{name}, + basedir => $basedir, + testdir => $glob_mysql_test_dir, + template_path => $tinfo->{template_path}, + extra_template_path => $tinfo->{extra_template_path}, + vardir => $opt_vardir, + tmpdir => $opt_tmpdir, + baseport => $baseport, + user => $opt_user, + password => '', + ssl => $opt_ssl_supported, + embedded => $opt_embedded_server, + } + ); + + fix_servers($tinfo); + + # Write config files: + my %config_files = config_files($tinfo); + while (my ($file, $generate) = each %config_files) { + next unless $generate; + my ($path) = "$opt_vardir/$file"; + open (F, '>', $path) or die "Could not open '$path': $!"; + print F &$generate($config); + close F; + } + + # Remember current config so a restart can occur when a test need + # to use a different one + $current_config_name= $tinfo->{template_path}; + + # + # Set variables in the ENV section + # + foreach my $option ($config->options_in_group("ENV")) + { + my ($name, $val)= ($option->name(), $option->value()); + + # Save old value to restore it before next time + $old_env{$name}= $ENV{$name}; + + unless (defined $val) { + mtr_warning("Uninitialized value for ", $name, + ", group [ENV], file ", $current_config_name); + } else { + mtr_verbose($name, "=", $val); + $ENV{$name}= $val; + } + } + } + + # Write start of testcase to log + mark_log($path_current_testlog, $tinfo); + + # Make sure the safe_process also exits from now on + if ($opt_start_exit) { + My::SafeProcess->start_exit(); + } + + if (start_servers($tinfo)) + { + report_failure_and_restart($tinfo); + unlink $path_current_testlog; + return 1; + } + } + mark_time_used('restart'); + + # -------------------------------------------------------------------- + # If --start or --start-dirty given, stop here to let user manually + # run tests + # If --wait-all is also given, do the same, but don't die if one + # server exits + # ---------------------------------------------------------------------- + + if ( $start_only ) + { + mtr_print("\nStarted", started(all_servers())); + mtr_print("Using config for test", $tinfo->{name}); + mtr_print("Port and socket path for server(s):"); + foreach my $mysqld ( mysqlds() ) + { + mtr_print ($mysqld->name() . " " . $mysqld->value('port') . + " " . $mysqld->value('socket')); + } + if ( $opt_start_exit ) + { + mtr_print("Server(s) started, not waiting for them to finish"); + if (IS_WINDOWS) + { + POSIX::_exit(0); # exit hangs here in ActiveState Perl + } + else + { + exit(0); + } + } + mtr_print("Waiting for server(s) to exit..."); + if ( $opt_wait_all ) { + My::SafeProcess->wait_all(); + mtr_print( "All servers exited" ); + exit(1); + } + else { + my $proc= My::SafeProcess->wait_any(); + if ( grep($proc eq $_, started(all_servers())) ) + { + mtr_print("Server $proc died"); + exit(1); + } + mtr_print("Unknown process $proc died"); + exit(1); + } + } + + my $test_timeout= start_timer(testcase_timeout($tinfo)); + + do_before_run_mysqltest($tinfo); + + mark_time_used('admin'); + + if ( $opt_check_testcases and check_testcase($tinfo, "before") ){ + # Failed to record state of server or server crashed + report_failure_and_restart($tinfo); + + return 1; + } + + my $test= $tinfo->{suite}->start_test($tinfo); + # Set to a list of processes we have to keep waiting (expectedly died servers) + my %keep_waiting_proc = (); + my $print_timeout= start_timer($print_freq * 60); + + while (1) + { + my $proc; + if (%keep_waiting_proc) + { + # Any other process exited? + $proc = My::SafeProcess->check_any(); + if ($proc) + { + mtr_verbose ("Found exited process $proc"); + } + else + { + # Also check if timer has expired, if so cancel waiting + if ( has_expired($test_timeout) ) + { + %keep_waiting_proc = (); + } + } + } + if (!%keep_waiting_proc && !$proc) + { + if ($test_timeout > $print_timeout) + { + $proc= My::SafeProcess->wait_any_timeout($print_timeout); + if ($proc->{timeout}) + { + #print out that the test is still on + mtr_print("Test still running: $tinfo->{name}"); + #reset the timer + $print_timeout= start_timer($print_freq * 60); + next; + } + } + else + { + $proc= My::SafeProcess->wait_any_timeout($test_timeout); + } + } + + if ($proc and $proc eq $test) # mysqltest itself exited + { + my $res= $test->exit_status(); + + if (($res == 0 or $res == 62) and $opt_warnings and check_warnings($tinfo) ) + { + # If test case suceeded, but it has produced unexpected + # warnings, continue with $res == 1; + # but if the test was skipped, it should remain skipped + $res= 1 if $res == 0; + resfile_output($tinfo->{'warnings'}) if $opt_resfile; + } + + if ( $res == 0 ) + { + my $check_res; + if ( restart_forced_by_test('force_restart') ) + { + stop_all_servers($opt_shutdown_timeout); + } + elsif ( $opt_check_testcases and + $check_res= check_testcase($tinfo, "after")) + { + if ($check_res == 1) { + # Test case had sideeffects, not fatal error, just continue + if ($opt_warnings) { + # Checking error logs for warnings, so need to stop server + # gracefully so that memory leaks etc. can be properly detected. + stop_servers(reverse all_servers()); + check_warnings_post_shutdown($server_socket); + # Even if we got warnings here, we should not fail this + # particular test, as the warnings may be caused by an earlier + # test. + } else { + # Not checking warnings, so can do a hard shutdown. + stop_all_servers($opt_shutdown_timeout); + } + mtr_report("Resuming tests...\n"); + resfile_output($tinfo->{'check'}) if $opt_resfile; + } + else { + # Test case check failed fatally, probably a server crashed + report_failure_and_restart($tinfo); + return 1; + } + } + mtr_report_test_passed($tinfo); + } + elsif ( $res == 62 ) + { + # Testcase itself tell us to skip this one + $tinfo->{skip_detected_by_test}= 1; + # Try to get reason from test log file + find_testcase_skipped_reason($tinfo); + mtr_report_test_skipped($tinfo); + # Restart if skipped due to missing perl, it may have had side effects + if ( restart_forced_by_test('force_restart_if_skipped') || + $tinfo->{'comment'} =~ /^perl not found/ ) + { + stop_all_servers($opt_shutdown_timeout); + } + } + elsif ( $res == 65 ) + { + # Testprogram killed by signal + $tinfo->{comment}= + "testprogram crashed(returned code $res)"; + report_failure_and_restart($tinfo); + } + elsif ( $res == 1 ) + { + # Check if the test tool requests that + # an analyze script should be run + my $analyze= find_analyze_request(); + if ($analyze){ + run_on_all($tinfo, "analyze-$analyze"); + } + + # Wait a bit and see if a server died, if so report that instead + mtr_milli_sleep(100); + my $srvproc= My::SafeProcess::check_any(); + if ($srvproc && grep($srvproc eq $_, started(all_servers()))) { + $proc= $srvproc; + goto SRVDIED; + } + + # Test case failure reported by mysqltest + report_failure_and_restart($tinfo); + } + else + { + # mysqltest failed, probably crashed + $tinfo->{comment}= + "mysqltest failed with unexpected return code $res\n"; + report_failure_and_restart($tinfo); + } + + # Save info from this testcase run to mysqltest.log + if( -f $path_current_testlog) + { + if ($opt_resfile && $res && $res != 62) { + resfile_output_file($path_current_testlog); + } + mtr_appendfile_to_file($path_current_testlog, $path_testlog); + unlink($path_current_testlog); + } + + return ($res == 62) ? 0 : $res; + } # if ($proc and $proc eq $test) + elsif ($proc) + { + # It was not mysqltest that exited, add to a wait-to-be-started-again list. + $keep_waiting_proc{$proc} = 1; + } + + mtr_verbose("Got " . join(",", keys(%keep_waiting_proc))); + + mark_time_used('test'); + foreach my $wait_for_proc (keys(%keep_waiting_proc)) { + # ---------------------------------------------------- + # Check if it was an expected crash + # ---------------------------------------------------- + my @mysqld = grep($wait_for_proc eq $_->{proc}, mysqlds()); + goto SRVDIED unless @mysqld; + my $check_crash = check_expected_crash_and_restart($mysqld[0]); + if ($check_crash == 0) # unexpected exit/crash of $wait_for_proc + { + $proc= $mysqld[0]->{proc}; + goto SRVDIED; + } + elsif ($check_crash == 1) # $wait_for_proc was started again by check_expected_crash_and_restart() + { + delete $keep_waiting_proc{$wait_for_proc}; + } + elsif ($check_crash == 2) # we must keep waiting + { + # do nothing + } + } # foreach my $wait_for_proc + + next; + + SRVDIED: + # ---------------------------------------------------- + # Stop the test case timer + # ---------------------------------------------------- + $test_timeout= 0; + + # ---------------------------------------------------- + # Check if it was a server that died + # ---------------------------------------------------- + if ( grep($proc eq $_, started(all_servers())) ) + { + # Server failed, probably crashed + $tinfo->{comment}= + "Server $proc failed during test run" . + get_log_from_proc($proc, $tinfo->{name}); + + # ---------------------------------------------------- + # It's not mysqltest that has exited, kill it + # ---------------------------------------------------- + $test->kill(); + + report_failure_and_restart($tinfo); + return 1; + } + + # Try to dump core for mysqltest and all servers + foreach my $proc ($test, started(all_servers())) + { + mtr_print("Trying to dump core for $proc"); + if ($proc->dump_core()) + { + $proc->wait_one(20); + } + } + + # ---------------------------------------------------- + # It's not mysqltest that has exited, kill it + # ---------------------------------------------------- + $test->kill(); + + # ---------------------------------------------------- + # Check if testcase timer expired + # ---------------------------------------------------- + if ( $proc->{timeout} ) + { + my $log_file_name= $opt_vardir."/log/".$tinfo->{shortname}.".log"; + $tinfo->{comment}= + "Test case timeout after ".testcase_timeout($tinfo). + " seconds\n\n"; + # Add 20 last executed commands from test case log file + if (-e $log_file_name) + { + $tinfo->{comment}.= + "== $log_file_name == \n". + mtr_lastlinesfromfile($log_file_name, 20)."\n"; + } + $tinfo->{'timeout'}= testcase_timeout($tinfo); # Mark as timeout + run_on_all($tinfo, 'analyze-timeout'); + + report_failure_and_restart($tinfo); + return 1; + } + + mtr_error("Unhandled process $proc exited"); + } + mtr_error("Should never come here"); +} + + +# Keep track of last position in mysqld error log where we scanned for +# warnings, so we can attribute any warnings found to the correct test +# suite or server restart. +our $last_warning_position= { }; + +# Called just before a mysqld server is started or a testcase is run, +# to keep track of which tests have been run since last restart, and +# of when the error log is reset. +# +# Second argument $test_name is test name, or undef for server restart. +sub pre_write_errorlog { + my ($error_log, $test_name)= @_; + + if (! -e $error_log) { + # If the error log is moved away, reset the warning parse position. + delete $last_warning_position->{$error_log}; + } + + if (defined($test_name)) { + $last_warning_position->{$error_log}{test_names}= [] + unless exists($last_warning_position->{$error_log}{test_names}); + push @{$last_warning_position->{$error_log}{test_names}}, $test_name; + } else { + # Server restart, so clear the list of tests run since last restart. + # (except the last one (if any), which is the test about to be run). + if (defined($last_warning_position->{$error_log}{test_names}) && + @{$last_warning_position->{$error_log}{test_names}}) { + $last_warning_position->{$error_log}{test_names}= + [$last_warning_position->{$error_log}{test_names}[-1]]; + } else { + $last_warning_position->{$error_log}{test_names}= []; + } + } +} + +# Extract server log from after the last occurrence of named test +# Return as an array of lines +# + +sub extract_server_log ($$) { + my ($error_log, $tname) = @_; + + return unless $error_log; + + # Open the servers .err log file and read all lines + # belonging to current test into @lines + my $Ferr = IO::File->new($error_log) + or mtr_error("Could not open file '$error_log' for reading: $!"); + + my @lines; + my $found_test= 0; # Set once we've found the log of this test + while ( my $line = <$Ferr> ) + { + if ($found_test) + { + # If test wasn't last after all, discard what we found, test again. + if ( $line =~ /^CURRENT_TEST:/) + { + @lines= (); + $found_test= $line =~ /^CURRENT_TEST: $tname/; + } + else + { + push(@lines, $line); + if (scalar(@lines) > 1000000) { + $Ferr = undef; + mtr_warning("Too much log in $error_log, bailing out from extracting"); + return (); + } + } + } + else + { + # Search for beginning of test, until found + $found_test= 1 if ($line =~ /^CURRENT_TEST: $tname/); + } + } + $Ferr = undef; # Close error log file + + return @lines; +} + +# Get log from server identified from its $proc object, from named test +# Return as a single string +# + +sub get_log_from_proc ($$) { + my ($proc, $name)= @_; + my $srv_log= ""; + + foreach my $mysqld (all_servers()) { + if ($mysqld->{proc} eq $proc) { + my @srv_lines= extract_server_log($mysqld->if_exist('log-error'), $name); + $srv_log= "\nServer log from this test:\n" . + "----------SERVER LOG START-----------\n". join ("", @srv_lines) . + "----------SERVER LOG END-------------\n"; + last; + } + } + return $srv_log; +} + +# +# Perform a rough examination of the servers +# error log and write all lines that look +# suspicious into $error_log.warnings +# + +sub extract_warning_lines ($$) { + my ($error_log, $append) = @_; + + # Open the servers .err log file and read all lines + # belonging to current tets into @lines + my $Ferr = IO::File->new($error_log) + or return []; + my $last_pos= $last_warning_position->{$error_log}{seek_pos}; + $Ferr->seek($last_pos, 0) if defined($last_pos); + # If the seek fails, we will parse the whole error log, at least we will not + # miss any warnings. + + my @lines= <$Ferr>; + $last_warning_position->{$error_log}{seek_pos}= $Ferr->tell(); + $Ferr = undef; # Close error log file + + # mysql_client_test.test sends a COM_DEBUG packet to the server + # to provoke a safemalloc leak report, ignore any warnings + # between "Begin/end safemalloc memory dump" + if ( grep(/Begin safemalloc memory dump:/, @lines) > 0) + { + my $discard_lines= 1; + foreach my $line ( @lines ) + { + if ($line =~ /Begin safemalloc memory dump:/){ + $discard_lines = 1; + } elsif ($line =~ /End safemalloc memory dump./){ + $discard_lines = 0; + } + + if ($discard_lines){ + $line = "ignored"; + } + } + } + + # Write all suspicious lines to $error_log.warnings file + my $warning_log = "$error_log.warnings"; + my $Fwarn = IO::File->new($warning_log, $append ? "a+" : "w") + or die("Could not open file '$warning_log' for writing: $!"); + if (!$append) + { + print $Fwarn "Suspicious lines from $error_log\n"; + } + + my @patterns = + ( + qr/^Warning|(mysqld|mariadbd): Warning|\[Warning\]/, + qr/^Error:|\[ERROR\]/, + qr/^==\d+==\s+\S/, # valgrind errors + qr/InnoDB: Warning|InnoDB: Error/, + qr/^safe_mutex:|allocated at line/, + qr/missing DBUG_RETURN/, + qr/Attempting backtrace/, + qr/Assertion .* failed/, + qr/Sanitizer/, + qr/runtime error:/, + ); + # These are taken from the include/mtr_warnings.sql global suppression + # list. They occur delayed, so they can be parsed during shutdown rather + # than during the per-test check. + # + # ToDo: having the warning suppressions inside the mysqld we are trying to + # check is in any case horrible. We should change it to all be done here + # within the Perl code, which is both simpler, easier, faster, and more + # robust. We could still have individual test cases put in suppressions by + # parsing statically or by writing dynamically to a CSV table read by the + # Perl code. + my @antipatterns = + ( + @global_suppressions, + qr/error .*connecting to master/, + qr/InnoDB: Error: in ALTER TABLE `test`.`t[12]`/, + qr/InnoDB: Error: table `test`.`t[12]` .*does not exist in the InnoDB internal/, + qr/InnoDB: Warning: a long semaphore wait:/, + qr/InnoDB: Dumping buffer pool.*/, + qr/InnoDB: Buffer pool.*/, + qr/InnoDB: Warning: Writer thread is waiting this semaphore:/, + qr/InnoDB: innodb_open_files .* should not be greater than/, + qr/Slave: Unknown table 't1' .* 1051/, + qr/Slave SQL:.*(Internal MariaDB error code: [[:digit:]]+|Query:.*)/, + qr/slave SQL thread aborted/, + qr/unknown option '--loose[-_]/, + qr/unknown variable 'loose[-_]/, + #qr/Invalid .*old.* table or database name/, + qr/Now setting lower_case_table_names to [02]/, + qr/Setting lower_case_table_names=2/, + qr/You have forced lower_case_table_names to 0/, + qr/deprecated/, + qr/Slave SQL thread retried transaction/, + qr/Slave \(additional info\)/, + qr/Incorrect information in file/, + qr/Simulating error for/, + qr/Slave I\/O: Get master SERVER_ID failed with error:.*/, + qr/Slave I\/O: Get master clock failed with error:.*/, + qr/Slave I\/O: Get master COLLATION_SERVER failed with error:.*/, + qr/Slave I\/O: Get master TIME_ZONE failed with error:.*/, + qr/Slave I\/O: Get master \@\@GLOBAL.gtid_domain_id failed with error:.*/, + qr/Slave I\/O: Setting \@slave_connect_state failed with error:.*/, + qr/Slave I\/O: Setting \@slave_gtid_strict_mode failed with error:.*/, + qr/Slave I\/O: Setting \@slave_gtid_ignore_duplicates failed with error:.*/, + qr/Slave I\/O: Setting \@slave_until_gtid failed with error:.*/, + qr/Slave I\/O: Get master GTID position failed with error:.*/, + qr/Slave I\/O: error reconnecting to master '.*' - retry-time: [1-3] retries/, + qr/Slave I\/0: Master command COM_BINLOG_DUMP failed/, + qr/Error reading packet/, + qr/Lost connection to MariaDB server at 'reading initial communication packet'/, + qr/Failed on request_dump/, + qr/Slave: Can't drop database.* database doesn't exist/, + qr/Slave: Operation DROP USER failed for 'create_rout_db'/, + qr|Checking table: '\..mtr.test_suppressions'|, + qr|Table \./test/bug53592 has a primary key in InnoDB data dictionary, but not in|, + qr|Table '\..mtr.test_suppressions' is marked as crashed and should be repaired|, + qr|Table 'test_suppressions' is marked as crashed and should be repaired|, + qr|Can't open shared library|, + qr|Couldn't load plugin named .*EXAMPLE.*|, + qr|InnoDB: Error: table 'test/bug39438'|, + qr| entry '.*' ignored in --skip-name-resolve mode|, + qr|mysqld got signal 6|, + qr|Error while setting value 'pool-of-threads' to 'thread_handling'|, + qr|Access denied for user|, + qr|Aborted connection|, + qr|table.*is full|, + qr/\[ERROR\] (mysqld|mariadbd): \Z/, # Warning from Aria recovery + qr|Linux Native AIO|, # warning that aio does not work on /dev/shm + qr|InnoDB: io_setup\(\) attempt|, + qr|InnoDB: io_setup\(\) failed with EAGAIN|, + qr|io_uring_queue_init\(\) failed with|, + qr|InnoDB: liburing disabled|, + qr/InnoDB: Failed to set (O_DIRECT|DIRECTIO_ON) on file/, + qr|setrlimit could not change the size of core files to 'infinity';|, + qr|feedback plugin: failed to retrieve the MAC address|, + qr|Plugin 'FEEDBACK' init function returned error|, + qr|Plugin 'FEEDBACK' registration as a INFORMATION SCHEMA failed|, + qr|'log-bin-use-v1-row-events' is MySQL .* compatible option|, + qr|InnoDB: Setting thread \d+ nice to \d+ failed, current nice \d+, errno 13|, # setpriority() fails under valgrind + qr|Failed to setup SSL|, + qr|SSL error: Failed to set ciphers to use|, + qr/Plugin 'InnoDB' will be forced to shutdown/, + qr|Could not increase number of max_open_files to more than|, + qr|Changed limits: max_open_files|, + qr/InnoDB: Error table encrypted but encryption service not available.*/, + qr/InnoDB: Could not find a valid tablespace file for*/, + qr/InnoDB: Tablespace open failed for*/, + qr/InnoDB: Failed to find tablespace for table*/, + qr/InnoDB: Space */, + qr|InnoDB: You may have to recover from a backup|, + qr|InnoDB: It is also possible that your operatingsystem has corrupted its own file cache|, + qr|InnoDB: and rebooting your computer removes the error|, + qr|InnoDB: If the corrupt page is an index page you can also try to|, + qr|nnoDB: fix the corruption by dumping, dropping, and reimporting|, + qr|InnoDB: the corrupt table. You can use CHECK|, + qr|InnoDB: TABLE to scan your table for corruption|, + qr/InnoDB: See also */, + qr/InnoDB: Cannot open .*ib_buffer_pool.* for reading: No such file or directory*/, + qr/InnoDB: Table .*mysql.*innodb_table_stats.* not found./, + qr/InnoDB: User stopword table .* does not exist./, + qr/Dump thread [0-9]+ last sent to server [0-9]+ binlog file:pos .+/, + qr/Detected table cache mutex contention at instance .* waits. Additional table cache instance cannot be activated: consider raising table_open_cache_instances. Number of active instances/, + qr/WSREP: Failed to guess base node address/, + qr/WSREP: Guessing address for incoming client/, + + # for UBSAN + qr/decimal\.c.*: runtime error: signed integer overflow/, + # Disable test for UBSAN on dynamically loaded objects + qr/runtime error: member call.*object.*'Handler_share'/, + qr/sql_type\.cc.* runtime error: member call.*object.* 'Type_collection'/, + ); + + my $matched_lines= []; + LINE: foreach my $line ( @lines ) + { + PAT: foreach my $pat ( @patterns ) + { + if ( $line =~ /$pat/ ) + { + foreach my $apat (@antipatterns) + { + next LINE if $line =~ $apat; + } + print $Fwarn $line; + push @$matched_lines, $line; + last PAT; + } + } + } + $Fwarn = undef; # Close file + + if (scalar(@$matched_lines) > 0 && + defined($last_warning_position->{$error_log}{test_names})) { + return ($last_warning_position->{$error_log}{test_names}, $matched_lines); + } else { + return ([], $matched_lines); + } +} + + +# Run include/check-warnings.test +# +# RETURN VALUE +# 0 OK +# 1 Check failed +# +sub start_check_warnings ($$) { + my $tinfo= shift; + my $mysqld= shift; + + my $name= "warnings-".$mysqld->name(); + + my $log_error= $mysqld->value('log-error'); + # To be communicated to the test + $ENV{MTR_LOG_ERROR}= $log_error; + extract_warning_lines($log_error, 0); + + my $args; + mtr_init_args(\$args); + + mtr_add_arg($args, "--defaults-file=%s", $path_config_file); + mtr_add_arg($args, "--defaults-group-suffix=%s", $mysqld->after('mysqld')); + mtr_add_arg($args, "--test-file=%s", "include/check-warnings.test"); + + if ( $opt_embedded_server ) + { + + # Get the args needed for the embedded server + # and append them to args prefixed + # with --sever-arg= + + my $mysqld= $config->group('embedded') + or mtr_error("Could not get [embedded] section"); + + my $mysqld_args; + mtr_init_args(\$mysqld_args); + my $extra_opts= get_extra_opts($mysqld, $tinfo); + mysqld_arguments($mysqld_args, $mysqld, $extra_opts); + mtr_add_arg($args, "--server-arg=%s", $_) for @$mysqld_args; + } + + my $errfile= "$opt_vardir/tmp/$name.err"; + my $proc= My::SafeProcess->new + ( + name => $name, + path => $exe_mysqltest, + error => $errfile, + output => $errfile, + args => \$args, + user_data => [$errfile, $mysqld], + verbose => $opt_verbose, + ); + mtr_verbose("Started $proc"); + return $proc; +} + + +# +# Loop through our list of processes and check the error log +# for unexpected errors and warnings +# +sub check_warnings ($) { + my ($tinfo)= @_; + my $res= 0; + + my $tname= $tinfo->{name}; + + # Clear previous warnings + delete($tinfo->{warnings}); + + # Start the mysqltest processes in parallel to save time + # also makes it possible to wait for any process to exit during the check + my %started; + foreach my $mysqld ( mysqlds() ) + { + if ( defined $mysqld->{'proc'} ) + { + my $proc= start_check_warnings($tinfo, $mysqld); + $started{$proc->pid()}= $proc; + } + } + + # Return immediately if no check proceess was started + return 0 unless ( keys %started ); + + my $timeout= start_timer(check_timeout($tinfo)); + + my $result= 0; + while (1){ + my $proc= My::SafeProcess->wait_any_timeout($timeout); + mtr_report("Got $proc"); + + if ( delete $started{$proc->pid()} ) { + # One check warning process returned + my $res= $proc->exit_status(); + my ($err_file, $mysqld)= @{$proc->user_data()}; + + if ( $res == 0 or $res == 62 ){ + + if ( $res == 0 ) { + # Check completed with problem + my $report= mtr_grab_file($err_file); + # Log to var/log/warnings file + mtr_tofile("$opt_vardir/log/warnings", + $tname."\n".$report); + + $tinfo->{'warnings'}.= $report; + $result= 1; + } + + if ( $res == 62 ) { + # Test case was ok and called "skip" + # Remove the .err file the check generated + unlink($err_file); + } + + if ( keys(%started) == 0){ + # All checks completed + mark_time_used('ch-warn'); + return $result; + } + # Wait for next process to exit + next; + } + else + { + my $report= mtr_grab_file($err_file); + $tinfo->{comment}.= + "Could not execute 'check-warnings' for ". + "testcase '$tname' (res: $res) server: '". + $mysqld->name() ."':\n"; + $tinfo->{comment}.= $report; + + $result= 2; + } + } + elsif ( $proc->{timeout} ) { + $tinfo->{comment}.= "Timeout for 'check warnings' expired after " + .check_timeout($tinfo)." seconds"; + $result= 4; + } + else { + # Unknown process returned, most likley a crash, abort everything + $tinfo->{comment}= + "The server $proc crashed while running 'check warnings'". + get_log_from_proc($proc, $tinfo->{name}); + $result= 3; + } + + # Kill any check processes still running + map($_->kill(), values(%started)); + + mark_time_used('ch-warn'); + return $result; + } + + mtr_error("INTERNAL_ERROR: check_warnings"); +} + +# Check for warnings generated during shutdown of a mysqld server. +# If any, report them to master server, and return true; else just return +# false. + +sub check_warnings_post_shutdown { + my ($server_socket)= @_; + my $testname_hash= { }; + my $report= ''; + foreach my $mysqld ( mysqlds()) + { + my ($testlist, $match_lines)= + extract_warning_lines($mysqld->value('log-error'), 1); + $testname_hash->{$_}= 1 for @$testlist; + $report.= join('', @$match_lines); + } + my @warning_tests= keys(%$testname_hash); + if (@warning_tests) { + my $fake_test= My::Test->new(testnames => \@warning_tests); + $fake_test->{'warnings'}= $report; + $fake_test->write_test($server_socket, 'WARNINGS'); + } +} + +# +# Check for the file indicating expected crash and restart it. +# +sub check_expected_crash_and_restart { + my $mysqld = shift; + + # Check if crash expected by looking at the .expect file + # in var/tmp + my $expect_file= "$opt_vardir/tmp/".$mysqld->name().".expect"; + if ( -f $expect_file ) + { + mtr_verbose("Crash was expected, file '$expect_file' exists"); + + for (my $waits = 0; $waits < 50; mtr_milli_sleep(100), $waits++) + { + # Race condition seen on Windows: try again until file not empty + next if -z $expect_file; + # If last line in expect file starts with "wait" + # sleep a little and try again, thus allowing the + # test script to control when the server should start + # up again. Keep trying for up to 5s at a time. + my $last_line= mtr_lastlinesfromfile($expect_file, 1); + if ($last_line =~ /^wait/ ) + { + mtr_verbose("Test says wait before restart") if $waits == 0; + next; + } + delete $ENV{MTR_BINDIR_FORCED}; + + # Ignore any partial or unknown command + next unless $last_line =~ /^restart/; + # If last line begins "restart:", the rest of the line is read as + # extra command line options to add to the restarted mysqld. + # Anything other than 'wait' or 'restart:' (with a colon) will + # result in a restart with original mysqld options. + if ($last_line =~ /restart_bindir\s+(\S+)(:.+)?/) { + $ENV{MTR_BINDIR_FORCED}= $1; + if ($2) { + my @rest_opt= split(' ', $2); + $mysqld->{'restart_opts'}= \@rest_opt; + } + } elsif ($last_line =~ /restart:(.+)/) { + my @rest_opt= split(' ', $1); + $mysqld->{'restart_opts'}= \@rest_opt; + } else { + delete $mysqld->{'restart_opts'}; + } + unlink($expect_file); + + # Start server with same settings as last time + return mysqld_start($mysqld, $mysqld->{'started_opts'}); + } + # Loop ran through: we should keep waiting after a re-check + return 2; + } + + # Not an expected crash + return 0; +} + + +# Remove all files and subdirectories of a directory +sub clean_dir { + my ($dir)= @_; + mtr_verbose("clean_dir: $dir"); + finddepth( + { no_chdir => 1, + wanted => sub { + if (-d $_){ + # A dir + if ($_ eq $dir){ + # The dir to clean + return; + } else { + mtr_verbose("rmdir: '$_'"); + rmdir($_) or mtr_warning("rmdir($_) failed: $!"); + } + } else { + # Hopefully a file + mtr_verbose("unlink: '$_'"); + unlink($_) or mtr_warning("unlink($_) failed: $!"); + } + } + }, + $dir); +} + + +sub clean_datadir { + mtr_verbose("Cleaning datadirs..."); + + if (started(all_servers()) != 0){ + mtr_error("Trying to clean datadir before all servers stopped"); + } + + for (all_servers()) + { + my $dir= "$opt_vardir/".$_->{name}; + mtr_verbose(" - removing '$dir'"); + rmtree($dir); + } + + # Remove all files in tmp and var/tmp + clean_dir("$opt_vardir/tmp"); + if ($opt_tmpdir ne "$opt_vardir/tmp"){ + clean_dir($opt_tmpdir); + } +} + + +# +# Save datadir before it's removed +# +sub save_datadir_after_failure($$) { + my ($dir, $savedir)= @_; + + mtr_report(" - saving '$dir'"); + my $dir_name= basename($dir); + rename("$dir", "$savedir/$dir_name"); +} + + +sub after_failure ($) { + my ($tinfo)= @_; + + mtr_report("Saving datadirs..."); + + my $save_dir= "$opt_vardir/log/"; + $save_dir.= $tinfo->{name}; + # Add combination name if any + $save_dir.= '-' . join(',', sort @{$tinfo->{combinations}}) + if defined $tinfo->{combinations}; + + # Save savedir path for server + $tinfo->{savedir}= $save_dir; + + mkpath($save_dir) if ! -d $save_dir; + + # + # Create a log of files in vardir (good for buildbot) + # + if (!IS_WINDOWS) + { + my $Flog= IO::File->new("$opt_vardir/log/files.log", "w"); + if ($Flog) + { + print $Flog scalar(`/bin/ls -Rl $opt_vardir/*`); + close($Flog); + } + } + + # Save the used config files + my %config_files = config_files($tinfo); + while (my ($file, $generate) = each %config_files) { + copy("$opt_vardir/$file", $save_dir); + } + + # Copy the tmp dir + copytree("$opt_vardir/tmp/", "$save_dir/tmp/"); + + foreach (all_servers()) { + my $dir= "$opt_vardir/".$_->{name}; + save_datadir_after_failure($dir, $save_dir); + } +} + + +sub report_failure_and_restart ($) { + my $tinfo= shift; + + if ($opt_valgrind && ($tinfo->{'warnings'} || $tinfo->{'timeout'}) && + $opt_core_on_failure == 0) + { + # In these cases we may want valgrind report from normal termination + $tinfo->{'dont_kill_server'}= 1; + } + # Shutdown properly if not to be killed (for valgrind) + stop_all_servers($tinfo->{'dont_kill_server'} ? $opt_shutdown_timeout : 0); + + $tinfo->{'result'}= 'MTR_RES_FAILED'; + + my $test_failures= $tinfo->{'failures'} || 0; + $tinfo->{'failures'}= $test_failures + 1; + + + 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" + ; + } + + if ( !defined $tinfo->{logfile} ) + { + my $logfile= $path_current_testlog; + if ( defined $logfile ) + { + if ( -f $logfile ) + { + # Test failure was detected by test tool and its report + # about what failed has been saved to file. Save the report + # in tinfo + $tinfo->{logfile}= mtr_fromfile($logfile); + # If no newlines in the test log: + # (it will contain the CURRENT_TEST written by mtr, so is not empty) + if ($tinfo->{logfile} !~ /\n/) + { + # Show how far it got before suddenly failing + $tinfo->{comment}.= "mysqltest failed but provided no output\n"; + my $log_file_name= $opt_vardir."/log/".$tinfo->{shortname}.".log"; + if (-e $log_file_name) { + $tinfo->{comment}.= + "The result from queries just before the failure was:". + "\n< snip >\n". + mtr_lastlinesfromfile($log_file_name, $opt_tail_lines)."\n"; + } + } + } + else + { + # The test tool report didn't exist, display an + # error message + $tinfo->{logfile}= "Could not open test tool report '$logfile'"; + } + } + } + + after_failure($tinfo); + + mtr_report_test($tinfo); + +} + + +sub run_system(@) { + mtr_verbose("Running '$_[0]'"); + my $ret= system(@_) >> 8; + return $ret; +} + + +sub mysqld_stop { + my $mysqld= shift or die "usage: mysqld_stop(<mysqld>)"; + + my $args; + mtr_init_args(\$args); + + mtr_add_arg($args, "--no-defaults"); + mtr_add_arg($args, "--character-sets-dir=%s", $mysqld->value('character-sets-dir')); + mtr_add_arg($args, "--user=%s", $opt_user); + mtr_add_arg($args, "--password="); + mtr_add_arg($args, "--port=%d", $mysqld->value('port')); + mtr_add_arg($args, "--host=%s", $mysqld->value('#host')); + mtr_add_arg($args, "--connect_timeout=20"); + mtr_add_arg($args, "--protocol=tcp"); + + mtr_add_arg($args, "shutdown"); + + My::SafeProcess->run + ( + name => "mysqladmin shutdown ".$mysqld->name(), + path => $exe_mysqladmin, + args => \$args, + error => "$opt_vardir/log/mysqladmin.err", + + ); +} + + +sub mysqld_arguments ($$$) { + my $args= shift; + my $mysqld= shift; + my $extra_opts= shift; + + my @defaults = grep(/^--defaults-file=/, @$extra_opts); + if (@defaults > 0) { + mtr_add_arg($args, pop(@defaults)) + } + else { + mtr_add_arg($args, "--defaults-file=%s", $path_config_file); + } + + # When mysqld is run by a root user(euid is 0), it will fail + # to start unless we specify what user to run as, see BUG#30630 + my $euid= $>; + if (!IS_WINDOWS and $euid == 0 and + (grep(/^--user/, @$extra_opts)) == 0) { + mtr_add_arg($args, "--user=root"); + } + + if (!using_extern() and !$opt_user_args) + { + # Turn on logging to file + mtr_add_arg($args, "%s--log-output=file"); + } + + # Check if "extra_opt" contains --log-bin + my $skip_binlog= not grep /^--(loose-)?log-bin/, @$extra_opts; + + my $found_skip_core= 0; + foreach my $arg ( @$extra_opts ) + { + # Skip --defaults-file option since it's handled above. + next if $arg =~ /^--defaults-file/; + + # Allow --skip-core-file to be set in <testname>-[master|slave].opt file + if ($arg eq "--skip-core-file") + { + $found_skip_core= 1; + } + elsif ($skip_binlog and mtr_match_prefix($arg, "--binlog-format")) + { + ; # Dont add --binlog-format when running without binlog + } + elsif ($arg eq "--loose-skip-log-bin" and + $mysqld->option("log-slave-updates")) + { + ; # Dont add --skip-log-bin when mysqld have --log-slave-updates in config + } + else + { + mtr_add_arg($args, "%s", $arg); + } + } + $opt_skip_core = $found_skip_core; + if ( !$found_skip_core && !$opt_user_args ) + { + mtr_add_arg($args, "%s", "--core-file"); + } + + # Enable the debug sync facility, set default wait timeout. + # Facility stays disabled if timeout value is zero. + mtr_add_arg($args, "--loose-debug-sync-timeout=%s", + $opt_debug_sync_timeout) unless $opt_user_args; + + return $args; +} + + + +sub mysqld_start ($$) { + my $mysqld= shift; + my $extra_opts= shift; + + mtr_verbose(My::Options::toStr("mysqld_start", @$extra_opts)); + + my $exe= find_mysqld($mysqld->value('basedir')); + + mtr_error("Internal error: mysqld should never be started for embedded") + if $opt_embedded_server; + + my $args; + mtr_init_args(\$args); + + mtr_add_arg($args, "--defaults-group-suffix=%s", $mysqld->after('mysqld')); + + # Add any additional options from an in-test restart + my @all_opts= @$extra_opts; + if (exists $mysqld->{'restart_opts'}) { + push (@all_opts, @{$mysqld->{'restart_opts'}}); + mtr_verbose(My::Options::toStr("mysqld_start restart", + @{$mysqld->{'restart_opts'}})); + } + mysqld_arguments($args,$mysqld,\@all_opts); + + if ( $opt_debug ) + { + mtr_add_arg($args, "--debug-dbug=$debug_d:t:i:A,%s/log/%s.trace", + $path_vardir_trace, $mysqld->name()); + } + + + # Command line for mysqld started for *this particular test*. + # Differs from "generic" MYSQLD_CMD by including all command line + # options from *.opt and *.combination files. + $ENV{'MYSQLD_LAST_CMD'}= "$exe @$args"; + my $oldexe= $exe; + + My::Debugger::setup_args(\$args, \$exe, $mysqld->name()); + $ENV{'VALGRIND_TEST'}= $opt_valgrind = int(($exe || '') eq 'valgrind'); + + # Remove the old pidfile if any + unlink($mysqld->value('pid-file')); + + my $output= $mysqld->value('log-error'); + + if ( $opt_valgrind and $opt_debug ) + { + # When both --valgrind and --debug is selected, send + # all output to the trace file, making it possible to + # see the exact location where valgrind complains + + # Write a message about this to the normal log file + my $trace_name= "$opt_vardir/log/".$mysqld->name().".trace"; + mtr_tofile($output, + "NOTE: When running with --valgrind --debug the output from ", + "mysqld (where the valgrind messages shows up) is stored ", + "together with the trace file to make it ", + "easier to find the exact position of valgrind errors.", + "See trace file $trace_name.\n"); + $output= $trace_name; + + } + # Remember this log file for valgrind error report search + $mysqld_logs{$output}= 1 if $opt_valgrind; + # Remember data dir for gmon.out files if using gprof + $gprof_dirs{$mysqld->value('datadir')}= 1 if $opt_gprof; + + if ( defined $exe ) + { + mtr_tofile($output, "\$ $exe @$args\n"); + pre_write_errorlog($output); + $mysqld->{'proc'}= My::SafeProcess->new + ( + name => $mysqld->name(), + path => $exe, + args => \$args, + output => $output, + error => $output, + append => 1, + verbose => $opt_verbose, + nocore => $opt_skip_core, + host => undef, + shutdown => sub { mysqld_stop($mysqld) }, + envs => \@opt_mysqld_envs, + ); + mtr_verbose("Started $mysqld->{proc}"); + } + + $mysqld->{'started_opts'}= $extra_opts; + + my $expect_file= "$opt_vardir/tmp/".$mysqld->name().".expect"; + my $rc= $oldexe eq ($exe || '') || + sleep_until_file_created($mysqld->value('pid-file'), $expect_file, + $opt_start_timeout, $mysqld->{'proc'}, $warn_seconds); + if (!$rc) + { + # Report failure about the last test case before exit + my $test_name= mtr_grab_file($path_current_testlog); + $test_name =~ s/^CURRENT_TEST:\s//; + my $tinfo = My::Test->new(name => $test_name); + $tinfo->{result}= 'MTR_RES_FAILED'; + $tinfo->{failures}= 1; + $tinfo->{logfile}=get_log_from_proc($mysqld->{'proc'}, $tinfo->{name}); + report_option('verbose', 1); + mtr_report_test($tinfo); + } + return $rc; +} + + +sub stop_all_servers () { + my $shutdown_timeout = $_[0] or 0; + + mtr_verbose("Stopping all servers..."); + + # Kill all started servers + My::SafeProcess::shutdown($shutdown_timeout, + started(all_servers())); + + # Remove pidfiles + foreach my $server ( all_servers() ) + { + my $pid_file= $server->if_exist('pid-file'); + unlink($pid_file) if defined $pid_file; + } + + # Mark servers as stopped + map($_->{proc}= undef, all_servers()); + +} + + +# Find out if server should be restarted for this test +sub server_need_restart { + my ($tinfo, $server)= @_; + + if ( using_extern() ) + { + mtr_verbose_restart($server, "no restart for --extern server"); + return 0; + } + + if ( $tinfo->{'force_restart'} ) { + mtr_verbose_restart($server, "forced in .opt file"); + return 1; + } + + if ( $opt_force_restart ) { + mtr_verbose_restart($server, "forced restart turned on"); + return 1; + } + + if ( $tinfo->{template_path} ne $current_config_name) + { + mtr_verbose_restart($server, "using different config file"); + return 1; + } + + if ( $tinfo->{'master_sh'} || $tinfo->{'slave_sh'} ) + { + mtr_verbose_restart($server, "sh script to run"); + return 1; + } + + if ( ! started($server) ) + { + mtr_verbose_restart($server, "not started"); + return 1; + } + + my $started_tinfo= $server->{'started_tinfo'}; + if ( defined $started_tinfo ) + { + + # Check if timezone of test that server was started + # with differs from timezone of next test + if ( timezone($started_tinfo) ne timezone($tinfo) ) + { + mtr_verbose_restart($server, "different timezone"); + return 1; + } + } + + if ($server->name() =~ /^mysqld\./) + { + + # Check that running process was started with same options + # as the current test requires + my $extra_opts= get_extra_opts($server, $tinfo); + my $started_opts= $server->{'started_opts'}; + + # Also, always restart if server had been restarted with additional + # options within test. + if (!My::Options::same($started_opts, $extra_opts) || + exists $server->{'restart_opts'}) + { + delete $server->{'restart_opts'}; + my $use_dynamic_option_switch= 0; + my $restart_opts = delete $server->{'restart_opts'} || []; + if (!$use_dynamic_option_switch) + { + mtr_verbose_restart($server, "running with different options '" . + join(" ", @{$extra_opts}) . "' != '" . + join(" ", @{$started_opts}, @{$restart_opts}) . "'" ); + return 1; + } + + mtr_verbose(My::Options::toStr("started_opts", @$started_opts)); + mtr_verbose(My::Options::toStr("extra_opts", @$extra_opts)); + + # Get diff and check if dynamic switch is possible + my @diff_opts= My::Options::diff($started_opts, $extra_opts); + mtr_verbose(My::Options::toStr("diff_opts", @diff_opts)); + + my $query= My::Options::toSQL(@diff_opts); + mtr_verbose("Attempting dynamic switch '$query'"); + if (run_query($tinfo, $server, $query)){ + mtr_verbose("Restart: running with different options '" . + join(" ", @{$extra_opts}) . "' != '" . + join(" ", @{$started_opts}) . "'" ); + return 1; + } + + # Remember the dynamically set options + $server->{'started_opts'}= $extra_opts; + } + } + + # Default, no restart + return 0; +} + + +sub servers_need_restart($) { + my ($tinfo)= @_; + return grep { server_need_restart($tinfo, $_); } all_servers(); +} + + + +############################################ + +############################################ + +# +# Filter a list of servers and return the SafeProcess +# for only those that are started or stopped +# +sub started { return grep(defined $_, map($_->{proc}, @_)); } +sub stopped { return grep(!defined $_, map($_->{proc}, @_)); } + + +sub get_extra_opts { + # No extra options if --user-args + return \@opt_extra_mysqld_opt if $opt_user_args; + + my ($mysqld, $tinfo)= @_; + + my $opts= + $mysqld->option("#!use-slave-opt") ? + $tinfo->{slave_opt} : $tinfo->{master_opt}; + + # Expand environment variables + foreach my $opt ( @$opts ) + { + no warnings 'uninitialized'; + $opt =~ s/\$\{(\w+)\}/$ENV{$1}/ge; + $opt =~ s/\$(\w+)/$ENV{$1}/ge; + } + return $opts; +} + + +sub stop_servers($$) { + my (@servers)= @_; + + mtr_report("Stopping ", started(@servers)); + + My::SafeProcess::shutdown($opt_shutdown_timeout, + started(@servers)); + + foreach my $server (@servers) + { + # Mark server as stopped + $server->{proc}= undef; + + # Forget history + delete $server->{'started_tinfo'}; + delete $server->{'started_opts'}; + delete $server->{'started_cnf'}; + } +} + +# +# run_query_output +# +# Run a query against a server using mysql client. The output of +# the query will be written into outfile. +# +sub run_query_output { + my ($mysqld, $query, $outfile)= @_; + my $args; + + mtr_init_args(\$args); + mtr_add_arg($args, "--defaults-file=%s", $path_config_file); + mtr_add_arg($args, "--defaults-group-suffix=%s", $mysqld->after('mysqld')); + mtr_add_arg($args, "--silent"); + mtr_add_arg($args, "--execute=%s", $query); + + my $res= My::SafeProcess->run + ( + name => "run_query_output -> ".$mysqld->name(), + path => $exe_mysql, + args => \$args, + output => $outfile, + error => $outfile + ); + + return $res +} + + +# +# wsrep_wait_ready +# +# Wait until the server has been joined to the cluster and is +# ready for operation. +# +# RETURN +# 1 Server is ready +# 0 Server didn't transition to ready state within start timeout +# +sub wait_wsrep_ready($$) { + my ($tinfo, $mysqld)= @_; + + my $sleeptime= 100; # Milliseconds + my $loops= ($opt_start_timeout * 1000) / $sleeptime; + + my $name= $mysqld->name(); + my $outfile= "$opt_vardir/tmp/$name.wsrep_ready"; + my $query= "SET SESSION wsrep_sync_wait = 0; + SELECT VARIABLE_NAME, VARIABLE_VALUE + FROM INFORMATION_SCHEMA.GLOBAL_STATUS + WHERE VARIABLE_NAME = 'wsrep_ready'"; + + for (my $loop= 1; $loop <= $loops; $loop++) + { + # Careful... if MTR runs with option 'verbose' then the + # file contains also SafeProcess verbose output + if (run_query_output($mysqld, $query, $outfile) == 0 && + mtr_grab_file($outfile) =~ /WSREP_READY\s+ON/) + { + unlink($outfile); + return 1; + } + mtr_milli_sleep($sleeptime); + } + + $tinfo->{logfile}= "WSREP did not transition to state READY"; + return 0; +} + +# +# wsrep_is_bootstrap_server +# +# Check if the server is the first one to be started in the +# cluster. +# +# RETURN +# 1 The server is a bootstrap server +# 0 The server is not a bootstrap server +# +sub wsrep_is_bootstrap_server($) { + my $mysqld= shift; + + my $cluster_address= $mysqld->if_exist('wsrep-cluster-address') || + $mysqld->if_exist('wsrep_cluster_address'); + if (defined $cluster_address) + { + return $cluster_address eq "gcomm://" || $cluster_address eq "'gcomm://'"; + } + return 0; +} + +# +# wsrep_on +# +# Check if wsrep has been enabled for a server. +# +# RETURN +# 1 Wsrep has been enabled +# 0 Wsrep is not enabled +# +sub wsrep_on($) { + my $mysqld= shift; + #check if wsrep_on= is set in configuration + if ($mysqld->if_exist('wsrep-on')) { + my $on= "".$mysqld->value('wsrep-on'); + if ($on eq "1" || $on eq "ON") { + return 1; + } + } + return 0; +} + + +# +# start_servers +# +# Start servers not already started +# +# RETURN +# 0 OK +# 1 Start failed +# +sub start_servers($) { + my ($tinfo)= @_; + + for (all_servers()) { + $_->{START}->($_, $tinfo) if $_->{START}; + } + + for (all_servers()) { + next unless $_->{WAIT} and started($_); + if ($_->{WAIT}->($_, $tinfo)) { + return 1; + } + } + return 0; +} + + +# +# Run include/check-testcase.test +# Before a testcase, run in record mode and save result file to var/tmp +# After testcase, run and compare with the recorded file, they should be equal! +# +# RETURN VALUE +# The newly started process +# +sub start_check_testcase ($$$) { + my $tinfo= shift; + my $mode= shift; + my $mysqld= shift; + + my $name= "check-".$mysqld->name(); + # Replace dots in name with underscore to avoid that mysqltest + # misinterpret's what the filename extension is :( + $name=~ s/\./_/g; + + my $args; + mtr_init_args(\$args); + + mtr_add_arg($args, "--defaults-file=%s", $path_config_file); + mtr_add_arg($args, "--defaults-group-suffix=%s", $mysqld->after('mysqld')); + mtr_add_arg($args, "--result-file=%s", "$opt_vardir/tmp/$name.result"); + mtr_add_arg($args, "--test-file=%s", "include/check-testcase.test"); + mtr_add_arg($args, "--verbose"); + + if ( $mode eq "before" ) + { + mtr_add_arg($args, "--record"); + } + my $errfile= "$opt_vardir/tmp/$name.err"; + my $proc= My::SafeProcess->new + ( + name => $name, + path => $exe_mysqltest, + error => $errfile, + output => $errfile, + args => \$args, + user_data => $errfile, + verbose => $opt_verbose, + ); + + mtr_report("Started $proc"); + return $proc; +} + + +sub run_mysqltest ($) { + my $proc= start_mysqltest(@_); + $proc->wait(); +} + + +sub start_mysqltest ($) { + my ($tinfo)= @_; + my $exe= $exe_mysqltest; + my $args; + + mark_time_used('admin'); + + mtr_init_args(\$args); + + mtr_add_arg($args, "--defaults-file=%s", $path_config_file); + if ($opt_verbose > 1) { + mtr_add_arg($args, "--verbose"); + } else { + mtr_add_arg($args, "--silent"); + } + mtr_add_arg($args, "--tmpdir=%s", $opt_tmpdir); + mtr_add_arg($args, "--character-sets-dir=%s", $path_charsetsdir); + mtr_add_arg($args, "--logdir=%s/log", $opt_vardir); + + # Log line number and time for each line in .test file + mtr_add_arg($args, "--mark-progress") + if $opt_mark_progress; + + mtr_add_arg($args, "--database=test"); + + if ( $opt_ps_protocol ) + { + mtr_add_arg($args, "--ps-protocol"); + } + + if ( $opt_sp_protocol ) + { + mtr_add_arg($args, "--sp-protocol"); + } + + if ( $opt_view_protocol ) + { + mtr_add_arg($args, "--view-protocol"); + } + + if ( $opt_cursor_protocol ) + { + mtr_add_arg($args, "--cursor-protocol"); + } + + if ( $opt_non_blocking_api ) + { + mtr_add_arg($args, "--non-blocking-api"); + } + + mtr_add_arg($args, "--timer-file=%s/log/timer", $opt_vardir); + + if ( $opt_compress ) + { + mtr_add_arg($args, "--compress"); + } + + if ( $opt_sleep ) + { + mtr_add_arg($args, "--sleep=%d", $opt_sleep); + } + + if ( $opt_ssl ) + { + # Turn on SSL for _all_ test cases if option --ssl was used + mtr_add_arg($args, "--ssl"); + } + + if ( $opt_max_connections ) { + mtr_add_arg($args, "--max-connections=%d", $opt_max_connections); + } + + if ( $opt_embedded_server ) + { + + # Get the args needed for the embedded server + # and append them to args prefixed + # with --sever-arg= + + my $mysqld= $config->group('embedded') + or mtr_error("Could not get [embedded] section"); + + my $mysqld_args; + mtr_init_args(\$mysqld_args); + my $extra_opts= get_extra_opts($mysqld, $tinfo); + mysqld_arguments($mysqld_args, $mysqld, $extra_opts); + mtr_add_arg($args, "--server-arg=%s", $_) for @$mysqld_args; + } + + # ---------------------------------------------------------------------- + # export MYSQL_TEST variable containing <path>/mysqltest <args> + # ---------------------------------------------------------------------- + $ENV{'MYSQL_TEST'}= mtr_args2str($exe_mysqltest, @$args); + + if ($opt_force > 1) + { + mtr_add_arg($args, "--continue-on-error"); + } + + my $suite = $tinfo->{suite}; + if ($suite->{parent}) { + mtr_add_arg($args, "--overlay-dir=%s/", $suite->{dir}); + mtr_add_arg($args, "--suite-dir=%s/", $suite->{parent}->{dir}); + } else { + mtr_add_arg($args, "--suite-dir=%s/", $suite->{dir}); + } + + mtr_add_arg($args, "--test-file=%s", $tinfo->{'path'}); + + # Number of lines of resut to include in failure report + mtr_add_arg($args, "--tail-lines=%d", $opt_tail_lines); + + if ( defined $tinfo->{'result_file'} ) { + mtr_add_arg($args, "--result-file=%s", $tinfo->{'result_file'}); + } + + client_debug_arg($args, "mysqltest"); + + if ( $opt_record ) + { + mtr_add_arg($args, "--record"); + + # When recording to a non existing result file + # the name of that file is in "record_file" + if ( defined $tinfo->{'record_file'} ) { + mtr_add_arg($args, "--result-file=%s", $tinfo->{record_file}); + } + } + + My::Debugger::setup_client_args(\$args, \$exe); + + my $proc= My::SafeProcess->new + ( + name => "mysqltest", + path => $exe, + args => \$args, + append => 1, + error => $path_current_testlog, + verbose => $opt_verbose, + ); + mtr_verbose("Started $proc"); + return $proc; +} + +# +# Search server logs for valgrind reports printed at mysqld termination +# +sub valgrind_exit_reports() { + my $found_err= 0; + + foreach my $log_file (keys %mysqld_logs) + { + my @culprits= (); + my $valgrind_rep= ""; + my $found_report= 0; + my $err_in_report= 0; + + my $LOGF = IO::File->new($log_file) + or mtr_error("Could not open file '$log_file' for reading: $!"); + + while ( my $line = <$LOGF> ) + { + if ($line =~ /^CURRENT_TEST: (.+)$/) + { + my $testname= $1; + # If we have a report, report it if needed and start new list of tests + if ($found_report) + { + if ($err_in_report) + { + mtr_print ("Valgrind report from $log_file after tests:\n", + @culprits); + mtr_print_line(); + print ("$valgrind_rep\n"); + $found_err= 1; + $err_in_report= 0; + } + # Make ready to collect new report + @culprits= (); + $found_report= 0; + $valgrind_rep= ""; + } + push (@culprits, $testname); + next; + } + # This line marks the start of a valgrind report + $found_report= 1 if $line =~ /^==\d+== .* SUMMARY:/; + + if ($found_report) { + $line=~ s/^==\d+== //; + $valgrind_rep .= $line; + $err_in_report= 1 if $line =~ /ERROR SUMMARY: [1-9]/; + $err_in_report= 1 if $line =~ /definitely lost: [1-9]/; + $err_in_report= 1 if $line =~ /possibly lost: [1-9]/; + } + } + + $LOGF= undef; + + if ($err_in_report) { + mtr_print ("Valgrind report from $log_file after tests:\n", @culprits); + mtr_print_line(); + print ("$valgrind_rep\n"); + $found_err= 1; + } + } + + return $found_err; +} + +# +# Usage +# +sub usage ($) { + my ($message)= @_; + + if ( $message ) + { + print STDERR "$message\n"; + print STDERR "For full list of options, use $0 --help\n"; + exit(1); + } + + local $"= ','; # for @DEFAULT_SUITES below + + print <<HERE . My::Debugger::help() . My::CoreDump::help() . <<HERE; + +$0 [ OPTIONS ] [ TESTCASE ] + +Where test case can be specified as: + +testcase[.test] Runs the test case named 'testcase' from all suits +path-to-testcase +[suite.]testcase[,combination] + +Examples: + +alias +main.alias 'main' is the name of the main test suite +rpl.rpl_invoked_features,mix,innodb +suite/rpl/t/rpl.rpl_invoked_features + +Options to control what engine/variation to run: + + embedded-server Use the embedded server, i.e. no mysqld daemons + ps-protocol Use the binary protocol between client and server + cursor-protocol Use the cursor protocol between client and server + (implies --ps-protocol) + view-protocol Create a view to execute all non updating queries + sp-protocol Create a stored procedure to execute all queries + non-blocking-api Use the non-blocking client API + compress Use the compressed protocol between client and server + ssl Use ssl protocol between client and server + skip-ssl Don't start server with support for ssl connections + vs-config Visual Studio configuration used to create executables + (default: MTR_VS_CONFIG environment variable) + parallel=# How many parallel test should be run + defaults-file=<config template> Use fixed config template for all + tests + defaults-extra-file=<config template> Extra config template to add to + all generated configs + combination=<opt> Use at least twice to run tests with specified + options to mysqld + dry-run Don't run any tests, print the list of tests + that were selected for execution + +Options to control directories to use + tmpdir=DIR The directory where temporary files are stored + (default: ./var/tmp). + vardir=DIR The directory where files generated from the test run + is stored (default: ./var). Specifying a ramdisk or + tmpfs will speed up tests. + mem[=DIR] Run testsuite in "memory" using tmpfs or ramdisk + Attempts to use DIR first if specified else + uses a builtin list of standard locations + for tmpfs (/run/shm, /dev/shm, /tmp) + The option can also be set using environment + variable MTR_MEM=[DIR] + clean-vardir Clean vardir if tests were successful and if + running in "memory". Otherwise this option is ignored + client-bindir=PATH Path to the directory where client binaries are located + client-libdir=PATH Path to the directory where client libraries are located + + +Options to control what test suites or cases to run + + force Continue after a failure. When specified once, a + failure in a test file will abort this test file, and + the execution will continue from the next test file. + When specified twice, execution will continue executing + the failed test file from the next command. + skip-not-found It is not an error if a test was not found in a + specified test suite. Test will be marked as skipped. + do-test=PREFIX or REGEX + Run test cases which name are prefixed with PREFIX + or fulfills REGEX + skip-test=PREFIX or REGEX + Skip test cases which name are prefixed with PREFIX + or fulfills REGEX + start-from=PREFIX Run test cases starting test prefixed with PREFIX where + prefix may be suite.testname or just testname + suite[s]=NAME1,..,NAMEN + Collect tests in suites from the comma separated + list of suite names. + The default is: "@DEFAULT_SUITES" + skip-rpl Skip the replication test cases. + big-test Also run tests marked as "big". Repeat this option + twice to run only "big" tests. + staging-run Run a limited number of tests (no slow tests). Used + for running staging trees with valgrind. + enable-disabled Run also tests marked as disabled + print-testcases Don't run the tests but print details about all the + selected tests, in the order they would be run. + skip-test-list=FILE Skip the tests listed in FILE. Each line in the file + is an entry and should be formatted as: + <TESTNAME> : <COMMENT> + force-restart Always restart servers between tests. This makes it + easier to see from which test warnings may come from. + +Options that specify ports + + mtr-port-base=# Base for port numbers, ports from this number to + port-base=# number+9 are reserved. Should be divisible by 10; + if not it will be rounded down. May be set with + environment variable MTR_PORT_BASE. If this value is + set and is not "auto", it overrides build-thread. + mtr-build-thread=# Specify unique number to calculate port number(s) from. + build-thread=# Can be set in environment variable MTR_BUILD_THREAD. + Set MTR_BUILD_THREAD="auto" to automatically aquire + a build thread id that is unique to current host + port-group-size=N Reserve groups of TCP ports of size N for each MTR thread + + +Options for test case authoring + + record TESTNAME (Re)genereate the result file for TESTNAME + check-testcases Check testcases for sideeffects + mark-progress Log line number and elapsed time to <testname>.progress + +Options that pass on options (these may be repeated) + + mariadbd=ARGS Specify additional arguments to "mariadbd" + mysqld Alias for mariadbd. + mariadbd-env=VAR=VAL Specify additional environment settings for "mariadbd" + mysqld-env Alias for mariadbd-env. + +Options to run test on running server + + extern option=value Run only the tests against an already started server + the options to use for connection to the extern server + must be specified using name-value pair notation + For example: + ./$0 --extern socket=/tmp/mysqld.sock + +Options for debugging the product + + debug Dump trace output for all servers and client programs + debug-common Same as debug, but sets 'd' debug flags to + "query,info,error,enter,exit" + debug-server Use debug version of server, but without turning on + tracing + max-save-core Limit the number of core files saved (to avoid filling + up disks for heavily crashing server). Defaults to + $opt_max_save_core. Set its default with + MTR_MAX_SAVE_CORE + max-save-datadir Limit the number of datadir saved (to avoid filling + up disks for heavily crashing server). Defaults to + $opt_max_save_datadir. Set its default with + MTR_MAX_SAVE_DATADIR + max-test-fail Limit the number of test failures before aborting + the current test run. Defaults to + $opt_max_test_fail, set to 0 for no limit. Set + it's default with MTR_MAX_TEST_FAIL + core-in-failure Generate a core even if run server is run with valgrind +HERE + +Misc options + user=USER User for connecting to mysqld(default: $opt_user) + comment=STR Write STR to the output + timer Show test case execution time. + verbose More verbose output(use multiple times for even more) + verbose-restart Write when and why servers are restarted + start Only initialize and start the servers, using the + startup settings for the first specified test case + Example: + $0 --start alias & + start-and-exit Same as --start, but mysql-test-run terminates and + leaves just the server running + start-dirty Only start the servers (without initialization) for + the first specified test case + user-args In combination with start* and no test name, drops + arguments to mariadbd except those specified with + --mariadbd (if any) + wait-all If --start or --start-dirty option is used, wait for all + servers to exit before finishing the process + fast Run as fast as possible, don't wait for servers + to shutdown etc. + force-restart Always restart servers between tests + parallel=N Run tests in N parallel threads (default 1) + Use parallel=auto for auto-setting of N + repeat=N Run each test N number of times + retry=N Retry tests that fail up to N times (default $opt_retry). + Retries are also limited by the maximum number of + failures before stopping, set with the --retry-failure + option + retry-failure=N When using the --retry option to retry failed tests, + stop when N failures have occurred (default $opt_retry_failure) + reorder Reorder tests to get fewer server restarts + help Get this help text + + testcase-timeout=MINUTES Max test case run time (default $opt_testcase_timeout) + suite-timeout=MINUTES Max test suite run time (default $opt_suite_timeout) + shutdown-timeout=SECONDS Max number of seconds to wait for server shutdown + before killing servers (default $opt_shutdown_timeout) + warnings Scan the log files for warnings. Use --nowarnings + to turn off. + + stop-file=file (also MTR_STOP_FILE environment variable) if this + file detected mysql test will not start new tests + until the file will be removed. + stop-keep-alive=sec (also MTR_STOP_KEEP_ALIVE environment variable) + works with stop-file, print messages every sec + seconds when mysql test is waiting to removing + the file (for buildbot) + + sleep=SECONDS Passed to mysqltest, will be used as fixed sleep time + debug-sync-timeout=NUM Set default timeout for WAIT_FOR debug sync + actions. Disable facility with NUM=0. + gcov Collect coverage information after the test. + The result is a gcov file per source and header file. + gprof Collect profiling information using gprof. + experimental=<file> Refer to list of tests considered experimental; + failures will be marked exp-fail instead of fail. + timestamp Print timestamp before each test report line + timediff With --timestamp, also print time passed since + *previous* test started + max-connections=N Max number of open connection to server in mysqltest + report-times Report how much time has been spent on different + phases of test execution. + stress=ARGS Run stress test, providing options to + mysql-stress-test.pl. Options are separated by comma. + xml-report=<file> Output jUnit xml file of the results. + tail-lines=N Number of lines of the result to include in a failure + report. + +Some options that control enabling a feature for normal test runs, +can be turned off by prepending 'no' to the option, e.g. --notimer. +This applies to reorder, timer, check-testcases and warnings. + +HERE + exit(1); + +} + +sub list_options ($) { + my $hash= shift; + + for (keys %$hash) { + s/([:=].*|[+!])$//; + s/\|/\n--/g; + print "--$_\n"; + } + + exit(1); +} |