summaryrefslogtreecommitdiff
path: root/mysql-test/lib
diff options
context:
space:
mode:
Diffstat (limited to 'mysql-test/lib')
-rw-r--r--mysql-test/lib/My/Config.pm106
-rw-r--r--mysql-test/lib/My/ConfigFactory.pm655
-rw-r--r--mysql-test/lib/My/CoreDump.pm131
-rw-r--r--mysql-test/lib/My/File/Path.pm177
-rw-r--r--mysql-test/lib/My/Find.pm245
-rwxr-xr-xmysql-test/lib/My/Handles.pm69
-rw-r--r--mysql-test/lib/My/Options.pm199
-rw-r--r--mysql-test/lib/My/Platform.pm156
-rw-r--r--mysql-test/lib/My/SafeProcess.pm580
-rw-r--r--mysql-test/lib/My/SafeProcess/Base.pm212
-rw-r--r--mysql-test/lib/My/SafeProcess/CMakeLists.txt17
-rw-r--r--mysql-test/lib/My/SafeProcess/Makefile.am28
-rwxr-xr-xmysql-test/lib/My/SafeProcess/safe_kill_win.cc85
-rw-r--r--mysql-test/lib/My/SafeProcess/safe_process.cc277
-rw-r--r--mysql-test/lib/My/SafeProcess/safe_process.pl151
-rwxr-xr-xmysql-test/lib/My/SafeProcess/safe_process_win.cc316
-rw-r--r--mysql-test/lib/My/SysInfo.pm211
-rw-r--r--mysql-test/lib/My/Test.pm123
-rw-r--r--mysql-test/lib/mtr_cases.pm1177
-rw-r--r--mysql-test/lib/mtr_diff.pl297
-rw-r--r--mysql-test/lib/mtr_gcov.pl36
-rw-r--r--mysql-test/lib/mtr_io.pl164
-rw-r--r--mysql-test/lib/mtr_match.pm97
-rw-r--r--mysql-test/lib/mtr_misc.pl177
-rw-r--r--mysql-test/lib/mtr_process.pl1057
-rw-r--r--mysql-test/lib/mtr_report.pm464
-rw-r--r--mysql-test/lib/mtr_stress.pl10
-rw-r--r--mysql-test/lib/mtr_unique.pm185
-rw-r--r--mysql-test/lib/t/Base.t27
-rw-r--r--mysql-test/lib/t/Find.t33
-rw-r--r--mysql-test/lib/t/Options.t127
-rw-r--r--mysql-test/lib/t/Platform.t18
-rw-r--r--mysql-test/lib/t/SafeProcess.t102
-rwxr-xr-xmysql-test/lib/t/SafeProcessStress.pl149
-rw-r--r--mysql-test/lib/t/copytree.t34
-rw-r--r--mysql-test/lib/t/dummyd.pl38
-rw-r--r--mysql-test/lib/t/rmtree.t52
-rwxr-xr-xmysql-test/lib/t/testMyConfig.t131
-rwxr-xr-xmysql-test/lib/t/testMyConfigFactory.t98
-rwxr-xr-xmysql-test/lib/t/test_child.pl21
-rw-r--r--mysql-test/lib/v1/My/Config.pm422
-rw-r--r--mysql-test/lib/v1/mtr_cases.pl (renamed from mysql-test/lib/mtr_cases.pl)17
-rw-r--r--mysql-test/lib/v1/mtr_gcov.pl74
-rw-r--r--mysql-test/lib/v1/mtr_gprof.pl64
-rw-r--r--mysql-test/lib/v1/mtr_im.pl (renamed from mysql-test/lib/mtr_im.pl)0
-rw-r--r--mysql-test/lib/v1/mtr_io.pl218
-rw-r--r--mysql-test/lib/v1/mtr_match.pl (renamed from mysql-test/lib/mtr_match.pl)0
-rw-r--r--mysql-test/lib/v1/mtr_misc.pl312
-rw-r--r--mysql-test/lib/v1/mtr_process.pl1142
-rw-r--r--mysql-test/lib/v1/mtr_report.pl (renamed from mysql-test/lib/mtr_report.pl)51
-rw-r--r--mysql-test/lib/v1/mtr_stress.pl191
-rw-r--r--mysql-test/lib/v1/mtr_timer.pl (renamed from mysql-test/lib/mtr_timer.pl)0
-rw-r--r--mysql-test/lib/v1/mtr_unique.pl (renamed from mysql-test/lib/mtr_unique.pl)0
-rwxr-xr-xmysql-test/lib/v1/mysql-test-run.pl5393
-rw-r--r--mysql-test/lib/v1/ndb_config_1_node.ini47
-rw-r--r--mysql-test/lib/v1/ndb_config_2_node.ini55
56 files changed, 14496 insertions, 1722 deletions
diff --git a/mysql-test/lib/My/Config.pm b/mysql-test/lib/My/Config.pm
index 5491e341ddc..f8416e3df3a 100644
--- a/mysql-test/lib/My/Config.pm
+++ b/mysql-test/lib/My/Config.pm
@@ -4,6 +4,7 @@ package My::Config::Option;
use strict;
use warnings;
+use Carp;
sub new {
@@ -26,12 +27,22 @@ sub value {
return $self->{value};
}
+sub option {
+ my ($self)= @_;
+ my $name= $self->{name};
+ my $value= $self->{value};
+
+ my $opt= $name;
+ $opt= "$name=$value" if ($value);
+ $opt= "--$opt" unless ($opt =~ /^--/);
+ return $opt;
+}
package My::Config::Group;
use strict;
use warnings;
-
+use Carp;
sub new {
my ($class, $group_name)= @_;
@@ -68,7 +79,7 @@ sub remove {
return undef unless defined $option;
# Remove from the hash
- delete($self->{options_by_name}->{$option_name}) or die;
+ delete($self->{options_by_name}->{$option_name}) or croak;
# Remove from the array
@{$self->{options}}= grep { $_->name ne $option_name } @{$self->{options}};
@@ -88,6 +99,33 @@ sub name {
return $self->{name};
}
+sub suffix {
+ my ($self)= @_;
+ # Everything in name from the last .
+ my @parts= split(/\./, $self->{name});
+ my $suffix= pop(@parts);
+ return ".$suffix";
+}
+
+sub after {
+ my ($self, $prefix)= @_;
+ die unless defined $prefix;
+
+ # everything after $prefix
+ my $name= $self->{name};
+ if ($name =~ /^\Q$prefix\E(.*)$/)
+ {
+ return $1;
+ }
+ die "Failed to extract the value after '$prefix' in $name";
+}
+
+
+sub split {
+ my ($self)= @_;
+ # Return an array with name parts
+ return split(/\./, $self->{name});
+}
#
# Return a specific option in the group
@@ -100,23 +138,37 @@ sub option {
#
-# Return a specific value for an option in the group
+# Return value for an option in the group, fail if it does not exist
#
sub value {
my ($self, $option_name)= @_;
my $option= $self->option($option_name);
- die "No option named '$option_name' in this group"
+ croak "No option named '$option_name' in group '$self->{name}'"
if ! defined($option);
return $option->value();
}
+#
+# Return value for an option if it exist
+#
+sub if_exist {
+ my ($self, $option_name)= @_;
+ my $option= $self->option($option_name);
+
+ return undef if ! defined($option);
+
+ return $option->value();
+}
+
+
package My::Config;
use strict;
use warnings;
+use Carp;
use IO::File;
use File::Basename;
@@ -132,13 +184,13 @@ sub new {
my $self= bless { groups => [] }, $class;
my $F= IO::File->new($path, "<")
- or die "Could not open '$path': $!";
+ or croak "Could not open '$path': $!";
while ( my $line= <$F> ) {
chomp($line);
# [group]
- if ( $line =~ /\[(.*)\]/ ) {
+ if ( $line =~ /^\[(.*)\]/ ) {
# New group found
$group_name= $1;
#print "group: $group_name\n";
@@ -149,7 +201,7 @@ sub new {
# Magic #! comments
elsif ( $line =~ /^#\!/) {
my $magic= $line;
- die "Found magic comment '$magic' outside of group"
+ croak "Found magic comment '$magic' outside of group"
unless $group_name;
#print "$magic\n";
@@ -171,8 +223,13 @@ sub new {
# !include <filename>
elsif ( $line =~ /^\!include\s*(.*?)\s*$/ ) {
my $include_file_name= dirname($path)."/".$1;
- # Check that the file exists
- die "The include file '$include_file_name' does not exist"
+
+ # Check that the file exists relative to path of first config file
+ if (! -f $include_file_name){
+ # Try to include file relativ to current dir
+ $include_file_name= $1;
+ }
+ croak "The include file '$include_file_name' does not exist"
unless -f $include_file_name;
$self->append(My::Config->new($include_file_name));
@@ -182,7 +239,7 @@ sub new {
elsif ( $line =~ /^([\@\w-]+)\s*$/ ) {
my $option= $1;
- die "Found option '$option' outside of group"
+ croak "Found option '$option' outside of group"
unless $group_name;
#print "$option\n";
@@ -194,13 +251,13 @@ sub new {
my $option= $1;
my $value= $2;
- die "Found option '$option=$value' outside of group"
+ croak "Found option '$option=$value' outside of group"
unless $group_name;
#print "$option=$value\n";
$self->insert($group_name, $option, $value);
} else {
- die "Unexpected line '$line' found in '$path'";
+ croak "Unexpected line '$line' found in '$path'";
}
}
@@ -231,6 +288,7 @@ sub insert {
# Add the option to the group
$group->insert($option, $value, $if_not_exist);
}
+ return $group;
}
#
@@ -240,11 +298,11 @@ sub remove {
my ($self, $group_name, $option_name)= @_;
my $group= $self->group($group_name);
- die "group '$group_name' does not exist"
+ croak "group '$group_name' does not exist"
unless defined($group);
$group->remove($option_name) or
- die "option '$option_name' does not exist";
+ croak "option '$option_name' does not exist";
}
@@ -267,10 +325,10 @@ sub group_exists {
#
sub _group_insert {
my ($self, $group_name)= @_;
- caller eq __PACKAGE__ or die;
+ caller eq __PACKAGE__ or croak;
# Check that group does not already exist
- die "Group already exists" if $self->group_exists($group_name);
+ croak "Group already exists" if $self->group_exists($group_name);
my $group= My::Config::Group->new($group_name);
push(@{$self->{groups}}, $group);
@@ -354,11 +412,11 @@ sub value {
my ($self, $group_name, $option_name)= @_;
my $group= $self->group($group_name);
- die "group '$group_name' does not exist"
+ croak "group '$group_name' does not exist"
unless defined($group);
my $option= $group->option($option_name);
- die "option '$option_name' does not exist"
+ croak "option '$option_name' does not exist"
unless defined($option);
return $option->value();
@@ -372,7 +430,7 @@ sub exists {
my ($self, $group_name, $option_name)= @_;
my $group= $self->group($group_name);
- die "group '$group_name' does not exist"
+ croak "group '$group_name' does not exist"
unless defined($group);
my $option= $group->option($option_name);
@@ -412,11 +470,11 @@ sub stringify {
# Save the config to named file
#
sub save {
- my ($self, $path)= @_;
- my $F= IO::File->new($path, ">")
- or die "Could not open '$path': $!";
- print $F $self;
- undef $F; # Close the file
+ my ($self, $path)= @_;
+ my $F= IO::File->new($path, ">")
+ or croak "Could not open '$path': $!";
+ print $F $self;
+ undef $F; # Close the file
}
1;
diff --git a/mysql-test/lib/My/ConfigFactory.pm b/mysql-test/lib/My/ConfigFactory.pm
new file mode 100644
index 00000000000..567a05ac7a1
--- /dev/null
+++ b/mysql-test/lib/My/ConfigFactory.pm
@@ -0,0 +1,655 @@
+# -*- cperl -*-
+package My::ConfigFactory;
+
+use strict;
+use warnings;
+use Carp;
+
+use My::Config;
+use My::Find;
+
+use File::Basename;
+
+
+#
+# Rules to run first of all
+#
+my @pre_rules=
+(
+);
+
+
+my @share_locations= ("share/mysql", "sql/share", "share");
+
+
+sub get_basedir {
+ my ($self, $group)= @_;
+ my $basedir= $group->if_exist('basedir') ||
+ $self->{ARGS}->{basedir};
+ return $basedir;
+}
+
+
+sub fix_charset_dir {
+ my ($self, $config, $group_name, $group)= @_;
+ return my_find_dir($self->get_basedir($group),
+ \@share_locations, "charsets");
+}
+
+sub fix_language {
+ my ($self, $config, $group_name, $group)= @_;
+ return my_find_dir($self->get_basedir($group),
+ \@share_locations, "english");
+}
+
+sub fix_datadir {
+ my ($self, $config, $group_name)= @_;
+ my $vardir= $self->{ARGS}->{vardir};
+ return "$vardir/$group_name/data";
+}
+
+sub fix_pidfile {
+ my ($self, $config, $group_name, $group)= @_;
+ my $vardir= $self->{ARGS}->{vardir};
+ return "$vardir/run/$group_name.pid";
+}
+
+sub fix_port {
+ my ($self, $config, $group_name, $group)= @_;
+ my $hostname= $group->value('#host');
+ return $self->{HOSTS}->{$hostname}++;
+}
+
+sub fix_host {
+ my ($self)= @_;
+ # Get next host from HOSTS array
+ my @hosts= keys(%{$self->{HOSTS}});;
+ my $host_no= $self->{NEXT_HOST}++ % @hosts;
+ return $hosts[$host_no];
+}
+
+sub is_unique {
+ my ($config, $name, $value)= @_;
+
+ foreach my $group ( $config->groups() ) {
+ if ($group->option($name)) {
+ if ($group->value($name) eq $value){
+ return 0;
+ }
+ }
+ }
+ return 1;
+}
+
+sub fix_server_id {
+ my ($self, $config, $group_name, $group)= @_;
+#define in the order that mysqlds are listed in my.cnf
+
+ my $server_id= $group->if_exist('server-id');
+ if (defined $server_id){
+ if (!is_unique($config, 'server-id', $server_id)) {
+ croak "The server-id($server_id) for '$group_name' is not unique";
+ }
+ return $server_id;
+ }
+
+ do {
+ $server_id= $self->{SERVER_ID}++;
+ } while(!is_unique($config, 'server-id', $server_id));
+
+ #print "$group_name: server_id: $server_id\n";
+ return $server_id;
+}
+
+sub fix_socket {
+ my ($self, $config, $group_name, $group)= @_;
+ # Put socket file in tmpdir
+ my $dir= $self->{ARGS}->{tmpdir};
+ return "$dir/$group_name.sock";
+}
+
+sub fix_tmpdir {
+ my ($self, $config, $group_name, $group)= @_;
+ my $dir= $self->{ARGS}->{tmpdir};
+ return "$dir/$group_name";
+}
+
+sub fix_log_error {
+ my ($self, $config, $group_name, $group)= @_;
+ my $dir= dirname($group->value('datadir'));
+ return "$dir/mysqld.err";
+}
+
+sub fix_log {
+ my ($self, $config, $group_name, $group)= @_;
+ my $dir= dirname($group->value('datadir'));
+ return "$dir/mysqld.log";
+}
+
+sub fix_log_slow_queries {
+ my ($self, $config, $group_name, $group)= @_;
+ my $dir= dirname($group->value('datadir'));
+ return "$dir/mysqld-slow.log";
+}
+
+sub fix_secure_file_priv {
+ my ($self)= @_;
+ my $vardir= $self->{ARGS}->{vardir};
+ # By default, prevent the started mysqld to access files outside of vardir
+ return $vardir;
+}
+
+sub fix_std_data {
+ my ($self, $config, $group_name, $group)= @_;
+ my $basedir= $self->get_basedir($group);
+ return "$basedir/mysql-test/std_data";
+}
+
+sub ssl_supported {
+ my ($self)= @_;
+ return $self->{ARGS}->{ssl};
+}
+
+sub fix_skip_ssl {
+ return if !ssl_supported(@_);
+ # Add skip-ssl if ssl is supported to avoid
+ # that mysqltest connects with SSL by default
+ return 1;
+}
+
+sub fix_ssl_ca {
+ return if !ssl_supported(@_);
+ my $std_data= fix_std_data(@_);
+ return "$std_data/cacert.pem"
+}
+
+sub fix_ssl_server_cert {
+ return if !ssl_supported(@_);
+ my $std_data= fix_std_data(@_);
+ return "$std_data/server-cert.pem"
+}
+
+sub fix_ssl_client_cert {
+ return if !ssl_supported(@_);
+ my $std_data= fix_std_data(@_);
+ return "$std_data/client-cert.pem"
+}
+
+sub fix_ssl_server_key {
+ return if !ssl_supported(@_);
+ my $std_data= fix_std_data(@_);
+ return "$std_data/server-key.pem"
+}
+
+sub fix_ssl_client_key {
+ return if !ssl_supported(@_);
+ my $std_data= fix_std_data(@_);
+ return "$std_data/client-key.pem"
+}
+
+
+#
+# Rules to run for each mysqld in the config
+# - will be run in order listed here
+#
+my @mysqld_rules=
+ (
+ { 'basedir' => sub { return shift->{ARGS}->{basedir}; } },
+ { 'tmpdir' => \&fix_tmpdir },
+ { 'character-sets-dir' => \&fix_charset_dir },
+ { 'language' => \&fix_language },
+ { 'datadir' => \&fix_datadir },
+ { 'pid-file' => \&fix_pidfile },
+ { '#host' => \&fix_host },
+ { 'port' => \&fix_port },
+ { 'socket' => \&fix_socket },
+ { 'log-error' => \&fix_log_error },
+ { 'log' => \&fix_log },
+ { 'log-slow-queries' => \&fix_log_slow_queries },
+ { '#user' => sub { return shift->{ARGS}->{user} || ""; } },
+ { '#password' => sub { return shift->{ARGS}->{password} || ""; } },
+ { 'server-id' => \&fix_server_id, },
+ # By default, prevent the started mysqld to access files outside of vardir
+ { 'secure-file-priv' => sub { return shift->{ARGS}->{vardir}; } },
+ { 'ssl-ca' => \&fix_ssl_ca },
+ { 'ssl-cert' => \&fix_ssl_server_cert },
+ { 'ssl-key' => \&fix_ssl_server_key },
+ );
+
+
+sub fix_ndb_mgmd_port {
+ my ($self, $config, $group_name, $group)= @_;
+ my $hostname= $group->value('HostName');
+ return $self->{HOSTS}->{$hostname}++;
+}
+
+
+sub fix_cluster_dir {
+ my ($self, $config, $group_name, $group)= @_;
+ my $vardir= $self->{ARGS}->{vardir};
+ my (undef, $process_type, $idx, $suffix)= split(/\./, $group_name);
+ return "$vardir/mysql_cluster.$suffix/$process_type.$idx";
+}
+
+
+sub fix_cluster_backup_dir {
+ my ($self, $config, $group_name, $group)= @_;
+ my $vardir= $self->{ARGS}->{vardir};
+ my (undef, $process_type, $idx, $suffix)= split(/\./, $group_name);
+ return "$vardir/mysql_cluster.$suffix/";
+}
+
+
+#
+# Rules to run for each ndb_mgmd in the config
+# - will be run in order listed here
+#
+my @ndb_mgmd_rules=
+(
+ { 'PortNumber' => \&fix_ndb_mgmd_port },
+ { 'DataDir' => \&fix_cluster_dir },
+);
+
+
+#
+# Rules to run for each ndbd in the config
+# - will be run in order listed here
+#
+my @ndbd_rules=
+(
+ { 'HostName' => \&fix_host },
+ { 'DataDir' => \&fix_cluster_dir },
+ { 'BackupDataDir' => \&fix_cluster_backup_dir },
+);
+
+
+#
+# Rules to run for each cluster_config section
+# - will be run in order listed here
+#
+my @cluster_config_rules=
+(
+ { 'ndb_mgmd' => \&fix_host },
+ { 'ndbd' => \&fix_host },
+ { 'mysqld' => \&fix_host },
+ { 'ndbapi' => \&fix_host },
+);
+
+
+#
+# Rules to run for [client] section
+# - will be run in order listed here
+#
+my @client_rules=
+(
+);
+
+
+#
+# Rules to run for [mysqltest] section
+# - will be run in order listed here
+#
+my @mysqltest_rules=
+(
+ { 'ssl-ca' => \&fix_ssl_ca },
+ { 'ssl-cert' => \&fix_ssl_client_cert },
+ { 'ssl-key' => \&fix_ssl_client_key },
+ { 'skip-ssl' => \&fix_skip_ssl },
+);
+
+
+#
+# Rules to run for [mysqlbinlog] section
+# - will be run in order listed here
+#
+my @mysqlbinlog_rules=
+(
+ { 'character-sets-dir' => \&fix_charset_dir },
+);
+
+
+#
+# Rules to run for [mysql_upgrade] section
+# - will be run in order listed here
+#
+my @mysql_upgrade_rules=
+(
+ { 'tmpdir' => sub { return shift->{ARGS}->{tmpdir}; } },
+);
+
+
+#
+# Generate a [client.<suffix>] group to be
+# used for connecting to [mysqld.<suffix>]
+#
+sub post_check_client_group {
+ my ($self, $config, $client_group_name, $mysqld_group_name)= @_;
+
+ # Settings needed for client, copied from its "mysqld"
+ my %client_needs=
+ (
+ port => 'port',
+ socket => 'socket',
+ host => '#host',
+ user => '#user',
+ password => '#password',
+ );
+
+ my $group_to_copy_from= $config->group($mysqld_group_name);
+ while (my ($name_to, $name_from)= each( %client_needs )) {
+ my $option= $group_to_copy_from->option($name_from);
+
+ if (! defined $option){
+ #print $config;
+ croak "Could not get value for '$name_from'";
+ }
+ $config->insert($client_group_name, $name_to, $option->value())
+ }
+}
+
+
+sub post_check_client_groups {
+ my ($self, $config)= @_;
+
+ my $first_mysqld= $config->first_like('mysqld.');
+
+ return unless $first_mysqld;
+
+ # Always generate [client] pointing to the first
+ # [mysqld.<suffix>]
+ $self->post_check_client_group($config,
+ 'client',
+ $first_mysqld->name());
+
+ # Then generate [client.<suffix>] for each [mysqld.<suffix>]
+ foreach my $mysqld ( $config->like('mysqld.') ) {
+ $self->post_check_client_group($config,
+ 'client'.$mysqld->after('mysqld'),
+ $mysqld->name())
+ }
+
+}
+
+
+#
+# Generate [embedded] by copying the values
+# needed from the default [mysqld] section
+# and from first [mysqld.<suffix>]
+#
+sub post_check_embedded_group {
+ my ($self, $config)= @_;
+
+ return unless $self->{ARGS}->{embedded};
+
+ my $mysqld= $config->group('mysqld') or
+ croak "Can't run with embedded, config has no default mysqld section";
+
+ my $first_mysqld= $config->first_like('mysqld.') or
+ croak "Can't run with embedded, config has no mysqld";
+
+ my @no_copy =
+ (
+ 'log-error', # Embedded server writes stderr to mysqltest's log file
+ 'slave-net-timeout', # Embedded server are not build with replication
+ );
+
+ foreach my $option ( $mysqld->options(), $first_mysqld->options() ) {
+ # Don't copy options whose name is in "no_copy" list
+ next if grep ( $option->name() eq $_, @no_copy);
+
+ $config->insert('embedded', $option->name(), $option->value())
+ }
+
+}
+
+
+sub resolve_at_variable {
+ my ($self, $config, $group, $option)= @_;
+
+ # Split the options value on last .
+ my @parts= split(/\./, $option->value());
+ my $option_name= pop(@parts);
+ my $group_name= join('.', @parts);
+
+ $group_name =~ s/^\@//; # Remove at
+
+ my $from_group= $config->group($group_name)
+ or croak "There is no group named '$group_name' that ",
+ "can be used to resolve '$option_name'";
+
+ my $from= $from_group->value($option_name);
+ $config->insert($group->name(), $option->name(), $from)
+}
+
+
+sub post_fix_resolve_at_variables {
+ my ($self, $config)= @_;
+
+ foreach my $group ( $config->groups() ) {
+ foreach my $option ( $group->options()) {
+ next unless defined $option->value();
+
+ $self->resolve_at_variable($config, $group, $option)
+ if ($option->value() =~ /^\@/);
+ }
+ }
+}
+
+sub post_fix_mysql_cluster_section {
+ my ($self, $config)= @_;
+
+ # Add a [mysl_cluster.<suffix>] section for each
+ # defined [cluster_config.<suffix>] section
+ foreach my $group ( $config->like('cluster_config\.\w*$') )
+ {
+ my @urls;
+ # Generate ndb_connectstring for this cluster
+ foreach my $ndb_mgmd ( $config->like('cluster_config.ndb_mgmd.')) {
+ if ($ndb_mgmd->suffix() eq $group->suffix()) {
+ my $host= $ndb_mgmd->value('HostName');
+ my $port= $ndb_mgmd->value('PortNumber');
+ push(@urls, "$host:$port");
+ }
+ }
+ croak "Could not generate valid ndb_connectstring for '$group'"
+ unless @urls > 0;
+ my $ndb_connectstring= join(";", @urls);
+
+ # Add ndb_connectstring to [mysql_cluster.<suffix>]
+ $config->insert('mysql_cluster'.$group->suffix(),
+ 'ndb_connectstring', $ndb_connectstring);
+
+ # Add ndb_connectstring to each mysqld connected to this
+ # cluster
+ foreach my $mysqld ( $config->like('cluster_config.mysqld.')) {
+ if ($mysqld->suffix() eq $group->suffix()) {
+ my $after= $mysqld->after('cluster_config.mysqld');
+ $config->insert("mysqld$after",
+ 'ndb_connectstring', $ndb_connectstring);
+ }
+ }
+ }
+}
+
+#
+# Rules to run last of all
+#
+my @post_rules=
+(
+ \&post_check_client_groups,
+ \&post_fix_mysql_cluster_section,
+ \&post_fix_resolve_at_variables,
+ \&post_check_embedded_group,
+);
+
+
+sub run_rules_for_group {
+ my ($self, $config, $group, @rules)= @_;
+ foreach my $hash ( @rules ) {
+ while (my ($option, $rule)= each( %{$hash} )) {
+ # Only run this rule if the value is not already defined
+ if (!$config->exists($group->name(), $option)) {
+ my $value;
+ if (ref $rule eq "CODE") {
+ # Call the rule function
+ $value= &$rule($self, $config, $group->name(),
+ $config->group($group->name()));
+ } else {
+ $value= $rule;
+ }
+ if (defined $value) {
+ $config->insert($group->name(), $option, $value, 1);
+ }
+ }
+ }
+ }
+}
+
+
+sub run_section_rules {
+ my ($self, $config, $name, @rules)= @_;
+
+ foreach my $group ( $config->like($name) ) {
+ $self->run_rules_for_group($config, $group, @rules);
+ }
+}
+
+
+sub run_generate_sections_from_cluster_config {
+ my ($self, $config)= @_;
+
+ my @options= ('ndb_mgmd', 'ndbd',
+ 'mysqld', 'ndbapi');
+
+ foreach my $group ( $config->like('cluster_config\.\w*$') ) {
+
+ # Keep track of current index per process type
+ my %idxes;
+ map { $idxes{$_}= 1; } @options;
+
+ foreach my $option_name ( @options ) {
+ my $value= $group->value($option_name);
+ my @hosts= split(/,/, $value, -1); # -1 => return also empty strings
+
+ # Add at least one host
+ push(@hosts, undef) unless scalar(@hosts);
+
+ # Assign hosts unless already fixed
+ @hosts= map { $self->fix_host() unless $_; } @hosts;
+
+ # Write the hosts value back
+ $group->insert($option_name, join(",", @hosts));
+
+ # Generate sections for each host
+ foreach my $host ( @hosts ){
+ my $idx= $idxes{$option_name}++;
+
+ my $suffix= $group->suffix();
+ # Generate a section for ndb_mgmd to read
+ $config->insert("cluster_config.$option_name.$idx$suffix",
+ "HostName", $host);
+
+ if ($option_name eq 'mysqld'){
+ my $datadir=
+ $self->fix_cluster_dir($config,
+ "cluster_config.mysqld.$idx$suffix",
+ $group);
+ $config->insert("mysqld.$idx$suffix",
+ 'datadir', "$datadir/data");
+ }
+ }
+ }
+ }
+}
+
+
+sub new_config {
+ my ($class, $args)= @_;
+
+ my @required_args= ('basedir', 'baseport', 'vardir', 'template_path');
+
+ foreach my $required ( @required_args ) {
+ croak "you must pass '$required'" unless defined $args->{$required};
+ }
+
+ # Fill in hosts/port hash
+ my $hosts= {};
+ my $baseport= $args->{baseport};
+ $args->{hosts}= [ 'localhost' ] unless exists($args->{hosts});
+ foreach my $host ( @{$args->{hosts}} ) {
+ $hosts->{$host}= $baseport;
+ }
+
+ # Open the config template
+ my $config= My::Config->new($args->{'template_path'});
+ my $extra_template_path= $args->{'extra_template_path'};
+ if ($extra_template_path){
+ $config->append(My::Config->new($extra_template_path));
+ }
+ my $self= bless {
+ CONFIG => $config,
+ ARGS => $args,
+ HOSTS => $hosts,
+ NEXT_HOST => 0,
+ SERVER_ID => 1,
+ }, $class;
+
+
+ {
+ # Run pre rules
+ foreach my $rule ( @pre_rules ) {
+ &$rule($self, $config);
+ }
+ }
+
+
+ $self->run_section_rules($config,
+ 'cluster_config\.\w*$',
+ @cluster_config_rules);
+ $self->run_generate_sections_from_cluster_config($config);
+
+ $self->run_section_rules($config,
+ 'cluster_config.ndb_mgmd.',
+ @ndb_mgmd_rules);
+ $self->run_section_rules($config,
+ 'cluster_config.ndbd',
+ @ndbd_rules);
+
+ $self->run_section_rules($config,
+ 'mysqld.',
+ @mysqld_rules);
+
+ # [mysqlbinlog] need additional settings
+ $self->run_rules_for_group($config,
+ $config->insert('mysqlbinlog'),
+ @mysqlbinlog_rules);
+
+ # [mysql_upgrade] need additional settings
+ $self->run_rules_for_group($config,
+ $config->insert('mysql_upgrade'),
+ @mysql_upgrade_rules);
+
+ # Additional rules required for [client]
+ $self->run_rules_for_group($config,
+ $config->insert('client'),
+ @client_rules);
+
+
+ # Additional rules required for [mysqltest]
+ $self->run_rules_for_group($config,
+ $config->insert('mysqltest'),
+ @mysqltest_rules);
+
+ {
+ # Run post rules
+ foreach my $rule ( @post_rules ) {
+ &$rule($self, $config);
+ }
+ }
+
+ return $config;
+}
+
+
+1;
+
diff --git a/mysql-test/lib/My/CoreDump.pm b/mysql-test/lib/My/CoreDump.pm
new file mode 100644
index 00000000000..599f9ccbfca
--- /dev/null
+++ b/mysql-test/lib/My/CoreDump.pm
@@ -0,0 +1,131 @@
+# -*- cperl -*-
+# Copyright (C) 2004-2006 MySQL AB
+#
+# 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-1301 USA
+
+package My::CoreDump;
+
+use strict;
+use Carp;
+use My::Platform;
+
+use File::Temp qw/ tempfile tempdir /;
+
+sub _gdb {
+ my ($core_name)= @_;
+
+ print "\nTrying 'gdb' to get a backtrace\n";
+
+ return unless -f $core_name;
+
+ # Find out name of binary that generated core
+ `gdb -c '$core_name' --batch 2>&1` =~
+ /Core was generated by `([^\s\'\`]+)/;
+ my $binary= $1 or return;
+ print "Core generated by '$binary'\n";
+
+ # Create tempfile containing gdb commands
+ my ($tmp, $tmp_name) = tempfile();
+ print $tmp
+ "bt\n",
+ "thread apply all bt\n",
+ "quit\n";
+ close $tmp or die "Error closing $tmp_name: $!";
+
+ # Run gdb
+ my $gdb_output=
+ `gdb '$binary' -c '$core_name' -x '$tmp_name' --batch 2>&1`;
+
+ unlink $tmp_name or die "Error removing $tmp_name: $!";
+
+ return if $? >> 8;
+ return unless $gdb_output;
+
+ print <<EOF, $gdb_output, "\n";
+Output from gdb follows. The first stack trace is from the failing thread.
+The following stack traces are from all threads (so the failing one is
+duplicated).
+--------------------------
+EOF
+ return 1;
+}
+
+
+sub _dbx {
+ my ($core_name)= @_;
+
+ print "\nTrying 'dbx' to get a backtrace\n";
+
+ return unless -f $core_name;
+
+ # Find out name of binary that generated core
+ `echo | dbx - '$core_name' 2>&1` =~
+ /Corefile specified executable: "([^"]+)"/;
+ my $binary= $1 or return;
+ print "Core generated by '$binary'\n";
+
+ # Find all threads
+ my @thr_ids = `echo threads | dbx '$binary' '$core_name' 2>&1` =~ /t@\d+/g;
+
+ # Create tempfile containing dbx commands
+ my ($tmp, $tmp_name) = tempfile();
+ foreach my $thread (@thr_ids) {
+ print $tmp "where $thread\n";
+ }
+ print $tmp "exit\n";
+ close $tmp or die "Error closing $tmp_name: $!";
+
+ # Run dbx
+ my $dbx_output=
+ `cat '$tmp_name' | dbx '$binary' '$core_name' 2>&1`;
+
+ unlink $tmp_name or die "Error removing $tmp_name: $!";
+
+ return if $? >> 8;
+ return unless $dbx_output;
+
+ print <<EOF, $dbx_output, "\n";
+Output from dbx follows. Stack trace is printed for all threads in order,
+above this you should see info about which thread was the failing one.
+----------------------------
+EOF
+ return 1;
+}
+
+
+sub show {
+ my ($class, $core_name)= @_;
+
+ # We try dbx first; gdb itself may coredump if run on a Sun Studio
+ # compiled binary on Solaris.
+
+ my @debuggers =
+ (
+ \&_dbx,
+ \&_gdb,
+ # TODO...
+ );
+
+ # Try debuggers until one succeeds
+
+ foreach my $debugger (@debuggers){
+ if ($debugger->($core_name)){
+ return;
+ }
+ }
+ return;
+}
+
+
+1;
diff --git a/mysql-test/lib/My/File/Path.pm b/mysql-test/lib/My/File/Path.pm
new file mode 100644
index 00000000000..99edeecdaf7
--- /dev/null
+++ b/mysql-test/lib/My/File/Path.pm
@@ -0,0 +1,177 @@
+# -*- cperl -*-
+package My::File::Path;
+use strict;
+
+
+#
+# File::Path::rmtree has a problem with deleting files
+# and directories where it hasn't got read permission
+#
+# Patch this by installing a 'rmtree' function in local
+# scope that first chmod all files to 0777 before calling
+# the original rmtree function.
+#
+# This is almost gone in version 1.08 of File::Path -
+# but unfortunately some hosts still suffers
+# from this also in 1.08
+#
+
+use Exporter;
+use base "Exporter";
+our @EXPORT= qw / rmtree mkpath copytree /;
+
+use File::Find;
+use File::Copy;
+use File::Spec;
+use Carp;
+use My::Handles;
+use My::Platform;
+
+sub rmtree {
+ my ($dir)= @_;
+ find( {
+ bydepth => 1,
+ no_chdir => 1,
+ wanted => sub {
+ my $name= $_;
+ if (!-l $name && -d _){
+ return if (rmdir($name) == 1);
+
+ chmod(0777, $name) or carp("couldn't chmod(0777, $name): $!");
+
+ return if (rmdir($name) == 1);
+
+ # Failed to remove the directory, analyze
+ carp("Couldn't remove directory '$name': $!");
+ My::Handles::show_handles($name);
+ } else {
+ return if (unlink($name) == 1);
+
+ chmod(0777, $name) or carp("couldn't chmod(0777, $name): $!");
+
+ return if (unlink($name) == 1);
+
+ carp("Couldn't delete file '$name': $!");
+ My::Handles::show_handles($name);
+ }
+ }
+ }, $dir );
+};
+
+
+use File::Basename;
+sub _mkpath_debug {
+ my ($message, $path, $dir, $err)= @_;
+
+ print "=" x 40, "\n";
+ print $message, "\n";
+ print "err: '$err'\n";
+ print "path: '$path'\n";
+ print "dir: '$dir'\n";
+
+ print "-" x 40, "\n";
+ my $dirname= dirname($path);
+ print "ls -l $dirname\n";
+ print `ls -l $dirname`, "\n";
+ print "-" x 40, "\n";
+ print "dir $dirname\n";
+ print `dir $dirname`, "\n";
+ print "-" x 40, "\n";
+ my $dirname2= dirname($dirname);
+ print "ls -l $dirname2\n";
+ print `ls -l $dirname2`, "\n";
+ print "-" x 40, "\n";
+ print "dir $dirname2\n";
+ print `dir $dirname2`, "\n";
+ print "-" x 40, "\n";
+ print "file exists\n" if (-e $path);
+ print "file is a plain file\n" if (-f $path);
+ print "file is a directory\n" if (-d $path);
+ print "-" x 40, "\n";
+ print "showing handles for $path\n";
+ My::Handles::show_handles($path);
+
+ print "=" x 40, "\n";
+
+}
+
+
+sub mkpath {
+ my $path;
+
+ die "Usage: mkpath(<path>)" unless @_ == 1;
+
+ foreach my $dir ( File::Spec->splitdir( @_ ) ) {
+ #print "dir: $dir\n";
+ if ($dir =~ /^[a-z]:/i){
+ # Found volume ie. C:
+ $path= $dir;
+ next;
+ }
+
+ $path= File::Spec->catdir($path, $dir);
+ #print "path: $path\n";
+
+ next if -d $path; # Path already exists and is a directory
+ croak("File already exists but is not a directory: '$path'") if -e $path;
+ next if mkdir($path);
+ _mkpath_debug("mkdir failed", $path, $dir, $!);
+
+ # mkdir failed, try one more time
+ next if mkdir($path);
+ _mkpath_debug("mkdir failed, second time", $path, $dir, $!);
+
+ # mkdir failed again, try two more time after sleep(s)
+ sleep(1);
+ next if mkdir($path);
+ _mkpath_debug("mkdir failed, third time", $path, $dir, $!);
+
+ sleep(1);
+ next if mkdir($path);
+ _mkpath_debug("mkdir failed, fourth time", $path, $dir, $!);
+
+ # Report failure and die
+ croak("Couldn't create directory '$path' ",
+ " after 4 attempts and 2 sleep(1): $!");
+ }
+};
+
+
+sub copytree {
+ my ($from_dir, $to_dir, $use_umask) = @_;
+
+ die "Usage: copytree(<fromdir>, <todir>, [<umask>])"
+ unless @_ == 2 or @_ == 3;
+
+ my $orig_umask;
+ if ($use_umask){
+ # Set new umask and remember the original
+ $orig_umask= umask(oct($use_umask));
+ }
+
+ mkpath("$to_dir");
+ opendir(DIR, "$from_dir")
+ or croak("Can't find $from_dir$!");
+ for(readdir(DIR)) {
+
+ next if "$_" eq "." or "$_" eq "..";
+
+ # Skip SCCS/ directories
+ next if "$_" eq "SCCS";
+
+ if ( -d "$from_dir/$_" )
+ {
+ copytree("$from_dir/$_", "$to_dir/$_");
+ next;
+ }
+ copy("$from_dir/$_", "$to_dir/$_");
+ }
+ closedir(DIR);
+
+ if ($orig_umask){
+ # Set the original umask
+ umask($orig_umask);
+ }
+}
+
+1;
diff --git a/mysql-test/lib/My/Find.pm b/mysql-test/lib/My/Find.pm
new file mode 100644
index 00000000000..8557584bbc8
--- /dev/null
+++ b/mysql-test/lib/My/Find.pm
@@ -0,0 +1,245 @@
+# -*- cperl -*-
+# Copyright (C) 2004-2006 MySQL AB
+#
+# 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-1301 USA
+
+
+package My::Find;
+
+#
+# Utility functions to find files in a MySQL source or bindist
+#
+
+use strict;
+use Carp;
+use My::Platform;
+
+use base qw(Exporter);
+our @EXPORT= qw(my_find_bin my_find_dir my_find_file NOT_REQUIRED);
+
+our $vs_config_dir;
+
+my $bin_extension= ".exe" if IS_WINDOWS;
+
+# Helper function to be used for fourth parameter to find functions
+sub NOT_REQUIRED { return 0; }
+
+#
+# my_find_bin - find an executable with "name_1...name_n" in
+# paths "path_1...path_n" and return the full path
+#
+# Example:
+# my $mysqld_exe= my_find_bin($basedir.
+# ["sql", "bin"],
+# ["mysqld", "mysqld-debug"]);
+# my $mysql_exe= my_find_bin($basedir,
+# ["client", "bin"],
+# "mysql");
+#
+#
+# To check if something exists, use the required parameter
+# set to 0, the function will return an empty string if the
+# binary is not found
+# my $mysql_exe= my_find_bin($basedir,
+# ["client", "bin"],
+# "mysql", NOT_REQUIRED);
+#
+# NOTE: The function honours MTR_VS_CONFIG environment variable
+#
+#
+sub my_find_bin {
+ my ($base, $paths, $names, $required)= @_;
+ croak "usage: my_find_bin(<base>, <paths>, <names>, [<required>])"
+ unless @_ == 4 or @_ == 3;
+
+ # -------------------------------------------------------
+ # Find and return the first executable
+ # -------------------------------------------------------
+ foreach my $path (my_find_paths($base, $paths, $names, $bin_extension)) {
+ return $path if ( -x $path or (IS_WINDOWS and -f $path) );
+ }
+ if (defined $required and $required == NOT_REQUIRED){
+ # Return empty string to indicate not found
+ return "";
+ }
+ find_error($base, $paths, $names);
+}
+
+
+#
+# my_find_file - find a file with "name_1...name_n" in
+# paths "path_1...path_n" and return the full path
+#
+# Example:
+# my $mysqld_exe= my_find_file($basedir.
+# ["sql", "bin"],
+# "filename");
+#
+#
+# Also supports NOT_REQUIRED flag
+#
+# NOTE: The function honours MTR_VS_CONFIG environment variable
+#
+#
+sub my_find_file {
+ my ($base, $paths, $names, $required)= @_;
+ croak "usage: my_find_file(<base>, <paths>, <names>, [<required>])"
+ unless @_ == 4 or @_ == 3;
+
+ # -------------------------------------------------------
+ # Find and return the first executable
+ # -------------------------------------------------------
+ foreach my $path (my_find_paths($base, $paths, $names, $bin_extension)) {
+ return $path if ( -f $path );
+ }
+ if (defined $required and $required == NOT_REQUIRED){
+ # Return empty string to indicate not found
+ return "";
+ }
+ find_error($base, $paths, $names);
+}
+
+
+#
+# my_find_dir - find the first existing directory in one of
+# the given paths
+#
+# Example:
+# my $charset_set= my_find_dir($basedir,
+# ["mysql/share","sql/share", "share"],
+# ["charset"]);
+# or
+# my $charset_set= my_find_dir($basedir,
+# ['client_release', 'client_debug',
+# 'client', 'bin']);
+#
+# NOTE: The function honours MTR_VS_CONFIG environment variable
+#
+#
+sub my_find_dir {
+ my ($base, $paths, $dirs, $required)= @_;
+ croak "usage: my_find_dir(<base>, <paths>[, <dirs>])"
+ unless (@_ == 3 or @_ == 2);
+
+ # -------------------------------------------------------
+ # Find and return the first directory
+ # -------------------------------------------------------
+ foreach my $path (my_find_paths($base, $paths, $dirs)) {
+ return $path if ( -d $path );
+ }
+ find_error($base, $paths, $dirs);
+}
+
+
+sub my_find_paths {
+ my ($base, $paths, $names, $extension)= @_;
+
+ # Convert the arguments into two normal arrays to ease
+ # further mappings
+ my (@names, @paths);
+ push(@names, ref $names eq "ARRAY" ? @$names : $names);
+ push(@paths, ref $paths eq "ARRAY" ? @$paths : $paths);
+
+ #print "base: $base\n";
+ #print "names: @names\n";
+ #print "paths: @paths\n";
+
+ # User can select to look in a special build dir
+ # which is a subdirectory of any of the paths
+ my @extra_dirs;
+ my $build_dir= $vs_config_dir || $ENV{MTR_VS_CONFIG} || $ENV{MTR_BUILD_DIR};
+ push(@extra_dirs, $build_dir) if defined $build_dir;
+
+ if (defined $extension){
+ # Append extension to names, if name does not already have extension
+ map { $_.=$extension unless /\.(.*)+$/ } @names;
+ }
+
+ # -------------------------------------------------------
+ # Windows specific
+ # -------------------------------------------------------
+ if (IS_WINDOWS) {
+ # Add the default extra build dirs unless a specific one has
+ # already been selected
+ push(@extra_dirs,
+ ("release",
+ "relwithdebinfo",
+ "debug")) if @extra_dirs == 0;
+ }
+
+ #print "extra_build_dir: @extra_dirs\n";
+
+ # -------------------------------------------------------
+ # Build cross product of "paths * extra_build_dirs"
+ # -------------------------------------------------------
+ push(@paths, map { my $path= $_;
+ map { "$path/$_" } @extra_dirs
+ } @paths);
+ #print "paths: @paths\n";
+
+ # -------------------------------------------------------
+ # Build cross product of "paths * names"
+ # -------------------------------------------------------
+ @paths= map { my $path= $_;
+ map { "$path/$_" } @names
+ } @paths;
+ #print "paths: @paths\n";
+
+ # -------------------------------------------------------
+ # Prepend base to all paths
+ # -------------------------------------------------------
+ @paths= map { "$base/$_" } @paths;
+ #print "paths: @paths\n";
+
+ # -------------------------------------------------------
+ # Glob all paths to expand wildcards
+ # -------------------------------------------------------
+ @paths= map { glob("$_") } @paths;
+ #print "paths: @paths\n";
+
+ # -------------------------------------------------------
+ # Return the list of paths
+ # -------------------------------------------------------
+ return @paths;
+}
+
+
+sub commify {
+ return
+ (@_ == 0) ? '' :
+ (@_ == 1) ? $_[0] :
+ (@_ == 2) ? join(" or ", @_) :
+ join(", ", @_[0..($#_-1)], "or $_[-1]");
+
+}
+
+
+sub fnuttify {
+ return map('\''.$_.'\'', @_);
+}
+
+
+sub find_error {
+ my ($base, $paths, $names)= @_;
+
+ my (@names, @paths);
+ push(@names, ref $names eq "ARRAY" ? @$names : $names);
+ push(@paths, ref $paths eq "ARRAY" ? @$paths : $paths);
+
+ croak "** ERROR: Could not find ",
+ commify(fnuttify(@names)), " in ",
+ commify(fnuttify(my_find_paths($base, $paths, $names))), "\n";
+}
+
+1;
diff --git a/mysql-test/lib/My/Handles.pm b/mysql-test/lib/My/Handles.pm
new file mode 100755
index 00000000000..66ee22b403f
--- /dev/null
+++ b/mysql-test/lib/My/Handles.pm
@@ -0,0 +1,69 @@
+# -*- cperl -*-
+# Copyright (C) 2008 MySQL AB
+#
+# 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-1301 USA
+
+package My::Handles;
+
+
+use strict;
+use Carp;
+
+use My::Platform;
+
+my $handle_exe;
+
+
+if (IS_WINDOWS){
+ # Check if handle.exe is available
+ # Pass switch to accept the EULA to avoid hanging
+ # if the program hasn't been run before.
+ my $list= `handle.exe -? -accepteula 2>&1`;
+ foreach my $line (split('\n', $list))
+ {
+ $handle_exe= "$1.$2"
+ if ($line =~ /Handle v([0-9]*)\.([0-9]*)/);
+ }
+ if ($handle_exe){
+ print "Found handle.exe version $handle_exe\n";
+ }
+}
+
+
+sub show_handles
+{
+ my ($dir)= @_;
+ return unless $handle_exe;
+ return unless $dir;
+
+ $dir= native_path($dir);
+
+ # Get a list of open handles in a particular directory
+ my $list= `handle.exe "$dir" 2>&1` or return;
+
+ foreach my $line (split('\n', $list))
+ {
+ return if ($line =~ /No matching handles found/);
+ }
+
+ print "\n";
+ print "=" x 50, "\n";
+ print "Open handles in '$dir':\n";
+ print "$list\n";
+ print "=" x 50, "\n\n";
+
+ return;
+}
+
+1;
diff --git a/mysql-test/lib/My/Options.pm b/mysql-test/lib/My/Options.pm
new file mode 100644
index 00000000000..40f05c41d1c
--- /dev/null
+++ b/mysql-test/lib/My/Options.pm
@@ -0,0 +1,199 @@
+# -*- cperl -*-
+# Copyright (C) 2004-2006 MySQL AB
+#
+# 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-1301 USA
+
+
+package My::Options;
+
+#
+# Utility functions to work with list of options
+#
+
+use strict;
+
+
+sub same($$) {
+ my $l1= shift;
+ my $l2= shift;
+ return compare($l1,$l2) == 0;
+}
+
+
+sub compare ($$) {
+ my $l1= shift;
+ my $l2= shift;
+
+ my @l1= @$l1;
+ my @l2= @$l2;
+
+ return -1 if @l1 < @l2;
+ return 1 if @l1 > @l2;
+
+ while ( @l1 ) # Same length
+ {
+ my $e1= shift @l1;
+ my $e2= shift @l2;
+ my $cmp= ($e1 cmp $e2);
+ return $cmp if $cmp != 0;
+ }
+
+ return 0; # They are the same
+}
+
+
+sub _split_option {
+ my ($option)= @_;
+ if ($option=~ /^--(.*)=(.*)$/){
+ return ($1, $2);
+ }
+ elsif ($option=~ /^--(.*)$/){
+ return ($1, undef)
+ }
+ elsif ($option=~ /^\$(.*)$/){ # $VAR
+ return ($1, undef)
+ }
+ elsif ($option=~ /^(.*)=(.*)$/){
+ return ($1, $2)
+ }
+ elsif ($option=~ /^-O$/){
+ return (undef, undef);
+ }
+ die "Unknown option format '$option'";
+}
+
+
+sub _build_option {
+ my ($name, $value)= @_;
+ if ($name =~ /^O, /){
+ return "-".$name."=".$value;
+ }
+ elsif ($value){
+ return "--".$name."=".$value;
+ }
+ return "--".$name;
+}
+
+
+#
+# Compare two list of options and return what would need
+# to be done to get the server running with the new settings
+#
+sub diff {
+ my ($from_opts, $to_opts)= @_;
+
+ my %from;
+ foreach my $from (@$from_opts)
+ {
+ my ($opt, $value)= _split_option($from);
+ next unless defined($opt);
+ $from{$opt}= $value;
+ }
+
+ #print "from: ", %from, "\n";
+
+ my %to;
+ foreach my $to (@$to_opts)
+ {
+ my ($opt, $value)= _split_option($to);
+ next unless defined($opt);
+ $to{$opt}= $value;
+ }
+
+ #print "to: ", %to, "\n";
+
+ # Remove the ones that are in both lists
+ foreach my $name (keys %from){
+ if (exists $to{$name} and $to{$name} eq $from{$name}){
+ #print "removing '$name' from both lists\n";
+ delete $to{$name};
+ delete $from{$name};
+ }
+ }
+
+ #print "from: ", %from, "\n";
+ #print "to: ", %to, "\n";
+
+ # Add all keys in "to" to result
+ my @result;
+ foreach my $name (keys %to){
+ push(@result, _build_option($name, $to{$name}));
+ }
+
+ # Add all keys in "from" that are not in "to"
+ # to result as "set to default"
+ foreach my $name (keys %from){
+ if (not exists $to{$name}) {
+ push(@result, _build_option($name, "default"));
+ }
+ }
+
+ return @result;
+}
+
+
+sub is_set {
+ my ($opts, $set_opts)= @_;
+
+ foreach my $opt (@$opts){
+
+ my ($opt_name1, $value1)= _split_option($opt);
+
+ foreach my $set_opt (@$set_opts){
+ my ($opt_name2, $value2)= _split_option($set_opt);
+
+ if ($opt_name1 eq $opt_name2){
+ # Option already set
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+sub toSQL {
+ my (@options)= @_;
+ my @sql;
+
+ foreach my $option (@options) {
+ my ($name, $value)= _split_option($option);
+ #print "name: $name\n";
+ #print "value: $value\n";
+ if ($name =~ /^O, (.*)/){
+ push(@sql, "SET GLOBAL $1=$value");
+ }
+ elsif ($name =~ /^set-variable=(.*)/){
+ push(@sql, "SET GLOBAL $1=$value");
+ }
+ else {
+ my $sql_name= $name;
+ $sql_name=~ s/-/_/g;
+ push(@sql, "SET GLOBAL $sql_name=$value");
+ }
+ }
+ return join("; ", @sql);
+}
+
+
+sub toStr {
+ my $name= shift;
+ return "$name: ",
+ "['", join("', '", @_), "']\n";
+}
+
+
+1;
+
diff --git a/mysql-test/lib/My/Platform.pm b/mysql-test/lib/My/Platform.pm
new file mode 100644
index 00000000000..3dd5c552b10
--- /dev/null
+++ b/mysql-test/lib/My/Platform.pm
@@ -0,0 +1,156 @@
+# -*- cperl -*-
+# Copyright (C) 2004-2006 MySQL AB
+#
+# 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-1301 USA
+
+package My::Platform;
+
+use strict;
+use File::Basename;
+use File::Path;
+
+use base qw(Exporter);
+our @EXPORT= qw(IS_CYGWIN IS_WINDOWS IS_WIN32PERL
+ native_path posix_path mixed_path
+ check_socket_path_length process_alive);
+
+BEGIN {
+ if ($^O eq "cygwin") {
+ # Make sure cygpath works
+ if ((system("cygpath > /dev/null 2>&1") >> 8) != 1){
+ die "Could not execute 'cygpath': $!";
+ }
+ eval 'sub IS_CYGWIN { 1 }';
+ }
+ else {
+ eval 'sub IS_CYGWIN { 0 }';
+ }
+ if ($^O eq "MSWin32") {
+ eval 'sub IS_WIN32PERL { 1 }';
+ }
+ else {
+ eval 'sub IS_WIN32PERL { 0 }';
+ }
+}
+
+BEGIN {
+ if (IS_CYGWIN or IS_WIN32PERL) {
+ eval 'sub IS_WINDOWS { 1 }';
+ }
+ else {
+ eval 'sub IS_WINDOWS { 0 }';
+ }
+}
+
+
+#
+# native_path
+# Convert from path format used by perl to the underlying
+# operating systems format
+#
+# NOTE
+# Used when running windows binaries (that expect windows paths)
+# in cygwin perl (that uses unix paths)
+#
+
+use Memoize;
+if (!IS_WIN32PERL){
+ memoize('mixed_path');
+ memoize('native_path');
+ memoize('posix_path');
+}
+
+sub mixed_path {
+ my ($path)= @_;
+ if (IS_CYGWIN){
+ return unless defined $path;
+ my $cmd= "cygpath -m $path";
+ $path= `$cmd` or
+ print "Failed to run: '$cmd', $!\n";
+ chomp $path;
+ }
+ return $path;
+}
+
+sub native_path {
+ my ($path)= @_;
+ $path=~ s/\//\\/g
+ if (IS_CYGWIN or IS_WIN32PERL);
+ return $path;
+}
+
+sub posix_path {
+ my ($path)= @_;
+ if (IS_CYGWIN){
+ return unless defined $path;
+ $path= `cygpath $path`;
+ chomp $path;
+ }
+ return $path;
+}
+
+use File::Temp qw /tempdir/;
+
+sub check_socket_path_length {
+ my ($path)= @_;
+
+ return 0 if IS_WINDOWS;
+
+ require IO::Socket::UNIX;
+
+ my $truncated= 1; # Be negative
+
+ # Create a tempfile name with same length as "path"
+ my $tmpdir = tempdir( CLEANUP => 0);
+ my $len = length($path) - length($tmpdir);
+ my $testfile = $tmpdir . "x" x ($len > 0 ? $len : 1);
+ my $sock;
+ eval {
+ $sock= new IO::Socket::UNIX
+ (
+ Local => $testfile,
+ Listen => 1,
+ );
+
+ die "Could not create UNIX domain socket: $!"
+ unless defined $sock;
+
+ die "UNIX domain socket patch was truncated"
+ unless ($testfile eq $sock->hostpath());
+
+ $truncated= 0; # Yes, it worked!
+
+ };
+ #print "check_socket_path_length, failed: ", $@, '\n' if ($@);
+
+ $sock= undef; # Close socket
+ unlink($testfile); # Remove the physical file
+ rmdir($tmpdir); # Remove the tempdir
+ return $truncated;
+}
+
+
+sub process_alive {
+ my ($pid)= @_;
+ die "usage: process_alive(pid)" unless $pid;
+
+ return kill(0, $pid) unless IS_WINDOWS;
+
+ my @list= split(/,/, `tasklist /FI "PID eq $pid" /NH /FO CSV`);
+ my $ret_pid= eval($list[1]);
+ return ($ret_pid == $pid);
+}
+
+
+1;
diff --git a/mysql-test/lib/My/SafeProcess.pm b/mysql-test/lib/My/SafeProcess.pm
new file mode 100644
index 00000000000..0e3aa968052
--- /dev/null
+++ b/mysql-test/lib/My/SafeProcess.pm
@@ -0,0 +1,580 @@
+# -*- cperl -*-
+# Copyright (C) 2004-2006 MySQL AB
+#
+# 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-1301 USA
+
+package My::SafeProcess;
+
+#
+# Class that encapsulates process creation, monitoring and cleanup
+#
+# Spawns a monitor process which spawns a new process locally or
+# remote using subclasses My::Process::Local or My::Process::Remote etc.
+#
+# The monitor process runs a simple event loop more or less just
+# waiting for a reason to zap the process it monitors. Thus the user
+# of this class does not need to care about process cleanup, it's
+# handled automatically.
+#
+# The monitor process wait for:
+# - the parent process to close the pipe, in that case it
+# will zap the "monitored process" and exit
+# - the "monitored process" to exit, in which case it will exit
+# itself with same exit code as the "monitored process"
+# - the parent process to send the "shutdown" signal in wich case
+# monitor will kill the "monitored process" hard and exit
+#
+#
+# When used it will look something like this:
+# $> ps
+# [script.pl]
+# - [monitor for `mysqld`]
+# - [mysqld]
+# - [monitor for `mysqld`]
+# - [mysqld]
+# - [monitor for `mysqld`]
+# - [mysqld]
+#
+#
+
+use strict;
+use Carp;
+use POSIX qw(WNOHANG);
+
+use My::SafeProcess::Base;
+use base 'My::SafeProcess::Base';
+
+use My::Find;
+use My::Platform;
+
+my %running;
+my $_verbose= 0;
+
+END {
+ # Kill any children still running
+ for my $proc (values %running){
+ if ( $proc->is_child($$) ){
+ #print "Killing: $proc\n";
+ if ($proc->wait_one(0)){
+ $proc->kill();
+ }
+ }
+ }
+}
+
+
+sub is_child {
+ my ($self, $parent_pid)= @_;
+ croak "usage: \$safe_proc->is_child()" unless (@_ == 2 and ref $self);
+ return ($self->{PARENT} == $parent_pid);
+}
+
+
+# Find the safe process binary or script
+my @safe_process_cmd;
+my $safe_kill;
+if (IS_WIN32PERL or IS_CYGWIN){
+ # Use my_safe_process.exe
+ my $exe= my_find_bin(".", ["lib/My/SafeProcess", "My/SafeProcess"],
+ "my_safe_process");
+ push(@safe_process_cmd, $exe);
+
+ # Use my_safe_kill.exe
+ $safe_kill= my_find_bin(".", "lib/My/SafeProcess", "my_safe_kill");
+}
+else
+{
+ # Use my_safe_process
+ my $exe= my_find_bin(".", ["lib/My/SafeProcess", "My/SafeProcess"],
+ "my_safe_process");
+ push(@safe_process_cmd, $exe);
+}
+
+
+sub new {
+ my $class= shift;
+
+ my %opts=
+ (
+ verbose => 0,
+ @_
+ );
+
+ my $path = delete($opts{'path'}) or croak "path required @_";
+ my $args = delete($opts{'args'}) or croak "args required @_";
+ my $input = delete($opts{'input'});
+ my $output = delete($opts{'output'});
+ my $error = delete($opts{'error'});
+ my $verbose = delete($opts{'verbose'});
+ my $host = delete($opts{'host'});
+ my $shutdown = delete($opts{'shutdown'});
+ my $user_data= delete($opts{'user_data'});
+
+# if (defined $host) {
+# $safe_script= "lib/My/SafeProcess/safe_process_cpcd.pl";
+# }
+
+ if (IS_CYGWIN){
+ $path= mixed_path($path);
+ $input= mixed_path($input);
+ $output= mixed_path($output);
+ $error= mixed_path($error);
+ }
+
+ my @safe_args;
+ my ($safe_path, $safe_script)= @safe_process_cmd;
+ push(@safe_args, $safe_script) if defined $safe_script;
+
+ push(@safe_args, "--verbose") if $verbose > 0;
+
+ # Point the safe_process at the right parent if running on cygwin
+ push(@safe_args, "--parent-pid=".Cygwin::pid_to_winpid($$)) if IS_CYGWIN;
+
+ push(@safe_args, "--");
+ push(@safe_args, $path); # The program safe_process should execute
+ push(@safe_args, @$$args);
+
+ print "### safe_path: ", $safe_path, " ", join(" ", @safe_args), "\n"
+ if $verbose > 1;
+
+ my $pid= create_process(
+ path => $safe_path,
+ input => $input,
+ output => $output,
+ error => $error,
+ append => $opts{append},
+ args => \@safe_args,
+ );
+
+ my $name = delete($opts{'name'}) || "SafeProcess$pid";
+ my $proc= bless
+ ({
+ SAFE_PID => $pid,
+ SAFE_WINPID => $pid, # Inidicates this is always a real process
+ SAFE_NAME => $name,
+ SAFE_SHUTDOWN => $shutdown,
+ PARENT => $$,
+ SAFE_USER_DATA => $user_data,
+ }, $class);
+
+ # Put the new process in list of running
+ $running{$pid}= $proc;
+ return $proc;
+
+}
+
+
+sub run {
+ my $proc= new(@_);
+ $proc->wait_one();
+ return $proc->exit_status();
+}
+
+#
+# Start a process that returns after "duration" seconds
+# or when it's parent process does not exist anymore
+#
+sub timer {
+ my $class= shift;
+ my $duration= shift or croak "duration required";
+ my $parent_pid= $$;
+
+ my $pid= My::SafeProcess::Base::_safe_fork();
+ if ($pid){
+ # Parent
+ my $proc= bless
+ ({
+ SAFE_PID => $pid,
+ SAFE_NAME => "timer",
+ PARENT => $$,
+ }, $class);
+
+ # Put the new process in list of running
+ $running{$pid}= $proc;
+ return $proc;
+ }
+
+ # Child, install signal handlers and sleep for "duration"
+ $SIG{INT}= 'IGNORE';
+
+ $SIG{TERM}= sub {
+ #print STDERR "timer $$: woken up, exiting!\n";
+ exit(0);
+ };
+
+ $0= "safe_timer($duration)";
+
+ if (IS_WIN32PERL){
+ # Just a thread in same process
+ sleep($duration);
+ print STDERR "timer $$: expired after $duration seconds\n";
+ exit(0);
+ }
+
+ my $count_down= $duration;
+ while($count_down--){
+
+ # Check that parent is still alive
+ if (kill(0, $parent_pid) == 0){
+ #print STDERR "timer $$: parent gone, exiting!\n";
+ exit(0);
+ }
+
+ sleep(1);
+ }
+ print STDERR "timer $$: expired after $duration seconds\n";
+ exit(0);
+}
+
+
+#
+# Shutdown process nicely, and wait for shutdown_timeout seconds
+# If processes hasn't shutdown, kill them hard and wait for return
+#
+sub shutdown {
+ my $shutdown_timeout= shift;
+ my @processes= @_;
+ _verbose("shutdown, timeout: $shutdown_timeout, @processes");
+
+ return if (@processes == 0);
+
+ # Call shutdown function if process has one, else
+ # use kill
+ foreach my $proc (@processes){
+ _verbose(" proc: $proc");
+ my $shutdown= $proc->{SAFE_SHUTDOWN};
+ if ($shutdown_timeout > 0 and defined $shutdown){
+ $shutdown->();
+ $proc->{WAS_SHUTDOWN}= 1;
+ }
+ else {
+ $proc->start_kill();
+ }
+ }
+
+ my @kill_processes= ();
+
+ # Wait max shutdown_timeout seconds for those process
+ # that has been shutdown
+ foreach my $proc (@processes){
+ next unless $proc->{WAS_SHUTDOWN};
+ my $ret= $proc->wait_one($shutdown_timeout);
+ if ($ret != 0) {
+ push(@kill_processes, $proc);
+ }
+ # Only wait for the first process with shutdown timeout
+ $shutdown_timeout= 0;
+ }
+
+ # Wait infinitely for those process
+ # that has been killed
+ foreach my $proc (@processes){
+ next if $proc->{WAS_SHUTDOWN};
+ my $ret= $proc->wait_one(undef);
+ if ($ret != 0) {
+ warn "Wait for killed process failed!";
+ push(@kill_processes, $proc);
+ # Try one more time, best option...
+ }
+ }
+
+ # Return if all servers has exited
+ return if (@kill_processes == 0);
+
+ foreach my $proc (@kill_processes){
+ $proc->start_kill();
+ }
+
+ foreach my $proc (@kill_processes){
+ $proc->wait_one(undef);
+ }
+
+ return;
+}
+
+
+sub _winpid ($) {
+ my ($pid)= @_;
+
+ # In win32 perl, the pid is already the winpid
+ return $pid unless IS_CYGWIN;
+
+ # In cygwin, the pid is the pseudo process ->
+ # get the real winpid of my_safe_process
+ return Cygwin::pid_to_winpid($pid);
+}
+
+
+#
+# Tell the process to die as fast as possible
+#
+sub start_kill {
+ my ($self)= @_;
+ croak "usage: \$safe_proc->start_kill()" unless (@_ == 1 and ref $self);
+ _verbose("start_kill: $self");
+ my $ret= 1;
+
+ my $pid= $self->{SAFE_PID};
+ die "INTERNAL ERROR: no pid" unless defined $pid;
+
+ if (IS_WINDOWS and defined $self->{SAFE_WINPID})
+ {
+ die "INTERNAL ERROR: no safe_kill" unless defined $safe_kill;
+
+ my $winpid= _winpid($pid);
+ $ret= system($safe_kill, $winpid) >> 8;
+
+ if ($ret == 3){
+ print "Couldn't open the winpid: $winpid ",
+ "for pid: $pid, try one more time\n";
+ sleep(1);
+ $winpid= _winpid($pid);
+ $ret= system($safe_kill, $winpid) >> 8;
+ print "Couldn't open the winpid: $winpid ",
+ "for pid: $pid, continue and see what happens...\n";
+ }
+ }
+ else
+ {
+ $pid= $self->{SAFE_PID};
+ die "Can't kill not started process" unless defined $pid;
+ $ret= kill("TERM", $pid);
+ }
+
+ return $ret;
+}
+
+
+sub dump_core {
+ my ($self)= @_;
+ return if IS_WINDOWS;
+ my $pid= $self->{SAFE_PID};
+ die "Can't cet core from not started process" unless defined $pid;
+ _verbose("Sending ABRT to $self");
+ kill ("ABRT", $pid);
+ return 1;
+}
+
+
+#
+# Kill the process as fast as possible
+# and wait for it to return
+#
+sub kill {
+ my ($self)= @_;
+ croak "usage: \$safe_proc->kill()" unless (@_ == 1 and ref $self);
+
+ $self->start_kill();
+ $self->wait_one();
+ return 1;
+}
+
+
+sub _collect {
+ my ($self)= @_;
+
+ $self->{EXIT_STATUS}= $?;
+ _verbose("_collect: $self");
+
+ # Take the process out of running list
+ my $pid= $self->{SAFE_PID};
+ die unless delete($running{$pid});
+}
+
+
+# Wait for process to exit
+# optionally with a timeout
+#
+# timeout
+# undef -> wait blocking infinitely
+# 0 -> just poll with WNOHANG
+# >0 -> wait blocking for max timeout seconds
+#
+# RETURN VALUES
+# 0 Not running
+# 1 Still running
+#
+sub wait_one {
+ my ($self, $timeout)= @_;
+ croak "usage: \$safe_proc->wait_one([timeout])" unless ref $self;
+
+ _verbose("wait_one $self, $timeout");
+
+ if ( ! defined($self->{SAFE_PID}) ) {
+ # No pid => not running
+ _verbose("No pid => not running");
+ return 0;
+ }
+
+ if ( defined $self->{EXIT_STATUS} ) {
+ # Exit status already set => not running
+ _verbose("Exit status already set => not running");
+ return 0;
+ }
+
+ my $pid= $self->{SAFE_PID};
+
+ my $use_alarm;
+ my $blocking;
+ if (defined $timeout)
+ {
+ if ($timeout == 0)
+ {
+ # 0 -> just poll with WNOHANG
+ $blocking= 0;
+ $use_alarm= 0;
+ }
+ else
+ {
+ # >0 -> wait blocking for max timeout seconds
+ $blocking= 1;
+ $use_alarm= 1;
+ }
+ }
+ else
+ {
+ # undef -> wait blocking infinitely
+ $blocking= 1;
+ $use_alarm= 0;
+ }
+ #_verbose("blocking: $blocking, use_alarm: $use_alarm");
+
+ my $retpid;
+ eval
+ {
+ # alarm should break the wait
+ local $SIG{ALRM}= sub { die "waitpid timeout"; };
+
+ alarm($timeout) if $use_alarm;
+
+ $retpid= waitpid($pid, $blocking ? 0 : &WNOHANG);
+
+ alarm(0) if $use_alarm;
+ };
+
+ if ($@)
+ {
+ die "Got unexpected: $@" if ($@ !~ /waitpid timeout/);
+ if (!defined $retpid) {
+ # Got timeout
+ _verbose("Got timeout");
+ return 1;
+ }
+ # Got pid _and_ alarm, continue
+ _verbose("Got pid and alarm, continue");
+ }
+
+ if ( $retpid == 0 ) {
+ # 0 => still running
+ _verbose("0 => still running");
+ return 1;
+ }
+
+ if ( not $blocking and $retpid == -1 ) {
+ # still running
+ _verbose("still running");
+ return 1;
+ }
+
+ #warn "wait_one: expected pid $pid but got $retpid"
+ # unless( $retpid == $pid );
+
+ $self->_collect();
+ return 0;
+}
+
+
+#
+# Wait for any process to exit
+#
+# Returns a reference to the SafeProcess that
+# exited or undefined
+#
+sub wait_any {
+ my $ret_pid;
+ if (IS_WIN32PERL) {
+ # Can't wait for -1 => use a polling loop
+ do {
+ Win32::Sleep(10); # 10 milli seconds
+ foreach my $pid (keys %running){
+ $ret_pid= waitpid($pid, &WNOHANG);
+ last if $pid == $ret_pid;
+ }
+ } while ($ret_pid == 0);
+ }
+ else
+ {
+ $ret_pid= waitpid(-1, 0);
+ if ($ret_pid <= 0){
+ # No more processes to wait for
+ print STDERR "wait_any, got invalid pid: $ret_pid\n";
+ return undef;
+ }
+ }
+
+ # Look it up in "running" table
+ my $proc= $running{$ret_pid};
+ unless (defined $proc){
+ print STDERR "Could not find pid: $ret_pid in running list\n";
+ print STDERR "running: ". join(", ", keys(%running)). "\n";
+ return undef;
+ }
+ $proc->_collect;
+ return $proc;
+}
+
+#
+# Overload string operator
+# and fallback to default functions if no
+# overloaded function is found
+#
+use overload
+ '""' => \&self2str,
+ fallback => 1;
+
+
+#
+# Return the process as a nicely formatted string
+#
+sub self2str {
+ my ($self)= @_;
+ my $pid= $self->{SAFE_PID};
+ my $winpid= $self->{SAFE_WINPID};
+ my $name= $self->{SAFE_NAME};
+ my $exit_status= $self->{EXIT_STATUS};
+
+ my $str= "[$name - pid: $pid";
+ $str.= ", winpid: $winpid" if defined $winpid;
+ $str.= ", exit: $exit_status" if defined $exit_status;
+ $str.= "]";
+}
+
+sub _verbose {
+ return unless $_verbose;
+ print STDERR " ## ", @_, "\n";
+}
+
+
+sub pid {
+ my ($self)= @_;
+ return $self->{SAFE_PID};
+}
+
+sub user_data {
+ my ($self)= @_;
+ return $self->{SAFE_USER_DATA};
+}
+
+
+1;
diff --git a/mysql-test/lib/My/SafeProcess/Base.pm b/mysql-test/lib/My/SafeProcess/Base.pm
new file mode 100644
index 00000000000..3fc1b1be017
--- /dev/null
+++ b/mysql-test/lib/My/SafeProcess/Base.pm
@@ -0,0 +1,212 @@
+# -*- cperl -*-
+# Copyright (C) 2004-2006 MySQL AB
+#
+# 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-1301 USA
+
+# This is a library file used by the Perl version of mysql-test-run,
+# and is part of the translation of the Bourne shell script with the
+# same name.
+
+use strict;
+
+package My::SafeProcess::Base;
+
+#
+# Utility functions for Process management
+#
+
+use Carp;
+use IO::Pipe;
+
+use base qw(Exporter);
+our @EXPORT= qw(create_process);
+
+
+
+#
+# safe_fork
+# Retry a couple of times if fork returns EAGAIN
+#
+sub _safe_fork {
+ my $retries= 5;
+ my $pid;
+
+ FORK:
+ {
+ $pid= fork;
+ if ( not defined($pid)) {
+
+ croak("fork failed after: $!") if (!$retries--);
+
+ warn("fork failed sleep 1 second and redo: $!");
+ sleep(1);
+ redo FORK;
+ }
+ }
+
+ return $pid;
+};
+
+
+#
+# Decode exit status
+#
+sub exit_status {
+ my $self= shift;
+ my $raw= $self->{EXIT_STATUS};
+
+ croak("Can't call exit_status before process has died")
+ unless defined $raw;
+
+ if ($raw & 127)
+ {
+ # Killed by signal
+ my $signal_num= $raw & 127;
+ my $dumped_core= $raw & 128;
+ return 1; # Return error code
+ }
+ else
+ {
+ # Normal process exit
+ return $raw >> 8;
+ };
+}
+
+
+#
+# Create a new process
+# Return pid of the new process
+#
+sub create_process {
+ my %opts=
+ (
+ @_
+ );
+
+ my $path = delete($opts{'path'}) or die "path required";
+ my $args = delete($opts{'args'}) or die "args required";
+ my $input = delete($opts{'input'});
+ my $output = delete($opts{'output'});
+ my $error = delete($opts{'error'});
+
+ my $open_mode= $opts{append} ? ">>" : ">";
+
+ if ($^O eq "MSWin32"){
+
+ #printf STDERR "stdin %d, stdout %d, stderr %d\n",
+ # fileno STDIN, fileno STDOUT, fileno STDERR;
+
+ # input output redirect
+ my ($oldin, $oldout, $olderr);
+ open $oldin, '<&', \*STDIN or die "Failed to save old stdin: $!";
+ open $oldout, '>&', \*STDOUT or die "Failed to save old stdout: $!";
+ open $olderr, '>&', \*STDERR or die "Failed to save old stderr: $!";
+
+ if ( $input ) {
+ if ( ! open(STDIN, "<", $input) ) {
+ croak("can't redirect STDIN to '$input': $!");
+ }
+ }
+
+ if ( $output ) {
+ if ( ! open(STDOUT, $open_mode, $output) ) {
+ croak("can't redirect STDOUT to '$output': $!");
+ }
+ }
+
+ if ( $error ) {
+ if ( $output eq $error ) {
+ if ( ! open(STDERR, ">&STDOUT") ) {
+ croak("can't dup STDOUT: $!");
+ }
+ }
+ elsif ( ! open(STDERR, $open_mode, $error) ) {
+ croak("can't redirect STDERR to '$error': $!");
+ }
+ }
+
+
+ # Magic use of 'system(1, @args)' to spawn a process
+ # and get a proper Win32 pid
+ unshift (@$args, $path);
+ my $pid= system(1, @$args);
+ if ( $pid == 0 ){
+ print $olderr "create_process failed: $^E\n";
+ die "create_process failed: $^E";
+ }
+
+ # Retore IO redirects
+ open STDERR, '>&', $olderr
+ or croak("unable to reestablish STDERR");
+ open STDOUT, '>&', $oldout
+ or croak("unable to reestablish STDOUT");
+ open STDIN, '<&', $oldin
+ or croak("unable to reestablish STDIN");
+ #printf STDERR "stdin %d, stdout %d, stderr %d\n",
+ # fileno STDIN, fileno STDOUT, fileno STDERR;
+ return $pid;
+
+ }
+
+ local $SIG{PIPE}= sub { print STDERR "Got signal $@\n"; };
+ my $pipe= IO::Pipe->new();
+ my $pid= _safe_fork();
+ if ($pid){
+ # Parent
+ $pipe->reader();
+ my $line= <$pipe>; # Wait for child to say it's ready
+ return $pid;
+ }
+
+ $SIG{INT}= 'DEFAULT';
+
+ # Make this process it's own process group to be able to kill
+ # it and any childs(that hasn't changed group themself)
+ setpgrp(0,0) if $opts{setpgrp};
+
+ if ( $output and !open(STDOUT, $open_mode, $output) ) {
+ croak("can't redirect STDOUT to '$output': $!");
+ }
+
+ if ( $error ) {
+ if ( defined $output and $output eq $error ) {
+ if ( ! open(STDERR, ">&STDOUT") ) {
+ croak("can't dup STDOUT: $!");
+ }
+ }
+ elsif ( ! open(STDERR, $open_mode, $error) ) {
+ croak("can't redirect STDERR to '$error': $!");
+ }
+ }
+
+ if ( $input ) {
+ if ( ! open(STDIN, "<", $input) ) {
+ croak("can't redirect STDIN to '$input': $!");
+ }
+ }
+
+ # Tell parent to continue
+ $pipe->writer();
+ print $pipe "ready\n";
+
+ if ( !exec($path, @$args) ){
+ croak("Failed to exec '$path': $!");
+ }
+
+ croak("Should never come here");
+
+}
+
+1;
+
diff --git a/mysql-test/lib/My/SafeProcess/CMakeLists.txt b/mysql-test/lib/My/SafeProcess/CMakeLists.txt
new file mode 100644
index 00000000000..97fab820f95
--- /dev/null
+++ b/mysql-test/lib/My/SafeProcess/CMakeLists.txt
@@ -0,0 +1,17 @@
+# Copyright (C) 2006 MySQL AB
+#
+# 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-1301 USA
+
+ADD_EXECUTABLE(my_safe_process safe_process_win.cc)
+ADD_EXECUTABLE(my_safe_kill safe_kill_win.cc)
diff --git a/mysql-test/lib/My/SafeProcess/Makefile.am b/mysql-test/lib/My/SafeProcess/Makefile.am
new file mode 100644
index 00000000000..623c0e9a87a
--- /dev/null
+++ b/mysql-test/lib/My/SafeProcess/Makefile.am
@@ -0,0 +1,28 @@
+# Copyright (C) 2000-2006 MySQL AB
+#
+# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+safedir = $(prefix)/mysql-test/lib/My/SafeProcess
+#nobase_bin_PROGRAMS = ...
+safe_PROGRAMS = my_safe_process
+
+my_safe_process_SOURCES = safe_process.cc
+
+EXTRA_DIST = safe_kill_win.cc \
+ safe_process_win.cc \
+ CMakeLists.txt
+
+
+# Don't update the files from bitkeeper
+%::SCCS/s.%
diff --git a/mysql-test/lib/My/SafeProcess/safe_kill_win.cc b/mysql-test/lib/My/SafeProcess/safe_kill_win.cc
new file mode 100755
index 00000000000..c6256fd92e1
--- /dev/null
+++ b/mysql-test/lib/My/SafeProcess/safe_kill_win.cc
@@ -0,0 +1,85 @@
+/* Copyright (C) 2004 MySQL AB
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+/*
+ Utility program used to signal a safe_process it's time to shutdown
+
+ Usage:
+ safe_kill <pid>
+*/
+
+#include <windows.h>
+#include <stdio.h>
+#include <signal.h>
+
+int main(int argc, const char** argv )
+{
+ DWORD pid= -1;
+ HANDLE shutdown_event;
+ char safe_process_name[32]= {0};
+ int retry_open_event= 100;
+ /* Ignore any signals */
+ signal(SIGINT, SIG_IGN);
+ signal(SIGBREAK, SIG_IGN);
+ signal(SIGTERM, SIG_IGN);
+
+ if (argc != 2) {
+ fprintf(stderr, "safe_kill <pid>\n");
+ exit(2);
+ }
+ pid= atoi(argv[1]);
+
+ _snprintf(safe_process_name, sizeof(safe_process_name),
+ "safe_process[%d]", pid);
+
+ /* Open the event to signal */
+ while ((shutdown_event=
+ OpenEvent(EVENT_MODIFY_STATE, FALSE, safe_process_name)) == NULL)
+ {
+ /*
+ Check if the process is alive, otherwise there is really
+ no idea to retry the open of the event
+ */
+ HANDLE process;
+ if ((process= OpenProcess(SYNCHRONIZE, FALSE, pid)) == NULL)
+ {
+ fprintf(stderr, "Could not open event or process %d, error: %d\n",
+ pid, GetLastError());
+ exit(3);
+ }
+ CloseHandle(process);
+
+ if (retry_open_event--)
+ Sleep(100);
+ else
+ {
+ fprintf(stderr, "Failed to open shutdown_event '%s', error: %d\n",
+ safe_process_name, GetLastError());
+ exit(3);
+ }
+ }
+
+ if(SetEvent(shutdown_event) == 0)
+ {
+ fprintf(stderr, "Failed to signal shutdown_event '%s', error: %d\n",
+ safe_process_name, GetLastError());
+ CloseHandle(shutdown_event);
+ exit(4);
+ }
+ CloseHandle(shutdown_event);
+ exit(0);
+}
+
diff --git a/mysql-test/lib/My/SafeProcess/safe_process.cc b/mysql-test/lib/My/SafeProcess/safe_process.cc
new file mode 100644
index 00000000000..7932f3fd2d6
--- /dev/null
+++ b/mysql-test/lib/My/SafeProcess/safe_process.cc
@@ -0,0 +1,277 @@
+/* Copyright (C) 2008 MySQL AB
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+/*
+ Utility program that encapsulates process creation, monitoring
+ and bulletproof process cleanup
+
+ Usage:
+ safe_process [options to safe_process] -- progname arg1 ... argn
+
+ To safeguard mysqld you would invoke safe_process with a few options
+ for safe_process itself followed by a double dash to indicate start
+ of the command line for the program you really want to start
+
+ $> safe_process --output=output.log -- mysqld --datadir=var/data1 ...
+
+ This would redirect output to output.log and then start mysqld,
+ once it has done that it will continue to monitor the child as well
+ as the parent.
+
+ The safe_process then checks the follwing things:
+ 1. Child exits, propagate the childs return code to the parent
+ by exiting with the same return code as the child.
+
+ 2. Parent dies, immediately kill the child and exit, thus the
+ parent does not need to properly cleanup any child, it is handled
+ automatically.
+
+ 3. Signal's recieced by the process will trigger same action as 2)
+
+*/
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+
+int verbose= 0;
+int terminated= 0;
+pid_t child_pid= -1;
+char safe_process_name[32]= {0};
+
+
+static void message(const char* fmt, ...)
+{
+ if (!verbose)
+ return;
+ va_list args;
+ fprintf(stderr, "%s: ", safe_process_name);
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\n");
+ va_end(args);
+ fflush(stderr);
+}
+
+
+static void die(const char* fmt, ...)
+{
+ va_list args;
+ fprintf(stderr, "%s: FATAL ERROR, ", safe_process_name);
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\n");
+ va_end(args);
+ if (int last_err= errno)
+ fprintf(stderr, "error: %d, %s\n", last_err, strerror(last_err));
+ exit(1);
+}
+
+
+static void kill_child (void)
+{
+ int status= 0;
+
+ message("Killing child: %d", child_pid);
+ // Terminate whole process group
+ kill(-child_pid, SIGKILL);
+
+ pid_t ret_pid= waitpid(child_pid, &status, 0);
+ if (ret_pid == child_pid)
+ {
+ int exit_code= 1;
+ if (WIFEXITED(status))
+ {
+ // Process has exited, collect return status
+ exit_code= WEXITSTATUS(status);
+ message("Child exit: %d", exit_code);
+ // Exit with exit status of the child
+ exit(exit_code);
+ }
+
+ if (WIFSIGNALED(status))
+ message("Child killed by signal: %d", WTERMSIG(status));
+
+ exit(exit_code);
+ }
+ exit(1);
+}
+
+
+static void handle_abort (int sig)
+{
+ message("Got signal %d, child_pid: %d, sending ABRT", sig, child_pid);
+
+ if (child_pid > 0) {
+ kill (-child_pid, SIGABRT); // Don't wait for it to terminate
+ }
+}
+
+
+static void handle_signal (int sig)
+{
+ message("Got signal %d, child_pid: %d", sig, child_pid);
+ terminated= 1;
+
+ if (child_pid > 0)
+ kill_child();
+
+ // Ignore further signals
+ signal(SIGTERM, SIG_IGN);
+ signal(SIGINT, SIG_IGN);
+
+ // Continune execution, allow the child to be started and
+ // finally terminated by monitor loop
+}
+
+
+int main(int argc, char* const argv[] )
+{
+ char* const* child_argv= 0;
+ pid_t own_pid= getpid();
+ pid_t parent_pid= getppid();
+
+ /* Install signal handlers */
+ signal(SIGTERM, handle_signal);
+ signal(SIGINT, handle_signal);
+ signal(SIGCHLD, handle_signal);
+ signal(SIGABRT, handle_abort);
+
+ sprintf(safe_process_name, "safe_process[%d]", own_pid);
+
+ message("Started");
+
+ /* Parse arguments */
+ for (int i= 1; i < argc; i++) {
+ const char* arg= argv[i];
+ if (strcmp(arg, "--") == 0 && strlen(arg) == 2) {
+ /* Got the "--" delimiter */
+ if (i >= argc)
+ die("No real args -> nothing to do");
+ child_argv= &argv[i+1];
+ break;
+ } else {
+ if ( strcmp(arg, "--verbose") == 0 )
+ verbose++;
+ else if ( strncmp(arg, "--parent-pid", 10) == 0 )
+ {
+ /* Override parent_pid with a value provided by user */
+ const char* start;
+ if ((start= strstr(arg, "=")) == NULL)
+ die("Could not find start of option value in '%s'", arg);
+ start++; /* Step past = */
+ if ((parent_pid= atoi(start)) == 0)
+ die("Invalid value '%s' passed to --parent-id", start);
+ }
+ else
+ die("Unknown option: %s", arg);
+ }
+ }
+ if (!child_argv || *child_argv == 0)
+ die("nothing to do");
+
+ message("parent_pid: %d", parent_pid);
+ if (parent_pid == own_pid)
+ die("parent_pid is equal to own pid!");
+
+ char buf;
+ int pfd[2];
+ if (pipe(pfd) == -1)
+ die("Failed to create pipe");
+
+ /* Create the child process */
+ while((child_pid= fork()) == -1)
+ {
+ message("fork failed");
+ sleep(1);
+ }
+
+ if (child_pid == 0)
+ {
+ close(pfd[0]); // Close unused read end
+
+ // Use default signal handlers in child
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGCHLD, SIG_DFL);
+
+ // Make this process it's own process group to be able to kill
+ // it and any childs(that hasn't changed group themself)
+ setpgid(0, 0);
+
+ // Signal that child is ready
+ buf= 37;
+ write(pfd[1], &buf, 1);
+ // Close write end
+ close(pfd[1]);
+
+ if (execvp(child_argv[0], child_argv) < 0)
+ die("Failed to exec child");
+ }
+
+ close(pfd[1]); // Close unused write end
+
+ // Wait for child to signal it's ready
+ read(pfd[0], &buf, 1);
+ if(buf != 37)
+ die("Didn't get 37 from pipe");
+ close(pfd[0]); // Close read end
+
+ /* Monitor loop */
+ message("Started child %d, terminated: %d", child_pid, terminated);
+
+ while(!terminated)
+ {
+ // Check if parent is still alive
+ if (kill(parent_pid, 0) != 0){
+ message("Parent is not alive anymore");
+ break;
+ }
+
+ // Check if child has exited, normally this will be
+ // detected immediately with SIGCHLD handler
+ int status= 0;
+ pid_t ret_pid= waitpid(child_pid, &status, WNOHANG);
+ if (ret_pid == child_pid)
+ {
+ int ret_code= 2;
+ if (WIFEXITED(status))
+ {
+ // Process has exited, collect return status
+ int ret_code= WEXITSTATUS(status);
+ message("Child exit: %d", ret_code);
+ // Exit with exit status of the child
+ exit(ret_code);
+ }
+
+ if (WIFSIGNALED(status))
+ message("Child killed by signal: %d", WTERMSIG(status));
+
+ exit(ret_code);
+ }
+ sleep(1);
+ }
+ kill_child();
+
+ exit(1);
+}
+
diff --git a/mysql-test/lib/My/SafeProcess/safe_process.pl b/mysql-test/lib/My/SafeProcess/safe_process.pl
new file mode 100644
index 00000000000..e3114a749d3
--- /dev/null
+++ b/mysql-test/lib/My/SafeProcess/safe_process.pl
@@ -0,0 +1,151 @@
+#!/usr/bin/perl
+# -*- cperl -*-
+
+use strict;
+use warnings;
+
+use lib 'lib';
+use My::SafeProcess::Base;
+use POSIX qw(WNOHANG);
+
+###########################################################################
+# Util functions
+###########################################################################
+
+#
+#Print message to stderr
+#
+my $verbose= 0;
+sub message {
+ if ($verbose > 0){
+ use Time::localtime;
+ my $tm= localtime();
+ my $timestamp= sprintf("%02d%02d%02d %2d:%02d:%02d",
+ $tm->year % 100, $tm->mon+1, $tm->mday,
+ $tm->hour, $tm->min, $tm->sec);
+ print STDERR $timestamp, " monitor[$$]: ", @_, "\n";
+ }
+}
+
+
+###########################################################################
+# Main program
+###########################################################################
+
+my $terminated= 0;
+
+# Protect against being killed in the middle
+# of child creation, just set the terminated flag
+# to make sure the child will be killed off
+# when program is ready to do that
+$SIG{TERM}= sub { message("!Got signal @_"); $terminated= 1; };
+$SIG{INT}= sub { message("!Got signal @_"); $terminated= 1; };
+
+my $parent_pid= getppid();
+
+my $found_double_dash= 0;
+while (my $arg= shift(@ARGV)){
+
+ if ($arg =~ /^--$/){
+ $found_double_dash= 1;
+ last;
+ }
+ elsif ($arg =~ /^--verbose$/){
+ $verbose= 1;
+ }
+ else {
+ die "Unknown option: $arg";
+ }
+}
+
+my $path= shift(@ARGV); # Executable
+
+die "usage:\n" .
+ " safe_process.pl [opts] -- <path> [<args> [...<args_n>]]"
+ unless defined $path || $found_double_dash;
+
+
+message("started");
+#message("path: '$path'");
+message("parent: $parent_pid");
+
+# Start process to monitor
+my $child_pid=
+ create_process(
+ path => $path,
+ args => \@ARGV,
+ setpgrp => 1,
+ );
+message("Started child $child_pid");
+
+eval {
+ sub handle_signal {
+ $terminated= 1;
+ message("Got signal @_");
+
+ # Ignore all signals
+ foreach my $name (keys %SIG){
+ $SIG{$name}= 'IGNORE';
+ }
+
+ die "signaled\n";
+ };
+ local $SIG{TERM}= \&handle_signal;
+ local $SIG{INT}= \&handle_signal;
+ local $SIG{CHLD}= sub {
+ message("Got signal @_");
+ kill(9, -$child_pid);
+ my $ret= waitpid($child_pid, 0);
+ if ($? & 127){
+ exit(65); # Killed by signal
+ }
+ exit($? >> 8);
+ };
+
+ # Monitoring loop
+ while(!$terminated) {
+
+ # Check if parent is still alive
+ if (kill(0, $parent_pid) < 1){
+ message("Parent is not alive anymore");
+ last;
+ }
+
+ # Wait for child to terminate but wakeup every
+ # second to also check that parent is still alive
+ my $ret_pid;
+ $ret_pid= waitpid($child_pid, &WNOHANG);
+ if ($ret_pid == $child_pid) {
+ # Process has exited, collect return status
+ my $ret_code= $? >> 8;
+ message("Child exit: $ret_code");
+ # Exit with exit status of the child
+ exit ($ret_code);
+ }
+ sleep(1);
+ }
+};
+if ( $@ ) {
+ # The monitoring loop should have been
+ # broken by handle_signal
+ warn "Unexpected: $@" unless ( $@ =~ /signaled/ );
+}
+
+# Use negative pid in order to kill the whole
+# process group
+#
+my $ret= kill(9, -$child_pid);
+message("Killed child: $child_pid, ret: $ret");
+if ($ret > 0) {
+ message("Killed child: $child_pid");
+ # Wait blocking for the child to return
+ my $ret_pid= waitpid($child_pid, 0);
+ if ($ret_pid != $child_pid){
+ message("unexpected pid $ret_pid returned from waitpid($child_pid)");
+ }
+}
+
+message("DONE!");
+exit (1);
+
+
diff --git a/mysql-test/lib/My/SafeProcess/safe_process_win.cc b/mysql-test/lib/My/SafeProcess/safe_process_win.cc
new file mode 100755
index 00000000000..8aa603a8793
--- /dev/null
+++ b/mysql-test/lib/My/SafeProcess/safe_process_win.cc
@@ -0,0 +1,316 @@
+/* Copyright (C) 2004 MySQL AB
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+/*
+ Utility program that encapsulates process creation, monitoring
+ and bulletproof process cleanup
+
+ Usage:
+ safe_process [options to safe_process] -- progname arg1 ... argn
+
+ To safeguard mysqld you would invoke safe_process with a few options
+ for safe_process itself followed by a double dash to indicate start
+ of the command line for the program you really want to start
+
+ $> safe_process --output=output.log -- mysqld --datadir=var/data1 ...
+
+ This would redirect output to output.log and then start mysqld,
+ once it has done that it will continue to monitor the child as well
+ as the parent.
+
+ The safe_process then checks the follwing things:
+ 1. Child exits, propagate the childs return code to the parent
+ by exiting with the same return code as the child.
+
+ 2. Parent dies, immediately kill the child and exit, thus the
+ parent does not need to properly cleanup any child, it is handled
+ automatically.
+
+ 3. Signal's recieced by the process will trigger same action as 2)
+
+ 4. The named event "safe_process[pid]" can be signaled and will
+ trigger same action as 2)
+
+ WARNING! Be careful when using ProcessExplorer, since it will open
+ a handle to each process(and maybe also the Job), the process
+ spawned by safe_process will not be closed off when safe_process
+ is killed.
+*/
+
+/* Requires Windows 2000 or higher */
+#define _WIN32_WINNT 0x0500
+
+#include <windows.h>
+#include <stdio.h>
+#include <tlhelp32.h>
+#include <signal.h>
+
+static int verbose= 0;
+static char safe_process_name[32]= {0};
+
+static void message(const char* fmt, ...)
+{
+ if (!verbose)
+ return;
+ va_list args;
+ fprintf(stderr, "%s: ", safe_process_name);
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\n");
+ va_end(args);
+ fflush(stderr);
+}
+
+
+static void die(const char* fmt, ...)
+{
+ va_list args;
+ fprintf(stderr, "%s: FATAL ERROR, ", safe_process_name);
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\n");
+ va_end(args);
+ if (int last_err= GetLastError())
+ fprintf(stderr, "error: %d, %s\n", last_err, strerror(last_err));
+ fflush(stderr);
+ exit(1);
+}
+
+
+DWORD get_parent_pid(DWORD pid)
+{
+ HANDLE snapshot;
+ DWORD parent_pid= -1;
+ PROCESSENTRY32 pe32;
+ pe32.dwSize= sizeof(PROCESSENTRY32);
+
+ snapshot= CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (snapshot == INVALID_HANDLE_VALUE)
+ die("CreateToolhelp32Snapshot failed");
+
+ if (!Process32First(snapshot, &pe32))
+ {
+ CloseHandle(snapshot);
+ die("Process32First failed");
+ }
+
+ do
+ {
+ if (pe32.th32ProcessID == pid)
+ parent_pid= pe32.th32ParentProcessID;
+ } while(Process32Next( snapshot, &pe32));
+ CloseHandle(snapshot);
+
+ if (parent_pid == -1)
+ die("Could not find parent pid");
+
+ return parent_pid;
+}
+
+
+enum {
+ PARENT,
+ CHILD,
+ EVENT,
+ NUM_HANDLES
+};
+
+
+HANDLE shutdown_event;
+void handle_signal (int signal)
+{
+ message("Got signal: %d", signal);
+ if(SetEvent(shutdown_event) == 0) {
+ /* exit safe_process and (hopefully) kill off the child */
+ die("Failed to SetEvent");
+ }
+}
+
+
+int main(int argc, const char** argv )
+{
+ char child_args[4096]= {0};
+ DWORD pid= GetCurrentProcessId();
+ DWORD parent_pid= get_parent_pid(pid);
+ HANDLE job_handle;
+ HANDLE wait_handles[NUM_HANDLES]= {0};
+ PROCESS_INFORMATION process_info= {0};
+
+ sprintf(safe_process_name, "safe_process[%d]", pid);
+
+ /* Create an event for the signal handler */
+ if ((shutdown_event=
+ CreateEvent(NULL, TRUE, FALSE, safe_process_name)) == NULL)
+ die("Failed to create shutdown_event");
+ wait_handles[EVENT]= shutdown_event;
+
+ signal(SIGINT, handle_signal);
+ signal(SIGBREAK, handle_signal);
+ signal(SIGTERM, handle_signal);
+
+ message("Started");
+
+ /* Parse arguments */
+ for (int i= 1; i < argc; i++) {
+ const char* arg= argv[i];
+ char* to= child_args;
+ if (strcmp(arg, "--") == 0 && strlen(arg) == 2) {
+ /* Got the "--" delimiter */
+ if (i >= argc)
+ die("No real args -> nothing to do");
+ /* Copy the remaining args to child_arg */
+ for (int j= i+1; j < argc; j++) {
+ to+= _snprintf(to, child_args + sizeof(child_args) - to, "%s ", argv[j]);
+ }
+ break;
+ } else {
+ if ( strcmp(arg, "--verbose") == 0 )
+ verbose++;
+ else if ( strncmp(arg, "--parent-pid", 10) == 0 )
+ {
+ /* Override parent_pid with a value provided by user */
+ const char* start;
+ if ((start= strstr(arg, "=")) == NULL)
+ die("Could not find start of option value in '%s'", arg);
+ start++; /* Step past = */
+ if ((parent_pid= atoi(start)) == 0)
+ die("Invalid value '%s' passed to --parent-id", start);
+ }
+ else
+ die("Unknown option: %s", arg);
+ }
+ }
+ if (*child_args == '\0')
+ die("nothing to do");
+
+ /* Open a handle to the parent process */
+ message("parent_pid: %d", parent_pid);
+ if (parent_pid == pid)
+ die("parent_pid is equal to own pid!");
+
+ if ((wait_handles[PARENT]=
+ OpenProcess(SYNCHRONIZE, FALSE, parent_pid)) == NULL)
+ die("Failed to open parent process with pid: %d", parent_pid);
+
+ /* Create the child process in a job */
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };
+ STARTUPINFO si = { 0 };
+ si.cb = sizeof(si);
+
+ /*
+ Create the job object to make it possible to kill the process
+ and all of it's children in one go
+ */
+ if ((job_handle= CreateJobObject(NULL, NULL)) == NULL)
+ die("CreateJobObject failed");
+
+ /*
+ Make all processes associated with the job terminate when the
+ last handle to the job is closed.
+ */
+ jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+ if (SetInformationJobObject(job_handle, JobObjectExtendedLimitInformation,
+ &jeli, sizeof(jeli)) == 0)
+ message("SetInformationJobObject failed, continue anyway...");
+
+#if 0
+ /* Setup stdin, stdout and stderr redirect */
+ si.dwFlags= STARTF_USESTDHANDLES;
+ si.hStdInput= GetStdHandle(STD_INPUT_HANDLE);
+ si.hStdOutput= GetStdHandle(STD_OUTPUT_HANDLE);
+ si.hStdError= GetStdHandle(STD_ERROR_HANDLE);
+#endif
+
+ /*
+ Create the process suspended to make sure it's assigned to the
+ Job before it creates any process of it's own
+ */
+ if (CreateProcess(NULL, (LPSTR)child_args,
+ NULL,
+ NULL,
+ TRUE, /* inherit handles */
+ CREATE_SUSPENDED,
+ NULL,
+ NULL,
+ &si,
+ &process_info) == 0)
+ die("CreateProcess failed");
+
+ if (AssignProcessToJobObject(job_handle, process_info.hProcess) == 0)
+ {
+ TerminateProcess(process_info.hProcess, 200);
+ die("AssignProcessToJobObject failed");
+ }
+ ResumeThread(process_info.hThread);
+ CloseHandle(process_info.hThread);
+
+ wait_handles[CHILD]= process_info.hProcess;
+
+ message("Started child %d", process_info.dwProcessId);
+
+ /* Monitor loop */
+ DWORD child_exit_code= 1;
+ DWORD wait_res= WaitForMultipleObjects(NUM_HANDLES, wait_handles,
+ FALSE, INFINITE);
+ switch (wait_res)
+ {
+ case WAIT_OBJECT_0 + PARENT:
+ message("Parent exit");
+ break;
+ case WAIT_OBJECT_0 + CHILD:
+ if (GetExitCodeProcess(wait_handles[CHILD], &child_exit_code) == 0)
+ message("Child exit: could not get exit_code");
+ else
+ message("Child exit: exit_code: %d", child_exit_code);
+ break;
+ case WAIT_OBJECT_0 + EVENT:
+ message("Wake up from shutdown_event");
+ break;
+
+ default:
+ message("Unexpected result %d from WaitForMultipleObjects", wait_res);
+ break;
+ }
+ message("Exiting, child: %d", process_info.dwProcessId);
+
+ if (TerminateJobObject(job_handle, 201) == 0)
+ message("TerminateJobObject failed");
+ CloseHandle(job_handle);
+ message("Job terminated and closed");
+ if (wait_res != WAIT_OBJECT_0 + CHILD)
+ {
+ /* The child has not yet returned, wait for it */
+ message("waiting for child to exit");
+ if ((wait_res= WaitForSingleObject(wait_handles[CHILD], INFINITE))
+ != WAIT_OBJECT_0)
+ {
+ message("child wait failed: %d", wait_res);
+ }
+ else
+ {
+ message("child wait succeeded");
+ }
+ /* Child's exit code should now be 201, no need to get it */
+ }
+
+ message("Closing handles");
+ for (int i= 0; i < NUM_HANDLES; i++)
+ CloseHandle(wait_handles[i]);
+
+ message("Exiting, exit_code: %d", child_exit_code);
+ exit(child_exit_code);
+}
+
diff --git a/mysql-test/lib/My/SysInfo.pm b/mysql-test/lib/My/SysInfo.pm
new file mode 100644
index 00000000000..f1ba5fb610f
--- /dev/null
+++ b/mysql-test/lib/My/SysInfo.pm
@@ -0,0 +1,211 @@
+# -*- cperl -*-
+# Copyright (C) 2004-2006 MySQL AB
+#
+# 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-1301 USA
+
+
+package My::SysInfo;
+
+use strict;
+use Carp;
+use My::Platform;
+
+use constant DEFAULT_BOGO_MIPS => 2000;
+
+sub _cpuinfo {
+ my ($self)= @_;
+
+ my $info_file= "/proc/cpuinfo";
+ if ( !( -e $info_file and -f $info_file) ) {
+ return undef;
+ }
+
+ my $F= IO::File->new($info_file) or return undef;
+
+ # Set input separator to blank line
+ local $/ = '';
+
+ while ( my $cpu_chunk= <$F>) {
+ chomp($cpu_chunk);
+
+ my $cpuinfo = {};
+
+ foreach my $cpuline ( split(/\n/, $cpu_chunk) ) {
+ my ( $attribute, $value ) = split(/\s*:\s*/, $cpuline);
+
+ $attribute =~ s/\s+/_/;
+ $attribute = lc($attribute);
+
+ if ( $value =~ /^(no|not available|yes)$/ ) {
+ $value = $value eq 'yes' ? 1 : 0;
+ }
+
+ if ( $attribute eq 'flags' ) {
+ @{ $cpuinfo->{flags} } = split / /, $value;
+ } else {
+ $cpuinfo->{$attribute} = $value;
+ }
+ }
+
+ # Make sure bogomips is set to some value
+ $cpuinfo->{bogomips} |= DEFAULT_BOGO_MIPS;
+
+ # Cpus reported once, but with 'cpu_count' set to the actual number
+ my $cpu_count= $cpuinfo->{cpu_count} || 1;
+ for(1..$cpu_count){
+ push(@{$self->{cpus}}, $cpuinfo);
+ }
+ }
+ $F= undef; # Close file
+ return $self;
+}
+
+
+sub _kstat {
+ my ($self)= @_;
+ while (1){
+ my $instance_num= $self->{cpus} ? @{$self->{cpus}} : 0;
+ my $list= `kstat -p -m cpu_info -i $instance_num 2> /dev/null`;
+ my @lines= split('\n', $list) or last; # Break loop
+
+ my $cpuinfo= {};
+ foreach my $line (@lines)
+ {
+ my ($module, $instance, $name, $statistic, $value)=
+ $line=~ /(\w*):(\w*):(\w*):(\w*)\t(.*)/;
+
+ $cpuinfo->{$statistic}= $value;
+ }
+
+ # Default value, the actual cpu values can be used to decrease this
+ # on slower cpus
+ $cpuinfo->{bogomips}= DEFAULT_BOGO_MIPS;
+
+ push(@{$self->{cpus}}, $cpuinfo);
+ }
+
+ # At least one cpu should have been found
+ # if this method worked
+ if ( $self->{cpus} ) {
+ return $self;
+ }
+ return undef;
+}
+
+
+sub _unamex {
+ my ($self)= @_;
+ # TODO
+ return undef;
+}
+
+
+sub new {
+ my ($class)= @_;
+
+
+ my $self= bless {
+ cpus => (),
+ }, $class;
+
+ my @info_methods =
+ (
+ \&_cpuinfo,
+ \&_kstat,
+ \&_unamex,
+ );
+
+ # Detect virtual machines
+ my $isvm= 0;
+
+ if (IS_WINDOWS) {
+ # Detect vmware service
+ $isvm= `tasklist` =~ /vmwareservice/i;
+ }
+ $self->{isvm}= $isvm;
+
+ foreach my $method (@info_methods){
+ if ($method->($self)){
+ return $self;
+ }
+ }
+
+ # Push a dummy cpu
+ push(@{$self->{cpus}},
+ {
+ bogomips => DEFAULT_BOGO_MIPS,
+ model_name => "unknown",
+ });
+
+ return $self;
+}
+
+
+# Return the list of cpus found
+sub cpus {
+ my ($self)= @_;
+ return @{$self->{cpus}} or
+ confess "INTERNAL ERROR: No cpus in list";
+}
+
+
+# Return the number of cpus found
+sub num_cpus {
+ my ($self)= @_;
+ return int(@{$self->{cpus}}) or
+ confess "INTERNAL ERROR: No cpus in list";
+}
+
+
+# Return the smallest bogomips value amongst the processors
+sub min_bogomips {
+ my ($self)= @_;
+
+ my $bogomips;
+
+ foreach my $cpu (@{$self->{cpus}}) {
+ if (!defined $bogomips or $bogomips > $cpu->{bogomips}) {
+ $bogomips= $cpu->{bogomips};
+ }
+ }
+
+ return $bogomips;
+}
+
+sub isvm {
+ my ($self)= @_;
+
+ return $self->{isvm};
+}
+
+
+# Prit the cpuinfo
+sub print_info {
+ my ($self)= @_;
+
+ foreach my $cpu (@{$self->{cpus}}) {
+ while ((my ($key, $value)) = each(%$cpu)) {
+ print " ", $key, "= ";
+ if (ref $value eq "ARRAY") {
+ print "[", join(", ", @$value), "]";
+ } else {
+ print $value;
+ }
+ print "\n";
+ }
+ print "\n";
+ }
+}
+
+1;
diff --git a/mysql-test/lib/My/Test.pm b/mysql-test/lib/My/Test.pm
new file mode 100644
index 00000000000..68b100f91af
--- /dev/null
+++ b/mysql-test/lib/My/Test.pm
@@ -0,0 +1,123 @@
+# -*- cperl -*-
+
+
+#
+# One test
+#
+package My::Test;
+
+use strict;
+use warnings;
+use Carp;
+
+
+sub new {
+ my $class= shift;
+ my $self= bless {
+ @_,
+ }, $class;
+ return $self;
+}
+
+
+#
+# Return a unique key that can be used to
+# identify this test in a hash
+#
+sub key {
+ my ($self)= @_;
+ return $self->{key};
+}
+
+
+sub _encode {
+ my ($value)= @_;
+ $value =~ s/([|\\\x{0a}\x{0d}])/sprintf('\%02X', ord($1))/eg;
+ return $value;
+}
+
+sub _decode {
+ my ($value)= @_;
+ $value =~ s/\\([0-9a-fA-F]{2})/chr(hex($1))/ge;
+ return $value;
+}
+
+sub is_failed {
+ my ($self)= @_;
+ my $result= $self->{result};
+ croak "'is_failed' can't be called until test has been run!"
+ unless defined $result;
+
+ return ($result eq 'MTR_RES_FAILED');
+}
+
+
+sub write_test {
+ my ($test, $sock, $header)= @_;
+
+ # Give the test a unique key before serializing it
+ $test->{key}= "$test" unless defined $test->{key};
+
+ print $sock $header, "\n";
+ while ((my ($key, $value)) = each(%$test)) {
+ print $sock $key, "= ";
+ if (ref $value eq "ARRAY") {
+ print $sock "[", _encode(join(", ", @$value)), "]";
+ } else {
+ print $sock _encode($value);
+ }
+ print $sock "\n";
+ }
+ print $sock "\n";
+}
+
+
+sub read_test {
+ my ($sock)= @_;
+ my $test= My::Test->new();
+ # Read the : separated key value pairs until a
+ # single newline on it's own line
+ my $line;
+ while (defined($line= <$sock>)) {
+ # List is terminated by newline on it's own
+ if ($line eq "\n") {
+ # Correctly terminated reply
+ # print "Got newline\n";
+ last;
+ }
+ chomp($line);
+
+ # Split key/value on the first "="
+ my ($key, $value)= split("= ", $line, 2);
+
+ if ($value =~ /^\[(.*)\]/){
+ my @values= split(", ", _decode($1));
+ push(@{$test->{$key}}, @values);
+ }
+ else
+ {
+ $test->{$key}= _decode($value);
+ }
+ }
+ return $test;
+}
+
+
+sub print_test {
+ my ($self)= @_;
+
+ print "[", $self->{name}, "]", "\n";
+ while ((my ($key, $value)) = each(%$self)) {
+ print " ", $key, "= ";
+ if (ref $value eq "ARRAY") {
+ print "[", join(", ", @$value), "]";
+ } else {
+ print $value;
+ }
+ print "\n";
+ }
+ print "\n";
+}
+
+
+1;
diff --git a/mysql-test/lib/mtr_cases.pm b/mysql-test/lib/mtr_cases.pm
new file mode 100644
index 00000000000..34d24270eb6
--- /dev/null
+++ b/mysql-test/lib/mtr_cases.pm
@@ -0,0 +1,1177 @@
+# -*- cperl -*-
+# Copyright (C) 2005-2006 MySQL AB
+#
+# 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-1301 USA
+
+# This is a library file used by the Perl version of mysql-test-run,
+# and is part of the translation of the Bourne shell script with the
+# same name.
+
+package mtr_cases;
+use strict;
+
+use base qw(Exporter);
+our @EXPORT= qw(collect_option collect_test_cases);
+
+use mtr_report;
+use mtr_match;
+
+# Options used for the collect phase
+our $start_from;
+our $print_testcases;
+our $skip_rpl;
+our $do_test;
+our $skip_test;
+our $opt_skip_combination;
+our $binlog_format;
+our $enable_disabled;
+our $default_storage_engine;
+our $opt_with_ndbcluster_only;
+our $defaults_file;
+our $defaults_extra_file;
+our $reorder= 1;
+
+sub collect_option {
+ my ($opt, $value)= @_;
+
+ # Evaluate $opt as string to use "Getopt::Long::Callback legacy API"
+ my $opt_name = "$opt";
+
+ # Convert - to _ in option name
+ $opt_name =~ s/-/_/g;
+ no strict 'refs';
+ ${$opt_name}= $value;
+}
+
+use File::Basename;
+use File::Spec::Functions qw / splitdir /;
+use IO::File();
+use My::Config;
+use My::Platform;
+use My::Test;
+use My::Find;
+
+require "mtr_misc.pl";
+
+# Precompiled regex's for tests to do or skip
+my $do_test_reg;
+my $skip_test_reg;
+
+sub init_pattern {
+ my ($from, $what)= @_;
+ return undef unless defined $from;
+ if ( $from =~ /^[a-z0-9\.]*$/ ) {
+ # Does not contain any regex (except . that we allow as
+ # separator betwen suite and testname), make the pattern match
+ # beginning of string
+ $from= "^$from";
+ mtr_verbose("$what='$from'");
+ }
+ # Check that pattern is a valid regex
+ eval { "" =~/$from/; 1 } or
+ mtr_error("Invalid regex '$from' passed to $what\nPerl says: $@");
+ return $from;
+}
+
+
+##############################################################################
+#
+# Collect information about test cases to be run
+#
+##############################################################################
+
+sub collect_test_cases ($$) {
+ my $suites= shift; # Semicolon separated list of test suites
+ my %found_suites;
+ my $opt_cases= shift;
+ my $cases= []; # Array of hash(one hash for each testcase)
+
+ $do_test_reg= init_pattern($do_test, "--do-test");
+ $skip_test_reg= init_pattern($skip_test, "--skip-test");
+
+ foreach my $suite (split(",", $suites))
+ {
+ push(@$cases, collect_one_suite($suite, $opt_cases));
+ $found_suites{$suite}= 1;
+ }
+
+ if ( @$opt_cases )
+ {
+ # A list of tests was specified on the command line
+ # Check that the tests specified was found
+ # in at least one suite
+ foreach my $test_name_spec ( @$opt_cases )
+ {
+ my $found= 0;
+ my ($sname, $tname, $extension)= split_testname($test_name_spec);
+ if (defined($sname) && !defined($found_suites{$sname}))
+ {
+ $found_suites{$sname}= 1;
+ push(@$cases, collect_one_suite($sname));
+ }
+
+ foreach my $test ( @$cases )
+ {
+ # test->{name} is always in suite.name format
+ if ( $test->{name} =~ /.*\.$tname/ )
+ {
+ $found= 1;
+ }
+ }
+ if ( not $found )
+ {
+ mtr_error("Could not find '$tname' in '$suites' suite(s)");
+ }
+ }
+ }
+
+ if ( $reorder )
+ {
+ # Reorder the test cases in an order that will make them faster to run
+ my %sort_criteria;
+
+ # Make a mapping of test name to a string that represents how that test
+ # should be sorted among the other tests. Put the most important criterion
+ # first, then a sub-criterion, then sub-sub-criterion, etc.
+ foreach my $tinfo (@$cases)
+ {
+ my @criteria = ();
+
+ #
+ # Append the criteria for sorting, in order of importance.
+ #
+ push(@criteria, "ndb=" . ($tinfo->{'ndb_test'} ? "A" : "B"));
+ # Group test with equal options together.
+ # Ending with "~" makes empty sort later than filled
+ my $opts= $tinfo->{'master_opt'} ? $tinfo->{'master_opt'} : [];
+ push(@criteria, join("!", sort @{$opts}) . "~");
+
+ $sort_criteria{$tinfo->{name}} = join(" ", @criteria);
+ }
+
+ @$cases = sort {
+ $sort_criteria{$a->{'name'}} . $a->{'name'} cmp
+ $sort_criteria{$b->{'name'}} . $b->{'name'}; } @$cases;
+
+ # For debugging the sort-order
+ # foreach my $tinfo (@$cases)
+ # {
+ # print("$sort_criteria{$tinfo->{'name'}} -> \t$tinfo->{'name'}\n");
+ # }
+
+ }
+
+ if (defined $print_testcases){
+ print_testcases(@$cases);
+ exit(1);
+ }
+
+ return $cases;
+
+}
+
+
+# Returns (suitename, testname, extension)
+sub split_testname {
+ my ($test_name)= @_;
+
+ # Get rid of directory part and split name on .'s
+ my @parts= split(/\./, basename($test_name));
+
+ if (@parts == 1){
+ # Only testname given, ex: alias
+ return (undef , $parts[0], undef);
+ } elsif (@parts == 2) {
+ # Either testname.test or suite.testname given
+ # Ex. main.alias or alias.test
+
+ if ($parts[1] eq "test")
+ {
+ return (undef , $parts[0], $parts[1]);
+ }
+ else
+ {
+ return ($parts[0], $parts[1], undef);
+ }
+
+ } elsif (@parts == 3) {
+ # Fully specified suitename.testname.test
+ # ex main.alias.test
+ return ( $parts[0], $parts[1], $parts[2]);
+ }
+
+ mtr_error("Illegal format of test name: $test_name");
+}
+
+
+sub collect_one_suite($)
+{
+ my $suite= shift; # Test suite name
+ my $opt_cases= shift;
+ my @cases; # Array of hash
+
+ mtr_verbose("Collecting: $suite");
+
+ my $suitedir= "$::glob_mysql_test_dir"; # Default
+ if ( $suite ne "main" )
+ {
+ # Allow suite to be path to "some dir" if $suite has at least
+ # one directory part
+ if ( -d $suite and splitdir($suite) > 1 ){
+ $suitedir= $suite;
+ mtr_report(" - from '$suitedir'");
+
+ }
+ else
+ {
+ $suitedir= my_find_dir($::basedir,
+ ["mysql-test/suite",
+ "mysql-test",
+ # Look in storage engine specific suite dirs
+ "storage/*/mysql-test-suites"
+ ],
+ [$suite]);
+ }
+ mtr_verbose("suitedir: $suitedir");
+ }
+
+ my $testdir= "$suitedir/t";
+ my $resdir= "$suitedir/r";
+
+ # Check if t/ exists
+ if (-d $testdir){
+ # t/ exists
+
+ if ( -d $resdir )
+ {
+ # r/exists
+ }
+ else
+ {
+ # No r/, use t/ as result dir
+ $resdir= $testdir;
+ }
+
+ }
+ else {
+ # No t/ dir => there can' be any r/ dir
+ mtr_error("Can't have r/ dir without t/") if -d $resdir;
+
+ # No t/ or r/ => use suitedir
+ $resdir= $testdir= $suitedir;
+ }
+
+ mtr_verbose("testdir: $testdir");
+ mtr_verbose("resdir: $resdir");
+
+ # ----------------------------------------------------------------------
+ # Build a hash of disabled testcases for this suite
+ # ----------------------------------------------------------------------
+ my %disabled;
+ if ( open(DISABLED, "$testdir/disabled.def" ) )
+ {
+ while ( <DISABLED> )
+ {
+ chomp;
+ if ( /^\s*(\S+)\s*:\s*(.*?)\s*$/ )
+ {
+ $disabled{$1}= $2;
+ }
+ }
+ close DISABLED;
+ }
+
+ # Read suite.opt file
+ my $suite_opt_file= "$testdir/suite.opt";
+ my $suite_opts= [];
+ if ( -f $suite_opt_file )
+ {
+ $suite_opts= opts_from_file($suite_opt_file);
+ }
+
+ if ( @$opt_cases )
+ {
+ # Collect in specified order
+ foreach my $test_name_spec ( @$opt_cases )
+ {
+ my ($sname, $tname, $extension)= split_testname($test_name_spec);
+
+ # The test name parts have now been defined
+ #print " suite_name: $sname\n";
+ #print " tname: $tname\n";
+ #print " extension: $extension\n";
+
+ # Check cirrect suite if suitename is defined
+ next if (defined $sname and $suite ne $sname);
+
+ if ( defined $extension )
+ {
+ my $full_name= "$testdir/$tname.$extension";
+ # Extension was specified, check if the test exists
+ if ( ! -f $full_name)
+ {
+ # This is only an error if suite was specified, otherwise it
+ # could exist in another suite
+ mtr_error("Test '$full_name' was not found in suite '$sname'")
+ if $sname;
+
+ next;
+ }
+ }
+ else
+ {
+ # No extension was specified, use default
+ $extension= "test";
+ my $full_name= "$testdir/$tname.$extension";
+
+ # Test not found here, could exist in other suite
+ next if ( ! -f $full_name );
+ }
+
+ push(@cases,
+ collect_one_test_case($suitedir,
+ $testdir,
+ $resdir,
+ $suite,
+ $tname,
+ "$tname.$extension",
+ \%disabled,
+ $suite_opts));
+ }
+ }
+ else
+ {
+ opendir(TESTDIR, $testdir) or mtr_error("Can't open dir \"$testdir\": $!");
+
+ foreach my $elem ( sort readdir(TESTDIR) )
+ {
+ my $tname= mtr_match_extension($elem, 'test');
+
+ next unless defined $tname;
+
+ # Skip tests that does not match the --do-test= filter
+ next if ($do_test_reg and not $tname =~ /$do_test_reg/o);
+
+ push(@cases,
+ collect_one_test_case($suitedir,
+ $testdir,
+ $resdir,
+ $suite,
+ $tname,
+ $elem,
+ \%disabled,
+ $suite_opts));
+ }
+ closedir TESTDIR;
+ }
+
+ # Return empty list if no testcases found
+ return if (@cases == 0);
+
+ # ----------------------------------------------------------------------
+ # Read combinations for this suite and build testcases x combinations
+ # if any combinations exists
+ # ----------------------------------------------------------------------
+ if ( ! $opt_skip_combination )
+ {
+ my @combinations;
+ my $combination_file= "$suitedir/combinations";
+ #print "combination_file: $combination_file\n";
+ if (@::opt_combinations)
+ {
+ # take the combination from command-line
+ mtr_verbose("Take the combination from command line");
+ foreach my $combination (@::opt_combinations) {
+ my $comb= {};
+ $comb->{name}= $combination;
+ push(@{$comb->{comb_opt}}, $combination);
+ push(@combinations, $comb);
+ }
+ }
+ elsif (-f $combination_file )
+ {
+ # Read combinations file in my.cnf format
+ mtr_verbose("Read combinations file");
+ my $config= My::Config->new($combination_file);
+ foreach my $group ($config->groups()) {
+ my $comb= {};
+ $comb->{name}= $group->name();
+ foreach my $option ( $group->options() ) {
+ push(@{$comb->{comb_opt}}, $option->option());
+ }
+ push(@combinations, $comb);
+ }
+ }
+
+ if (@combinations)
+ {
+ print " - adding combinations for $suite\n";
+ #print_testcases(@cases);
+
+ my @new_cases;
+ foreach my $comb (@combinations)
+ {
+ foreach my $test (@cases)
+ {
+
+ next if ( $test->{'skip'} );
+
+ # Skip this combination if the values it provides
+ # already are set in master_opt or slave_opt
+ if (My::Options::is_set($test->{master_opt}, $comb->{comb_opt}) &&
+ My::Options::is_set($test->{slave_opt}, $comb->{comb_opt}) ){
+ next;
+ }
+
+ # Copy test options
+ my $new_test= My::Test->new();
+ while (my ($key, $value) = each(%$test)) {
+ if (ref $value eq "ARRAY") {
+ push(@{$new_test->{$key}}, @$value);
+ } else {
+ $new_test->{$key}= $value;
+ }
+ }
+
+ # Append the combination options to master_opt and slave_opt
+ push(@{$new_test->{master_opt}}, @{$comb->{comb_opt}});
+ push(@{$new_test->{slave_opt}}, @{$comb->{comb_opt}});
+
+ # Add combination name short name
+ $new_test->{combination}= $comb->{name};
+
+ # Add the new test to new test cases list
+ push(@new_cases, $new_test);
+ }
+ }
+
+ # Add the plain test if it was not already added
+ # as part of a combination
+ my %added;
+ foreach my $new_test (@new_cases){
+ $added{$new_test->{name}}= 1;
+ }
+ foreach my $test (@cases){
+ push(@new_cases, $test) unless $added{$test->{name}};
+ }
+
+
+ #print_testcases(@new_cases);
+ @cases= @new_cases;
+ #print_testcases(@cases);
+ }
+ }
+ optimize_cases(\@cases);
+ #print_testcases(@cases);
+
+ return @cases;
+}
+
+
+
+#
+# Loop through all test cases
+# - optimize which test to run by skipping unnecessary ones
+# - update settings if necessary
+#
+sub optimize_cases {
+ my ($cases)= @_;
+
+ foreach my $tinfo ( @$cases )
+ {
+ # Skip processing if already marked as skipped
+ next if $tinfo->{skip};
+
+ # =======================================================
+ # If a special binlog format was selected with
+ # --mysqld=--binlog-format=x, skip all test that does not
+ # support it
+ # =======================================================
+ #print "binlog_format: $binlog_format\n";
+ if (defined $binlog_format )
+ {
+ # =======================================================
+ # Fixed --binlog-format=x specified on command line
+ # =======================================================
+ if ( defined $tinfo->{'binlog_formats'} )
+ {
+ #print "binlog_formats: ". join(", ", @{$tinfo->{binlog_formats}})."\n";
+
+ # The test supports different binlog formats
+ # check if the selected one is ok
+ my $supported=
+ grep { $_ eq $binlog_format } @{$tinfo->{'binlog_formats'}};
+ if ( !$supported )
+ {
+ $tinfo->{'skip'}= 1;
+ $tinfo->{'comment'}=
+ "Doesn't support --binlog-format='$binlog_format'";
+ }
+ }
+ }
+ else
+ {
+ # =======================================================
+ # Use dynamic switching of binlog format
+ # =======================================================
+
+ # Get binlog-format used by this test from master_opt
+ my $test_binlog_format;
+ foreach my $opt ( @{$tinfo->{master_opt}} ) {
+ $test_binlog_format=
+ mtr_match_prefix($opt, "--binlog-format=") || $test_binlog_format;
+ }
+
+ if (defined $test_binlog_format and
+ defined $tinfo->{binlog_formats} )
+ {
+ my $supported=
+ grep { $_ eq $test_binlog_format } @{$tinfo->{'binlog_formats'}};
+ if ( !$supported )
+ {
+ $tinfo->{'skip'}= 1;
+ $tinfo->{'comment'}=
+ "Doesn't support --binlog-format='$test_binlog_format'";
+ next;
+ }
+ }
+ }
+
+ # =======================================================
+ # Check that engine selected by
+ # --default-storage-engine=<engine> is supported
+ # =======================================================
+ my %builtin_engines = ('myisam' => 1, 'memory' => 1);
+
+ foreach my $opt ( @{$tinfo->{master_opt}} ) {
+ my $default_engine=
+ mtr_match_prefix($opt, "--default-storage-engine=");
+
+ if (defined $default_engine){
+
+ #print " $tinfo->{name}\n";
+ #print " - The test asked to use '$default_engine'\n";
+
+ #my $engine_value= $::mysqld_variables{$default_engine};
+ #print " - The mysqld_variables says '$engine_value'\n";
+
+ if ( ! exists $::mysqld_variables{$default_engine} and
+ ! exists $builtin_engines{$default_engine} )
+ {
+ $tinfo->{'skip'}= 1;
+ $tinfo->{'comment'}=
+ "'$default_engine' not supported";
+ }
+
+ $tinfo->{'ndb_test'}= 1
+ if ( $default_engine =~ /^ndb/i );
+ $tinfo->{'innodb_test'}= 1
+ if ( $default_engine =~ /^innodb/i );
+ }
+ }
+
+ # =======================================================
+ # Check that engine selected by
+ # --default-storage-engine=<engine> is supported
+ # =======================================================
+ my %builtin_engines = ('myisam' => 1, 'memory' => 1);
+
+ foreach my $opt ( @{$tinfo->{master_opt}} ) {
+ my $default_engine=
+ mtr_match_prefix($opt, "--default-storage-engine=");
+
+ if (defined $default_engine){
+
+
+ my $engine_value= $::mysqld_variables{$default_engine};
+
+ if ( ! exists $::mysqld_variables{$default_engine} and
+ ! exists $builtin_engines{$default_engine} )
+ {
+ $tinfo->{'skip'}= 1;
+ $tinfo->{'comment'}=
+ "'$default_engine' not supported";
+ }
+
+ $tinfo->{'ndb_test'}= 1
+ if ( $default_engine =~ /^ndb/i );
+ $tinfo->{'innodb_test'}= 1
+ if ( $default_engine =~ /^innodb/i );
+ }
+ }
+ }
+}
+
+
+#
+# Read options from the given opt file and append them as an array
+# to $tinfo->{$opt_name}
+#
+sub process_opts_file {
+ my ($tinfo, $opt_file, $opt_name)= @_;
+
+ if ( -f $opt_file )
+ {
+ my $opts= opts_from_file($opt_file);
+
+ foreach my $opt ( @$opts )
+ {
+ my $value;
+
+ # The opt file is used both to send special options to the mysqld
+ # as well as pass special test case specific options to this
+ # script
+
+ $value= mtr_match_prefix($opt, "--timezone=");
+ if ( defined $value )
+ {
+ $tinfo->{'timezone'}= $value;
+ next;
+ }
+
+ $value= mtr_match_prefix($opt, "--result-file=");
+ if ( defined $value )
+ {
+ # Specifies the file mysqltest should compare
+ # output against
+ $tinfo->{'result_file'}= "r/$value.result";
+ next;
+ }
+
+ $value= mtr_match_prefix($opt, "--config-file-template=");
+ if ( defined $value)
+ {
+ # Specifies the configuration file to use for this test
+ $tinfo->{'template_path'}= dirname($tinfo->{path})."/$value";
+ next;
+ }
+
+ # If we set default time zone, remove the one we have
+ $value= mtr_match_prefix($opt, "--default-time-zone=");
+ if ( defined $value )
+ {
+ # Set timezone for this test case to something different
+ $tinfo->{'timezone'}= "GMT-8";
+ # Fallthrough, add the --default-time-zone option
+ }
+
+ # The --restart option forces a restart even if no special
+ # option is set. If the options are the same as next testcase
+ # there is no need to restart after the testcase
+ # has completed
+ if ( $opt eq "--force-restart" )
+ {
+ $tinfo->{'force_restart'}= 1;
+ next;
+ }
+
+ # Ok, this was a real option, add it
+ push(@{$tinfo->{$opt_name}}, $opt);
+ }
+ }
+}
+
+
+##############################################################################
+#
+# Collect information about a single test case
+#
+##############################################################################
+
+sub collect_one_test_case {
+ my $suitedir= shift;
+ my $testdir= shift;
+ my $resdir= shift;
+ my $suitename= shift;
+ my $tname= shift;
+ my $filename= shift;
+ my $disabled= shift;
+ my $suite_opts= shift;
+
+ #print "collect_one_test_case\n";
+ #print " suitedir: $suitedir\n";
+ #print " testdir: $testdir\n";
+ #print " resdir: $resdir\n";
+ #print " suitename: $suitename\n";
+ #print " tname: $tname\n";
+ #print " filename: $filename\n";
+
+ # ----------------------------------------------------------------------
+ # Check --start-from
+ # ----------------------------------------------------------------------
+ if ( $start_from )
+ {
+ # start_from can be specified as [suite.].testname_prefix
+ my ($suite, $test, $ext)= split_testname($start_from);
+
+ if ( $suite and $suitename lt $suite){
+ return; # Skip silently
+ }
+ if ( $tname lt $test ){
+ return; # Skip silently
+ }
+ }
+
+ # ----------------------------------------------------------------------
+ # Set defaults
+ # ----------------------------------------------------------------------
+ my $tinfo= My::Test->new
+ (
+ name => "$suitename.$tname",
+ shortname => $tname,
+ path => "$testdir/$filename",
+
+ );
+
+ my $result_file= "$resdir/$tname.result";
+ if (-f $result_file) {
+ # Allow nonexistsing result file
+ # in that case .test must issue "exit" otherwise test
+ # should fail by default
+ $tinfo->{result_file}= $result_file;
+ }
+ else {
+ # No .result file exist
+ # Remember the path where it should be
+ # saved in case of --record
+ $tinfo->{record_file}= $result_file;
+ }
+
+ # ----------------------------------------------------------------------
+ # Skip some tests but include in list, just mark them as skipped
+ # ----------------------------------------------------------------------
+ if ( $skip_test_reg and $tname =~ /$skip_test_reg/o )
+ {
+ $tinfo->{'skip'}= 1;
+ return $tinfo;
+ }
+
+ # ----------------------------------------------------------------------
+ # Check for disabled tests
+ # ----------------------------------------------------------------------
+ my $marked_as_disabled= 0;
+ if ( $disabled->{$tname} )
+ {
+ # Test was marked as disabled in suites disabled.def file
+ $marked_as_disabled= 1;
+ $tinfo->{'comment'}= $disabled->{$tname};
+ }
+
+ my $disabled_file= "$testdir/$tname.disabled";
+ if ( -f $disabled_file )
+ {
+ $marked_as_disabled= 1;
+ $tinfo->{'comment'}= mtr_fromfile($disabled_file);
+ }
+
+ if ( $marked_as_disabled )
+ {
+ if ( $enable_disabled )
+ {
+ # User has selected to run all disabled tests
+ mtr_report(" - $tinfo->{name} wil be run although it's been disabled\n",
+ " due to '$tinfo->{comment}'");
+ }
+ else
+ {
+ $tinfo->{'skip'}= 1;
+ $tinfo->{'disable'}= 1; # Sub type of 'skip'
+ return $tinfo;
+ }
+ }
+
+ # ----------------------------------------------------------------------
+ # Append suite extra options to both master and slave
+ # ----------------------------------------------------------------------
+ push(@{$tinfo->{'master_opt'}}, @$suite_opts);
+ push(@{$tinfo->{'slave_opt'}}, @$suite_opts);
+
+ #-----------------------------------------------------------------------
+ # Check for test specific config file
+ #-----------------------------------------------------------------------
+ my $test_cnf_file= "$testdir/$tname.cnf";
+ if ( -f $test_cnf_file) {
+ # Specifies the configuration file to use for this test
+ $tinfo->{'template_path'}= $test_cnf_file;
+ }
+
+ # ----------------------------------------------------------------------
+ # Check for test specific config file
+ # ----------------------------------------------------------------------
+ my $test_cnf_file= "$testdir/$tname.cnf";
+ if ( -f $test_cnf_file ) {
+ # Specifies the configuration file to use for this test
+ $tinfo->{'template_path'}= $test_cnf_file;
+ }
+
+ # ----------------------------------------------------------------------
+ # master sh
+ # ----------------------------------------------------------------------
+ my $master_sh= "$testdir/$tname-master.sh";
+ if ( -f $master_sh )
+ {
+ if ( IS_WIN32PERL )
+ {
+ $tinfo->{'skip'}= 1;
+ $tinfo->{'comment'}= "No tests with sh scripts on Windows";
+ return $tinfo;
+ }
+ else
+ {
+ $tinfo->{'master_sh'}= $master_sh;
+ }
+ }
+
+ # ----------------------------------------------------------------------
+ # slave sh
+ # ----------------------------------------------------------------------
+ my $slave_sh= "$testdir/$tname-slave.sh";
+ if ( -f $slave_sh )
+ {
+ if ( IS_WIN32PERL )
+ {
+ $tinfo->{'skip'}= 1;
+ $tinfo->{'comment'}= "No tests with sh scripts on Windows";
+ return $tinfo;
+ }
+ else
+ {
+ $tinfo->{'slave_sh'}= $slave_sh;
+ }
+ }
+
+ # ----------------------------------------------------------------------
+ # <tname>.slave-mi
+ # ----------------------------------------------------------------------
+ mtr_error("$tname: slave-mi not supported anymore")
+ if ( -f "$testdir/$tname.slave-mi");
+
+
+ tags_from_test_file($tinfo,"$testdir/${tname}.test");
+
+ if ( defined $default_storage_engine )
+ {
+ # Different default engine is used
+ # tag test to require that engine
+ $tinfo->{'ndb_test'}= 1
+ if ( $default_storage_engine =~ /^ndb/i );
+
+ $tinfo->{'innodb_test'}= 1
+ if ( $default_storage_engine =~ /^innodb/i );
+
+ }
+
+ if ( $tinfo->{'big_test'} and ! $::opt_big_test )
+ {
+ $tinfo->{'skip'}= 1;
+ $tinfo->{'comment'}= "Test needs 'big-test' option";
+ return $tinfo
+ }
+
+ if ( $tinfo->{'need_debug'} && ! $::debug_compiled_binaries )
+ {
+ $tinfo->{'skip'}= 1;
+ $tinfo->{'comment'}= "Test needs debug binaries";
+ return $tinfo
+ }
+
+ if ( $tinfo->{'ndb_test'} )
+ {
+ # This is a NDB test
+ if ( $::opt_skip_ndbcluster == 2 )
+ {
+ # Ndb is not supported, skip it
+ $tinfo->{'skip'}= 1;
+ $tinfo->{'comment'}= "No ndbcluster support";
+ return $tinfo;
+ }
+ elsif ( $::opt_skip_ndbcluster )
+ {
+ # All ndb test's should be skipped
+ $tinfo->{'skip'}= 1;
+ $tinfo->{'comment'}= "No ndbcluster tests(--skip-ndbcluster)";
+ return $tinfo;
+ }
+ }
+ else
+ {
+ # This is not a ndb test
+ if ( $opt_with_ndbcluster_only )
+ {
+ # Only the ndb test should be run, all other should be skipped
+ $tinfo->{'skip'}= 1;
+ $tinfo->{'comment'}= "Only ndbcluster tests";
+ return $tinfo;
+ }
+ }
+
+ if ($tinfo->{'federated_test'})
+ {
+ # This is a test that need federated, enable it
+ push(@{$tinfo->{'master_opt'}}, "--loose-federated");
+ push(@{$tinfo->{'slave_opt'}}, "--loose-federated");
+ }
+
+ if ( $tinfo->{'innodb_test'} )
+ {
+ # This is a test that need innodb
+ if ( $::mysqld_variables{'innodb'} ne "TRUE" )
+ {
+ # innodb is not supported, skip it
+ $tinfo->{'skip'}= 1;
+ $tinfo->{'comment'}= "No innodb support";
+ return $tinfo;
+ }
+ }
+ else
+ {
+ push(@{$tinfo->{'master_opt'}}, "--loose-skip-innodb");
+ push(@{$tinfo->{'slave_opt'}}, "--loose-skip-innodb");
+ }
+
+ if ( $tinfo->{'need_binlog'} )
+ {
+ if (grep(/^--skip-log-bin/, @::opt_extra_mysqld_opt) )
+ {
+ $tinfo->{'skip'}= 1;
+ $tinfo->{'comment'}= "Test needs binlog";
+ return $tinfo;
+ }
+ }
+ else
+ {
+ # Test does not need binlog, add --skip-binlog to
+ # the options used when starting
+ push(@{$tinfo->{'master_opt'}}, "--loose-skip-log-bin");
+ push(@{$tinfo->{'slave_opt'}}, "--loose-skip-log-bin");
+ }
+
+ if ( $tinfo->{'rpl_test'} )
+ {
+ if ( $skip_rpl )
+ {
+ $tinfo->{'skip'}= 1;
+ $tinfo->{'comment'}= "No replication tests(--skip-rpl)";
+ return $tinfo;
+ }
+ }
+
+ if ( $::opt_embedded_server )
+ {
+ if ( $tinfo->{'not_embedded'} )
+ {
+ $tinfo->{'skip'}= 1;
+ $tinfo->{'comment'}= "Not run for embedded server";
+ return $tinfo;
+ }
+ }
+
+ # ----------------------------------------------------------------------
+ # Find config file to use if not already selected in <testname>.opt file
+ # ----------------------------------------------------------------------
+ if (defined $defaults_file) {
+ # Using same config file for all tests
+ $tinfo->{template_path}= $defaults_file;
+ }
+ elsif (! $tinfo->{template_path} )
+ {
+ my $config= "$suitedir/my.cnf";
+ if (! -f $config )
+ {
+ # assume default.cnf will be used
+ $config= "include/default_my.cnf";
+
+ # Suite has no config, autodetect which one to use
+ if ( $tinfo->{rpl_test} ){
+ $config= "suite/rpl/my.cnf";
+ if ( $tinfo->{ndb_test} ){
+ $config= "suite/rpl_ndb/my.cnf";
+ }
+ }
+ elsif ( $tinfo->{ndb_test} ){
+ $config= "suite/ndb/my.cnf";
+ }
+ }
+ $tinfo->{template_path}= $config;
+ }
+
+ # Set extra config file to use
+ if (defined $defaults_extra_file) {
+ $tinfo->{extra_template_path}= $defaults_extra_file;
+ }
+
+ # ----------------------------------------------------------------------
+ # Append mysqld extra options to both master and slave
+ # ----------------------------------------------------------------------
+ push(@{$tinfo->{'master_opt'}}, @::opt_extra_mysqld_opt);
+ push(@{$tinfo->{'slave_opt'}}, @::opt_extra_mysqld_opt);
+
+ # ----------------------------------------------------------------------
+ # Add master opts, extra options only for master
+ # ----------------------------------------------------------------------
+ process_opts_file($tinfo, "$testdir/$tname-master.opt", 'master_opt');
+
+ # ----------------------------------------------------------------------
+ # Add slave opts, list of extra option only for slave
+ # ----------------------------------------------------------------------
+ process_opts_file($tinfo, "$testdir/$tname-slave.opt", 'slave_opt');
+
+ return $tinfo;
+}
+
+
+# List of tags in the .test files that if found should set
+# the specified value in "tinfo"
+my @tags=
+(
+ ["include/have_binlog_format_row.inc", "binlog_formats", ["row"]],
+ ["include/have_binlog_format_statement.inc", "binlog_formats", ["statement"]],
+ ["include/have_binlog_format_mixed.inc", "binlog_formats", ["mixed"]],
+ ["include/have_binlog_format_mixed_or_row.inc",
+ "binlog_formats", ["mixed", "row"]],
+ ["include/have_binlog_format_mixed_or_statement.inc",
+ "binlog_formats", ["mixed", "statement"]],
+ ["include/have_binlog_format_row_or_statement.inc",
+ "binlog_formats", ["row", "statement"]],
+
+ ["include/have_log_bin.inc", "need_binlog", 1],
+
+ ["include/have_innodb.inc", "innodb_test", 1],
+ ["include/big_test.inc", "big_test", 1],
+ ["include/have_debug.inc", "need_debug", 1],
+ ["include/have_ndb.inc", "ndb_test", 1],
+ ["include/have_multi_ndb.inc", "ndb_test", 1],
+ ["include/master-slave.inc", "rpl_test", 1],
+ ["include/ndb_master-slave.inc", "rpl_test", 1],
+ ["include/ndb_master-slave.inc", "ndb_test", 1],
+ ["federated.inc", "federated_test", 1],
+ ["include/not_embedded.inc", "not_embedded", 1],
+);
+
+
+sub tags_from_test_file {
+ my $tinfo= shift;
+ my $file= shift;
+ #mtr_verbose("$file");
+ my $F= IO::File->new($file) or mtr_error("can't open file \"$file\": $!");
+
+ while ( my $line= <$F> )
+ {
+
+ # Skip line if it start's with #
+ next if ( $line =~ /^#/ );
+
+ # Match this line against tag in "tags" array
+ foreach my $tag (@tags)
+ {
+ if ( index($line, $tag->[0]) >= 0 )
+ {
+ # Tag matched, assign value to "tinfo"
+ $tinfo->{"$tag->[1]"}= $tag->[2];
+ }
+ }
+
+ # If test sources another file, open it as well
+ if ( $line =~ /^\-\-([[:space:]]*)source(.*)$/ or
+ $line =~ /^([[:space:]]*)source(.*);$/ )
+ {
+ my $value= $2;
+ $value =~ s/^\s+//; # Remove leading space
+ $value =~ s/[[:space:]]+$//; # Remove ending space
+
+ # Sourced file may exist relative to test or
+ # in global location
+ foreach my $sourced_file (dirname($file). "/$value",
+ "$::glob_mysql_test_dir/$value")
+ {
+ if ( -f $sourced_file )
+ {
+ # Only source the file if it exists, we may get
+ # false positives in the regexes above if someone
+ # writes "source nnnn;" in a test case(such as mysqltest.test)
+ tags_from_test_file($tinfo, $sourced_file);
+ last;
+ }
+ }
+ }
+
+ }
+}
+
+sub unspace {
+ my $string= shift;
+ my $quote= shift;
+ $string =~ s/[ \t]/\x11/g;
+ return "$quote$string$quote";
+}
+
+
+sub opts_from_file ($) {
+ my $file= shift;
+
+ open(FILE,"<",$file) or mtr_error("can't open file \"$file\": $!");
+ my @args;
+ while ( <FILE> )
+ {
+ chomp;
+
+ # --set-variable=init_connect=set @a='a\\0c'
+ s/^\s+//; # Remove leading space
+ s/\s+$//; # Remove ending space
+
+ # This is strange, but we need to fill whitespace inside
+ # quotes with something, to remove later. We do this to
+ # be able to split on space. Else, we have trouble with
+ # options like
+ #
+ # --someopt="--insideopt1 --insideopt2"
+ #
+ # But still with this, we are not 100% sure it is right,
+ # we need a shell to do it right.
+
+ s/\'([^\'\"]*)\'/unspace($1,"\x0a")/ge;
+ s/\"([^\'\"]*)\"/unspace($1,"\x0b")/ge;
+ s/\'([^\'\"]*)\'/unspace($1,"\x0a")/ge;
+ s/\"([^\'\"]*)\"/unspace($1,"\x0b")/ge;
+
+ foreach my $arg (split(/[ \t]+/))
+ {
+ $arg =~ tr/\x11\x0a\x0b/ \'\"/; # Put back real chars
+ # The outermost quotes has to go
+ $arg =~ s/^([^\'\"]*)\'(.*)\'([^\'\"]*)$/$1$2$3/
+ or $arg =~ s/^([^\'\"]*)\"(.*)\"([^\'\"]*)$/$1$2$3/;
+ $arg =~ s/\\\\/\\/g;
+
+ # Do not pass empty string since my_getopt is not capable to handle it.
+ if (length($arg)) {
+ push(@args, $arg);
+ }
+ }
+ }
+ close FILE;
+ return \@args;
+}
+
+sub print_testcases {
+ my (@cases)= @_;
+
+ print "=" x 60, "\n";
+ foreach my $test (@cases){
+ $test->print_test();
+ }
+ print "=" x 60, "\n";
+}
+
+
+1;
diff --git a/mysql-test/lib/mtr_diff.pl b/mysql-test/lib/mtr_diff.pl
deleted file mode 100644
index 26e556de5e8..00000000000
--- a/mysql-test/lib/mtr_diff.pl
+++ /dev/null
@@ -1,297 +0,0 @@
-# -*- cperl -*-
-# Copyright (C) 2005 MySQL AB
-#
-# 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-1301 USA
-
-# This is a library file used by the Perl version of mysql-test-run,
-# and is part of the translation of the Bourne shell script with the
-# same name.
-
-#use Data::Dumper;
-use strict;
-
-# $Data::Dumper::Indent= 1;
-
-sub mtr_diff($$);
-
-##############################################################################
-#
-# This is a simplified unified diff, with some special handling
-# of unsorted result sets
-#
-##############################################################################
-
-# FIXME replace die with mtr_error
-
-#require "mtr_report.pl";
-#mtr_diff("a.txt","b.txt");
-
-sub mtr_diff ($$) {
- my $file1 = shift;
- my $file2 = shift;
-
- # ----------------------------------------------------------------------
- # We read in all of the files at once
- # ----------------------------------------------------------------------
-
- unless ( open(FILE1, $file1) )
- {
- mtr_warning("can't open \"$file1\": $!");
- return;
- }
-
- unless ( open(FILE2, $file2) )
- {
- mtr_warning("can't open \"$file2\": $!");
- return;
- }
-
- my $lines1= collect_lines(<FILE1>);
- my $lines2= collect_lines(<FILE2>);
- close FILE1;
- close FILE2;
-
-# print Dumper($lines1);
-# print Dumper($lines2);
-
- # ----------------------------------------------------------------------
- # We compare line by line, but don't shift off elements until we know
- # what to do. This way we use the "restart" method, do simple change
- # and restart by entering the diff loop from the beginning again.
- # ----------------------------------------------------------------------
-
- my @context;
- my @info; # Collect information, and output later
- my $lno1= 1;
- my $lno2= 1;
-
- while ( @$lines1 or @$lines2 )
- {
- unless ( @$lines1 )
- {
- push(@info, map {['+',$lno1,$lno2++,$_]} @$lines2);
- last;
- }
- unless ( @$lines2 )
- {
- push(@info, map {['-',$lno1++,$lno2,$_]} @$lines1);
- last;
- }
-
- # ----------------------------------------------------------------------
- # We know both have lines
- # ----------------------------------------------------------------------
-
- if ( $lines1->[0] eq $lines2->[0] )
- {
- # Simple case, first line match and all is well
- push(@info, ['',$lno1++,$lno2++,$lines1->[0]]);
- shift @$lines1;
- shift @$lines2;
- next;
- }
-
- # ----------------------------------------------------------------------
- # Now, we know they differ
- # ----------------------------------------------------------------------
-
- # How far in the other one, is there a match?
-
- my $idx2= find_next_match($lines1->[0], $lines2);
- my $idx1= find_next_match($lines2->[0], $lines1);
-
- # Here we could test "if ( !defined $idx2 or !defined $idx1 )" and
- # use a more complicated diff algorithm in the case both contains
- # each others lines, just dislocated. But for this application, there
- # should be no need.
-
- if ( !defined $idx2 )
- {
- push(@info, ['-',$lno1++,$lno2,$lines1->[0]]);
- shift @$lines1;
- }
- else
- {
- push(@info, ['+',$lno1,$lno2++,$lines2->[0]]);
- shift @$lines2;
- }
- }
-
- # ----------------------------------------------------------------------
- # Try to output nicely
- # ----------------------------------------------------------------------
-
-# print Dumper(\@info);
-
- # We divide into "chunks" to output
- # We want at least three lines of context
-
- my @chunks;
- my @chunk;
- my $state= 'pre'; # 'pre', 'in' and 'post' difference
- my $post_count= 0;
-
- foreach my $info ( @info )
- {
- if ( $info->[0] eq '' and $state eq 'pre' )
- {
- # Collect no more than three lines of context before diff
- push(@chunk, $info);
- shift(@chunk) if @chunk > 3;
- next;
- }
-
- if ( $info->[0] =~ /(\+|\-)/ and $state =~ /(pre|in)/ )
- {
- # Start/continue collecting diff
- $state= 'in';
- push(@chunk, $info);
- next;
- }
-
- if ( $info->[0] eq '' and $state eq 'in' )
- {
- # Stop collecting diff, and collect context after diff
- $state= 'post';
- $post_count= 1;
- push(@chunk, $info);
- next;
- }
-
- if ( $info->[0] eq '' and $state eq 'post' and $post_count < 6 )
- {
- # We might find a new diff sequence soon, continue to collect
- # non diffs but five up on 6.
- $post_count++;
- push(@chunk, $info);
- next;
- }
-
- if ( $info->[0] eq '' and $state eq 'post' )
- {
- # We put an end to this, giving three non diff lines to
- # the old chunk, and three to the new one.
- my @left= splice(@chunk, -3, 3);
- push(@chunks, [@chunk]);
- $state= 'pre';
- $post_count= 0;
- @chunk= @left;
- next;
- }
-
- if ( $info->[0] =~ /(\+|\-)/ and $state eq 'post' )
- {
- # We didn't split, continue collect diff
- $state= 'in';
- push(@chunk, $info);
- next;
- }
-
- }
-
- if ( $post_count > 3 )
- {
- $post_count -= 3;
- splice(@chunk, -$post_count, $post_count);
- }
- push(@chunks, [@chunk]) if @chunk and $state ne 'pre';
-
- foreach my $chunk ( @chunks )
- {
- my $from_file_start= $chunk->[0]->[1];
- my $to_file_start= $chunk->[0]->[2];
- my $from_file_offset= $chunk->[$#$chunk]->[1] - $from_file_start;
- my $to_file_offset= $chunk->[$#$chunk]->[2] - $to_file_start;
- print "\@\@ -$from_file_start,$from_file_offset ",
- "+$to_file_start,$to_file_offset \@\@\n";
-
- foreach my $info ( @$chunk )
- {
- if ( $info->[0] eq '' )
- {
- print " $info->[3]\n";
- }
- elsif ( $info->[0] eq '-' )
- {
- print "- $info->[3]\n";
- }
- elsif ( $info->[0] eq '+' )
- {
- print "+ $info->[3]\n";
- }
- }
- }
-
-# print Dumper(\@chunks);
-
-}
-
-
-##############################################################################
-# Find if the string is found in the array, return the index if found,
-# if not found, return "undef"
-##############################################################################
-
-sub find_next_match {
- my $line= shift;
- my $lines= shift;
-
- for ( my $idx= 0; $idx < @$lines; $idx++ )
- {
- return $idx if $lines->[$idx] eq $line;
- }
-
- return undef; # No match found
-}
-
-
-##############################################################################
-# Just read the lines, but handle "sets" of lines that are unordered
-##############################################################################
-
-sub collect_lines {
-
- my @recordset;
- my @lines;
-
- while (@_)
- {
- my $line= shift @_;
- chomp($line);
-
- if ( $line =~ /^\Q%unordered%\E\t/ )
- {
- push(@recordset, $line);
- }
- elsif ( @recordset )
- {
- push(@lines, sort @recordset);
- @recordset= (); # Clear it
- }
- else
- {
- push(@lines, $line);
- }
- }
-
- if ( @recordset )
- {
- push(@lines, sort @recordset);
- @recordset= (); # Clear it
- }
-
- return \@lines;
-}
-
-1;
diff --git a/mysql-test/lib/mtr_gcov.pl b/mysql-test/lib/mtr_gcov.pl
index a2de1fcbdff..5049fdd6063 100644
--- a/mysql-test/lib/mtr_gcov.pl
+++ b/mysql-test/lib/mtr_gcov.pl
@@ -20,25 +20,14 @@
use strict;
-# These are not to be prefixed with "mtr_"
+sub gcov_prepare ($) {
+ my ($dir)= @_;
-sub gcov_prepare ();
-sub gcov_collect ();
-
-##############################################################################
-#
-#
-#
-##############################################################################
-
-sub gcov_prepare () {
-
- `find $::glob_basedir -name \*.gcov \
+ `find $dir -name \*.gcov \
-or -name \*.da | xargs rm`;
}
-# Used by gcov
-our @mysqld_src_dirs=
+my @mysqld_src_dirs=
(
"strings",
"mysys",
@@ -53,21 +42,24 @@ our @mysqld_src_dirs=
"sql",
);
-sub gcov_collect () {
+sub gcov_collect ($$$) {
+ my ($dir, $gcov, $gcov_msg, $gcov_err)= @_;
+
+ my $start_dir= cwd();
print "Collecting source coverage info...\n";
- -f $::opt_gcov_msg and unlink($::opt_gcov_msg);
- -f $::opt_gcov_err and unlink($::opt_gcov_err);
+ -f $gcov_msg and unlink($gcov_msg);
+ -f $gcov_err and unlink($gcov_err);
foreach my $d ( @mysqld_src_dirs )
{
- chdir("$::glob_basedir/$d");
+ chdir("$dir/$d");
foreach my $f ( (glob("*.h"), glob("*.cc"), glob("*.c")) )
{
- `$::opt_gcov $f 2>>$::opt_gcov_err >>$::opt_gcov_msg`;
+ `$gcov $f 2>>$gcov_err >>$gcov_msg`;
}
- chdir($::glob_mysql_test_dir);
+ chdir($start_dir);
}
- print "gcov info in $::opt_gcov_msg, errors in $::opt_gcov_err\n";
+ print "gcov info in $gcov_msg, errors in $gcov_err\n";
}
diff --git a/mysql-test/lib/mtr_io.pl b/mysql-test/lib/mtr_io.pl
index aa671c0f4f7..21581798ddc 100644
--- a/mysql-test/lib/mtr_io.pl
+++ b/mysql-test/lib/mtr_io.pl
@@ -19,135 +19,15 @@
# same name.
use strict;
+use Carp;
-sub mtr_get_pid_from_file ($);
-sub mtr_get_opts_from_file ($);
sub mtr_fromfile ($);
sub mtr_tofile ($@);
sub mtr_tonewfile($@);
-sub mtr_lastlinefromfile($);
sub mtr_appendfile_to_file ($$);
sub mtr_grab_file($);
-
-
-##############################################################################
-#
-#
-#
-##############################################################################
-
-sub mtr_get_pid_from_file ($) {
- my $pid_file_path= shift;
- my $TOTAL_ATTEMPTS= 30;
- my $timeout= 1;
-
- # We should read from the file until we get correct pid. As it is
- # stated in BUG#21884, pid file can be empty at some moment. So, we should
- # read it until we get valid data.
-
- for (my $cur_attempt= 1; $cur_attempt <= $TOTAL_ATTEMPTS; ++$cur_attempt)
- {
- mtr_debug("Reading pid file '$pid_file_path' " .
- "($cur_attempt of $TOTAL_ATTEMPTS)...");
-
- open(FILE, '<', $pid_file_path)
- or mtr_error("can't open file \"$pid_file_path\": $!");
-
- # Read pid number from file
- my $pid= <FILE>;
- chomp $pid;
- close FILE;
-
- return $pid if $pid=~ /^(\d+)/;
-
- mtr_debug("Pid file '$pid_file_path' does not yet contain pid number.\n" .
- "Sleeping $timeout second(s) more...");
-
- sleep($timeout);
- }
-
- mtr_error("Pid file '$pid_file_path' is corrupted. " .
- "Can not retrieve PID in " .
- ($timeout * $TOTAL_ATTEMPTS) . " seconds.");
-}
-
-sub mtr_get_opts_from_file ($) {
- my $file= shift;
-
- open(FILE,"<",$file) or mtr_error("can't open file \"$file\": $!");
- my @args;
- while ( <FILE> )
- {
- chomp;
-
- # --set-variable=init_connect=set @a='a\\0c'
- s/^\s+//; # Remove leading space
- s/\s+$//; # Remove ending space
-
- # This is strange, but we need to fill whitespace inside
- # quotes with something, to remove later. We do this to
- # be able to split on space. Else, we have trouble with
- # options like
- #
- # --someopt="--insideopt1 --insideopt2"
- #
- # But still with this, we are not 100% sure it is right,
- # we need a shell to do it right.
-
-# print STDERR "\n";
-# print STDERR "AAA: $_\n";
-
- s/\'([^\'\"]*)\'/unspace($1,"\x0a")/ge;
- s/\"([^\'\"]*)\"/unspace($1,"\x0b")/ge;
- s/\'([^\'\"]*)\'/unspace($1,"\x0a")/ge;
- s/\"([^\'\"]*)\"/unspace($1,"\x0b")/ge;
-
-# print STDERR "BBB: $_\n";
-
-# foreach my $arg (/(--?\w.*?)(?=\s+--?\w|$)/)
-
- # FIXME ENV vars should be expanded!!!!
-
- foreach my $arg (split(/[ \t]+/))
- {
- $arg =~ tr/\x11\x0a\x0b/ \'\"/; # Put back real chars
- # The outermost quotes has to go
- $arg =~ s/^([^\'\"]*)\'(.*)\'([^\'\"]*)$/$1$2$3/
- or $arg =~ s/^([^\'\"]*)\"(.*)\"([^\'\"]*)$/$1$2$3/;
- $arg =~ s/\\\\/\\/g;
-
- $arg =~ s/\$\{(\w+)\}/envsubst($1)/ge;
- $arg =~ s/\$(\w+)/envsubst($1)/ge;
-
-# print STDERR "ARG: $arg\n";
- # Do not pass empty string since my_getopt is not capable to handle it.
- if (length($arg))
- {
- push(@args, $arg)
- }
- }
- }
- close FILE;
- return \@args;
-}
-
-sub envsubst {
- my $string= shift;
-
- if ( ! defined $ENV{$string} )
- {
- mtr_error("opt file referense \$$string that is unknown");
- }
-
- return $ENV{$string};
-}
-
-sub unspace {
- my $string= shift;
- my $quote= shift;
- $string =~ s/[ \t]/\x11/g;
- return "$quote$string$quote";
-}
+sub mtr_printfile($);
+sub mtr_lastlinesfromfile ($$);
# Read a whole file, stripping leading and trailing whitespace.
sub mtr_fromfile ($) {
@@ -161,19 +41,6 @@ sub mtr_fromfile ($) {
return $text;
}
-sub mtr_lastlinefromfile ($) {
- my $file= shift;
- my $text;
-
- open(FILE,"<",$file) or mtr_error("can't open file \"$file\": $!");
- while (my $line= <FILE>)
- {
- $text= $line;
- }
- close FILE;
- return $text;
-}
-
sub mtr_tofile ($@) {
my $file= shift;
@@ -183,6 +50,7 @@ sub mtr_tofile ($@) {
close FILE;
}
+
sub mtr_tonewfile ($@) {
my $file= shift;
@@ -191,6 +59,7 @@ sub mtr_tonewfile ($@) {
close FILE;
}
+
sub mtr_appendfile_to_file ($$) {
my $from_file= shift;
my $to_file= shift;
@@ -203,6 +72,7 @@ sub mtr_appendfile_to_file ($$) {
close TOFILE;
}
+
# Read a whole file verbatim.
sub mtr_grab_file($) {
my $file= shift;
@@ -215,4 +85,26 @@ sub mtr_grab_file($) {
}
+# Print the file to STDOUT
+sub mtr_printfile($) {
+ my $file= shift;
+ open(FILE, '<', $file)
+ or warn $!;
+ print while(<FILE>);
+ close FILE;
+ return;
+}
+
+sub mtr_lastlinesfromfile ($$) {
+ croak "usage: mtr_lastlinesfromfile(file,numlines)" unless (@_ == 2);
+ my ($file, $num_lines)= @_;
+ my $text;
+ open(FILE,"<",$file) or mtr_error("can't open file \"$file\": $!");
+ my @lines= reverse <FILE>;
+ close FILE;
+ my $size= scalar(@lines);
+ $num_lines= $size unless ($size >= $num_lines);
+ return join("", reverse(splice(@lines, 0, $num_lines)));
+}
+
1;
diff --git a/mysql-test/lib/mtr_match.pm b/mysql-test/lib/mtr_match.pm
new file mode 100644
index 00000000000..40afd4e0336
--- /dev/null
+++ b/mysql-test/lib/mtr_match.pm
@@ -0,0 +1,97 @@
+# -*- cperl -*-
+# Copyright (C) 2004-2006 MySQL AB
+#
+# 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-1301 USA
+
+# This is a library file used by the Perl version of mysql-test-run,
+# and is part of the translation of the Bourne shell script with the
+# same name.
+
+package mtr_match;
+use strict;
+
+use base qw(Exporter);
+our @EXPORT= qw(mtr_match_prefix
+ mtr_match_extension
+ mtr_match_substring);
+
+#
+# Match a prefix and return what is after the prefix
+#
+sub mtr_match_prefix ($$) {
+ my $string= shift;
+ my $prefix= shift;
+
+ if ( $string =~ /^\Q$prefix\E(.*)$/ ) # strncmp
+ {
+ return $1;
+ }
+ else
+ {
+ return undef; # NULL
+ }
+}
+
+
+#
+# Match extension and return the name without extension
+#
+sub mtr_match_extension ($$) {
+ my $file= shift;
+ my $ext= shift;
+
+ if ( $file =~ /^(.*)\.\Q$ext\E$/ ) # strchr+strcmp or something
+ {
+ return $1;
+ }
+ else
+ {
+ return undef; # NULL
+ }
+}
+
+
+#
+# Match a substring anywere in a string
+#
+sub mtr_match_substring ($$) {
+ my $string= shift;
+ my $substring= shift;
+
+ if ( $string =~ /(.*)\Q$substring\E(.*)$/ ) # strncmp
+ {
+ return $1;
+ }
+ else
+ {
+ return undef; # NULL
+ }
+}
+
+
+sub mtr_match_any_exact ($$) {
+ my $string= shift;
+ my $mlist= shift;
+
+ foreach my $m (@$mlist)
+ {
+ if ( $string eq $m )
+ {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+1;
diff --git a/mysql-test/lib/mtr_misc.pl b/mysql-test/lib/mtr_misc.pl
index 0173e8b8572..658eb270535 100644
--- a/mysql-test/lib/mtr_misc.pl
+++ b/mysql-test/lib/mtr_misc.pl
@@ -19,45 +19,25 @@
# same name.
use strict;
-use File::Find;
-sub mtr_native_path($);
+use My::Platform;
+
sub mtr_init_args ($);
sub mtr_add_arg ($$@);
+sub mtr_args2str($@);
sub mtr_path_exists(@);
sub mtr_script_exists(@);
sub mtr_file_exists(@);
sub mtr_exe_exists(@);
sub mtr_exe_maybe_exists(@);
-sub mtr_copy_dir($$);
-sub mtr_rmtree($);
-sub mtr_same_opts($$);
-sub mtr_cmp_opts($$);
+
##############################################################################
#
-# Misc
+# Args
#
##############################################################################
-# Convert path to OS native format
-sub mtr_native_path($)
-{
- my $path= shift;
-
- # MySQL version before 5.0 still use cygwin, no need
- # to convert path
- return $path
- if ($::mysql_version_id < 50000);
-
- $path=~ s/\//\\/g
- if ($::glob_win32);
- return $path;
-}
-
-
-# FIXME move to own lib
-
sub mtr_init_args ($) {
my $args = shift;
$$args = []; # Empty list
@@ -68,9 +48,18 @@ sub mtr_add_arg ($$@) {
my $format= shift;
my @fargs = @_;
+ # Quote args if args contain space
+ $format= "\"$format\""
+ if (IS_WINDOWS and grep(/\s/, @fargs));
+
push(@$args, sprintf($format, @fargs));
}
+sub mtr_args2str($@) {
+ my $exe= shift or die;
+ return join(" ", native_path($exe), @_);
+}
+
##############################################################################
#
@@ -100,7 +89,7 @@ sub mtr_path_exists (@) {
sub mtr_script_exists (@) {
foreach my $path ( @_ )
{
- if($::glob_win32)
+ if(IS_WINDOWS)
{
return $path if -f $path;
}
@@ -140,11 +129,10 @@ sub mtr_file_exists (@) {
sub mtr_exe_maybe_exists (@) {
my @path= @_;
- map {$_.= ".exe"} @path if $::glob_win32;
- map {$_.= ".nlm"} @path if $::glob_netware;
+ map {$_.= ".exe"} @path if IS_WINDOWS;
foreach my $path ( @path )
{
- if($::glob_win32)
+ if(IS_WINDOWS)
{
return $path if -f $path;
}
@@ -179,134 +167,11 @@ sub mtr_exe_exists (@) {
}
-sub mtr_copy_dir($$) {
- my $from_dir= shift;
- my $to_dir= shift;
-
- # mtr_verbose("Copying from $from_dir to $to_dir");
-
- mkpath("$to_dir");
- opendir(DIR, "$from_dir")
- or mtr_error("Can't find $from_dir$!");
- for(readdir(DIR)) {
- next if "$_" eq "." or "$_" eq "..";
- if ( -d "$from_dir/$_" )
- {
- mtr_copy_dir("$from_dir/$_", "$to_dir/$_");
- next;
- }
- copy("$from_dir/$_", "$to_dir/$_");
- }
- closedir(DIR);
-
-}
-
-
-sub mtr_rmtree($) {
- my ($dir)= @_;
- mtr_verbose("mtr_rmtree: $dir");
-
- # Try to use File::Path::rmtree. Recent versions
- # handles removal of directories and files that don't
- # have full permissions, while older versions
- # may have a problem with that and we use our own version
-
- eval { rmtree($dir); };
- if ( $@ ) {
- mtr_warning("rmtree($dir) failed, trying with File::Find...");
-
- my $errors= 0;
-
- # chmod
- find( {
- no_chdir => 1,
- wanted => sub {
- chmod(0777, $_)
- or mtr_warning("couldn't chmod(0777, $_): $!") and $errors++;
- }
- },
- $dir
- );
-
- # rm
- finddepth( {
- no_chdir => 1,
- wanted => sub {
- my $file= $_;
- # Use special underscore (_) filehandle, caches stat info
- if (!-l $file and -d _ ) {
- rmdir($file) or
- mtr_warning("couldn't rmdir($file): $!") and $errors++;
- } else {
- unlink($file)
- or mtr_warning("couldn't unlink($file): $!") and $errors++;
- }
- }
- },
- $dir
- );
+sub mtr_milli_sleep {
+ die "usage: mtr_milli_sleep(milliseconds)" unless @_ == 1;
+ my ($millis)= @_;
- mtr_error("Failed to remove '$dir'") if $errors;
-
- mtr_report("OK, that worked!");
- }
-}
-
-
-sub mtr_same_opts ($$) {
- my $l1= shift;
- my $l2= shift;
- return mtr_cmp_opts($l1,$l2) == 0;
-}
-
-sub mtr_cmp_opts ($$) {
- my $l1= shift;
- my $l2= shift;
-
- my @l1= @$l1;
- my @l2= @$l2;
-
- return -1 if @l1 < @l2;
- return 1 if @l1 > @l2;
-
- while ( @l1 ) # Same length
- {
- my $e1= shift @l1;
- my $e2= shift @l2;
- my $cmp= ($e1 cmp $e2);
- return $cmp if $cmp != 0;
- }
-
- return 0; # They are the same
-}
-
-#
-# Compare two arrays and put all unequal elements into a new one
-#
-sub mtr_diff_opts ($$) {
- my $l1= shift;
- my $l2= shift;
- my $f;
- my $l= [];
- foreach my $e1 (@$l1)
- {
- $f= undef;
- foreach my $e2 (@$l2)
- {
- $f= 1 unless ($e1 ne $e2);
- }
- push(@$l, $e1) unless (defined $f);
- }
- foreach my $e2 (@$l2)
- {
- $f= undef;
- foreach my $e1 (@$l1)
- {
- $f= 1 unless ($e1 ne $e2);
- }
- push(@$l, $e2) unless (defined $f);
- }
- return $l;
+ select(undef, undef, undef, ($millis/1000));
}
1;
diff --git a/mysql-test/lib/mtr_process.pl b/mysql-test/lib/mtr_process.pl
index 566ccfe0778..a99119a199d 100644
--- a/mysql-test/lib/mtr_process.pl
+++ b/mysql-test/lib/mtr_process.pl
@@ -18,997 +18,13 @@
# and is part of the translation of the Bourne shell script with the
# same name.
+use strict;
use Socket;
use Errno;
-use strict;
-
-use POSIX qw(WNOHANG SIGHUP);
-sub mtr_run ($$$$$$;$);
-sub mtr_spawn ($$$$$$;$);
-sub mtr_check_stop_servers ($);
-sub mtr_kill_leftovers ();
-sub mtr_wait_blocking ($);
-sub mtr_record_dead_children ();
-sub mtr_ndbmgm_start($$);
-sub mtr_mysqladmin_start($$$);
-sub mtr_exit ($);
sub sleep_until_file_created ($$$);
-sub mtr_kill_processes ($);
-sub mtr_ping_with_timeout($);
sub mtr_ping_port ($);
-# Local function
-sub spawn_impl ($$$$$$$);
-
-##############################################################################
-#
-# Execute an external command
-#
-##############################################################################
-
-sub mtr_run ($$$$$$;$) {
- my $path= shift;
- my $arg_list_t= shift;
- my $input= shift;
- my $output= shift;
- my $error= shift;
- my $pid_file= shift; # Not used
- my $spawn_opts= shift;
-
- return spawn_impl($path,$arg_list_t,'run',$input,$output,$error,
- $spawn_opts);
-}
-
-sub mtr_run_test ($$$$$$;$) {
- my $path= shift;
- my $arg_list_t= shift;
- my $input= shift;
- my $output= shift;
- my $error= shift;
- my $pid_file= shift; # Not used
- my $spawn_opts= shift;
-
- return spawn_impl($path,$arg_list_t,'test',$input,$output,$error,
- $spawn_opts);
-}
-
-sub mtr_spawn ($$$$$$;$) {
- my $path= shift;
- my $arg_list_t= shift;
- my $input= shift;
- my $output= shift;
- my $error= shift;
- my $pid_file= shift; # Not used
- my $spawn_opts= shift;
-
- return spawn_impl($path,$arg_list_t,'spawn',$input,$output,$error,
- $spawn_opts);
-}
-
-
-
-sub spawn_impl ($$$$$$$) {
- my $path= shift;
- my $arg_list_t= shift;
- my $mode= shift;
- my $input= shift;
- my $output= shift;
- my $error= shift;
- my $spawn_opts= shift;
-
- if ( $::opt_script_debug )
- {
- mtr_report("");
- mtr_debug("-" x 73);
- mtr_debug("STDIN $input") if $input;
- mtr_debug("STDOUT $output") if $output;
- mtr_debug("STDERR $error") if $error;
- mtr_debug("$mode: $path ", join(" ",@$arg_list_t));
- mtr_debug("spawn options:");
- if ($spawn_opts)
- {
- foreach my $key (sort keys %{$spawn_opts})
- {
- mtr_debug(" - $key: $spawn_opts->{$key}");
- }
- }
- else
- {
- mtr_debug(" none");
- }
- mtr_debug("-" x 73);
- mtr_report("");
- }
-
- mtr_error("Can't spawn with empty \"path\"") unless defined $path;
-
-
- FORK:
- {
- my $pid= fork();
-
- if ( ! defined $pid )
- {
- if ( $! == $!{EAGAIN} ) # See "perldoc Errno"
- {
- mtr_warning("Got EAGAIN from fork(), sleep 1 second and redo");
- sleep(1);
- redo FORK;
- }
-
- mtr_error("$path ($pid) can't be forked, error: $!");
-
- }
-
- if ( $pid )
- {
- select(STDOUT) if $::glob_win32_perl;
- return spawn_parent_impl($pid,$mode,$path);
- }
- else
- {
- # Child, redirect output and exec
-
- $SIG{INT}= 'DEFAULT'; # Parent do some stuff, we don't
-
- my $log_file_open_mode = '>';
-
- if ($spawn_opts and $spawn_opts->{'append_log_file'})
- {
- $log_file_open_mode = '>>';
- }
-
- if ( $output )
- {
- if ( $::glob_win32_perl )
- {
- # Don't redirect stdout on ActiveState perl since this is
- # just another thread in the same process.
- }
- elsif ( ! open(STDOUT,$log_file_open_mode,$output) )
- {
- mtr_child_error("can't redirect STDOUT to \"$output\": $!");
- }
- }
-
- if ( $error )
- {
- if ( !$::glob_win32_perl and $output eq $error )
- {
- if ( ! open(STDERR,">&STDOUT") )
- {
- mtr_child_error("can't dup STDOUT: $!");
- }
- }
- else
- {
- if ( ! open(STDERR,$log_file_open_mode,$error) )
- {
- mtr_child_error("can't redirect STDERR to \"$error\": $!");
- }
- }
- }
-
- if ( $input )
- {
- if ( ! open(STDIN,"<",$input) )
- {
- mtr_child_error("can't redirect STDIN to \"$input\": $!");
- }
- }
-
- if ( ! exec($path,@$arg_list_t) )
- {
- mtr_child_error("failed to execute \"$path\": $!");
- }
- mtr_error("Should never come here 1!");
- }
- mtr_error("Should never come here 2!");
- }
- mtr_error("Should never come here 3!");
-}
-
-
-sub spawn_parent_impl {
- my $pid= shift;
- my $mode= shift;
- my $path= shift;
-
- if ( $mode eq 'run' or $mode eq 'test' )
- {
- if ( $mode eq 'run' )
- {
- # Simple run of command, wait blocking for it to return
- my $ret_pid= waitpid($pid,0);
- if ( $ret_pid != $pid )
- {
- # The "simple" waitpid has failed, print debug info
- # and try to handle the error
- mtr_warning("waitpid($pid, 0) returned $ret_pid " .
- "when waiting for '$path', error: '$!'");
- if ( $ret_pid == -1 )
- {
- # waitpid returned -1, that would indicate the process
- # no longer exist and waitpid couldn't wait for it.
- return 1;
- }
- mtr_error("Error handling failed");
- }
-
- return mtr_process_exit_status($?);
- }
- else
- {
- # We run mysqltest and wait for it to return. But we try to
- # catch dying mysqld processes as well.
- #
- # We do blocking waitpid() until we get the return from the
- # "mysqltest" call. But if a mysqld process dies that we
- # started, we take this as an error, and kill mysqltest.
-
-
- my $exit_value= -1;
- my $saved_exit_value;
- my $ret_pid; # What waitpid() returns
-
- while ( ($ret_pid= waitpid(-1,0)) != -1 )
- {
- # Someone terminated, don't know who. Collect
- # status info first before $? is lost,
- # but not $exit_value, this is flagged from
-
- my $timer_name= mtr_timer_timeout($::glob_timers, $ret_pid);
- if ( $timer_name )
- {
- if ( $timer_name eq "suite" )
- {
- # We give up here
- # FIXME we should only give up the suite, not all of the run?
- print STDERR "\n";
- mtr_error("Test suite timeout");
- }
- elsif ( $timer_name eq "testcase" )
- {
- $saved_exit_value= 63; # Mark as timeout
- kill(9, $pid); # Kill mysqltest
- next; # Go on and catch the termination
- }
- }
-
- if ( $ret_pid == $pid )
- {
- # We got termination of mysqltest, we are done
- $exit_value= mtr_process_exit_status($?);
- last;
- }
-
- # One of the child processes died, unless this was expected
- # mysqltest should be killed and test aborted
-
- check_expected_crash_and_restart($ret_pid);
- }
-
- if ( $ret_pid != $pid )
- {
- # We terminated the waiting because a "mysqld" process died.
- # Kill the mysqltest process.
- mtr_verbose("Kill mysqltest because another process died");
- kill(9,$pid);
-
- $ret_pid= waitpid($pid,0);
-
- if ( $ret_pid != $pid )
- {
- mtr_error("$path ($pid) got lost somehow");
- }
- }
-
- return $saved_exit_value || $exit_value;
- }
- }
- else
- {
- # We spawned a process we don't wait for
- return $pid;
- }
-}
-
-
-# ----------------------------------------------------------------------
-# We try to emulate how an Unix shell calculates the exit code
-# ----------------------------------------------------------------------
-
-sub mtr_process_exit_status {
- my $raw_status= shift;
-
- if ( $raw_status & 127 )
- {
- return ($raw_status & 127) + 128; # Signal num + 128
- }
- else
- {
- return $raw_status >> 8; # Exit code
- }
-}
-
-
-##############################################################################
-#
-# Kill processes left from previous runs
-#
-##############################################################################
-
-
-# Kill all processes(mysqld, ndbd, ndb_mgmd and im) that would conflict with
-# this run
-# Make sure to remove the PID file, if any.
-# kill IM manager first, else it will restart the servers
-sub mtr_kill_leftovers () {
-
- mtr_report("Killing Possible Leftover Processes");
- mtr_debug("mtr_kill_leftovers(): started.");
-
- my @kill_pids;
- my %admin_pids;
-
- foreach my $srv (@{$::master}, @{$::slave})
- {
- mtr_debug(" - mysqld " .
- "(pid: $srv->{pid}; " .
- "pid file: '$srv->{path_pid}'; " .
- "socket: '$srv->{path_sock}'; ".
- "port: $srv->{port})");
-
- my $pid= mtr_mysqladmin_start($srv, "shutdown", 20);
-
- # Save the pid of the mysqladmin process
- $admin_pids{$pid}= 1;
-
- push(@kill_pids,{
- pid => $srv->{'pid'},
- pidfile => $srv->{'path_pid'},
- sockfile => $srv->{'path_sock'},
- port => $srv->{'port'},
- });
- $srv->{'pid'}= 0; # Assume we are done with it
- }
-
- if ( ! $::opt_skip_ndbcluster )
- {
-
- foreach my $cluster (@{$::clusters})
- {
-
- # Don't shut down a "running" cluster
- next if $cluster->{'use_running'};
-
- mtr_debug(" - cluster " .
- "(pid: $cluster->{pid}; " .
- "pid file: '$cluster->{path_pid})");
-
- my $pid= mtr_ndbmgm_start($cluster, "shutdown");
-
- # Save the pid of the ndb_mgm process
- $admin_pids{$pid}= 1;
-
- push(@kill_pids,{
- pid => $cluster->{'pid'},
- pidfile => $cluster->{'path_pid'}
- });
-
- $cluster->{'pid'}= 0; # Assume we are done with it
-
- foreach my $ndbd (@{$cluster->{'ndbds'}})
- {
- mtr_debug(" - ndbd " .
- "(pid: $ndbd->{pid}; " .
- "pid file: '$ndbd->{path_pid})");
-
- push(@kill_pids,{
- pid => $ndbd->{'pid'},
- pidfile => $ndbd->{'path_pid'},
- });
- $ndbd->{'pid'}= 0; # Assume we are done with it
- }
- }
- }
-
- # Wait for all the admin processes to complete
- mtr_wait_blocking(\%admin_pids);
-
- # If we trusted "mysqladmin --shutdown_timeout= ..." we could just
- # terminate now, but we don't (FIXME should be debugged).
- # So we try again to ping and at least wait the same amount of time
- # mysqladmin would for all to die.
-
- mtr_ping_with_timeout(\@kill_pids);
-
- # We now have tried to terminate nice. We have waited for the listen
- # port to be free, but can't really tell if the mysqld process died
- # or not. We now try to find the process PID from the PID file, and
- # send a kill to that process. Note that Perl let kill(0,@pids) be
- # a way to just return the numer of processes the kernel can send
- # signals to. So this can be used (except on Cygwin) to determine
- # if there are processes left running that we cound out might exists.
- #
- # But still after all this work, all we know is that we have
- # the ports free.
-
- # We scan the "var/run/" directory for other process id's to kill
-
- my $rundir= "$::opt_vardir/run";
-
- mtr_debug("Processing PID files in directory '$rundir'...");
-
- if ( -d $rundir )
- {
- opendir(RUNDIR, $rundir)
- or mtr_error("can't open directory \"$rundir\": $!");
-
- my @pids;
-
- while ( my $elem= readdir(RUNDIR) )
- {
- # Only read pid from files that end with .pid
- if ( $elem =~ /.*[.]pid$/)
- {
- my $pidfile= "$rundir/$elem";
-
- if ( -f $pidfile )
- {
- mtr_debug("Processing PID file: '$pidfile'...");
-
- my $pid= mtr_get_pid_from_file($pidfile);
-
- mtr_debug("Got pid: $pid from file '$pidfile'");
-
- if ( $::glob_cygwin_perl or kill(0, $pid) )
- {
- mtr_debug("There is process with pid $pid -- scheduling for kill.");
- push(@pids, $pid); # We know (cygwin guess) it exists
- }
- else
- {
- mtr_debug("There is no process with pid $pid -- skipping.");
- }
- }
- }
- }
- closedir(RUNDIR);
-
- if ( @pids )
- {
- mtr_debug("Killing the following processes with PID files: " .
- join(' ', @pids) . "...");
-
- start_reap_all();
-
- if ( $::glob_cygwin_perl )
- {
- # We have no (easy) way of knowing the Cygwin controlling
- # process, in the PID file we only have the Windows process id.
- system("kill -f " . join(" ",@pids)); # Hope for the best....
- mtr_debug("Sleep 5 seconds waiting for processes to die");
- sleep(5);
- }
- else
- {
- my $retries= 10; # 10 seconds
- do
- {
- mtr_debug("Sending SIGKILL to pids: " . join(' ', @pids));
- kill(9, @pids);
- mtr_report("Sleep 1 second waiting for processes to die");
- sleep(1) # Wait one second
- } while ( $retries-- and kill(0, @pids) );
-
- if ( kill(0, @pids) ) # Check if some left
- {
- mtr_warning("can't kill process(es) " . join(" ", @pids));
- }
- }
-
- stop_reap_all();
- }
- }
- else
- {
- mtr_debug("Directory for PID files ($rundir) does not exist.");
- }
-
- # We may have failed everything, but we now check again if we have
- # the listen ports free to use, and if they are free, just go for it.
-
- mtr_debug("Checking known mysqld servers...");
-
- foreach my $srv ( @kill_pids )
- {
- if ( defined $srv->{'port'} and mtr_ping_port($srv->{'port'}) )
- {
- mtr_warning("can't kill old process holding port $srv->{'port'}");
- }
- }
-
- mtr_debug("mtr_kill_leftovers(): finished.");
-}
-
-
-#
-# Check that all processes in "spec" are shutdown gracefully
-# else kill them off hard
-#
-sub mtr_check_stop_servers ($) {
- my $spec= shift;
-
- # Return if no processes are defined
- return if ! @$spec;
-
- mtr_verbose("mtr_check_stop_servers");
-
- # ----------------------------------------------------------------------
- # Wait until servers in "spec" has stopped listening
- # to their ports or timeout occurs
- # ----------------------------------------------------------------------
- mtr_ping_with_timeout(\@$spec);
-
- # ----------------------------------------------------------------------
- # Use waitpid() nonblocking for a little while, to see how
- # many process's will exit sucessfully.
- # This is the normal case.
- # ----------------------------------------------------------------------
- my $wait_counter= 50; # Max number of times to redo the loop
- foreach my $srv ( @$spec )
- {
- my $pid= $srv->{'pid'};
- my $ret_pid;
- if ( $pid )
- {
- $ret_pid= waitpid($pid,&WNOHANG);
- if ($ret_pid == $pid)
- {
- mtr_verbose("Caught exit of process $ret_pid");
- $srv->{'pid'}= 0;
- }
- elsif ($ret_pid == 0)
- {
- mtr_verbose("Process $pid is still alive");
- if ($wait_counter-- > 0)
- {
- # Give the processes more time to exit
- select(undef, undef, undef, (0.1));
- redo;
- }
- }
- else
- {
- mtr_warning("caught exit of unknown child $ret_pid");
- }
- }
- }
-
- # ----------------------------------------------------------------------
- # The processes that haven't yet exited need to
- # be killed hard, put them in "kill_pids" hash
- # ----------------------------------------------------------------------
- my %kill_pids;
- foreach my $srv ( @$spec )
- {
- my $pid= $srv->{'pid'};
- if ( $pid )
- {
- # Server is still alive, put it in list to be hard killed
- if ($::glob_win32_perl)
- {
- # Kill the real process if it's known
- $pid= $srv->{'real_pid'} if ($srv->{'real_pid'});
- }
- $kill_pids{$pid}= 1;
-
- # Write a message to the process's error log (if it has one)
- # that it's being killed hard.
- if ( defined $srv->{'errfile'} )
- {
- mtr_tofile($srv->{'errfile'}, "Note: Forcing kill of process $pid\n");
- }
- mtr_warning("Forcing kill of process $pid");
-
- }
- else
- {
- # Server is dead, remove the pidfile if it exists
- #
- # Race, could have been removed between test with -f
- # and the unlink() below, so better check again with -f
- if ( -f $srv->{'pidfile'} and ! unlink($srv->{'pidfile'}) and
- -f $srv->{'pidfile'} )
- {
- mtr_error("can't remove $srv->{'pidfile'}");
- }
- }
- }
-
- if ( ! keys %kill_pids )
- {
- # All processes has exited gracefully
- return;
- }
-
- mtr_kill_processes(\%kill_pids);
-
- # ----------------------------------------------------------------------
- # All processes are killed, cleanup leftover files
- # ----------------------------------------------------------------------
- {
- my $errors= 0;
- foreach my $srv ( @$spec )
- {
- if ( $srv->{'pid'} )
- {
- # Server has been hard killed, clean it's resources
- foreach my $file ($srv->{'pidfile'}, $srv->{'sockfile'})
- {
- # Know it is dead so should be no race, careful anyway
- if ( defined $file and -f $file and ! unlink($file) and -f $file )
- {
- $errors++;
- mtr_warning("couldn't delete $file");
- }
- }
-
- if ($::glob_win32_perl and $srv->{'real_pid'})
- {
- # Wait for the pseudo pid - if the real_pid was known
- # the pseudo pid has not been waited for yet, wai blocking
- # since it's "such a simple program"
- mtr_verbose("Wait for pseudo process $srv->{'pid'}");
- my $ret_pid= waitpid($srv->{'pid'}, 0);
- mtr_verbose("Pseudo process $ret_pid died");
- }
-
- $srv->{'pid'}= 0;
- }
- }
- if ( $errors )
- {
- # There where errors killing processes
- # do one last attempt to ping the servers
- # and if they can't be pinged, assume they are dead
- if ( ! mtr_ping_with_timeout( \@$spec ) )
- {
- mtr_error("we could not kill or clean up all processes");
- }
- else
- {
- mtr_verbose("All ports were free, continuing");
- }
- }
- }
-}
-
-
-# Wait for all the process in the list to terminate
-sub mtr_wait_blocking($) {
- my $admin_pids= shift;
-
-
- # Return if no processes defined
- return if ! %$admin_pids;
-
- mtr_verbose("mtr_wait_blocking");
-
- # Wait for all the started processes to exit
- # As mysqladmin is such a simple program, we trust it to terminate itself.
- # I.e. we wait blocking, and wait for them all before we go on.
- foreach my $pid (keys %{$admin_pids})
- {
- my $ret_pid= waitpid($pid,0);
-
- }
-}
-
-# Start "mysqladmin <command>" for a specific mysqld
-sub mtr_mysqladmin_start($$$) {
- my $srv= shift;
- my $command= shift;
- my $adm_shutdown_tmo= shift;
-
- my $args;
- mtr_init_args(\$args);
-
- mtr_add_arg($args, "--no-defaults");
- mtr_add_arg($args, "--user=%s", $::opt_user);
- mtr_add_arg($args, "--password=");
- mtr_add_arg($args, "--silent");
- if ( -e $srv->{'path_sock'} )
- {
- mtr_add_arg($args, "--socket=%s", $srv->{'path_sock'});
- }
- if ( $srv->{'port'} )
- {
- mtr_add_arg($args, "--port=%s", $srv->{'port'});
- }
- if ( $srv->{'port'} and ! -e $srv->{'path_sock'} )
- {
- mtr_add_arg($args, "--protocol=tcp"); # Needed if no --socket
- }
- mtr_add_arg($args, "--connect_timeout=5");
-
- # Shutdown time must be high as slave may be in reconnect
- mtr_add_arg($args, "--shutdown_timeout=$adm_shutdown_tmo");
- mtr_add_arg($args, "$command");
- my $pid= mtr_spawn($::exe_mysqladmin, $args,
- "", "", "", "",
- { append_log_file => 1 });
- mtr_verbose("mtr_mysqladmin_start, pid: $pid");
- return $pid;
-
-}
-
-# Start "ndb_mgm shutdown" for a specific cluster, it will
-# shutdown all data nodes and leave the ndb_mgmd running
-sub mtr_ndbmgm_start($$) {
- my $cluster= shift;
- my $command= shift;
-
- my $args;
-
- mtr_init_args(\$args);
-
- mtr_add_arg($args, "--no-defaults");
- mtr_add_arg($args, "--core");
- mtr_add_arg($args, "--try-reconnect=1");
- mtr_add_arg($args, "--ndb_connectstring=%s", $cluster->{'connect_string'});
- mtr_add_arg($args, "-e");
- mtr_add_arg($args, "$command");
-
- my $pid= mtr_spawn($::exe_ndb_mgm, $args,
- "", "/dev/null", "/dev/null", "",
- {});
- mtr_verbose("mtr_ndbmgm_start, pid: $pid");
- return $pid;
-
-}
-
-
-# Ping all servers in list, exit when none of them answers
-# or when timeout has passed
-sub mtr_ping_with_timeout($) {
- my $spec= shift;
- my $timeout= 200; # 20 seconds max
- my $res= 1; # If we just fall through, we are done
- # in the sense that the servers don't
- # listen to their ports any longer
-
- mtr_debug("Waiting for mysqld servers to stop...");
-
- TIME:
- while ( $timeout-- )
- {
- foreach my $srv ( @$spec )
- {
- $res= 1; # We are optimistic
- if ( $srv->{'pid'} and defined $srv->{'port'} )
- {
- if ( mtr_ping_port($srv->{'port'}) )
- {
- mtr_verbose("waiting for process $srv->{'pid'} to stop ".
- "using port $srv->{'port'}");
-
- # Millisceond sleep emulated with select
- select(undef, undef, undef, (0.1));
- $res= 0;
- next TIME;
- }
- else
- {
- # Process was not using port
- }
- }
- }
- last; # If we got here, we are done
- }
-
- if ($res)
- {
- mtr_debug("mtr_ping_with_timeout(): All mysqld instances are down.");
- }
- else
- {
- mtr_report("mtr_ping_with_timeout(): At least one server is alive.");
- }
-
- return $res;
-}
-
-
-#
-# Loop through our list of processes and look for and entry
-# with the provided pid
-# Set the pid of that process to 0 if found
-#
-sub mark_process_dead($)
-{
- my $ret_pid= shift;
-
- foreach my $mysqld (@{$::master}, @{$::slave})
- {
- if ( $mysqld->{'pid'} eq $ret_pid )
- {
- mtr_verbose("$mysqld->{'type'} $mysqld->{'idx'} exited, pid: $ret_pid");
- $mysqld->{'pid'}= 0;
- return;
- }
- }
-
- foreach my $cluster (@{$::clusters})
- {
- if ( $cluster->{'pid'} eq $ret_pid )
- {
- mtr_verbose("$cluster->{'name'} cluster ndb_mgmd exited, pid: $ret_pid");
- $cluster->{'pid'}= 0;
- return;
- }
-
- foreach my $ndbd (@{$cluster->{'ndbds'}})
- {
- if ( $ndbd->{'pid'} eq $ret_pid )
- {
- mtr_verbose("$cluster->{'name'} cluster ndbd exited, pid: $ret_pid");
- $ndbd->{'pid'}= 0;
- return;
- }
- }
- }
- mtr_warning("mark_process_dead couldn't find an entry for pid: $ret_pid");
-
-}
-
-#
-# Loop through our list of processes and look for and entry
-# with the provided pid, if found check for the file indicating
-# expected crash and restart it.
-#
-sub check_expected_crash_and_restart($)
-{
- my $ret_pid= shift;
-
- foreach my $mysqld (@{$::master}, @{$::slave})
- {
- if ( $mysqld->{'pid'} eq $ret_pid )
- {
- mtr_verbose("$mysqld->{'type'} $mysqld->{'idx'} exited, pid: $ret_pid");
- $mysqld->{'pid'}= 0;
-
- # Check if crash expected, and restart if it was
- my $expect_file= "$::opt_vardir/tmp/" . "$mysqld->{'type'}" .
- "$mysqld->{'idx'}" . ".expect";
- while ( 1 )
- {
- if ( -f $expect_file )
- {
- mtr_verbose("Crash was expected, file $expect_file exists");
- my $expect_file_handler;
- open($expect_file_handler, "<$expect_file") or die;
- my @expect_lines= <$expect_file_handler>;
- close $expect_file_handler;
- # look at most recent order by the test
- my $expect_content= pop @expect_lines;
- chomp $expect_content;
- if ( $expect_content =~ /^wait/ )
- {
- mtr_verbose("Test asks that we wait before restart");
- # Millisceond sleep emulated with select
- select(undef, undef, undef, (0.1));
- next;
- }
- unlink($expect_file);
- mysqld_start($mysqld, $mysqld->{'start_opts'},
- $mysqld->{'start_slave_master_info'});
- }
- last;
- }
-
- return;
- }
- }
-
- foreach my $cluster (@{$::clusters})
- {
- if ( $cluster->{'pid'} eq $ret_pid )
- {
- mtr_verbose("$cluster->{'name'} cluster ndb_mgmd exited, pid: $ret_pid");
- $cluster->{'pid'}= 0;
-
- # Check if crash expected and restart if it was
- my $expect_file= "$::opt_vardir/tmp/ndb_mgmd_" . "$cluster->{'type'}" .
- ".expect";
- if ( -f $expect_file )
- {
- mtr_verbose("Crash was expected, file $expect_file exists");
- unlink($expect_file);
- ndbmgmd_start($cluster);
- }
- return;
- }
-
- foreach my $ndbd (@{$cluster->{'ndbds'}})
- {
- if ( $ndbd->{'pid'} eq $ret_pid )
- {
- mtr_verbose("$cluster->{'name'} cluster ndbd exited, pid: $ret_pid");
- $ndbd->{'pid'}= 0;
-
- # Check if crash expected and restart if it was
- my $expect_file= "$::opt_vardir/tmp/ndbd_" . "$cluster->{'type'}" .
- "$ndbd->{'idx'}" . ".expect";
- if ( -f $expect_file )
- {
- mtr_verbose("Crash was expected, file $expect_file exists");
- unlink($expect_file);
- ndbd_start($cluster, $ndbd->{'idx'},
- $ndbd->{'start_extra_args'});
- }
- return;
- }
- }
- }
-
- if ($::instance_manager->{'spawner_pid'} eq $ret_pid)
- {
- return;
- }
-
- mtr_warning("check_expected_crash_and_restart couldn't find an entry for pid: $ret_pid");
-
-}
-
-##############################################################################
-#
-# The operating system will keep information about dead children,
-# we read this information here, and if we have records the process
-# is alive, we mark it as dead.
-#
-##############################################################################
-
-sub mtr_record_dead_children () {
-
- my $process_died= 0;
- my $ret_pid;
-
- # Wait without blockinng to see if any processes had died
- # -1 or 0 means there are no more procesess to wait for
- while ( ($ret_pid= waitpid(-1,&WNOHANG)) != 0 and $ret_pid != -1)
- {
- mtr_warning("mtr_record_dead_children: $ret_pid");
- mark_process_dead($ret_pid);
- $process_died= 1;
- }
- return $process_died;
-}
-
-sub start_reap_all {
- # This causes terminating processes to not become zombies, avoiding
- # the need for (or possibility of) explicit waitpid().
- $SIG{CHLD}= 'IGNORE';
-
- # On some platforms (Linux, QNX, OSX, ...) there is potential race
- # here. If a process terminated before setting $SIG{CHLD} (but after
- # any attempt to waitpid() it), it will still be a zombie. So we
- # have to handle any such process here.
- my $pid;
- while(($pid= waitpid(-1, &WNOHANG)) != 0 and $pid != -1)
- {
- mtr_warning("start_reap_all pid: $pid");
- mark_process_dead($pid);
- };
-}
-
-sub stop_reap_all {
- $SIG{CHLD}= 'DEFAULT';
-}
-
-
sub mtr_ping_port ($) {
my $port= shift;
@@ -1053,7 +69,7 @@ sub mtr_ping_port ($) {
sub sleep_until_file_created ($$$) {
my $pidfile= shift;
my $timeout= shift;
- my $pid= shift;
+ my $proc= shift;
my $sleeptime= 100; # Milliseconds
my $loops= ($timeout * 1000) / $sleeptime;
@@ -1065,9 +81,9 @@ sub sleep_until_file_created ($$$) {
}
# Check if it died after the fork() was successful
- if ( $pid != 0 && waitpid($pid,&WNOHANG) == $pid )
+ if ( defined $proc and ! $proc->wait_one(0) )
{
- mtr_warning("Process $pid died");
+ mtr_warning("Process $proc died");
return 0;
}
@@ -1082,73 +98,12 @@ sub sleep_until_file_created ($$$) {
"still waiting for $left seconds...");
}
- # Millisceond sleep emulated with select
- select(undef, undef, undef, ($sleeptime/1000));
- }
-
- return 0;
-}
-
+ mtr_milli_sleep($sleeptime);
-sub mtr_kill_processes ($) {
- my $pids = shift;
-
- mtr_verbose("mtr_kill_processes (" . join(" ", keys %{$pids}) . ")");
-
- foreach my $pid (keys %{$pids})
- {
-
- if ($pid <= 0)
- {
- mtr_warning("Trying to kill illegal pid: $pid");
- next;
- }
-
- my $signaled_procs= kill(9, $pid);
- if ($signaled_procs == 0)
- {
- # No such process existed, assume it's killed
- mtr_verbose("killed $pid(no such process)");
- }
- else
- {
- my $ret_pid= waitpid($pid,0);
- if ($ret_pid == $pid)
- {
- mtr_verbose("killed $pid(got the pid)");
- }
- elsif ($ret_pid == -1)
- {
- mtr_verbose("killed $pid(got -1)");
- }
- }
}
- mtr_verbose("done killing processes");
-}
-
-
-##############################################################################
-#
-# When we exit, we kill off all children
-#
-##############################################################################
-sub mtr_exit ($) {
- my $code= shift;
- mtr_timer_stop_all($::glob_timers);
- local $SIG{HUP} = 'IGNORE';
- # ToDo: Signalling -$$ will only work if we are the process group
- # leader (in fact on QNX it will signal our session group leader,
- # which might be Do-compile or Pushbuild, causing tests to be
- # aborted). So we only do it if we are the group leader. We might
- # set ourselves as the group leader at startup (with
- # POSIX::setpgrp(0,0)), but then care must be needed to always do
- # proper child process cleanup.
- POSIX::kill(SIGHUP, -$$) if !$::glob_win32_perl and $$ == getpgrp();
-
- exit($code);
+ return 0;
}
-###########################################################################
1;
diff --git a/mysql-test/lib/mtr_report.pm b/mysql-test/lib/mtr_report.pm
new file mode 100644
index 00000000000..f385379cfff
--- /dev/null
+++ b/mysql-test/lib/mtr_report.pm
@@ -0,0 +1,464 @@
+# -*- cperl -*-
+# Copyright 2004-2008 MySQL AB, 2008 Sun Microsystems, Inc.
+#
+# 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-1301 USA
+
+# This is a library file used by the Perl version of mysql-test-run,
+# and is part of the translation of the Bourne shell script with the
+# same name.
+
+package mtr_report;
+use strict;
+
+use base qw(Exporter);
+our @EXPORT= qw(report_option mtr_print_line mtr_print_thick_line
+ mtr_print_header mtr_report mtr_report_stats
+ mtr_warning mtr_error mtr_debug mtr_verbose
+ mtr_verbose_restart mtr_report_test_passed
+ mtr_report_test_skipped mtr_print
+ mtr_report_test);
+
+use mtr_match;
+require "mtr_io.pl";
+
+my $tot_real_time= 0;
+
+our $timestamp= 0;
+our $timediff= 0;
+our $name;
+our $verbose;
+our $verbose_restart= 0;
+our $timer= 1;
+
+sub report_option {
+ my ($opt, $value)= @_;
+
+ # Evaluate $opt as string to use "Getopt::Long::Callback legacy API"
+ my $opt_name = "$opt";
+
+ # Convert - to _ in option name
+ $opt_name =~ s/-/_/g;
+ no strict 'refs';
+ ${$opt_name}= $value;
+}
+
+sub _name {
+ return $name ? $name." " : undef;
+}
+
+sub _mtr_report_test_name ($) {
+ my $tinfo= shift;
+ my $tname= $tinfo->{name};
+
+ return unless defined $verbose;
+
+ # Add combination name if any
+ $tname.= " '$tinfo->{combination}'"
+ if defined $tinfo->{combination};
+
+ print _name(), _timestamp();
+ printf "%-40s ", $tname;
+}
+
+
+sub mtr_report_test_skipped ($) {
+ my ($tinfo)= @_;
+ $tinfo->{'result'}= 'MTR_RES_SKIPPED';
+
+ mtr_report_test($tinfo);
+}
+
+
+sub mtr_report_test_passed ($) {
+ my ($tinfo)= @_;
+
+ # Save the timer value
+ my $timer_str= "";
+ if ( $timer and -f "$::opt_vardir/log/timer" )
+ {
+ $timer_str= mtr_fromfile("$::opt_vardir/log/timer");
+ $tinfo->{timer}= $timer_str;
+ }
+
+ # Big warning if status already set
+ if ( $tinfo->{'result'} ){
+ mtr_warning("mtr_report_test_passed: Test result",
+ "already set to '", $tinfo->{'result'}, ",");
+ }
+
+ $tinfo->{'result'}= 'MTR_RES_PASSED';
+
+ mtr_report_test($tinfo);
+}
+
+
+sub mtr_report_test ($) {
+ my ($tinfo)= @_;
+ _mtr_report_test_name($tinfo);
+
+ my $comment= $tinfo->{'comment'};
+ my $logfile= $tinfo->{'logfile'};
+ my $warnings= $tinfo->{'warnings'};
+ my $result= $tinfo->{'result'};
+
+ if ($result eq 'MTR_RES_FAILED'){
+
+ my $timest = format_time();
+
+ if ( $warnings )
+ {
+ mtr_report("[ fail ] Found warnings/errors in server log file!");
+ mtr_report(" Test ended at $timest");
+ mtr_report($warnings);
+ return;
+ }
+ my $timeout= $tinfo->{'timeout'};
+ if ( $timeout )
+ {
+ mtr_report("[ fail ] timeout after $timeout seconds");
+ mtr_report(" Test ended at $timest");
+ mtr_report("\n$tinfo->{'comment'}");
+ return;
+ }
+ else
+ {
+ mtr_report("[ fail ]\n Test ended at $timest");
+ }
+
+ if ( $logfile )
+ {
+ # Test failure was detected by test tool and its report
+ # about what failed has been saved to file. Display the report.
+ mtr_report("\n$logfile\n");
+ }
+ if ( $comment )
+ {
+ # The test failure has been detected by mysql-test-run.pl
+ # when starting the servers or due to other error, the reason for
+ # failing the test is saved in "comment"
+ mtr_report("\n$comment\n");
+ }
+
+ if ( !$logfile and !$comment )
+ {
+ # Neither this script or the test tool has recorded info
+ # about why the test has failed. Should be debugged.
+ mtr_report("\nUnknown result, neither 'comment' or 'logfile' set");
+ }
+ }
+ elsif ($result eq 'MTR_RES_SKIPPED')
+ {
+ if ( $tinfo->{'disable'} )
+ {
+ mtr_report("[ disabled ] $comment");
+ }
+ elsif ( $comment )
+ {
+ mtr_report("[ skipped ] $comment");
+ }
+ else
+ {
+ mtr_report("[ skipped ]");
+ }
+ }
+ elsif ($result eq 'MTR_RES_PASSED')
+ {
+ my $timer_str= $tinfo->{timer} || "";
+ $tot_real_time += ($timer_str/1000);
+ mtr_report("[ pass ] ", sprintf("%5s", $timer_str));
+
+ # Show any problems check-testcase found
+ if ( defined $tinfo->{'check'} )
+ {
+ mtr_report($tinfo->{'check'});
+ }
+ }
+}
+
+
+sub mtr_report_stats ($) {
+ my $tests= shift;
+
+ # ----------------------------------------------------------------------
+ # Find out how we where doing
+ # ----------------------------------------------------------------------
+
+ my $tot_skiped= 0;
+ my $tot_passed= 0;
+ my $tot_failed= 0;
+ my $tot_tests= 0;
+ my $tot_restarts= 0;
+ my $found_problems= 0;
+
+ foreach my $tinfo (@$tests)
+ {
+ if ( $tinfo->{failures} )
+ {
+ # Test has failed at least one time
+ $tot_tests++;
+ $tot_failed++;
+ }
+ elsif ( $tinfo->{'result'} eq 'MTR_RES_SKIPPED' )
+ {
+ # Test was skipped
+ $tot_skiped++;
+ }
+ elsif ( $tinfo->{'result'} eq 'MTR_RES_PASSED' )
+ {
+ # Test passed
+ $tot_tests++;
+ $tot_passed++;
+ }
+
+ if ( $tinfo->{'restarted'} )
+ {
+ # Servers was restarted
+ $tot_restarts++;
+ }
+
+ # Look for warnings produced by mysqltest
+ my $base_file= mtr_match_extension($tinfo->{'result_file'},
+ "result"); # Trim extension
+ my $warning_file= "$base_file.warnings";
+ if ( -f $warning_file )
+ {
+ $found_problems= 1;
+ mtr_warning("Check myqltest warnings in '$warning_file'");
+ }
+ }
+
+ # ----------------------------------------------------------------------
+ # Print out a summary report to screen
+ # ----------------------------------------------------------------------
+ print "The servers were restarted $tot_restarts times\n";
+
+ if ( $timer )
+ {
+ use English;
+
+ mtr_report("Spent", sprintf("%.3f", $tot_real_time),"of",
+ time - $BASETIME, "seconds executing testcases");
+ }
+
+
+ my $warnlog= "$::opt_vardir/log/warnings";
+ if ( -f $warnlog )
+ {
+ mtr_warning("Got errors/warnings while running tests, please examine",
+ "'$warnlog' for details.");
+ }
+
+ print "\n";
+ # Print a list of check_testcases that failed(if any)
+ if ( $::opt_check_testcases )
+ {
+ my %check_testcases;
+
+ foreach my $tinfo (@$tests)
+ {
+ if ( defined $tinfo->{'check_testcase_failed'} )
+ {
+ $check_testcases{$tinfo->{'name'}}= 1;
+ }
+ }
+
+ if ( keys %check_testcases )
+ {
+ print "Check of testcase failed for: ";
+ print join(" ", keys %check_testcases);
+ print "\n\n";
+ }
+ }
+
+ # Print a list of testcases that failed
+ if ( $tot_failed != 0 )
+ {
+
+ # Print each failed test, again
+ #foreach my $test ( @$tests ){
+ # if ( $test->{failures} ) {
+ # mtr_report_test($test);
+ # }
+ #}
+
+ my $ratio= $tot_passed * 100 / $tot_tests;
+ print "Failed $tot_failed/$tot_tests tests, ";
+ printf("%.2f", $ratio);
+ print "\% were successful.\n\n";
+
+ # Print the list of test that failed in a format
+ # that can be copy pasted to rerun only failing tests
+ print "Failing test(s):";
+
+ my %seen= ();
+ foreach my $tinfo (@$tests)
+ {
+ my $tname= $tinfo->{'name'};
+ if ( $tinfo->{failures} and ! $seen{$tname})
+ {
+ print " $tname";
+ $seen{$tname}= 1;
+ }
+ }
+ print "\n\n";
+
+ # Print info about reporting the error
+ print
+ "The log files in var/log may give you some hint of what went wrong.\n\n",
+ "If you want to report this error, please read first ",
+ "the documentation\n",
+ "at http://dev.mysql.com/doc/mysql/en/mysql-test-suite.html\n\n";
+
+ }
+ else
+ {
+ print "All $tot_tests tests were successful.\n\n";
+ }
+
+ if ( $tot_failed != 0 || $found_problems)
+ {
+ mtr_error("there were failing test cases");
+ }
+}
+
+
+##############################################################################
+#
+# Text formatting
+#
+##############################################################################
+
+sub mtr_print_line () {
+ print '-' x 60, "\n";
+}
+
+
+sub mtr_print_thick_line {
+ my $char= shift || '=';
+ print $char x 78, "\n";
+}
+
+
+sub mtr_print_header () {
+ print "\n";
+ printf "TEST";
+ print " " x 38;
+ print "RESULT ";
+ print "TIME (ms)" if $timer;
+ print "\n";
+ mtr_print_line();
+ print "\n";
+}
+
+
+##############################################################################
+#
+# Log and reporting functions
+#
+##############################################################################
+
+use Time::localtime;
+
+use Time::HiRes qw(gettimeofday);
+
+sub format_time {
+ my $tm= localtime();
+ return sprintf("%4d-%02d-%02d %02d:%02d:%02d",
+ $tm->year + 1900, $tm->mon+1, $tm->mday,
+ $tm->hour, $tm->min, $tm->sec);
+}
+
+my $t0= gettimeofday();
+
+sub _timestamp {
+ return "" unless $timestamp;
+
+ my $diff;
+ if ($timediff){
+ my $t1= gettimeofday();
+ my $elapsed= $t1 - $t0;
+
+ $diff= sprintf(" +%02.3f", $elapsed);
+
+ # Save current time for next lap
+ $t0= $t1;
+
+ }
+
+ my $tm= localtime();
+ return sprintf("%02d%02d%02d %2d:%02d:%02d%s ",
+ $tm->year % 100, $tm->mon+1, $tm->mday,
+ $tm->hour, $tm->min, $tm->sec, $diff);
+}
+
+# Always print message to screen
+sub mtr_print (@) {
+ print _name(), join(" ", @_), "\n";
+}
+
+
+# Print message to screen if verbose is defined
+sub mtr_report (@) {
+ if (defined $verbose)
+ {
+ print _name(), join(" ", @_), "\n";
+ }
+}
+
+
+# Print warning to screen
+sub mtr_warning (@) {
+ print STDERR _name(), _timestamp(),
+ "mysql-test-run: WARNING: ", join(" ", @_), "\n";
+}
+
+
+# Print error to screen and then exit
+sub mtr_error (@) {
+ print STDERR _name(), _timestamp(),
+ "mysql-test-run: *** ERROR: ", join(" ", @_), "\n";
+ exit(1);
+}
+
+
+sub mtr_debug (@) {
+ if ( $verbose > 2 )
+ {
+ print STDERR _name(),
+ _timestamp(), "####: ", join(" ", @_), "\n";
+ }
+}
+
+
+sub mtr_verbose (@) {
+ if ( $verbose )
+ {
+ print STDERR _name(), _timestamp(),
+ "> ",join(" ", @_),"\n";
+ }
+}
+
+
+sub mtr_verbose_restart (@) {
+ my ($server, @args)= @_;
+ my $proc= $server->{proc};
+ if ( $verbose_restart )
+ {
+ print STDERR _name(),_timestamp(),
+ "> Restart $proc - ",join(" ", @args),"\n";
+ }
+}
+
+
+1;
diff --git a/mysql-test/lib/mtr_stress.pl b/mysql-test/lib/mtr_stress.pl
index 93b06b32c5f..cd5c7b0dbb7 100644
--- a/mysql-test/lib/mtr_stress.pl
+++ b/mysql-test/lib/mtr_stress.pl
@@ -135,7 +135,7 @@ sub run_stress_test ()
}
mtr_init_args(\$args);
-
+ mtr_add_args($args, "$::glob_mysql_test_dir/mysql-stress-test.pl");
mtr_add_arg($args, "--server-socket=%s", $::master->[0]->{'path_sock'});
mtr_add_arg($args, "--server-user=%s", $::opt_user);
mtr_add_arg($args, "--server-database=%s", "test");
@@ -181,7 +181,13 @@ sub run_stress_test ()
}
#Run stress test
- mtr_run("$::glob_mysql_test_dir/mysql-stress-test.pl", $args, "", "", "", "");
+ My::SafeProcess->run
+ (
+ name => "stress test",
+ path => $^X,
+ args => \$args,
+ );
+
if ( ! $::glob_use_embedded_server )
{
stop_all_servers();
diff --git a/mysql-test/lib/mtr_unique.pm b/mysql-test/lib/mtr_unique.pm
new file mode 100644
index 00000000000..b4093ab1dce
--- /dev/null
+++ b/mysql-test/lib/mtr_unique.pm
@@ -0,0 +1,185 @@
+# -*- cperl -*-
+# Copyright (C) 2006 MySQL AB
+#
+# 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-1301 USA
+
+package mtr_unique;
+
+use strict;
+use Fcntl ':flock';
+
+use base qw(Exporter);
+our @EXPORT= qw(mtr_get_unique_id mtr_release_unique_id);
+
+use My::Platform;
+
+sub msg {
+ # print "### unique($$) - ", join(" ", @_), "\n";
+}
+
+my $file= "/tmp/mysql-test-ports";
+
+my %mtr_unique_ids;
+
+END {
+ my $allocated_id= $mtr_unique_ids{$$};
+ if (defined $allocated_id)
+ {
+ mtr_release_unique_id($allocated_id);
+ }
+ delete $mtr_unique_ids{$$};
+}
+
+#
+# Get a unique, numerical ID, given a file name (where all
+# requested IDs are stored), a minimum and a maximum value.
+#
+# If no unique ID within the specified parameters can be
+# obtained, return undef.
+#
+sub mtr_get_unique_id($$) {
+ my ($min, $max)= @_;;
+
+ msg("get, '$file', $min-$max");
+
+ die "Can only get one unique id per process!" if $mtr_unique_ids{$$};
+
+ my $ret = undef;
+ my $changed = 0;
+
+ if(eval("readlink '$file'") || eval("readlink '$file.sem'")) {
+ die 'lock file is a symbolic link';
+ }
+
+ chmod 0777, "$file.sem";
+ open SEM, ">", "$file.sem" or die "can't write to $file.sem";
+ flock SEM, LOCK_EX or die "can't lock $file.sem";
+ if(! -e $file) {
+ open FILE, ">", $file or die "can't create $file";
+ close FILE;
+ }
+
+ msg("HAVE THE LOCK");
+
+ if(eval("readlink '$file'") || eval("readlink '$file.sem'")) {
+ die 'lock file is a symbolic link';
+ }
+
+ chmod 0777, $file;
+ open FILE, "+<", $file or die "can't open $file";
+ #select undef,undef,undef,0.2;
+ seek FILE, 0, 0;
+ my %taken = ();
+ while(<FILE>) {
+ chomp;
+ my ($id, $pid) = split / /;
+ $taken{$id} = $pid;
+ msg("taken: $id, $pid");
+ # Check if process with given pid is alive
+ if(!process_alive($pid)) {
+ print "Removing slot $id used by missing process $pid\n";
+ msg("Removing slot $id used by missing process $pid");
+ delete $taken{$id};
+ $changed++;
+ }
+ }
+ for(my $i=$min; $i<=$max; ++$i) {
+ if(! exists $taken{$i}) {
+ $ret = $i;
+ $taken{$i} = $$;
+ $changed++;
+ # Remember the id this process got
+ $mtr_unique_ids{$$}= $i;
+ msg(" got $i");
+ last;
+ }
+ }
+ if($changed) {
+ seek FILE, 0, 0;
+ truncate FILE, 0 or die "can't truncate $file";
+ for my $k (keys %taken) {
+ print FILE $k . ' ' . $taken{$k} . "\n";
+ }
+ }
+ close FILE;
+
+ msg("RELEASING THE LOCK");
+ flock SEM, LOCK_UN or warn "can't unlock $file.sem";
+ close SEM;
+
+ return $ret;
+}
+
+
+#
+# Release a unique ID.
+#
+sub mtr_release_unique_id($) {
+ my ($myid)= @_;
+
+ msg("release, $myid");
+
+
+ if(eval("readlink '$file'") || eval("readlink '$file.sem'")) {
+ die 'lock file is a symbolic link';
+ }
+
+ open SEM, ">", "$file.sem" or die "can't write to $file.sem";
+ flock SEM, LOCK_EX or die "can't lock $file.sem";
+
+ msg("HAVE THE LOCK");
+
+ if(eval("readlink '$file'") || eval("readlink '$file.sem'")) {
+ die 'lock file is a symbolic link';
+ }
+
+ if(! -e $file) {
+ open FILE, ">", $file or die "can't create $file";
+ close FILE;
+ }
+ open FILE, "+<", $file or die "can't open $file";
+ #select undef,undef,undef,0.2;
+ seek FILE, 0, 0;
+ my %taken = ();
+ while(<FILE>) {
+ chomp;
+ my ($id, $pid) = split / /;
+ msg(" taken, $id $pid");
+ $taken{$id} = $pid;
+ }
+
+ if ($taken{$myid} != $$)
+ {
+ msg(" The unique id for this process does not match pid");
+ }
+
+
+ msg(" removing $myid");
+ delete $taken{$myid};
+ seek FILE, 0, 0;
+ truncate FILE, 0 or die "can't truncate $file";
+ for my $k (keys %taken) {
+ print FILE $k . ' ' . $taken{$k} . "\n";
+ }
+ close FILE;
+
+ msg("RELEASE THE LOCK");
+
+ flock SEM, LOCK_UN or warn "can't unlock $file.sem";
+ close SEM;
+}
+
+
+1;
+
diff --git a/mysql-test/lib/t/Base.t b/mysql-test/lib/t/Base.t
new file mode 100644
index 00000000000..6ca7657d421
--- /dev/null
+++ b/mysql-test/lib/t/Base.t
@@ -0,0 +1,27 @@
+# -*- cperl -*-
+use Test::More qw(no_plan);
+use strict;
+
+use_ok ("My::SafeProcess::Base");
+
+
+my $count= 0;
+for (1..100){
+ my $pid= My::SafeProcess::Base::_safe_fork();
+ exit unless $pid;
+ (waitpid($pid, 0) == $pid) and $count++;
+}
+ok($count == 100, "safe_fork");
+
+# A nice little forkbomb
+SKIP: {
+ skip("forkbomb", 1);
+ eval {
+ while(1){
+ my $pid= My::SafeProcess::Base::_safe_fork();
+ exit unless $pid;
+ }
+ };
+ ok($@, "forkbomb");
+}
+
diff --git a/mysql-test/lib/t/Find.t b/mysql-test/lib/t/Find.t
new file mode 100644
index 00000000000..90489ba06dd
--- /dev/null
+++ b/mysql-test/lib/t/Find.t
@@ -0,0 +1,33 @@
+# -*- cperl -*-
+use Test::More qw(no_plan);
+use strict;
+
+use_ok ("My::Find");
+my $basedir= "../..";
+
+print "=" x 40, "\n";
+my $mysqld_exe= my_find_bin($basedir,
+ ["sql", "bin"],
+ ["mysqld", "mysqld-debug"]);
+print "mysqld_exe: $mysqld_exe\n";
+print "=" x 40, "\n";
+my $mysql_exe= my_find_bin($basedir,
+ ["client", "bin"],
+ "mysql");
+print "mysql_exe: $mysql_exe\n";
+print "=" x 40, "\n";
+
+my $mtr_build_dir= $ENV{MTR_BUILD_DIR};
+$ENV{MTR_BUILD_DIR}= "debug";
+my $mysql_exe= my_find_bin($basedir,
+ ["client", "bin"],
+ "mysql");
+print "mysql_exe: $mysql_exe\n";
+$ENV{MTR_BUILD_DIR}= $mtr_build_dir;
+print "=" x 40, "\n";
+
+my $charset_dir= my_find_dir($basedir,
+ ["share/mysql", "sql/share", "share"],
+ "charsets");
+print "charset_dir: $charset_dir\n";
+print "=" x 40, "\n";
diff --git a/mysql-test/lib/t/Options.t b/mysql-test/lib/t/Options.t
new file mode 100644
index 00000000000..7012f3da8de
--- /dev/null
+++ b/mysql-test/lib/t/Options.t
@@ -0,0 +1,127 @@
+
+# -*- cperl -*-
+use Test::More qw(no_plan);
+use strict;
+
+use_ok("My::Options");
+
+my @tests=
+(
+ [
+ ['--binlog-format=row', '--loose-skip-innodb', '--binlog-format=ms'],
+ ['--binlog-format=row', '--loose-skip-innodb', '--binlog-format=statement'],
+ ['--binlog-format=statement']
+ ],
+
+ [
+ ['--binlog-format=row', '--loose-skip-innodb', '--binlog-format=statement'],
+ ['--binlog-format=row', '--loose-skip-innodb', '--binlog-format=mixed'],
+ ['--binlog-format=mixed']
+ ],
+
+ [
+ ['--binlog-format=row', '--loose-skip-innodb', '--binlog-format=mixed'],
+ ['--binlog-format=row', '--loose-skip-innodb', '--binlog-format=statement'],
+ ['--binlog-format=statement']
+ ],
+
+ [
+ ['--binlog-format=mixed', '--loose-skip-innodb', '--binlog-format=row'],
+ ['--binlog-format=statement', '--loose-skip-innodb', '--binlog-format=row'],
+ [ ]
+ ],
+
+ [
+ ['--binlog-format=row'],
+ [ ],
+ ['--binlog-format=default']
+ ],
+
+ [
+ [ ],
+ ['--binlog-format=row'],
+ ['--binlog-format=row']
+ ],
+
+ [
+ [ ],
+ ['-O', 'max_binlog_size=1' ],
+ ['--max_binlog_size=1' ]
+ ],
+
+ [
+ ['-O', 'max_binlog_size=1' ],
+ ['-O', 'max_binlog_size=1' ],
+ [ ],
+ ],
+
+ [
+ ['-O', 'max_binlog_size=1' ],
+ [ ],
+ ['--max_binlog_size=default' ]
+ ],
+
+ [
+ [ ],
+ ['-O', 'max_binlog_size=1', '--binlog-format=row' ],
+ ['--max_binlog_size=1', '--binlog-format=row' ]
+ ],
+ [
+ ['--binlog-format=statement' ],
+ ['-O', 'max_binlog_size=1', '--binlog-format=row' ],
+ ['--max_binlog_size=1', '--binlog-format=row']
+ ],
+
+ [
+ [ '--binlog-format=statement' ],
+ ['-O', 'max_binlog_size=1', '--binlog-format=statement' ],
+ ['--max_binlog_size=1' ]
+ ],
+
+ [
+ [ '--binlog-format=statement' ],
+ ['-O', 'max_binlog_size=1', '--binlog-format=statement' ],
+ ['--max_binlog_size=1' ]
+ ],
+
+ [
+ [ '--binlog-format=statement' ],
+ ['--relay-log=/path/to/a/relay-log', '--binlog-format=row'],
+ ['--relay-log=/path/to/a/relay-log', '--binlog-format=row' ]
+ ],
+
+
+ [
+ [ '--binlog-format=statement' ],
+ ['--relay-log=/path/to/a/relay-log', '-O', 'max_binlog_size=1'],
+ ['--max_binlog_size=1', '--relay-log=/path/to/a/relay-log', '--binlog-format=default' ]
+ ],
+
+ [
+ [ '--slow-query-log=0' ],
+ [ '--slow-query-log' ],
+ [ '--slow-query-log' ]
+ ],
+
+
+);
+
+
+my $test_no= 0;
+foreach my $test (@tests){
+ print "test", $test_no++, "\n";
+ foreach my $opts (@$test){
+ print My::Options::toStr("", @$opts);
+ }
+ my $from= $test->[0];
+ my $to= $test->[1];
+ my @result= My::Options::diff($from, $to);
+ ok(My::Options::same(\@result, $test->[2]));
+ if (!My::Options::same(\@result, $test->[2])){
+ print "failed\n";
+ print My::Options::toStr("result", @result);
+ print My::Options::toStr("expect", @{$test->[2]});
+ }
+ print My::Options::toSQL(@result), "\n";
+ print "\n";
+}
diff --git a/mysql-test/lib/t/Platform.t b/mysql-test/lib/t/Platform.t
new file mode 100644
index 00000000000..a8cb7751925
--- /dev/null
+++ b/mysql-test/lib/t/Platform.t
@@ -0,0 +1,18 @@
+# -*- cperl -*-
+use Test::More qw(no_plan);
+use strict;
+
+use_ok ("My::Platform");
+use My::Platform;
+
+use File::Temp qw / tempdir /;
+my $dir = tempdir( CLEANUP => 1 );
+
+print "Running on Windows\n" if (IS_WINDOWS);
+print "Using ActiveState perl\n" if (IS_WIN32PERL);
+print "Using cygwin perl\n" if (IS_CYGWIN);
+
+print "dir: '$dir'\n";
+print "native: '".native_path($dir)."'\n";
+print "mixed: '".mixed_path($dir)."'\n";
+print "posix: '".posix_path($dir)."'\n";
diff --git a/mysql-test/lib/t/SafeProcess.t b/mysql-test/lib/t/SafeProcess.t
new file mode 100644
index 00000000000..d4a62ff8cca
--- /dev/null
+++ b/mysql-test/lib/t/SafeProcess.t
@@ -0,0 +1,102 @@
+# -*- cperl -*-
+
+use strict;
+use FindBin;
+use IO::File;
+
+use Test::More qw(no_plan);
+use_ok ("My::SafeProcess");
+
+
+my $perl_path= $^X;
+
+{
+ # Test exit codes
+ my $count= 32;
+ my $ok_count= 0;
+ for my $code (0..$count-1) {
+
+ my $args= [ "$FindBin::Bin/test_child.pl", "--exit-code=$code" ];
+ my $proc= My::SafeProcess->new
+ (
+ path => $perl_path,
+ args => \$args,
+ output => "/dev/null",
+ error => "/dev/null",
+ );
+ # Wait max 10 seconds for the process to finish
+ $ok_count++ if ($proc->wait_one(10) == 0 and
+ $proc->exit_status() == $code);
+ }
+ ok($count == $ok_count, "check exit_status, $ok_count");
+}
+
+
+{
+ # spawn a number of concurrent processes
+ my $count= 16;
+ my $ok_count= 0;
+ my %procs;
+ for my $code (0..$count-1) {
+
+ my $args= [ "$FindBin::Bin/test_child.pl", "--exit-code=$code" ];
+ $procs{$code}= My::SafeProcess->new
+ (
+ path => $perl_path,
+ args => \$args,
+ output => "/dev/null",
+ error => "/dev/null",
+ );
+ }
+
+ for my $code (0..$count-1) {
+ $ok_count++ if ($procs{$code}->wait_one(10) == 0 and
+ $procs{$code}->exit_status() == $code);
+ }
+ ok($count == $ok_count, "concurrent, $ok_count");
+}
+
+
+#
+# Test stdout, stderr
+#
+{
+ use File::Temp qw / tempdir /;
+ my $dir = tempdir( CLEANUP => 1 );
+
+ my $args= [ "$FindBin::Bin/test_child.pl" ];
+ my $proc= My::SafeProcess->new
+ (
+ path => $perl_path,
+ args => \$args,
+ output => "$dir/output.txt",
+ error => "$dir/error.txt",
+ );
+
+ $proc->wait_one(2); # Wait max 2 seconds for the process to finish
+
+ my $fh= IO::File->new("$dir/output.txt");
+ my @text= <$fh>;
+ ok(grep(/Hello stdout/, @text), "check stdout");
+ $fh= IO::File->new("$dir/error.txt");
+ my @text= <$fh>;
+ ok(grep(/Hello stderr/, @text), "check stderr");
+
+ # To same file
+ $proc= My::SafeProcess->new
+ (
+ path => $perl_path,
+ args => \$args,
+ output => "$dir/output.txt",
+ error => "$dir/output.txt",
+ debug => 1,
+ );
+
+ $proc->wait_one(2); # Wait max 2 seconds for the process to finish
+
+ my $fh= IO::File->new("$dir/output.txt");
+ my @text= <$fh>;
+ ok((grep(/Hello stdout/, @text) and grep(/Hello stderr/, @text)),
+ "check stdout and stderr");
+
+}
diff --git a/mysql-test/lib/t/SafeProcessStress.pl b/mysql-test/lib/t/SafeProcessStress.pl
new file mode 100755
index 00000000000..0f7a59d67f0
--- /dev/null
+++ b/mysql-test/lib/t/SafeProcessStress.pl
@@ -0,0 +1,149 @@
+#!/usr/bin/perl
+# -*- cperl -*-
+
+use strict;
+use FindBin;
+use My::SafeProcess;
+
+#
+# Test longterm running of SafeProcess
+#
+
+my $perl_path= $^X;
+my $verbose= 0;
+my $loops= 100;
+
+print "kill one and wait for one\n";
+for (1...$loops){
+ use File::Temp qw / tempdir /;
+ my $dir = tempdir( CLEANUP => 1 );
+
+ my @procs;
+ for (1..10){
+
+ my $args= [ "$FindBin::Bin/dummyd.pl", "--vardir=$dir" ];
+ my $proc= My::SafeProcess->new
+ (
+ path => $perl_path,
+ args => \$args,
+ verbose => $verbose,
+ );
+ push(@procs, $proc);
+ }
+
+ foreach my $proc (@procs) {
+ $proc->kill();
+ # dummyd will always be killed and thus
+ # exit_status should have been set to 1
+ die "oops, exit_status: ", $proc->exit_status()
+ unless $proc->exit_status() == 1;
+ }
+
+ print "=" x 60, "\n";
+}
+
+
+print "With 1 second sleep in dummyd\n";
+for (1...$loops){
+ use File::Temp qw / tempdir /;
+ my $dir = tempdir( CLEANUP => 1 );
+
+ my @procs;
+ for (1..10){
+
+ my $args= [ "$FindBin::Bin/dummyd.pl",
+ "--vardir=$dir",
+ "--sleep=1" ];
+ my $proc= My::SafeProcess->new
+ (
+ path => $perl_path,
+ args => \$args,
+ verbose => $verbose,
+ );
+ push(@procs, $proc);
+ }
+
+ foreach my $proc (@procs) {
+ $proc->kill();
+ }
+
+ print "=" x 60, "\n";
+}
+
+print "kill all and wait for one\n";
+for (1...$loops){
+ use File::Temp qw / tempdir /;
+ my $dir = tempdir( CLEANUP => 1 );
+
+ my @procs;
+ for (1..10){
+
+ my $args= [ "$FindBin::Bin/dummyd.pl", "--vardir=$dir" ];
+ my $proc= My::SafeProcess->new
+ (
+ path => $perl_path,
+ args => \$args,
+ verbose => $verbose,
+ );
+ push(@procs, $proc);
+ }
+
+ foreach my $proc (@procs) {
+ $proc->start_kill();
+ }
+
+ foreach my $proc (@procs) {
+ $proc->wait_one();
+ }
+
+ print "=" x 60, "\n";
+}
+
+print "kill all using shutdown without callback\n";
+for (1...$loops){
+ use File::Temp qw / tempdir /;
+ my $dir = tempdir( CLEANUP => 1 );
+
+ my @procs;
+ for (1..10){
+
+ my $args= [ "$FindBin::Bin/dummyd.pl", "--vardir=$dir" ];
+ my $proc= My::SafeProcess->new
+ (
+ path => $perl_path,
+ args => \$args,
+ verbose => $verbose,
+ );
+ push(@procs, $proc);
+ }
+
+ My::SafeProcess::shutdown(2, @procs);
+
+ print "=" x 60, "\n";
+}
+
+print "kill all using shutdown\n";
+for (1...$loops){
+ use File::Temp qw / tempdir /;
+ my $dir = tempdir( CLEANUP => 1 );
+
+ my @procs;
+ for (1..10){
+
+ my $args= [ "$FindBin::Bin/dummyd.pl", "--vardir=$dir" ];
+ my $proc= My::SafeProcess->new
+ (
+ path => $perl_path,
+ args => \$args,
+ verbose => $verbose,
+ shutdown => sub { }, # Does nothing
+ );
+ push(@procs, $proc);
+ }
+
+ My::SafeProcess::shutdown(2, @procs);
+
+ print "=" x 60, "\n";
+}
+
+exit(0);
diff --git a/mysql-test/lib/t/copytree.t b/mysql-test/lib/t/copytree.t
new file mode 100644
index 00000000000..15e4d1a7b1b
--- /dev/null
+++ b/mysql-test/lib/t/copytree.t
@@ -0,0 +1,34 @@
+#!/usr/bin/perl
+# -*- cperl -*-
+
+use strict;
+
+use My::File::Path;
+
+use Test::Simple tests => 7;
+use File::Temp qw / tempdir /;
+my $dir = tempdir( CLEANUP => 1 );
+my $testdir="$dir/test";
+my $test_todir="$dir/to";
+
+my $subdir= "$testdir/test1/test2/test3";
+
+#
+# 1. Create, copy and remove a directory structure
+#
+mkpath($subdir);
+ok( -d $subdir, "Check '$subdir' is created");
+
+copytree($testdir, $test_todir);
+ok( -d $test_todir, "Check '$test_todir' is created");
+ok( -d "$test_todir/test1", "Check 'test1' is created");
+ok( -d "$test_todir/test1/test2", "Check 'test2' is created");
+ok( -d "$test_todir/test1/test2/test3", "Check 'test3' is created");
+
+
+rmtree($testdir);
+ok( ! -d $testdir, "Check '$testdir' is gone");
+
+rmtree($test_todir);
+ok( ! -d $test_todir, "Check '$test_todir' is gone");
+
diff --git a/mysql-test/lib/t/dummyd.pl b/mysql-test/lib/t/dummyd.pl
new file mode 100644
index 00000000000..07336e3c2d2
--- /dev/null
+++ b/mysql-test/lib/t/dummyd.pl
@@ -0,0 +1,38 @@
+#!/usr/bin/perl
+# -*- cperl -*-
+
+use strict;
+use Getopt::Long;
+use IO::File;
+
+my $vardir;
+my $randie= 0;
+my $sleep= 0;
+GetOptions
+ (
+ # Directory where to write files
+ 'vardir=s' => \$vardir,
+ 'die-randomly' => \$randie,
+ 'sleep=i' => \$sleep,
+ );
+
+die("invalid vardir ") unless defined $vardir and -d $vardir;
+
+my $pid= $$;
+while(1){
+ for my $i (1..64){
+ # Write to file
+ my $name= "$vardir/$pid.$i.tmp";
+ my $F= IO::File->new($name, "w")
+ or warn "$$, Could not open $name: $!" and next;
+ print $F rand($.) for (1..1000);
+ $F->close();
+ sleep($sleep);
+ die "ooops!" if $randie and rand() < 0.0001
+ }
+}
+
+
+exit (0);
+
+
diff --git a/mysql-test/lib/t/rmtree.t b/mysql-test/lib/t/rmtree.t
new file mode 100644
index 00000000000..08c9077d001
--- /dev/null
+++ b/mysql-test/lib/t/rmtree.t
@@ -0,0 +1,52 @@
+#!/usr/bin/perl
+# -*- cperl -*-
+
+use strict;
+
+use My::File::Path;
+
+use Test::Simple tests => 8;
+use File::Temp qw / tempdir /;
+my $dir = tempdir( CLEANUP => 1 );
+my $testdir="$dir/test";
+
+my $subdir= "$testdir/test1/test2/test3";
+
+#
+# 1. Create and remove a directory structure
+#
+mkpath($subdir);
+ok( -d $subdir, "Check '$subdir' is created");
+
+rmtree($testdir);
+ok( ! -d $testdir, "Check '$testdir' is gone");
+
+#
+# 2. Create and remove a directory structure
+# where one directory is chmod to 0000
+#
+mkpath($subdir);
+ok( -d $subdir, "Check '$subdir' is created");
+
+ok( chmod(0000, $subdir) == 1 , "Check one dir was chmoded");
+
+rmtree($testdir);
+ok( ! -d $testdir, "Check '$testdir' is gone");
+
+#
+# 3. Create and remove a directory structure
+# where one file is chmod to 0000
+#
+mkpath($subdir);
+ok( -d $subdir, "Check '$subdir' is created");
+
+my $testfile= "$subdir/test.file";
+open(F, ">", $testfile) or die;
+print F "hello\n";
+close(F);
+
+ok( chmod(0000, $testfile) == 1 , "Check one file was chmoded");
+
+rmtree($testdir);
+ok( ! -d $testdir, "Check '$testdir' is gone");
+
diff --git a/mysql-test/lib/t/testMyConfig.t b/mysql-test/lib/t/testMyConfig.t
new file mode 100755
index 00000000000..da08cb8b4d1
--- /dev/null
+++ b/mysql-test/lib/t/testMyConfig.t
@@ -0,0 +1,131 @@
+#!/usr/bin/perl
+# -*- cperl -*-
+
+use strict;
+use warnings;
+use File::Temp qw / tempdir /;
+my $dir = tempdir( CLEANUP => 1 );
+
+use Test::More qw(no_plan);
+
+BEGIN { use_ok ( "My::Config" ) };
+
+my $test_cnf= "$dir/test.cnf";
+
+# Write test config file
+open(OUT, ">", $test_cnf) or die;
+print $test_cnf, "\n";
+
+print OUT <<EOF
+[mysqld]
+# Comment
+option1=values2
+option2= value4
+option4
+basedir=thebasedir
+[mysqld_1]
+[mysqld_2]
+[mysqld.9]
+[client]
+socket =\tasocketpath
+EOF
+;
+close OUT;
+
+my $config= My::Config->new($test_cnf);
+isa_ok( $config, "My::Config" );
+
+print $config;
+
+ok ( $config->group("mysqld_2"), "group mysqld_2 exists");
+ok ( $config->group("mysqld_1"), "group mysqld_1 exists");
+ok ( $config->group("mysqld.9"), "group mysqld.9 exists");
+ok ( $config->group("mysqld.9")->suffix() eq ".9", "group mysqld.9 has suffix .9");
+
+ok ( $config->group("mysqld"), "group mysqld exists");
+ok ( $config->group("client"), "group client exists");
+ok ( !$config->group("mysqld_3"), "group mysqld_3 does not exist");
+
+ok ( $config->options_in_group("mysqld") == 4, "options in [mysqld] is 4");
+ok ( $config->options_in_group("nonexist") == 0, "options in [nonexist] is 0");
+
+{
+ my @groups= $config->groups();
+ ok(@groups == 5, "5 groups");
+ my $idx= 0;
+ foreach my $name ('mysqld', 'mysqld_1', 'mysqld_2', 'mysqld.9', 'client') {
+ is($groups[$idx++]->name(), $name, "checking groups $idx");
+ }
+}
+
+{
+ my @groups= $config->like("mysqld");
+ ok(@groups == 4, "4 groups like mysqld");
+ my $idx= 0;
+ foreach my $name ('mysqld', 'mysqld_1', 'mysqld_2', 'mysqld.9') {
+ is($groups[$idx++]->name(), $name, "checking like(\"mysqld\") $idx");
+ }
+}
+
+{
+ my @groups= $config->like("not");
+ ok(@groups == 0, "checking like(\"not\")");
+}
+
+is($config->first_like("mysqld_")->name(), "mysqld_1", "first_like");
+
+is( $config->value('mysqld', 'option4'), undef,
+ "mysqld_option4 exists, does not have a value");
+
+ok( $config->exists('mysqld', 'option4'),
+ "mysqld_option4 exists");
+ok( $config->exists('mysqld', 'option2'),
+ "mysqld_option2 exists");
+ok( !$config->exists('mysqld', 'option5'),
+ "mysqld_option5 does not exists");
+
+# Save the config to file
+my $test2_cnf= "$dir/test2.cnf";
+$config->save($test2_cnf);
+
+# read it back and check it's the same
+my $config2= My::Config->new($test2_cnf);
+isa_ok( $config2, "My::Config" );
+is_deeply( \$config, \$config2, "test.cnf is equal to test2.cnf");
+
+
+my $test_include_cnf= "$dir/test_include.cnf";
+# Write test config file that includes test.cnf
+open(OUT, ">", $test_include_cnf) or die;
+
+print OUT <<EOF
+[mysqld]
+!include test.cnf
+# Comment
+option1=values3
+basedir=anotherbasedir
+EOF
+;
+close OUT;
+
+# Read the config file
+my $config3= My::Config->new($test_include_cnf);
+isa_ok( $config3, "My::Config" );
+print $config3;
+is( $config3->value('mysqld', 'basedir'), 'anotherbasedir',
+ "mysqld_basedir has been overriden by value in test_include.cnf");
+
+is( $config3->value('mysqld', 'option1'), 'values3',
+ "mysqld_option1 has been overriden by value in test_include.cnf");
+
+is( $config3->value('mysqld', 'option2'), 'value4',
+ "mysqld_option2 is from included file");
+
+is( $config3->value('client', 'socket'), 'asocketpath',
+ "client.socket is from included file");
+
+is( $config3->value('mysqld', 'option4'), undef,
+ "mysqld_option4 exists, does not have a value");
+
+print "$config3\n";
+
diff --git a/mysql-test/lib/t/testMyConfigFactory.t b/mysql-test/lib/t/testMyConfigFactory.t
new file mode 100755
index 00000000000..16fdd9db539
--- /dev/null
+++ b/mysql-test/lib/t/testMyConfigFactory.t
@@ -0,0 +1,98 @@
+#!/usr/bin/perl
+# -*- cperl -*-
+
+use strict;
+use warnings;
+
+use File::Temp qw / tempdir /;
+my $dir = tempdir( CLEANUP => 1 );
+
+use Test::More qw(no_plan);
+
+BEGIN { use_ok ( "My::ConfigFactory" ) };
+
+my $gen1_cnf= "$dir/gen1.cnf";
+open(OUT, ">", $gen1_cnf) or die;
+
+print OUT <<EOF
+[mysqld.master]
+# Comment
+option1=value1
+basedir=abasedir
+
+[mysqld.1]
+# Comment
+option1=value1
+option2=value2
+
+[ENV]
+MASTER_MY_PORT=\@mysqld.master.port
+
+EOF
+;
+close OUT;
+
+my $basedir= "../..";
+
+my $config= My::ConfigFactory->new_config
+(
+ {
+ basedir => $basedir,
+ template_path => $gen1_cnf,
+ vardir => "/path/to/var",
+ baseport => 10987,
+ #hosts => [ 'host1', 'host2' ],
+ }
+);
+
+print $config;
+
+ok ( $config->group("mysqld.master"), "group mysqld.master exists");
+ok ( $config->group("mysqld.1"), "group mysqld.1 exists");
+ok ( $config->group("client"), "group client exists");
+ok ( !$config->group("mysqld.3"), "group mysqld.3 does not exist");
+
+ok ( $config->first_like("mysqld"), "group like 'mysqld' exists");
+
+is( $config->value('mysqld.1', '#host'), 'localhost',
+ "mysqld.1.#host has been generated");
+
+is( $config->value('client', 'host'), 'localhost',
+ "client.host has been generated");
+
+is( $config->value('client', 'host'),
+ $config->value('mysqld.master', '#host'),
+ "client.host is same as mysqld.master.host");
+
+ok ( $config->value("mysqld.1", 'character-sets-dir') =~ /$basedir.*charsets$/,
+ "'character-sets-dir' generated");
+
+ok ( $config->value("mysqld.1", 'language') =~ /$basedir.*english$/,
+ "'language' generated");
+
+ok ( $config->value("ENV", 'MASTER_MY_PORT') =~ /\d/,
+ "'language' generated");
+
+my $gen2_cnf= "$dir/gen2.cnf";
+open(OUT, ">", $gen2_cnf) or die;
+
+print OUT <<EOF
+[mysqld.master]
+EOF
+;
+close OUT;
+
+my $config2= My::ConfigFactory->new_config
+(
+ {
+ basedir => $basedir,
+ template_path => $gen2_cnf,
+ vardir => "/path/to/var",
+ baseport => 10987,
+ #hosts => [ 'host1', 'host2' ],
+ }
+);
+
+print $config2;
+
+ok ( $config2->first_like("mysqld"), "group like 'mysqld' exists");
diff --git a/mysql-test/lib/t/test_child.pl b/mysql-test/lib/t/test_child.pl
new file mode 100755
index 00000000000..99f4e68003d
--- /dev/null
+++ b/mysql-test/lib/t/test_child.pl
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+# -*- cperl -*-
+
+use strict;
+use Getopt::Long;
+
+my $opt_exit_code= 0;
+
+GetOptions
+ (
+ # Exit with the specified exit code
+ 'exit-code=i' => \$opt_exit_code
+ );
+
+
+print "Hello stdout\n";
+print STDERR "Hello stderr\n";
+
+exit ($opt_exit_code);
+
+
diff --git a/mysql-test/lib/v1/My/Config.pm b/mysql-test/lib/v1/My/Config.pm
new file mode 100644
index 00000000000..5491e341ddc
--- /dev/null
+++ b/mysql-test/lib/v1/My/Config.pm
@@ -0,0 +1,422 @@
+# -*- cperl -*-
+
+package My::Config::Option;
+
+use strict;
+use warnings;
+
+
+sub new {
+ my ($class, $option_name, $option_value)= @_;
+ my $self= bless { name => $option_name,
+ value => $option_value
+ }, $class;
+ return $self;
+}
+
+
+sub name {
+ my ($self)= @_;
+ return $self->{name};
+}
+
+
+sub value {
+ my ($self)= @_;
+ return $self->{value};
+}
+
+
+package My::Config::Group;
+
+use strict;
+use warnings;
+
+
+sub new {
+ my ($class, $group_name)= @_;
+ my $self= bless { name => $group_name,
+ options => [],
+ options_by_name => {},
+ }, $class;
+ return $self;
+}
+
+
+sub insert {
+ my ($self, $option_name, $value, $if_not_exist)= @_;
+ my $option= $self->option($option_name);
+ if (defined($option) and !$if_not_exist) {
+ $option->{value}= $value;
+ }
+ else {
+ my $option= My::Config::Option->new($option_name, $value);
+ # Insert option in list
+ push(@{$self->{options}}, $option);
+ # Insert option in hash
+ $self->{options_by_name}->{$option_name}= $option;
+ }
+ return $option;
+}
+
+sub remove {
+ my ($self, $option_name)= @_;
+
+ # Check that option exists
+ my $option= $self->option($option_name);
+
+ return undef unless defined $option;
+
+ # Remove from the hash
+ delete($self->{options_by_name}->{$option_name}) or die;
+
+ # Remove from the array
+ @{$self->{options}}= grep { $_->name ne $option_name } @{$self->{options}};
+
+ return $option;
+}
+
+
+sub options {
+ my ($self)= @_;
+ return @{$self->{options}};
+}
+
+
+sub name {
+ my ($self)= @_;
+ return $self->{name};
+}
+
+
+#
+# Return a specific option in the group
+#
+sub option {
+ my ($self, $option_name)= @_;
+
+ return $self->{options_by_name}->{$option_name};
+}
+
+
+#
+# Return a specific value for an option in the group
+#
+sub value {
+ my ($self, $option_name)= @_;
+ my $option= $self->option($option_name);
+
+ die "No option named '$option_name' in this group"
+ if ! defined($option);
+
+ return $option->value();
+}
+
+
+package My::Config;
+
+use strict;
+use warnings;
+use IO::File;
+use File::Basename;
+
+#
+# Constructor for My::Config
+# - represents a my.cnf config file
+#
+# Array of arrays
+#
+sub new {
+ my ($class, $path)= @_;
+ my $group_name= undef;
+
+ my $self= bless { groups => [] }, $class;
+ my $F= IO::File->new($path, "<")
+ or die "Could not open '$path': $!";
+
+ while ( my $line= <$F> ) {
+ chomp($line);
+
+ # [group]
+ if ( $line =~ /\[(.*)\]/ ) {
+ # New group found
+ $group_name= $1;
+ #print "group: $group_name\n";
+
+ $self->insert($group_name, undef, undef);
+ }
+
+ # Magic #! comments
+ elsif ( $line =~ /^#\!/) {
+ my $magic= $line;
+ die "Found magic comment '$magic' outside of group"
+ unless $group_name;
+
+ #print "$magic\n";
+ $self->insert($group_name, $magic, undef);
+ }
+
+ # Comments
+ elsif ( $line =~ /^#/ || $line =~ /^;/) {
+ # Skip comment
+ next;
+ }
+
+ # Empty lines
+ elsif ( $line =~ /^$/ ) {
+ # Skip empty lines
+ next;
+ }
+
+ # !include <filename>
+ elsif ( $line =~ /^\!include\s*(.*?)\s*$/ ) {
+ my $include_file_name= dirname($path)."/".$1;
+ # Check that the file exists
+ die "The include file '$include_file_name' does not exist"
+ unless -f $include_file_name;
+
+ $self->append(My::Config->new($include_file_name));
+ }
+
+ # <option>
+ elsif ( $line =~ /^([\@\w-]+)\s*$/ ) {
+ my $option= $1;
+
+ die "Found option '$option' outside of group"
+ unless $group_name;
+
+ #print "$option\n";
+ $self->insert($group_name, $option, undef);
+ }
+
+ # <option>=<value>
+ elsif ( $line =~ /^([\@\w-]+)\s*=\s*(.*?)\s*$/ ) {
+ my $option= $1;
+ my $value= $2;
+
+ die "Found option '$option=$value' outside of group"
+ unless $group_name;
+
+ #print "$option=$value\n";
+ $self->insert($group_name, $option, $value);
+ } else {
+ die "Unexpected line '$line' found in '$path'";
+ }
+
+ }
+ undef $F; # Close the file
+
+ return $self;
+}
+
+#
+# Insert a new group if it does not already exist
+# and add option if defined
+#
+sub insert {
+ my ($self, $group_name, $option, $value, $if_not_exist)= @_;
+ my $group;
+
+ # Create empty array for the group if it doesn't exist
+ if ( !$self->group_exists($group_name) ) {
+ $group= $self->_group_insert($group_name);
+ }
+ else {
+ $group= $self->group($group_name);
+ }
+
+ if ( defined $option ) {
+ #print "option: $option, value: $value\n";
+
+ # Add the option to the group
+ $group->insert($option, $value, $if_not_exist);
+ }
+}
+
+#
+# Remove a option, given group and option name
+#
+sub remove {
+ my ($self, $group_name, $option_name)= @_;
+ my $group= $self->group($group_name);
+
+ die "group '$group_name' does not exist"
+ unless defined($group);
+
+ $group->remove($option_name) or
+ die "option '$option_name' does not exist";
+}
+
+
+
+#
+# Check if group with given name exists in config
+#
+sub group_exists {
+ my ($self, $group_name)= @_;
+
+ foreach my $group ($self->groups()) {
+ return 1 if $group->{name} eq $group_name;
+ }
+ return 0;
+}
+
+
+#
+# Insert a new group into config
+#
+sub _group_insert {
+ my ($self, $group_name)= @_;
+ caller eq __PACKAGE__ or die;
+
+ # Check that group does not already exist
+ die "Group already exists" if $self->group_exists($group_name);
+
+ my $group= My::Config::Group->new($group_name);
+ push(@{$self->{groups}}, $group);
+ return $group;
+}
+
+
+#
+# Append a configuration to current config
+#
+sub append {
+ my ($self, $from)= @_;
+
+ foreach my $group ($from->groups()) {
+ foreach my $option ($group->options()) {
+ $self->insert($group->name(), $option->name(), $option->value());
+ }
+
+ }
+}
+
+
+#
+# Return a list with all the groups in config
+#
+sub groups {
+ my ($self)= @_;
+ return ( @{$self->{groups}} );
+}
+
+
+#
+# Return a list of all the groups in config
+# starting with the given string
+#
+sub like {
+ my ($self, $prefix)= @_;
+ return ( grep ( $_->{name} =~ /^$prefix/, $self->groups()) );
+}
+
+
+#
+# Return the first group in config
+# starting with the given string
+#
+sub first_like {
+ my ($self, $prefix)= @_;
+ return ($self->like($prefix))[0];
+}
+
+
+#
+# Return a specific group in the config
+#
+sub group {
+ my ($self, $group_name)= @_;
+
+ foreach my $group ( $self->groups() ) {
+ return $group if $group->{name} eq $group_name;
+ }
+ return undef;
+}
+
+
+#
+# Return a list of all options in a specific group in the config
+#
+sub options_in_group {
+ my ($self, $group_name)= @_;
+
+ my $group= $self->group($group_name);
+ return () unless defined $group;
+ return $group->options();
+}
+
+
+#
+# Return a value given group and option name
+#
+sub value {
+ my ($self, $group_name, $option_name)= @_;
+ my $group= $self->group($group_name);
+
+ die "group '$group_name' does not exist"
+ unless defined($group);
+
+ my $option= $group->option($option_name);
+ die "option '$option_name' does not exist"
+ unless defined($option);
+
+ return $option->value();
+}
+
+
+#
+# Check if an option exists
+#
+sub exists {
+ my ($self, $group_name, $option_name)= @_;
+ my $group= $self->group($group_name);
+
+ die "group '$group_name' does not exist"
+ unless defined($group);
+
+ my $option= $group->option($option_name);
+ return defined($option);
+}
+
+
+# Overload "to string"-operator with 'stringify'
+use overload
+ '""' => \&stringify;
+
+#
+# Return the config as a string in my.cnf file format
+#
+sub stringify {
+ my ($self)= @_;
+ my $res;
+
+ foreach my $group ($self->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";
+ }
+ return $res;
+}
+
+
+#
+# Save the config to named file
+#
+sub save {
+ my ($self, $path)= @_;
+ my $F= IO::File->new($path, ">")
+ or die "Could not open '$path': $!";
+ print $F $self;
+ undef $F; # Close the file
+}
+
+1;
diff --git a/mysql-test/lib/mtr_cases.pl b/mysql-test/lib/v1/mtr_cases.pl
index 1bb793fba49..4d7b1f4ec70 100644
--- a/mysql-test/lib/mtr_cases.pl
+++ b/mysql-test/lib/v1/mtr_cases.pl
@@ -369,7 +369,7 @@ sub collect_one_suite($)
my $comb= {};
$comb->{name}= $group->name();
foreach my $option ( $group->options() ) {
- push(@{$comb->{comb_opt}}, $option->name()."=".$option->value());
+ push(@{$comb->{comb_opt}}, "--".$option->name()."=".$option->value());
}
push(@combinations, $comb);
}
@@ -511,10 +511,19 @@ sub collect_one_test_case($$$$$$$$$) {
my $suite_opts= shift;
my $path= "$testdir/$elem";
- my $name= basename($suite) . ".$tname";
+
+ # ----------------------------------------------------------------------
+ # Skip some tests silently
+ # ----------------------------------------------------------------------
+
+ if ( $::opt_start_from and $tname lt $::opt_start_from )
+ {
+ return;
+ }
+
my $tinfo= {};
- $tinfo->{'name'}= $name;
+ $tinfo->{'name'}= basename($suite) . ".$tname";
$tinfo->{'result_file'}= "$resdir/$tname.result";
$tinfo->{'component_id'} = $component_id;
push(@$cases, $tinfo);
@@ -523,7 +532,7 @@ sub collect_one_test_case($$$$$$$$$) {
# Skip some tests but include in list, just mark them to skip
# ----------------------------------------------------------------------
- if ( $skip_test and ($tname =~ /$skip_test/o || $name =~ /$skip_test/o))
+ if ( $skip_test and $tname =~ /$skip_test/o )
{
$tinfo->{'skip'}= 1;
return;
diff --git a/mysql-test/lib/v1/mtr_gcov.pl b/mysql-test/lib/v1/mtr_gcov.pl
new file mode 100644
index 00000000000..a2de1fcbdff
--- /dev/null
+++ b/mysql-test/lib/v1/mtr_gcov.pl
@@ -0,0 +1,74 @@
+# -*- cperl -*-
+# Copyright (C) 2004, 2006 MySQL AB
+#
+# 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-1301 USA
+
+# This is a library file used by the Perl version of mysql-test-run,
+# and is part of the translation of the Bourne shell script with the
+# same name.
+
+use strict;
+
+# These are not to be prefixed with "mtr_"
+
+sub gcov_prepare ();
+sub gcov_collect ();
+
+##############################################################################
+#
+#
+#
+##############################################################################
+
+sub gcov_prepare () {
+
+ `find $::glob_basedir -name \*.gcov \
+ -or -name \*.da | xargs rm`;
+}
+
+# Used by gcov
+our @mysqld_src_dirs=
+ (
+ "strings",
+ "mysys",
+ "include",
+ "extra",
+ "regex",
+ "isam",
+ "merge",
+ "myisam",
+ "myisammrg",
+ "heap",
+ "sql",
+ );
+
+sub gcov_collect () {
+
+ print "Collecting source coverage info...\n";
+ -f $::opt_gcov_msg and unlink($::opt_gcov_msg);
+ -f $::opt_gcov_err and unlink($::opt_gcov_err);
+ foreach my $d ( @mysqld_src_dirs )
+ {
+ chdir("$::glob_basedir/$d");
+ foreach my $f ( (glob("*.h"), glob("*.cc"), glob("*.c")) )
+ {
+ `$::opt_gcov $f 2>>$::opt_gcov_err >>$::opt_gcov_msg`;
+ }
+ chdir($::glob_mysql_test_dir);
+ }
+ print "gcov info in $::opt_gcov_msg, errors in $::opt_gcov_err\n";
+}
+
+
+1;
diff --git a/mysql-test/lib/v1/mtr_gprof.pl b/mysql-test/lib/v1/mtr_gprof.pl
new file mode 100644
index 00000000000..f6615301dd7
--- /dev/null
+++ b/mysql-test/lib/v1/mtr_gprof.pl
@@ -0,0 +1,64 @@
+# -*- cperl -*-
+# Copyright (C) 2004 MySQL AB
+#
+# 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-1301 USA
+
+# This is a library file used by the Perl version of mysql-test-run,
+# and is part of the translation of the Bourne shell script with the
+# same name.
+
+use strict;
+
+# These are not to be prefixed with "mtr_"
+
+sub gprof_prepare ();
+sub gprof_collect ();
+
+##############################################################################
+#
+#
+#
+##############################################################################
+
+sub gprof_prepare () {
+
+ rmtree($::opt_gprof_dir);
+ mkdir($::opt_gprof_dir);
+}
+
+# FIXME what about master1 and slave1?!
+sub gprof_collect () {
+
+ if ( -f "$::master->[0]->{'path_myddir'}/gmon.out" )
+ {
+ # FIXME check result code?!
+ mtr_run("gprof",
+ [$::exe_master_mysqld,
+ "$::master->[0]->{'path_myddir'}/gmon.out"],
+ $::opt_gprof_master, "", "", "");
+ print "Master execution profile has been saved in $::opt_gprof_master\n";
+ }
+ if ( -f "$::slave->[0]->{'path_myddir'}/gmon.out" )
+ {
+ # FIXME check result code?!
+ mtr_run("gprof",
+ [$::exe_slave_mysqld,
+ "$::slave->[0]->{'path_myddir'}/gmon.out"],
+ $::opt_gprof_slave, "", "", "");
+ print "Slave execution profile has been saved in $::opt_gprof_slave\n";
+ }
+}
+
+
+1;
diff --git a/mysql-test/lib/mtr_im.pl b/mysql-test/lib/v1/mtr_im.pl
index c8e332498d7..c8e332498d7 100644
--- a/mysql-test/lib/mtr_im.pl
+++ b/mysql-test/lib/v1/mtr_im.pl
diff --git a/mysql-test/lib/v1/mtr_io.pl b/mysql-test/lib/v1/mtr_io.pl
new file mode 100644
index 00000000000..aa671c0f4f7
--- /dev/null
+++ b/mysql-test/lib/v1/mtr_io.pl
@@ -0,0 +1,218 @@
+# -*- cperl -*-
+# Copyright (C) 2004-2006 MySQL AB
+#
+# 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-1301 USA
+
+# This is a library file used by the Perl version of mysql-test-run,
+# and is part of the translation of the Bourne shell script with the
+# same name.
+
+use strict;
+
+sub mtr_get_pid_from_file ($);
+sub mtr_get_opts_from_file ($);
+sub mtr_fromfile ($);
+sub mtr_tofile ($@);
+sub mtr_tonewfile($@);
+sub mtr_lastlinefromfile($);
+sub mtr_appendfile_to_file ($$);
+sub mtr_grab_file($);
+
+
+##############################################################################
+#
+#
+#
+##############################################################################
+
+sub mtr_get_pid_from_file ($) {
+ my $pid_file_path= shift;
+ my $TOTAL_ATTEMPTS= 30;
+ my $timeout= 1;
+
+ # We should read from the file until we get correct pid. As it is
+ # stated in BUG#21884, pid file can be empty at some moment. So, we should
+ # read it until we get valid data.
+
+ for (my $cur_attempt= 1; $cur_attempt <= $TOTAL_ATTEMPTS; ++$cur_attempt)
+ {
+ mtr_debug("Reading pid file '$pid_file_path' " .
+ "($cur_attempt of $TOTAL_ATTEMPTS)...");
+
+ open(FILE, '<', $pid_file_path)
+ or mtr_error("can't open file \"$pid_file_path\": $!");
+
+ # Read pid number from file
+ my $pid= <FILE>;
+ chomp $pid;
+ close FILE;
+
+ return $pid if $pid=~ /^(\d+)/;
+
+ mtr_debug("Pid file '$pid_file_path' does not yet contain pid number.\n" .
+ "Sleeping $timeout second(s) more...");
+
+ sleep($timeout);
+ }
+
+ mtr_error("Pid file '$pid_file_path' is corrupted. " .
+ "Can not retrieve PID in " .
+ ($timeout * $TOTAL_ATTEMPTS) . " seconds.");
+}
+
+sub mtr_get_opts_from_file ($) {
+ my $file= shift;
+
+ open(FILE,"<",$file) or mtr_error("can't open file \"$file\": $!");
+ my @args;
+ while ( <FILE> )
+ {
+ chomp;
+
+ # --set-variable=init_connect=set @a='a\\0c'
+ s/^\s+//; # Remove leading space
+ s/\s+$//; # Remove ending space
+
+ # This is strange, but we need to fill whitespace inside
+ # quotes with something, to remove later. We do this to
+ # be able to split on space. Else, we have trouble with
+ # options like
+ #
+ # --someopt="--insideopt1 --insideopt2"
+ #
+ # But still with this, we are not 100% sure it is right,
+ # we need a shell to do it right.
+
+# print STDERR "\n";
+# print STDERR "AAA: $_\n";
+
+ s/\'([^\'\"]*)\'/unspace($1,"\x0a")/ge;
+ s/\"([^\'\"]*)\"/unspace($1,"\x0b")/ge;
+ s/\'([^\'\"]*)\'/unspace($1,"\x0a")/ge;
+ s/\"([^\'\"]*)\"/unspace($1,"\x0b")/ge;
+
+# print STDERR "BBB: $_\n";
+
+# foreach my $arg (/(--?\w.*?)(?=\s+--?\w|$)/)
+
+ # FIXME ENV vars should be expanded!!!!
+
+ foreach my $arg (split(/[ \t]+/))
+ {
+ $arg =~ tr/\x11\x0a\x0b/ \'\"/; # Put back real chars
+ # The outermost quotes has to go
+ $arg =~ s/^([^\'\"]*)\'(.*)\'([^\'\"]*)$/$1$2$3/
+ or $arg =~ s/^([^\'\"]*)\"(.*)\"([^\'\"]*)$/$1$2$3/;
+ $arg =~ s/\\\\/\\/g;
+
+ $arg =~ s/\$\{(\w+)\}/envsubst($1)/ge;
+ $arg =~ s/\$(\w+)/envsubst($1)/ge;
+
+# print STDERR "ARG: $arg\n";
+ # Do not pass empty string since my_getopt is not capable to handle it.
+ if (length($arg))
+ {
+ push(@args, $arg)
+ }
+ }
+ }
+ close FILE;
+ return \@args;
+}
+
+sub envsubst {
+ my $string= shift;
+
+ if ( ! defined $ENV{$string} )
+ {
+ mtr_error("opt file referense \$$string that is unknown");
+ }
+
+ return $ENV{$string};
+}
+
+sub unspace {
+ my $string= shift;
+ my $quote= shift;
+ $string =~ s/[ \t]/\x11/g;
+ return "$quote$string$quote";
+}
+
+# Read a whole file, stripping leading and trailing whitespace.
+sub mtr_fromfile ($) {
+ my $file= shift;
+
+ open(FILE,"<",$file) or mtr_error("can't open file \"$file\": $!");
+ my $text= join('', <FILE>);
+ close FILE;
+ $text =~ s/^\s+//; # Remove starting space, incl newlines
+ $text =~ s/\s+$//; # Remove ending space, incl newlines
+ return $text;
+}
+
+sub mtr_lastlinefromfile ($) {
+ my $file= shift;
+ my $text;
+
+ open(FILE,"<",$file) or mtr_error("can't open file \"$file\": $!");
+ while (my $line= <FILE>)
+ {
+ $text= $line;
+ }
+ close FILE;
+ return $text;
+}
+
+
+sub mtr_tofile ($@) {
+ my $file= shift;
+
+ open(FILE,">>",$file) or mtr_error("can't open file \"$file\": $!");
+ print FILE join("", @_);
+ close FILE;
+}
+
+sub mtr_tonewfile ($@) {
+ my $file= shift;
+
+ open(FILE,">",$file) or mtr_error("can't open file \"$file\": $!");
+ print FILE join("", @_);
+ close FILE;
+}
+
+sub mtr_appendfile_to_file ($$) {
+ my $from_file= shift;
+ my $to_file= shift;
+
+ open(TOFILE,">>",$to_file) or mtr_error("can't open file \"$to_file\": $!");
+ open(FROMFILE,"<",$from_file)
+ or mtr_error("can't open file \"$from_file\": $!");
+ print TOFILE while (<FROMFILE>);
+ close FROMFILE;
+ close TOFILE;
+}
+
+# Read a whole file verbatim.
+sub mtr_grab_file($) {
+ my $file= shift;
+ open(FILE, '<', $file)
+ or return undef;
+ local $/= undef;
+ my $data= scalar(<FILE>);
+ close FILE;
+ return $data;
+}
+
+
+1;
diff --git a/mysql-test/lib/mtr_match.pl b/mysql-test/lib/v1/mtr_match.pl
index 96aa43f4fa2..96aa43f4fa2 100644
--- a/mysql-test/lib/mtr_match.pl
+++ b/mysql-test/lib/v1/mtr_match.pl
diff --git a/mysql-test/lib/v1/mtr_misc.pl b/mysql-test/lib/v1/mtr_misc.pl
new file mode 100644
index 00000000000..0173e8b8572
--- /dev/null
+++ b/mysql-test/lib/v1/mtr_misc.pl
@@ -0,0 +1,312 @@
+# -*- cperl -*-
+# Copyright (C) 2004-2006 MySQL AB
+#
+# 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-1301 USA
+
+# This is a library file used by the Perl version of mysql-test-run,
+# and is part of the translation of the Bourne shell script with the
+# same name.
+
+use strict;
+use File::Find;
+
+sub mtr_native_path($);
+sub mtr_init_args ($);
+sub mtr_add_arg ($$@);
+sub mtr_path_exists(@);
+sub mtr_script_exists(@);
+sub mtr_file_exists(@);
+sub mtr_exe_exists(@);
+sub mtr_exe_maybe_exists(@);
+sub mtr_copy_dir($$);
+sub mtr_rmtree($);
+sub mtr_same_opts($$);
+sub mtr_cmp_opts($$);
+
+##############################################################################
+#
+# Misc
+#
+##############################################################################
+
+# Convert path to OS native format
+sub mtr_native_path($)
+{
+ my $path= shift;
+
+ # MySQL version before 5.0 still use cygwin, no need
+ # to convert path
+ return $path
+ if ($::mysql_version_id < 50000);
+
+ $path=~ s/\//\\/g
+ if ($::glob_win32);
+ return $path;
+}
+
+
+# FIXME move to own lib
+
+sub mtr_init_args ($) {
+ my $args = shift;
+ $$args = []; # Empty list
+}
+
+sub mtr_add_arg ($$@) {
+ my $args= shift;
+ my $format= shift;
+ my @fargs = @_;
+
+ push(@$args, sprintf($format, @fargs));
+}
+
+##############################################################################
+
+#
+# NOTE! More specific paths should be given before less specific.
+# For example /client/debug should be listed before /client
+#
+sub mtr_path_exists (@) {
+ foreach my $path ( @_ )
+ {
+ return $path if -e $path;
+ }
+ if ( @_ == 1 )
+ {
+ mtr_error("Could not find $_[0]");
+ }
+ else
+ {
+ mtr_error("Could not find any of " . join(" ", @_));
+ }
+}
+
+
+#
+# NOTE! More specific paths should be given before less specific.
+# For example /client/debug should be listed before /client
+#
+sub mtr_script_exists (@) {
+ foreach my $path ( @_ )
+ {
+ if($::glob_win32)
+ {
+ return $path if -f $path;
+ }
+ else
+ {
+ return $path if -x $path;
+ }
+ }
+ if ( @_ == 1 )
+ {
+ mtr_error("Could not find $_[0]");
+ }
+ else
+ {
+ mtr_error("Could not find any of " . join(" ", @_));
+ }
+}
+
+
+#
+# NOTE! More specific paths should be given before less specific.
+# For example /client/debug should be listed before /client
+#
+sub mtr_file_exists (@) {
+ foreach my $path ( @_ )
+ {
+ return $path if -e $path;
+ }
+ return "";
+}
+
+
+#
+# NOTE! More specific paths should be given before less specific.
+# For example /client/debug should be listed before /client
+#
+sub mtr_exe_maybe_exists (@) {
+ my @path= @_;
+
+ map {$_.= ".exe"} @path if $::glob_win32;
+ map {$_.= ".nlm"} @path if $::glob_netware;
+ foreach my $path ( @path )
+ {
+ if($::glob_win32)
+ {
+ return $path if -f $path;
+ }
+ else
+ {
+ return $path if -x $path;
+ }
+ }
+ return "";
+}
+
+
+#
+# NOTE! More specific paths should be given before less specific.
+# For example /client/debug should be listed before /client
+#
+sub mtr_exe_exists (@) {
+ my @path= @_;
+ if (my $path= mtr_exe_maybe_exists(@path))
+ {
+ return $path;
+ }
+ # Could not find exe, show error
+ if ( @path == 1 )
+ {
+ mtr_error("Could not find $path[0]");
+ }
+ else
+ {
+ mtr_error("Could not find any of " . join(" ", @path));
+ }
+}
+
+
+sub mtr_copy_dir($$) {
+ my $from_dir= shift;
+ my $to_dir= shift;
+
+ # mtr_verbose("Copying from $from_dir to $to_dir");
+
+ mkpath("$to_dir");
+ opendir(DIR, "$from_dir")
+ or mtr_error("Can't find $from_dir$!");
+ for(readdir(DIR)) {
+ next if "$_" eq "." or "$_" eq "..";
+ if ( -d "$from_dir/$_" )
+ {
+ mtr_copy_dir("$from_dir/$_", "$to_dir/$_");
+ next;
+ }
+ copy("$from_dir/$_", "$to_dir/$_");
+ }
+ closedir(DIR);
+
+}
+
+
+sub mtr_rmtree($) {
+ my ($dir)= @_;
+ mtr_verbose("mtr_rmtree: $dir");
+
+ # Try to use File::Path::rmtree. Recent versions
+ # handles removal of directories and files that don't
+ # have full permissions, while older versions
+ # may have a problem with that and we use our own version
+
+ eval { rmtree($dir); };
+ if ( $@ ) {
+ mtr_warning("rmtree($dir) failed, trying with File::Find...");
+
+ my $errors= 0;
+
+ # chmod
+ find( {
+ no_chdir => 1,
+ wanted => sub {
+ chmod(0777, $_)
+ or mtr_warning("couldn't chmod(0777, $_): $!") and $errors++;
+ }
+ },
+ $dir
+ );
+
+ # rm
+ finddepth( {
+ no_chdir => 1,
+ wanted => sub {
+ my $file= $_;
+ # Use special underscore (_) filehandle, caches stat info
+ if (!-l $file and -d _ ) {
+ rmdir($file) or
+ mtr_warning("couldn't rmdir($file): $!") and $errors++;
+ } else {
+ unlink($file)
+ or mtr_warning("couldn't unlink($file): $!") and $errors++;
+ }
+ }
+ },
+ $dir
+ );
+
+ mtr_error("Failed to remove '$dir'") if $errors;
+
+ mtr_report("OK, that worked!");
+ }
+}
+
+
+sub mtr_same_opts ($$) {
+ my $l1= shift;
+ my $l2= shift;
+ return mtr_cmp_opts($l1,$l2) == 0;
+}
+
+sub mtr_cmp_opts ($$) {
+ my $l1= shift;
+ my $l2= shift;
+
+ my @l1= @$l1;
+ my @l2= @$l2;
+
+ return -1 if @l1 < @l2;
+ return 1 if @l1 > @l2;
+
+ while ( @l1 ) # Same length
+ {
+ my $e1= shift @l1;
+ my $e2= shift @l2;
+ my $cmp= ($e1 cmp $e2);
+ return $cmp if $cmp != 0;
+ }
+
+ return 0; # They are the same
+}
+
+#
+# Compare two arrays and put all unequal elements into a new one
+#
+sub mtr_diff_opts ($$) {
+ my $l1= shift;
+ my $l2= shift;
+ my $f;
+ my $l= [];
+ foreach my $e1 (@$l1)
+ {
+ $f= undef;
+ foreach my $e2 (@$l2)
+ {
+ $f= 1 unless ($e1 ne $e2);
+ }
+ push(@$l, $e1) unless (defined $f);
+ }
+ foreach my $e2 (@$l2)
+ {
+ $f= undef;
+ foreach my $e1 (@$l1)
+ {
+ $f= 1 unless ($e1 ne $e2);
+ }
+ push(@$l, $e2) unless (defined $f);
+ }
+ return $l;
+}
+
+1;
diff --git a/mysql-test/lib/v1/mtr_process.pl b/mysql-test/lib/v1/mtr_process.pl
new file mode 100644
index 00000000000..8fd900330da
--- /dev/null
+++ b/mysql-test/lib/v1/mtr_process.pl
@@ -0,0 +1,1142 @@
+# -*- cperl -*-
+# Copyright (C) 2004-2006 MySQL AB
+#
+# 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-1301 USA
+
+# This is a library file used by the Perl version of mysql-test-run,
+# and is part of the translation of the Bourne shell script with the
+# same name.
+
+use Socket;
+use Errno;
+use strict;
+
+use POSIX qw(WNOHANG SIGHUP);
+
+sub mtr_run ($$$$$$;$);
+sub mtr_spawn ($$$$$$;$);
+sub mtr_check_stop_servers ($);
+sub mtr_kill_leftovers ();
+sub mtr_wait_blocking ($);
+sub mtr_record_dead_children ();
+sub mtr_ndbmgm_start($$);
+sub mtr_mysqladmin_start($$$);
+sub mtr_exit ($);
+sub sleep_until_file_created ($$$);
+sub mtr_kill_processes ($);
+sub mtr_ping_with_timeout($);
+sub mtr_ping_port ($);
+
+# Local function
+sub spawn_impl ($$$$$$$);
+
+##############################################################################
+#
+# Execute an external command
+#
+##############################################################################
+
+sub mtr_run ($$$$$$;$) {
+ my $path= shift;
+ my $arg_list_t= shift;
+ my $input= shift;
+ my $output= shift;
+ my $error= shift;
+ my $pid_file= shift; # Not used
+ my $spawn_opts= shift;
+
+ return spawn_impl($path,$arg_list_t,'run',$input,$output,$error,
+ $spawn_opts);
+}
+
+sub mtr_run_test ($$$$$$;$) {
+ my $path= shift;
+ my $arg_list_t= shift;
+ my $input= shift;
+ my $output= shift;
+ my $error= shift;
+ my $pid_file= shift; # Not used
+ my $spawn_opts= shift;
+
+ return spawn_impl($path,$arg_list_t,'test',$input,$output,$error,
+ $spawn_opts);
+}
+
+sub mtr_spawn ($$$$$$;$) {
+ my $path= shift;
+ my $arg_list_t= shift;
+ my $input= shift;
+ my $output= shift;
+ my $error= shift;
+ my $pid_file= shift; # Not used
+ my $spawn_opts= shift;
+
+ return spawn_impl($path,$arg_list_t,'spawn',$input,$output,$error,
+ $spawn_opts);
+}
+
+
+
+sub spawn_impl ($$$$$$$) {
+ my $path= shift;
+ my $arg_list_t= shift;
+ my $mode= shift;
+ my $input= shift;
+ my $output= shift;
+ my $error= shift;
+ my $spawn_opts= shift;
+
+ if ( $::opt_script_debug )
+ {
+ mtr_report("");
+ mtr_debug("-" x 73);
+ mtr_debug("STDIN $input") if $input;
+ mtr_debug("STDOUT $output") if $output;
+ mtr_debug("STDERR $error") if $error;
+ mtr_debug("$mode: $path ", join(" ",@$arg_list_t));
+ mtr_debug("spawn options:");
+ if ($spawn_opts)
+ {
+ foreach my $key (sort keys %{$spawn_opts})
+ {
+ mtr_debug(" - $key: $spawn_opts->{$key}");
+ }
+ }
+ else
+ {
+ mtr_debug(" none");
+ }
+ mtr_debug("-" x 73);
+ mtr_report("");
+ }
+
+ mtr_error("Can't spawn with empty \"path\"") unless defined $path;
+
+
+ FORK:
+ {
+ my $pid= fork();
+
+ if ( ! defined $pid )
+ {
+ if ( $! == $!{EAGAIN} ) # See "perldoc Errno"
+ {
+ mtr_warning("Got EAGAIN from fork(), sleep 1 second and redo");
+ sleep(1);
+ redo FORK;
+ }
+
+ mtr_error("$path ($pid) can't be forked, error: $!");
+
+ }
+
+ if ( $pid )
+ {
+ select(STDOUT) if $::glob_win32_perl;
+ return spawn_parent_impl($pid,$mode,$path);
+ }
+ else
+ {
+ # Child, redirect output and exec
+
+ $SIG{INT}= 'DEFAULT'; # Parent do some stuff, we don't
+
+ my $log_file_open_mode = '>';
+
+ if ($spawn_opts and $spawn_opts->{'append_log_file'})
+ {
+ $log_file_open_mode = '>>';
+ }
+
+ if ( $output )
+ {
+ if ( $::glob_win32_perl )
+ {
+ # Don't redirect stdout on ActiveState perl since this is
+ # just another thread in the same process.
+ }
+ elsif ( ! open(STDOUT,$log_file_open_mode,$output) )
+ {
+ mtr_child_error("can't redirect STDOUT to \"$output\": $!");
+ }
+ }
+
+ if ( $error )
+ {
+ if ( !$::glob_win32_perl and $output eq $error )
+ {
+ if ( ! open(STDERR,">&STDOUT") )
+ {
+ mtr_child_error("can't dup STDOUT: $!");
+ }
+ }
+ else
+ {
+ if ( ! open(STDERR,$log_file_open_mode,$error) )
+ {
+ mtr_child_error("can't redirect STDERR to \"$error\": $!");
+ }
+ }
+ }
+
+ if ( $input )
+ {
+ if ( ! open(STDIN,"<",$input) )
+ {
+ mtr_child_error("can't redirect STDIN to \"$input\": $!");
+ }
+ }
+
+ if ( ! exec($path,@$arg_list_t) )
+ {
+ mtr_child_error("failed to execute \"$path\": $!");
+ }
+ mtr_error("Should never come here 1!");
+ }
+ mtr_error("Should never come here 2!");
+ }
+ mtr_error("Should never come here 3!");
+}
+
+
+sub spawn_parent_impl {
+ my $pid= shift;
+ my $mode= shift;
+ my $path= shift;
+
+ if ( $mode eq 'run' or $mode eq 'test' )
+ {
+ if ( $mode eq 'run' )
+ {
+ # Simple run of command, wait blocking for it to return
+ my $ret_pid= waitpid($pid,0);
+ if ( $ret_pid != $pid )
+ {
+ # The "simple" waitpid has failed, print debug info
+ # and try to handle the error
+ mtr_warning("waitpid($pid, 0) returned $ret_pid " .
+ "when waiting for '$path', error: '$!'");
+ if ( $ret_pid == -1 )
+ {
+ # waitpid returned -1, that would indicate the process
+ # no longer exist and waitpid couldn't wait for it.
+ return 1;
+ }
+ mtr_error("Error handling failed");
+ }
+
+ return mtr_process_exit_status($?);
+ }
+ else
+ {
+ # We run mysqltest and wait for it to return. But we try to
+ # catch dying mysqld processes as well.
+ #
+ # We do blocking waitpid() until we get the return from the
+ # "mysqltest" call. But if a mysqld process dies that we
+ # started, we take this as an error, and kill mysqltest.
+
+
+ my $exit_value= -1;
+ my $saved_exit_value;
+ my $ret_pid; # What waitpid() returns
+
+ while ( ($ret_pid= waitpid(-1,0)) != -1 )
+ {
+ # Someone terminated, don't know who. Collect
+ # status info first before $? is lost,
+ # but not $exit_value, this is flagged from
+
+ my $timer_name= mtr_timer_timeout($::glob_timers, $ret_pid);
+ if ( $timer_name )
+ {
+ if ( $timer_name eq "suite" )
+ {
+ # We give up here
+ # FIXME we should only give up the suite, not all of the run?
+ print STDERR "\n";
+ mtr_error("Test suite timeout");
+ }
+ elsif ( $timer_name eq "testcase" )
+ {
+ $saved_exit_value= 63; # Mark as timeout
+ kill(9, $pid); # Kill mysqltest
+ next; # Go on and catch the termination
+ }
+ }
+
+ if ( $ret_pid == $pid )
+ {
+ # We got termination of mysqltest, we are done
+ $exit_value= mtr_process_exit_status($?);
+ last;
+ }
+
+ # One of the child processes died, unless this was expected
+ # mysqltest should be killed and test aborted
+
+ check_expected_crash_and_restart($ret_pid);
+ }
+
+ if ( $ret_pid != $pid )
+ {
+ # We terminated the waiting because a "mysqld" process died.
+ # Kill the mysqltest process.
+ mtr_verbose("Kill mysqltest because another process died");
+ kill(9,$pid);
+
+ $ret_pid= waitpid($pid,0);
+
+ if ( $ret_pid != $pid )
+ {
+ mtr_error("$path ($pid) got lost somehow");
+ }
+ }
+
+ return $saved_exit_value || $exit_value;
+ }
+ }
+ else
+ {
+ # We spawned a process we don't wait for
+ return $pid;
+ }
+}
+
+
+# ----------------------------------------------------------------------
+# We try to emulate how an Unix shell calculates the exit code
+# ----------------------------------------------------------------------
+
+sub mtr_process_exit_status {
+ my $raw_status= shift;
+
+ if ( $raw_status & 127 )
+ {
+ return ($raw_status & 127) + 128; # Signal num + 128
+ }
+ else
+ {
+ return $raw_status >> 8; # Exit code
+ }
+}
+
+
+##############################################################################
+#
+# Kill processes left from previous runs
+#
+##############################################################################
+
+
+# Kill all processes(mysqld, ndbd, ndb_mgmd and im) that would conflict with
+# this run
+# Make sure to remove the PID file, if any.
+# kill IM manager first, else it will restart the servers
+sub mtr_kill_leftovers () {
+
+ mtr_report("Killing Possible Leftover Processes");
+ mtr_debug("mtr_kill_leftovers(): started.");
+
+ my @kill_pids;
+ my %admin_pids;
+
+ foreach my $srv (@{$::master}, @{$::slave})
+ {
+ mtr_debug(" - mysqld " .
+ "(pid: $srv->{pid}; " .
+ "pid file: '$srv->{path_pid}'; " .
+ "socket: '$srv->{path_sock}'; ".
+ "port: $srv->{port})");
+
+ my $pid= mtr_mysqladmin_start($srv, "shutdown", 20);
+
+ # Save the pid of the mysqladmin process
+ $admin_pids{$pid}= 1;
+
+ push(@kill_pids,{
+ pid => $srv->{'pid'},
+ pidfile => $srv->{'path_pid'},
+ sockfile => $srv->{'path_sock'},
+ port => $srv->{'port'},
+ });
+ $srv->{'pid'}= 0; # Assume we are done with it
+ }
+
+ if ( ! $::opt_skip_ndbcluster )
+ {
+
+ foreach my $cluster (@{$::clusters})
+ {
+
+ # Don't shut down a "running" cluster
+ next if $cluster->{'use_running'};
+
+ mtr_debug(" - cluster " .
+ "(pid: $cluster->{pid}; " .
+ "pid file: '$cluster->{path_pid})");
+
+ my $pid= mtr_ndbmgm_start($cluster, "shutdown");
+
+ # Save the pid of the ndb_mgm process
+ $admin_pids{$pid}= 1;
+
+ push(@kill_pids,{
+ pid => $cluster->{'pid'},
+ pidfile => $cluster->{'path_pid'}
+ });
+
+ $cluster->{'pid'}= 0; # Assume we are done with it
+
+ foreach my $ndbd (@{$cluster->{'ndbds'}})
+ {
+ mtr_debug(" - ndbd " .
+ "(pid: $ndbd->{pid}; " .
+ "pid file: '$ndbd->{path_pid})");
+
+ push(@kill_pids,{
+ pid => $ndbd->{'pid'},
+ pidfile => $ndbd->{'path_pid'},
+ });
+ $ndbd->{'pid'}= 0; # Assume we are done with it
+ }
+ }
+ }
+
+ # Wait for all the admin processes to complete
+ mtr_wait_blocking(\%admin_pids);
+
+ # If we trusted "mysqladmin --shutdown_timeout= ..." we could just
+ # terminate now, but we don't (FIXME should be debugged).
+ # So we try again to ping and at least wait the same amount of time
+ # mysqladmin would for all to die.
+
+ mtr_ping_with_timeout(\@kill_pids);
+
+ # We now have tried to terminate nice. We have waited for the listen
+ # port to be free, but can't really tell if the mysqld process died
+ # or not. We now try to find the process PID from the PID file, and
+ # send a kill to that process. Note that Perl let kill(0,@pids) be
+ # a way to just return the numer of processes the kernel can send
+ # signals to. So this can be used (except on Cygwin) to determine
+ # if there are processes left running that we cound out might exists.
+ #
+ # But still after all this work, all we know is that we have
+ # the ports free.
+
+ # We scan the "var/run/" directory for other process id's to kill
+
+ my $rundir= "$::opt_vardir/run";
+
+ mtr_debug("Processing PID files in directory '$rundir'...");
+
+ if ( -d $rundir )
+ {
+ opendir(RUNDIR, $rundir)
+ or mtr_error("can't open directory \"$rundir\": $!");
+
+ my @pids;
+
+ while ( my $elem= readdir(RUNDIR) )
+ {
+ # Only read pid from files that end with .pid
+ if ( $elem =~ /.*[.]pid$/)
+ {
+ my $pidfile= "$rundir/$elem";
+
+ if ( -f $pidfile )
+ {
+ mtr_debug("Processing PID file: '$pidfile'...");
+
+ my $pid= mtr_get_pid_from_file($pidfile);
+
+ mtr_debug("Got pid: $pid from file '$pidfile'");
+
+ if ( $::glob_cygwin_perl or kill(0, $pid) )
+ {
+ mtr_debug("There is process with pid $pid -- scheduling for kill.");
+ push(@pids, $pid); # We know (cygwin guess) it exists
+ }
+ else
+ {
+ mtr_debug("There is no process with pid $pid -- skipping.");
+ }
+ }
+ }
+ else
+ {
+ mtr_warning("Found non pid file $elem in $rundir")
+ if -f "$rundir/$elem";
+ next;
+ }
+ }
+ closedir(RUNDIR);
+
+ if ( @pids )
+ {
+ mtr_debug("Killing the following processes with PID files: " .
+ join(' ', @pids) . "...");
+
+ start_reap_all();
+
+ if ( $::glob_cygwin_perl )
+ {
+ # We have no (easy) way of knowing the Cygwin controlling
+ # process, in the PID file we only have the Windows process id.
+ system("kill -f " . join(" ",@pids)); # Hope for the best....
+ mtr_debug("Sleep 5 seconds waiting for processes to die");
+ sleep(5);
+ }
+ else
+ {
+ my $retries= 10; # 10 seconds
+ do
+ {
+ mtr_debug("Sending SIGKILL to pids: " . join(' ', @pids));
+ kill(9, @pids);
+ mtr_report("Sleep 1 second waiting for processes to die");
+ sleep(1) # Wait one second
+ } while ( $retries-- and kill(0, @pids) );
+
+ if ( kill(0, @pids) ) # Check if some left
+ {
+ mtr_warning("can't kill process(es) " . join(" ", @pids));
+ }
+ }
+
+ stop_reap_all();
+ }
+ }
+ else
+ {
+ mtr_debug("Directory for PID files ($rundir) does not exist.");
+ }
+
+ # We may have failed everything, but we now check again if we have
+ # the listen ports free to use, and if they are free, just go for it.
+
+ mtr_debug("Checking known mysqld servers...");
+
+ foreach my $srv ( @kill_pids )
+ {
+ if ( defined $srv->{'port'} and mtr_ping_port($srv->{'port'}) )
+ {
+ mtr_warning("can't kill old process holding port $srv->{'port'}");
+ }
+ }
+
+ mtr_debug("mtr_kill_leftovers(): finished.");
+}
+
+
+#
+# Check that all processes in "spec" are shutdown gracefully
+# else kill them off hard
+#
+sub mtr_check_stop_servers ($) {
+ my $spec= shift;
+
+ # Return if no processes are defined
+ return if ! @$spec;
+
+ mtr_verbose("mtr_check_stop_servers");
+
+ # ----------------------------------------------------------------------
+ # Wait until servers in "spec" has stopped listening
+ # to their ports or timeout occurs
+ # ----------------------------------------------------------------------
+ mtr_ping_with_timeout(\@$spec);
+
+ # ----------------------------------------------------------------------
+ # Use waitpid() nonblocking for a little while, to see how
+ # many process's will exit sucessfully.
+ # This is the normal case.
+ # ----------------------------------------------------------------------
+ my $wait_counter= 50; # Max number of times to redo the loop
+ foreach my $srv ( @$spec )
+ {
+ my $pid= $srv->{'pid'};
+ my $ret_pid;
+ if ( $pid )
+ {
+ $ret_pid= waitpid($pid,&WNOHANG);
+ if ($ret_pid == $pid)
+ {
+ mtr_verbose("Caught exit of process $ret_pid");
+ $srv->{'pid'}= 0;
+ }
+ elsif ($ret_pid == 0)
+ {
+ mtr_verbose("Process $pid is still alive");
+ if ($wait_counter-- > 0)
+ {
+ # Give the processes more time to exit
+ select(undef, undef, undef, (0.1));
+ redo;
+ }
+ }
+ else
+ {
+ mtr_warning("caught exit of unknown child $ret_pid");
+ }
+ }
+ }
+
+ # ----------------------------------------------------------------------
+ # The processes that haven't yet exited need to
+ # be killed hard, put them in "kill_pids" hash
+ # ----------------------------------------------------------------------
+ my %kill_pids;
+ foreach my $srv ( @$spec )
+ {
+ my $pid= $srv->{'pid'};
+ if ( $pid )
+ {
+ # Server is still alive, put it in list to be hard killed
+ if ($::glob_win32_perl)
+ {
+ # Kill the real process if it's known
+ $pid= $srv->{'real_pid'} if ($srv->{'real_pid'});
+ }
+ $kill_pids{$pid}= 1;
+
+ # Write a message to the process's error log (if it has one)
+ # that it's being killed hard.
+ if ( defined $srv->{'errfile'} )
+ {
+ mtr_tofile($srv->{'errfile'}, "Note: Forcing kill of process $pid\n");
+ }
+ mtr_warning("Forcing kill of process $pid");
+
+ }
+ else
+ {
+ # Server is dead, remove the pidfile if it exists
+ #
+ # Race, could have been removed between test with -f
+ # and the unlink() below, so better check again with -f
+ if ( -f $srv->{'pidfile'} and ! unlink($srv->{'pidfile'}) and
+ -f $srv->{'pidfile'} )
+ {
+ mtr_error("can't remove $srv->{'pidfile'}");
+ }
+ }
+ }
+
+ if ( ! keys %kill_pids )
+ {
+ # All processes has exited gracefully
+ return;
+ }
+
+ mtr_kill_processes(\%kill_pids);
+
+ # ----------------------------------------------------------------------
+ # All processes are killed, cleanup leftover files
+ # ----------------------------------------------------------------------
+ {
+ my $errors= 0;
+ foreach my $srv ( @$spec )
+ {
+ if ( $srv->{'pid'} )
+ {
+ # Server has been hard killed, clean it's resources
+ foreach my $file ($srv->{'pidfile'}, $srv->{'sockfile'})
+ {
+ # Know it is dead so should be no race, careful anyway
+ if ( defined $file and -f $file and ! unlink($file) and -f $file )
+ {
+ $errors++;
+ mtr_warning("couldn't delete $file");
+ }
+ }
+
+ if ($::glob_win32_perl and $srv->{'real_pid'})
+ {
+ # Wait for the pseudo pid - if the real_pid was known
+ # the pseudo pid has not been waited for yet, wai blocking
+ # since it's "such a simple program"
+ mtr_verbose("Wait for pseudo process $srv->{'pid'}");
+ my $ret_pid= waitpid($srv->{'pid'}, 0);
+ mtr_verbose("Pseudo process $ret_pid died");
+ }
+
+ $srv->{'pid'}= 0;
+ }
+ }
+ if ( $errors )
+ {
+ # There where errors killing processes
+ # do one last attempt to ping the servers
+ # and if they can't be pinged, assume they are dead
+ if ( ! mtr_ping_with_timeout( \@$spec ) )
+ {
+ mtr_error("we could not kill or clean up all processes");
+ }
+ else
+ {
+ mtr_verbose("All ports were free, continuing");
+ }
+ }
+ }
+}
+
+
+# Wait for all the process in the list to terminate
+sub mtr_wait_blocking($) {
+ my $admin_pids= shift;
+
+
+ # Return if no processes defined
+ return if ! %$admin_pids;
+
+ mtr_verbose("mtr_wait_blocking");
+
+ # Wait for all the started processes to exit
+ # As mysqladmin is such a simple program, we trust it to terminate itself.
+ # I.e. we wait blocking, and wait for them all before we go on.
+ foreach my $pid (keys %{$admin_pids})
+ {
+ my $ret_pid= waitpid($pid,0);
+
+ }
+}
+
+# Start "mysqladmin <command>" for a specific mysqld
+sub mtr_mysqladmin_start($$$) {
+ my $srv= shift;
+ my $command= shift;
+ my $adm_shutdown_tmo= shift;
+
+ my $args;
+ mtr_init_args(\$args);
+
+ mtr_add_arg($args, "--no-defaults");
+ mtr_add_arg($args, "--user=%s", $::opt_user);
+ mtr_add_arg($args, "--password=");
+ mtr_add_arg($args, "--silent");
+ if ( -e $srv->{'path_sock'} )
+ {
+ mtr_add_arg($args, "--socket=%s", $srv->{'path_sock'});
+ }
+ if ( $srv->{'port'} )
+ {
+ mtr_add_arg($args, "--port=%s", $srv->{'port'});
+ }
+ if ( $srv->{'port'} and ! -e $srv->{'path_sock'} )
+ {
+ mtr_add_arg($args, "--protocol=tcp"); # Needed if no --socket
+ }
+ mtr_add_arg($args, "--connect_timeout=5");
+
+ # Shutdown time must be high as slave may be in reconnect
+ mtr_add_arg($args, "--shutdown_timeout=$adm_shutdown_tmo");
+ mtr_add_arg($args, "$command");
+ my $pid= mtr_spawn($::exe_mysqladmin, $args,
+ "", "", "", "",
+ { append_log_file => 1 });
+ mtr_verbose("mtr_mysqladmin_start, pid: $pid");
+ return $pid;
+
+}
+
+# Start "ndb_mgm shutdown" for a specific cluster, it will
+# shutdown all data nodes and leave the ndb_mgmd running
+sub mtr_ndbmgm_start($$) {
+ my $cluster= shift;
+ my $command= shift;
+
+ my $args;
+
+ mtr_init_args(\$args);
+
+ mtr_add_arg($args, "--no-defaults");
+ mtr_add_arg($args, "--core");
+ mtr_add_arg($args, "--try-reconnect=1");
+ mtr_add_arg($args, "--ndb_connectstring=%s", $cluster->{'connect_string'});
+ mtr_add_arg($args, "-e");
+ mtr_add_arg($args, "$command");
+
+ my $pid= mtr_spawn($::exe_ndb_mgm, $args,
+ "", "/dev/null", "/dev/null", "",
+ {});
+ mtr_verbose("mtr_ndbmgm_start, pid: $pid");
+ return $pid;
+
+}
+
+
+# Ping all servers in list, exit when none of them answers
+# or when timeout has passed
+sub mtr_ping_with_timeout($) {
+ my $spec= shift;
+ my $timeout= 200; # 20 seconds max
+ my $res= 1; # If we just fall through, we are done
+ # in the sense that the servers don't
+ # listen to their ports any longer
+
+ mtr_debug("Waiting for mysqld servers to stop...");
+
+ TIME:
+ while ( $timeout-- )
+ {
+ foreach my $srv ( @$spec )
+ {
+ $res= 1; # We are optimistic
+ if ( $srv->{'pid'} and defined $srv->{'port'} )
+ {
+ if ( mtr_ping_port($srv->{'port'}) )
+ {
+ mtr_verbose("waiting for process $srv->{'pid'} to stop ".
+ "using port $srv->{'port'}");
+
+ # Millisceond sleep emulated with select
+ select(undef, undef, undef, (0.1));
+ $res= 0;
+ next TIME;
+ }
+ else
+ {
+ # Process was not using port
+ }
+ }
+ }
+ last; # If we got here, we are done
+ }
+
+ if ($res)
+ {
+ mtr_debug("mtr_ping_with_timeout(): All mysqld instances are down.");
+ }
+ else
+ {
+ mtr_report("mtr_ping_with_timeout(): At least one server is alive.");
+ }
+
+ return $res;
+}
+
+
+#
+# Loop through our list of processes and look for and entry
+# with the provided pid
+# Set the pid of that process to 0 if found
+#
+sub mark_process_dead($)
+{
+ my $ret_pid= shift;
+
+ foreach my $mysqld (@{$::master}, @{$::slave})
+ {
+ if ( $mysqld->{'pid'} eq $ret_pid )
+ {
+ mtr_verbose("$mysqld->{'type'} $mysqld->{'idx'} exited, pid: $ret_pid");
+ $mysqld->{'pid'}= 0;
+ return;
+ }
+ }
+
+ foreach my $cluster (@{$::clusters})
+ {
+ if ( $cluster->{'pid'} eq $ret_pid )
+ {
+ mtr_verbose("$cluster->{'name'} cluster ndb_mgmd exited, pid: $ret_pid");
+ $cluster->{'pid'}= 0;
+ return;
+ }
+
+ foreach my $ndbd (@{$cluster->{'ndbds'}})
+ {
+ if ( $ndbd->{'pid'} eq $ret_pid )
+ {
+ mtr_verbose("$cluster->{'name'} cluster ndbd exited, pid: $ret_pid");
+ $ndbd->{'pid'}= 0;
+ return;
+ }
+ }
+ }
+ mtr_warning("mark_process_dead couldn't find an entry for pid: $ret_pid");
+
+}
+
+#
+# Loop through our list of processes and look for and entry
+# with the provided pid, if found check for the file indicating
+# expected crash and restart it.
+#
+sub check_expected_crash_and_restart($)
+{
+ my $ret_pid= shift;
+
+ foreach my $mysqld (@{$::master}, @{$::slave})
+ {
+ if ( $mysqld->{'pid'} eq $ret_pid )
+ {
+ mtr_verbose("$mysqld->{'type'} $mysqld->{'idx'} exited, pid: $ret_pid");
+ $mysqld->{'pid'}= 0;
+
+ # Check if crash expected and restart if it was
+ my $expect_file= "$::opt_vardir/tmp/" . "$mysqld->{'type'}" .
+ "$mysqld->{'idx'}" . ".expect";
+ if ( -f $expect_file )
+ {
+ mtr_verbose("Crash was expected, file $expect_file exists");
+ mysqld_start($mysqld, $mysqld->{'start_opts'},
+ $mysqld->{'start_slave_master_info'});
+ unlink($expect_file);
+ }
+
+ return;
+ }
+ }
+
+ foreach my $cluster (@{$::clusters})
+ {
+ if ( $cluster->{'pid'} eq $ret_pid )
+ {
+ mtr_verbose("$cluster->{'name'} cluster ndb_mgmd exited, pid: $ret_pid");
+ $cluster->{'pid'}= 0;
+
+ # Check if crash expected and restart if it was
+ my $expect_file= "$::opt_vardir/tmp/ndb_mgmd_" . "$cluster->{'type'}" .
+ ".expect";
+ if ( -f $expect_file )
+ {
+ mtr_verbose("Crash was expected, file $expect_file exists");
+ ndbmgmd_start($cluster);
+ unlink($expect_file);
+ }
+ return;
+ }
+
+ foreach my $ndbd (@{$cluster->{'ndbds'}})
+ {
+ if ( $ndbd->{'pid'} eq $ret_pid )
+ {
+ mtr_verbose("$cluster->{'name'} cluster ndbd exited, pid: $ret_pid");
+ $ndbd->{'pid'}= 0;
+
+ # Check if crash expected and restart if it was
+ my $expect_file= "$::opt_vardir/tmp/ndbd_" . "$cluster->{'type'}" .
+ "$ndbd->{'idx'}" . ".expect";
+ if ( -f $expect_file )
+ {
+ mtr_verbose("Crash was expected, file $expect_file exists");
+ ndbd_start($cluster, $ndbd->{'idx'},
+ $ndbd->{'start_extra_args'});
+ unlink($expect_file);
+ }
+ return;
+ }
+ }
+ }
+
+ if ($::instance_manager->{'spawner_pid'} eq $ret_pid)
+ {
+ return;
+ }
+
+ mtr_warning("check_expected_crash_and_restart couldn't find an entry for pid: $ret_pid");
+
+}
+
+##############################################################################
+#
+# The operating system will keep information about dead children,
+# we read this information here, and if we have records the process
+# is alive, we mark it as dead.
+#
+##############################################################################
+
+sub mtr_record_dead_children () {
+
+ my $process_died= 0;
+ my $ret_pid;
+
+ # Wait without blockinng to see if any processes had died
+ # -1 or 0 means there are no more procesess to wait for
+ while ( ($ret_pid= waitpid(-1,&WNOHANG)) != 0 and $ret_pid != -1)
+ {
+ mtr_warning("mtr_record_dead_children: $ret_pid");
+ mark_process_dead($ret_pid);
+ $process_died= 1;
+ }
+ return $process_died;
+}
+
+sub start_reap_all {
+ # This causes terminating processes to not become zombies, avoiding
+ # the need for (or possibility of) explicit waitpid().
+ $SIG{CHLD}= 'IGNORE';
+
+ # On some platforms (Linux, QNX, OSX, ...) there is potential race
+ # here. If a process terminated before setting $SIG{CHLD} (but after
+ # any attempt to waitpid() it), it will still be a zombie. So we
+ # have to handle any such process here.
+ my $pid;
+ while(($pid= waitpid(-1, &WNOHANG)) != 0 and $pid != -1)
+ {
+ mtr_warning("start_reap_all pid: $pid");
+ mark_process_dead($pid);
+ };
+}
+
+sub stop_reap_all {
+ $SIG{CHLD}= 'DEFAULT';
+}
+
+
+sub mtr_ping_port ($) {
+ my $port= shift;
+
+ mtr_verbose("mtr_ping_port: $port");
+
+ my $remote= "localhost";
+ my $iaddr= inet_aton($remote);
+ if ( ! $iaddr )
+ {
+ mtr_error("can't find IP number for $remote");
+ }
+ my $paddr= sockaddr_in($port, $iaddr);
+ my $proto= getprotobyname('tcp');
+ if ( ! socket(SOCK, PF_INET, SOCK_STREAM, $proto) )
+ {
+ mtr_error("can't create socket: $!");
+ }
+
+ mtr_debug("Pinging server (port: $port)...");
+
+ if ( connect(SOCK, $paddr) )
+ {
+ close(SOCK); # FIXME check error?
+ mtr_verbose("USED");
+ return 1;
+ }
+ else
+ {
+ mtr_verbose("FREE");
+ return 0;
+ }
+}
+
+##############################################################################
+#
+# Wait for a file to be created
+#
+##############################################################################
+
+# FIXME check that the pidfile contains the expected pid!
+
+sub sleep_until_file_created ($$$) {
+ my $pidfile= shift;
+ my $timeout= shift;
+ my $pid= shift;
+ my $sleeptime= 100; # Milliseconds
+ my $loops= ($timeout * 1000) / $sleeptime;
+
+ for ( my $loop= 1; $loop <= $loops; $loop++ )
+ {
+ if ( -r $pidfile )
+ {
+ return 1;
+ }
+
+ # Check if it died after the fork() was successful
+ if ( $pid != 0 && waitpid($pid,&WNOHANG) == $pid )
+ {
+ mtr_warning("Process $pid died");
+ return 0;
+ }
+
+ mtr_debug("Sleep $sleeptime milliseconds waiting for $pidfile");
+
+ # Print extra message every 60 seconds
+ my $seconds= ($loop * $sleeptime) / 1000;
+ if ( $seconds > 1 and int($seconds * 10) % 600 == 0 )
+ {
+ my $left= $timeout - $seconds;
+ mtr_warning("Waited $seconds seconds for $pidfile to be created, " .
+ "still waiting for $left seconds...");
+ }
+
+ # Millisceond sleep emulated with select
+ select(undef, undef, undef, ($sleeptime/1000));
+ }
+
+ return 0;
+}
+
+
+sub mtr_kill_processes ($) {
+ my $pids = shift;
+
+ mtr_verbose("mtr_kill_processes (" . join(" ", keys %{$pids}) . ")");
+
+ foreach my $pid (keys %{$pids})
+ {
+
+ if ($pid <= 0)
+ {
+ mtr_warning("Trying to kill illegal pid: $pid");
+ next;
+ }
+
+ my $signaled_procs= kill(9, $pid);
+ if ($signaled_procs == 0)
+ {
+ # No such process existed, assume it's killed
+ mtr_verbose("killed $pid(no such process)");
+ }
+ else
+ {
+ my $ret_pid= waitpid($pid,0);
+ if ($ret_pid == $pid)
+ {
+ mtr_verbose("killed $pid(got the pid)");
+ }
+ elsif ($ret_pid == -1)
+ {
+ mtr_verbose("killed $pid(got -1)");
+ }
+ }
+ }
+ mtr_verbose("done killing processes");
+}
+
+
+##############################################################################
+#
+# When we exit, we kill off all children
+#
+##############################################################################
+
+sub mtr_exit ($) {
+ my $code= shift;
+ mtr_timer_stop_all($::glob_timers);
+ local $SIG{HUP} = 'IGNORE';
+ # ToDo: Signalling -$$ will only work if we are the process group
+ # leader (in fact on QNX it will signal our session group leader,
+ # which might be Do-compile or Pushbuild, causing tests to be
+ # aborted). So we only do it if we are the group leader. We might
+ # set ourselves as the group leader at startup (with
+ # POSIX::setpgrp(0,0)), but then care must be needed to always do
+ # proper child process cleanup.
+ POSIX::kill(SIGHUP, -$$) if !$::glob_win32_perl and $$ == getpgrp();
+
+ exit($code);
+}
+
+###########################################################################
+
+1;
diff --git a/mysql-test/lib/mtr_report.pl b/mysql-test/lib/v1/mtr_report.pl
index 7daf6743b03..3c78c3ca064 100644
--- a/mysql-test/lib/mtr_report.pl
+++ b/mysql-test/lib/v1/mtr_report.pl
@@ -220,7 +220,7 @@ sub mtr_report_stats ($) {
# the "var/log/*.err" files. We save this info in "var/log/warnings"
# ----------------------------------------------------------------------
- if ( ! $::glob_use_running_server && !$::opt_extern)
+ if ( ! $::glob_use_running_server )
{
# Save and report if there was any fatal warnings/errors in err logs
@@ -376,29 +376,30 @@ sub mtr_report_stats ($) {
/Slave: Can't DROP 'c7'.* 1091/ or
/Slave: Key column 'c6'.* 1072/ or
- # rpl_idempotency.test produces warnings for the slave.
- ($testname eq 'rpl.rpl_idempotency' and
- (/Slave: Can\'t find record in \'t1\' Error_code: 1032/ or
- /Slave: Cannot add or update a child row: a foreign key constraint fails .* Error_code: 1452/
- )) or
-
- # These tests does "kill" on queries, causing sporadic errors when writing to logs
- (($testname eq 'rpl.rpl_skip_error' or
- $testname eq 'rpl.rpl_err_ignoredtable' or
- $testname eq 'binlog.binlog_killed_simulate' or
- $testname eq 'binlog.binlog_killed') and
- (/Failed to write to mysql\.\w+_log/
- )) or
+ # rpl_idempotency.test produces warnings for the slave.
+ ($testname eq 'rpl.rpl_idempotency' and
+ (/Slave: Can\'t find record in \'t1\' Error_code: 1032/ or
+ /Slave: Cannot add or update a child row: a foreign key constraint fails .* Error_code: 1452/
+ )) or
+
+ # These tests does "kill" on queries, causing sporadic errors when writing to logs
+ (($testname eq 'rpl.rpl_skip_error' or
+ $testname eq 'rpl.rpl_err_ignoredtable' or
+ $testname eq 'binlog.binlog_killed_simulate' or
+ $testname eq 'binlog.binlog_killed') and
+ (/Failed to write to mysql\.\w+_log/
+ )) or
# rpl_bug33931 has deliberate failures
($testname eq 'rpl.rpl_bug33931' and
(/Failed during slave.*thread initialization/
)) or
- # rpl_temporary has an error on slave that can be ignored
- ($testname eq 'rpl.rpl_temporary' and
- (/Slave: Can\'t find record in \'user\' Error_code: 1032/
- )) or
+ # rpl_temporary has an error on slave that can be ignored
+ ($testname eq 'rpl.rpl_temporary' and
+ (/Slave: Can\'t find record in \'user\' Error_code: 1032/
+ )) or
+
# Test case for Bug#31590 produces the following error:
/Out of sort memory; increase server sort buffer size/ or
@@ -411,18 +412,8 @@ sub mtr_report_stats ($) {
# When trying to set lower_case_table_names = 2
# on a case sensitive file system. Bug#37402.
- /lower_case_table_names was set to 2, even though your the file system '.*' is case sensitive. Now setting lower_case_table_names to 0 to avoid future problems./ or
-
- # maria-recovery.test has warning about missing log file
- /File '.*maria_log.000.*' not found \(Errcode: 2\)/ or
- # and about marked-corrupted table
- /Table '..mysqltest.t_corrupted1' is crashed, skipping it. Please repair it with maria_chk -r/ or
- # maria-recover.test corrupts tables on purpose
- /Checking table: '..mysqltest.t_corrupted2'/ or
- /Recovering table: '..mysqltest.t_corrupted2'/ or
- /Table '..mysqltest.t_corrupted2' is marked as crashed and should be repaired/ or
- /Incorrect key file for table '..mysqltest.t_corrupted2.MAI'; try to repair it/
- )
+ /lower_case_table_names was set to 2, even though your the file system '.*' is case sensitive. Now setting lower_case_table_names to 0 to avoid future problems./
+ )
{
next; # Skip these lines
}
diff --git a/mysql-test/lib/v1/mtr_stress.pl b/mysql-test/lib/v1/mtr_stress.pl
new file mode 100644
index 00000000000..93b06b32c5f
--- /dev/null
+++ b/mysql-test/lib/v1/mtr_stress.pl
@@ -0,0 +1,191 @@
+# -*- cperl -*-
+# Copyright (C) 2006 MySQL AB
+#
+# 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-1301 USA
+
+# This is a library file used by the Perl version of mysql-test-run,
+# and is part of the translation of the Bourne shell script with the
+# same name.
+
+use strict;
+use File::Spec;
+
+# These are not to be prefixed with "mtr_"
+
+sub run_stress_test ();
+
+##############################################################################
+#
+# Run tests in the stress mode
+#
+##############################################################################
+
+sub run_stress_test ()
+{
+
+ my $args;
+ my $stress_suitedir;
+
+ mtr_report("Starting stress testing\n");
+
+ if ( ! $::glob_use_embedded_server )
+ {
+ if ( ! mysqld_start($::master->[0],[],[]) )
+ {
+ mtr_error("Can't start the mysqld server");
+ }
+ }
+
+ my $stress_basedir=File::Spec->catdir($::opt_vardir, "stress");
+
+ #Clean up stress dir
+ if ( -d $stress_basedir )
+ {
+ rmtree($stress_basedir);
+ }
+ mkpath($stress_basedir);
+
+ if ($::opt_stress_suite ne 'main' && $::opt_stress_suite ne 'default' )
+ {
+ $stress_suitedir=File::Spec->catdir($::glob_mysql_test_dir, "suite",
+ $::opt_stress_suite);
+ }
+ else
+ {
+ $stress_suitedir=$::glob_mysql_test_dir;
+ }
+
+ if ( -d $stress_suitedir )
+ {
+ #$stress_suite_t_dir=File::Spec->catdir($stress_suitedir, "t");
+ #$stress_suite_r_dir=File::Spec->catdir($stress_suitedir, "r");
+ #FIXME: check dirs above for existence to ensure that test suite
+ # contains tests and results dirs
+ }
+ else
+ {
+ mtr_error("Specified test suite $::opt_stress_suite doesn't exist");
+ }
+
+ if ( @::opt_cases )
+ {
+ $::opt_stress_test_file=File::Spec->catfile($stress_basedir, "stress_tests.txt");
+ open(STRESS_FILE, ">$::opt_stress_test_file");
+ print STRESS_FILE join("\n",@::opt_cases),"\n";
+ close(STRESS_FILE);
+ }
+ elsif ( $::opt_stress_test_file )
+ {
+ $::opt_stress_test_file=File::Spec->catfile($stress_suitedir,
+ $::opt_stress_test_file);
+ if ( ! -f $::opt_stress_test_file )
+ {
+ mtr_error("Specified file $::opt_stress_test_file with list of tests does not exist\n",
+ "Please ensure that file exists and has proper permissions");
+ }
+ }
+ else
+ {
+ $::opt_stress_test_file=File::Spec->catfile($stress_suitedir,
+ "stress_tests.txt");
+ if ( ! -f $::opt_stress_test_file )
+ {
+ mtr_error("Default file $::opt_stress_test_file with list of tests does not exist\n",
+ "Please use --stress-test-file option to specify custom one or you can\n",
+ "just specify name of test for testing as last argument in command line");
+
+ }
+ }
+
+ if ( $::opt_stress_init_file )
+ {
+ $::opt_stress_init_file=File::Spec->catfile($stress_suitedir,
+ $::opt_stress_init_file);
+ if ( ! -f $::opt_stress_init_file )
+ {
+ mtr_error("Specified file $::opt_stress_init_file with list of tests does not exist\n",
+ "Please ensure that file exists and has proper permissions");
+ }
+ }
+ else
+ {
+ $::opt_stress_init_file=File::Spec->catfile($stress_suitedir,
+ "stress_init.txt");
+ if ( ! -f $::opt_stress_init_file )
+ {
+ $::opt_stress_init_file='';
+ }
+ }
+
+ if ( $::opt_stress_mode ne 'random' && $::opt_stress_mode ne 'seq' )
+ {
+ mtr_error("You specified wrong mode $::opt_stress_mode for stress test\n",
+ "Correct values are 'random' or 'seq'");
+ }
+
+ mtr_init_args(\$args);
+
+ mtr_add_arg($args, "--server-socket=%s", $::master->[0]->{'path_sock'});
+ mtr_add_arg($args, "--server-user=%s", $::opt_user);
+ mtr_add_arg($args, "--server-database=%s", "test");
+ mtr_add_arg($args, "--stress-suite-basedir=%s", $::glob_mysql_test_dir);
+ mtr_add_arg($args, "--suite=%s", $::opt_stress_suite);
+ mtr_add_arg($args, "--stress-tests-file=%s", $::opt_stress_test_file);
+ mtr_add_arg($args, "--stress-basedir=%s", $stress_basedir);
+ mtr_add_arg($args, "--server-logs-dir=%s", $stress_basedir);
+ mtr_add_arg($args, "--stress-mode=%s", $::opt_stress_mode);
+ mtr_add_arg($args, "--mysqltest=%s", $::exe_mysqltest);
+ mtr_add_arg($args, "--threads=%s", $::opt_stress_threads);
+ mtr_add_arg($args, "--verbose");
+ mtr_add_arg($args, "--cleanup");
+ mtr_add_arg($args, "--log-error-details");
+ mtr_add_arg($args, "--abort-on-error");
+
+ if ( $::opt_stress_init_file )
+ {
+ mtr_add_arg($args, "--stress-init-file=%s", $::opt_stress_init_file);
+ }
+
+ if ( !$::opt_stress_loop_count && !$::opt_stress_test_count &&
+ !$::opt_stress_test_duration )
+ {
+ #Limit stress testing with 20 loops in case when any limit parameter
+ #was specified
+ $::opt_stress_test_count=20;
+ }
+
+ if ( $::opt_stress_loop_count )
+ {
+ mtr_add_arg($args, "--loop-count=%s", $::opt_stress_loop_count);
+ }
+
+ if ( $::opt_stress_test_count )
+ {
+ mtr_add_arg($args, "--test-count=%s", $::opt_stress_test_count);
+ }
+
+ if ( $::opt_stress_test_duration )
+ {
+ mtr_add_arg($args, "--test-duration=%s", $::opt_stress_test_duration);
+ }
+
+ #Run stress test
+ mtr_run("$::glob_mysql_test_dir/mysql-stress-test.pl", $args, "", "", "", "");
+ if ( ! $::glob_use_embedded_server )
+ {
+ stop_all_servers();
+ }
+}
+
+1;
diff --git a/mysql-test/lib/mtr_timer.pl b/mysql-test/lib/v1/mtr_timer.pl
index 326fbea74ec..326fbea74ec 100644
--- a/mysql-test/lib/mtr_timer.pl
+++ b/mysql-test/lib/v1/mtr_timer.pl
diff --git a/mysql-test/lib/mtr_unique.pl b/mysql-test/lib/v1/mtr_unique.pl
index a668fc097c7..a668fc097c7 100644
--- a/mysql-test/lib/mtr_unique.pl
+++ b/mysql-test/lib/v1/mtr_unique.pl
diff --git a/mysql-test/lib/v1/mysql-test-run.pl b/mysql-test/lib/v1/mysql-test-run.pl
new file mode 100755
index 00000000000..86ad5c485c1
--- /dev/null
+++ b/mysql-test/lib/v1/mysql-test-run.pl
@@ -0,0 +1,5393 @@
+#!/usr/bin/perl
+# -*- cperl -*-
+
+#
+##############################################################################
+#
+# mysql-test-run.pl
+#
+# Tool used for executing a suite of .test file
+#
+# See the "MySQL Test framework manual" for more information
+# http://dev.mysql.com/doc/mysqltest/en/index.html
+#
+# Please keep the test framework tools identical in all versions!
+#
+##############################################################################
+#
+# Coding style directions for this perl script
+#
+# - To make this Perl script easy to alter even for those that not
+# code Perl that often, keeep the coding style as close as possible to
+# the C/C++ MySQL coding standard.
+#
+# - All lists of arguments to send to commands are Perl lists/arrays,
+# not strings we append args to. Within reason, most string
+# concatenation for arguments should be avoided.
+#
+# - Functions defined in the main program are not to be prefixed,
+# functions in "library files" are to be prefixed with "mtr_" (for
+# Mysql-Test-Run). There are some exceptions, code that fits best in
+# the main program, but are put into separate files to avoid
+# clutter, may be without prefix.
+#
+# - All stat/opendir/-f/ is to be kept in collect_test_cases(). It
+# will create a struct that the rest of the program can use to get
+# the information. This separates the "find information" from the
+# "do the work" and makes the program more easy to maintain.
+#
+# - The rule when it comes to the logic of this program is
+#
+# command_line_setup() - is to handle the logic between flags
+# collect_test_cases() - is to do its best to select what tests
+# to run, dig out options, if needs restart etc.
+# run_testcase() - is to run a single testcase, and follow the
+# logic set in both above. No, or rare file
+# system operations. If a test seems complex,
+# it should probably not be here.
+#
+# A nice way to trace the execution of this script while debugging
+# is to use the Devel::Trace package found at
+# "http://www.plover.com/~mjd/perl/Trace/" and run this script like
+# "perl -d:Trace mysql-test-run.pl"
+#
+
+
+use lib "lib/v1/";
+
+$Devel::Trace::TRACE= 0; # Don't trace boring init stuff
+
+#require 5.6.1;
+use File::Path;
+use File::Basename;
+use File::Copy;
+use File::Temp qw /tempdir/;
+use File::Spec::Functions qw /splitdir/;
+use Cwd;
+use Getopt::Long;
+use IO::Socket;
+use IO::Socket::INET;
+use strict;
+use warnings;
+
+select(STDOUT);
+$| = 1; # Automatically flush STDOUT
+
+our $glob_win32_perl= ($^O eq "MSWin32"); # ActiveState Win32 Perl
+our $glob_cygwin_perl= ($^O eq "cygwin"); # Cygwin Perl
+our $glob_win32= ($glob_win32_perl or $glob_cygwin_perl);
+our $glob_netware= ($^O eq "NetWare"); # NetWare
+
+require "lib/v1/mtr_cases.pl";
+require "lib/v1/mtr_im.pl";
+require "lib/v1/mtr_process.pl";
+require "lib/v1/mtr_timer.pl";
+require "lib/v1/mtr_io.pl";
+require "lib/v1/mtr_gcov.pl";
+require "lib/v1/mtr_gprof.pl";
+require "lib/v1/mtr_report.pl";
+require "lib/v1/mtr_match.pl";
+require "lib/v1/mtr_misc.pl";
+require "lib/v1/mtr_stress.pl";
+require "lib/v1/mtr_unique.pl";
+
+$Devel::Trace::TRACE= 1;
+
+##############################################################################
+#
+# Default settings
+#
+##############################################################################
+
+# Misc global variables
+our $mysql_version_id;
+our $glob_mysql_test_dir= undef;
+our $glob_mysql_bench_dir= undef;
+our $glob_scriptname= undef;
+our $glob_timers= undef;
+our $glob_use_embedded_server= 0;
+our @glob_test_mode;
+
+our $glob_basedir;
+
+our $path_charsetsdir;
+our $path_client_bindir;
+our $path_client_libdir;
+our $path_share;
+our $path_language;
+our $path_timefile;
+our $path_snapshot;
+our $path_mysqltest_log;
+our $path_current_test_log;
+our $path_my_basedir;
+
+our $opt_vardir; # A path but set directly on cmd line
+our $path_vardir_trace; # unix formatted opt_vardir for trace files
+our $opt_tmpdir; # A path but set directly on cmd line
+
+# 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 $opt_vs_config = $ENV{'MTR_VS_CONFIG'};
+
+our $default_vardir;
+
+our $opt_usage;
+our $opt_suites;
+our $opt_suites_default= "main,binlog,rpl,rpl_ndb,ndb"; # Default suites to run
+our $opt_script_debug= 0; # Script debugging, enable with --script-debug
+our $opt_verbose= 0; # Verbose output, enable with --verbose
+
+our $exe_master_mysqld;
+our $exe_mysql;
+our $exe_mysqladmin;
+our $exe_mysql_upgrade;
+our $exe_mysqlbinlog;
+our $exe_mysql_client_test;
+our $exe_bug25714;
+our $exe_mysqld;
+our $exe_mysqlcheck;
+our $exe_mysqldump;
+our $exe_mysqlslap;
+our $exe_mysqlimport;
+our $exe_mysqlshow;
+our $exe_mysql_fix_system_tables;
+our $file_mysql_fix_privilege_tables;
+our $exe_mysqltest;
+our $exe_ndbd;
+our $exe_ndb_mgmd;
+our $exe_slave_mysqld;
+our $exe_im;
+our $exe_my_print_defaults;
+our $exe_perror;
+our $lib_udf_example;
+our $lib_example_plugin;
+our $exe_libtool;
+
+our $opt_bench= 0;
+our $opt_small_bench= 0;
+our $opt_big_test= 0;
+
+our @opt_combinations;
+our $opt_skip_combination;
+
+our @opt_extra_mysqld_opt;
+
+our $opt_compress;
+our $opt_ssl;
+our $opt_skip_ssl;
+our $opt_ssl_supported;
+our $opt_ps_protocol;
+our $opt_sp_protocol;
+our $opt_cursor_protocol;
+our $opt_view_protocol;
+
+our $opt_debug;
+our $opt_do_test;
+our @opt_cases; # The test cases names in argv
+our $opt_embedded_server;
+
+our $opt_extern= 0;
+our $opt_socket;
+
+our $opt_fast;
+our $opt_force;
+our $opt_reorder= 0;
+our $opt_enable_disabled;
+our $opt_mem= $ENV{'MTR_MEM'};
+
+our $opt_gcov;
+our $opt_gcov_err;
+our $opt_gcov_msg;
+
+our $glob_debugger= 0;
+our $opt_gdb;
+our $opt_client_gdb;
+our $opt_ddd;
+our $opt_client_ddd;
+our $opt_manual_gdb;
+our $opt_manual_ddd;
+our $opt_manual_debug;
+our $opt_mtr_build_thread=0;
+our $opt_debugger;
+our $opt_client_debugger;
+
+our $opt_gprof;
+our $opt_gprof_dir;
+our $opt_gprof_master;
+our $opt_gprof_slave;
+
+our $master;
+our $slave;
+our $clusters;
+
+our $instance_manager;
+
+our $opt_master_myport;
+our $opt_slave_myport;
+our $im_port;
+our $im_mysqld1_port;
+our $im_mysqld2_port;
+our $opt_ndbcluster_port;
+our $opt_ndbconnectstring;
+our $opt_ndbcluster_port_slave;
+our $opt_ndbconnectstring_slave;
+
+our $opt_record;
+my $opt_report_features;
+our $opt_check_testcases;
+our $opt_mark_progress;
+
+our $opt_skip_rpl;
+our $max_slave_num= 0;
+our $max_master_num= 1;
+our $use_innodb;
+our $opt_skip_test;
+our $opt_skip_im;
+
+our $opt_sleep;
+
+our $opt_testcase_timeout;
+our $opt_suite_timeout;
+my $default_testcase_timeout= 15; # 15 min max
+my $default_suite_timeout= 300; # 5 hours max
+
+our $opt_start_and_exit;
+our $opt_start_dirty;
+our $opt_start_from;
+
+our $opt_strace_client;
+
+our $opt_timer= 1;
+
+our $opt_user;
+
+my $opt_valgrind= 0;
+my $opt_valgrind_mysqld= 0;
+my $opt_valgrind_mysqltest= 0;
+my @default_valgrind_args= ("--show-reachable=yes");
+my @valgrind_args;
+my $opt_valgrind_path;
+my $opt_callgrind;
+
+our $opt_stress= "";
+our $opt_stress_suite= "main";
+our $opt_stress_mode= "random";
+our $opt_stress_threads= 5;
+our $opt_stress_test_count= 0;
+our $opt_stress_loop_count= 0;
+our $opt_stress_test_duration= 0;
+our $opt_stress_init_file= "";
+our $opt_stress_test_file= "";
+
+our $opt_warnings;
+
+our $opt_skip_ndbcluster= 0;
+our $opt_skip_ndbcluster_slave= 0;
+our $opt_with_ndbcluster= 0;
+our $opt_with_ndbcluster_only= 0;
+our $glob_ndbcluster_supported= 0;
+our $opt_ndb_extra_test= 0;
+our $opt_skip_master_binlog= 0;
+our $opt_skip_slave_binlog= 0;
+
+our $exe_ndb_mgm;
+our $exe_ndb_waiter;
+our $path_ndb_tools_dir;
+our $path_ndb_examples_dir;
+our $exe_ndb_example;
+our $path_ndb_testrun_log;
+
+our $path_sql_dir;
+
+our @data_dir_lst;
+
+our $used_binlog_format;
+our $used_default_engine;
+our $debug_compiled_binaries;
+
+our %mysqld_variables;
+
+my $source_dist= 0;
+
+our $opt_max_save_core= 5;
+my $num_saved_cores= 0; # Number of core files saved in vardir/log/ so far.
+
+######################################################################
+#
+# Function declarations
+#
+######################################################################
+
+sub main ();
+sub initial_setup ();
+sub command_line_setup ();
+sub set_mtr_build_thread_ports($);
+sub datadir_list_setup ();
+sub executable_setup ();
+sub environment_setup ();
+sub kill_running_servers ();
+sub remove_stale_vardir ();
+sub setup_vardir ();
+sub check_ssl_support ($);
+sub check_running_as_root();
+sub check_ndbcluster_support ($);
+sub rm_ndbcluster_tables ($);
+sub ndbcluster_start_install ($);
+sub ndbcluster_start ($$);
+sub ndbcluster_wait_started ($$);
+sub mysqld_wait_started($);
+sub run_benchmarks ($);
+sub initialize_servers ();
+sub mysql_install_db ();
+sub install_db ($$);
+sub copy_install_db ($$);
+sub run_testcase ($);
+sub run_testcase_stop_servers ($$$);
+sub run_testcase_start_servers ($);
+sub run_testcase_check_skip_test($);
+sub report_failure_and_restart ($);
+sub do_before_start_master ($);
+sub do_before_start_slave ($);
+sub ndbd_start ($$$);
+sub ndb_mgmd_start ($);
+sub mysqld_start ($$$);
+sub mysqld_arguments ($$$$);
+sub stop_all_servers ();
+sub run_mysqltest ($);
+sub usage ($);
+
+
+######################################################################
+#
+# Main program
+#
+######################################################################
+
+main();
+
+sub main () {
+
+ command_line_setup();
+
+ check_ndbcluster_support(\%mysqld_variables);
+ check_ssl_support(\%mysqld_variables);
+ check_debug_support(\%mysqld_variables);
+
+ executable_setup();
+
+ environment_setup();
+ signal_setup();
+
+ if ( $opt_gcov )
+ {
+ gcov_prepare();
+ }
+
+ if ( $opt_gprof )
+ {
+ gprof_prepare();
+ }
+
+ if ( $opt_bench )
+ {
+ initialize_servers();
+ run_benchmarks(shift); # Shift what? Extra arguments?!
+ }
+ elsif ( $opt_stress )
+ {
+ initialize_servers();
+ run_stress_test()
+ }
+ else
+ {
+ # Figure out which tests we are going to run
+ if (!$opt_suites)
+ {
+ $opt_suites= $opt_suites_default;
+
+ # Check for any extra suites to enable based on the path name
+ my %extra_suites=
+ (
+ "mysql-5.1-new-ndb" => "ndb_team",
+ "mysql-5.1-new-ndb-merge" => "ndb_team",
+ "mysql-5.1-telco-6.2" => "ndb_team",
+ "mysql-5.1-telco-6.2-merge" => "ndb_team",
+ "mysql-5.1-telco-6.3" => "ndb_team",
+ "mysql-6.0-ndb" => "ndb_team",
+ );
+
+ foreach my $dir ( reverse splitdir($glob_basedir) )
+ {
+ my $extra_suite= $extra_suites{$dir};
+ if (defined $extra_suite){
+ mtr_report("Found extra suite: $extra_suite");
+ $opt_suites= "$extra_suite,$opt_suites";
+ last;
+ }
+ }
+ }
+
+ my $tests= collect_test_cases($opt_suites);
+
+ # Turn off NDB and other similar options if no tests use it
+ my ($need_ndbcluster,$need_im);
+ foreach my $test (@$tests)
+ {
+ next if $test->{skip};
+
+ if (!$opt_extern)
+ {
+ $need_ndbcluster||= $test->{ndb_test};
+ $need_im||= $test->{component_id} eq 'im';
+
+ # Count max number of slaves used by a test case
+ if ( $test->{slave_num} > $max_slave_num) {
+ $max_slave_num= $test->{slave_num};
+ mtr_error("Too many slaves") if $max_slave_num > 3;
+ }
+
+ # Count max number of masters used by a test case
+ if ( $test->{master_num} > $max_master_num) {
+ $max_master_num= $test->{master_num};
+ mtr_error("Too many masters") if $max_master_num > 2;
+ mtr_error("Too few masters") if $max_master_num < 1;
+ }
+ }
+ $use_innodb||= $test->{'innodb_test'};
+ }
+
+ # Check if cluster can be skipped
+ if ( !$need_ndbcluster )
+ {
+ $opt_skip_ndbcluster= 1;
+ $opt_skip_ndbcluster_slave= 1;
+ }
+
+ # Check if slave cluster can be skipped
+ if ($max_slave_num == 0)
+ {
+ $opt_skip_ndbcluster_slave= 1;
+ }
+
+ # Check if im can be skipped
+ if ( ! $need_im )
+ {
+ $opt_skip_im= 1;
+ }
+
+ initialize_servers();
+
+ if ( $opt_report_features ) {
+ run_report_features();
+ }
+
+ run_tests($tests);
+ }
+
+ mtr_exit(0);
+}
+
+##############################################################################
+#
+# Default settings
+#
+##############################################################################
+
+#
+# When an option is no longer used by this program, it must be explicitly
+# ignored or else it will be passed through to mysqld. GetOptions will call
+# this subroutine once for each such option on the command line. See
+# Getopt::Long documentation.
+#
+
+sub warn_about_removed_option {
+ my ($option, $value, $hash_value) = @_;
+
+ warn "WARNING: This option is no longer used, and is ignored: --$option\n";
+}
+
+sub command_line_setup () {
+
+ # These are defaults for things that are set on the command line
+
+ my $opt_comment;
+
+ # Magic number -69.4 results in traditional test ports starting from 9306.
+ set_mtr_build_thread_ports(-69.4);
+
+ # If so requested, we try to avail ourselves of a unique build thread number.
+ if ( $ENV{'MTR_BUILD_THREAD'} ) {
+ if ( lc($ENV{'MTR_BUILD_THREAD'}) eq 'auto' ) {
+ print "Requesting build thread... ";
+ $ENV{'MTR_BUILD_THREAD'} = mtr_require_unique_id_and_wait("/tmp/mysql-test-ports", 200, 299);
+ print "got ".$ENV{'MTR_BUILD_THREAD'}."\n";
+ }
+ }
+
+ if ( $ENV{'MTR_BUILD_THREAD'} )
+ {
+ set_mtr_build_thread_ports($ENV{'MTR_BUILD_THREAD'});
+ }
+
+ # 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.
+ print "Logging: $0 ", join(" ", @ARGV), "\n";
+
+ # Read the command line
+ # Note: Keep list, and the order, in sync with usage at end of this file
+
+ # Options that are no longer used must still be processed, because all
+ # unprocessed options are passed directly to mysqld. The user will be
+ # warned that the option is being ignored.
+ #
+ # Put the complete option string here. For example, to remove the --suite
+ # option, remove it from GetOptions() below and put 'suite|suites=s' here.
+ my @removed_options = (
+ );
+
+ Getopt::Long::Configure("pass_through");
+ GetOptions(
+ # 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,
+ 'ssl|with-openssl' => \$opt_ssl,
+ 'skip-ssl' => \$opt_skip_ssl,
+ 'compress' => \$opt_compress,
+ 'bench' => \$opt_bench,
+ 'small-bench' => \$opt_small_bench,
+ 'with-ndbcluster|ndb' => \$opt_with_ndbcluster,
+ 'vs-config' => \$opt_vs_config,
+
+ # Control what test suites or cases to run
+ 'force' => \$opt_force,
+ 'with-ndbcluster-only' => \$opt_with_ndbcluster_only,
+ 'skip-ndbcluster|skip-ndb' => \$opt_skip_ndbcluster,
+ 'skip-ndbcluster-slave|skip-ndb-slave'
+ => \$opt_skip_ndbcluster_slave,
+ 'ndb-extra-test' => \$opt_ndb_extra_test,
+ 'skip-master-binlog' => \$opt_skip_master_binlog,
+ 'skip-slave-binlog' => \$opt_skip_slave_binlog,
+ 'do-test=s' => \$opt_do_test,
+ 'start-from=s' => \$opt_start_from,
+ 'suite|suites=s' => \$opt_suites,
+ 'skip-rpl' => \$opt_skip_rpl,
+ 'skip-im' => \$opt_skip_im,
+ 'skip-test=s' => \$opt_skip_test,
+ 'big-test' => \$opt_big_test,
+ 'combination=s' => \@opt_combinations,
+ 'skip-combination' => \$opt_skip_combination,
+
+ # Specify ports
+ 'master_port=i' => \$opt_master_myport,
+ 'slave_port=i' => \$opt_slave_myport,
+ 'ndbcluster-port|ndbcluster_port=i' => \$opt_ndbcluster_port,
+ 'ndbcluster-port-slave=i' => \$opt_ndbcluster_port_slave,
+ 'im-port=i' => \$im_port, # Instance Manager port.
+ 'im-mysqld1-port=i' => \$im_mysqld1_port, # Port of mysqld, controlled by IM
+ 'im-mysqld2-port=i' => \$im_mysqld2_port, # Port of mysqld, controlled by IM
+ 'mtr-build-thread=i' => \$opt_mtr_build_thread,
+
+ # Test case authoring
+ 'record' => \$opt_record,
+ 'check-testcases' => \$opt_check_testcases,
+ 'mark-progress' => \$opt_mark_progress,
+
+ # Extra options used when starting mysqld
+ 'mysqld=s' => \@opt_extra_mysqld_opt,
+
+ # Run test on running server
+ 'extern' => \$opt_extern,
+ 'ndb-connectstring=s' => \$opt_ndbconnectstring,
+ 'ndb-connectstring-slave=s' => \$opt_ndbconnectstring_slave,
+
+ # Debugging
+ 'gdb' => \$opt_gdb,
+ 'client-gdb' => \$opt_client_gdb,
+ 'manual-gdb' => \$opt_manual_gdb,
+ 'manual-debug' => \$opt_manual_debug,
+ 'ddd' => \$opt_ddd,
+ 'client-ddd' => \$opt_client_ddd,
+ 'manual-ddd' => \$opt_manual_ddd,
+ 'debugger=s' => \$opt_debugger,
+ 'client-debugger=s' => \$opt_client_debugger,
+ 'strace-client' => \$opt_strace_client,
+ 'master-binary=s' => \$exe_master_mysqld,
+ 'slave-binary=s' => \$exe_slave_mysqld,
+ 'max-save-core=i' => \$opt_max_save_core,
+
+ # Coverage, profiling etc
+ 'gcov' => \$opt_gcov,
+ 'gprof' => \$opt_gprof,
+ 'valgrind|valgrind-all' => \$opt_valgrind,
+ 'valgrind-mysqltest' => \$opt_valgrind_mysqltest,
+ 'valgrind-mysqld' => \$opt_valgrind_mysqld,
+ 'valgrind-options=s' => sub {
+ my ($opt, $value)= @_;
+ # Deprecated option unless it's what we know pushbuild uses
+ if ($value eq "--gen-suppressions=all --show-reachable=yes") {
+ push(@valgrind_args, $_) for (split(' ', $value));
+ return;
+ }
+ die("--valgrind-options=s is deprecated. Use ",
+ "--valgrind-option=s, to be specified several",
+ " times if necessary");
+ },
+ 'valgrind-option=s' => \@valgrind_args,
+ 'valgrind-path=s' => \$opt_valgrind_path,
+ 'callgrind' => \$opt_callgrind,
+
+ # Stress testing
+ 'stress' => \$opt_stress,
+ 'stress-suite=s' => \$opt_stress_suite,
+ 'stress-threads=i' => \$opt_stress_threads,
+ 'stress-test-file=s' => \$opt_stress_test_file,
+ 'stress-init-file=s' => \$opt_stress_init_file,
+ 'stress-mode=s' => \$opt_stress_mode,
+ 'stress-loop-count=i' => \$opt_stress_loop_count,
+ 'stress-test-count=i' => \$opt_stress_test_count,
+ 'stress-test-duration=i' => \$opt_stress_test_duration,
+
+ # Directories
+ 'tmpdir=s' => \$opt_tmpdir,
+ 'vardir=s' => \$opt_vardir,
+ 'benchdir=s' => \$glob_mysql_bench_dir,
+ 'mem' => \$opt_mem,
+ 'client-bindir=s' => \$path_client_bindir,
+ 'client-libdir=s' => \$path_client_libdir,
+
+ # Misc
+ 'report-features' => \$opt_report_features,
+ 'comment=s' => \$opt_comment,
+ 'debug' => \$opt_debug,
+ 'fast' => \$opt_fast,
+ 'reorder' => \$opt_reorder,
+ 'enable-disabled' => \$opt_enable_disabled,
+ 'script-debug' => \$opt_script_debug,
+ 'verbose' => \$opt_verbose,
+ 'sleep=i' => \$opt_sleep,
+ 'socket=s' => \$opt_socket,
+ 'start-dirty' => \$opt_start_dirty,
+ 'start-and-exit' => \$opt_start_and_exit,
+ 'timer!' => \$opt_timer,
+ 'user=s' => \$opt_user,
+ 'testcase-timeout=i' => \$opt_testcase_timeout,
+ 'suite-timeout=i' => \$opt_suite_timeout,
+ 'warnings|log-warnings' => \$opt_warnings,
+
+ # Options which are no longer used
+ (map { $_ => \&warn_about_removed_option } @removed_options),
+
+ 'help|h' => \$opt_usage,
+ ) or usage("Can't read options");
+
+ usage("") if $opt_usage;
+
+ $glob_scriptname= basename($0);
+
+ if ($opt_mtr_build_thread != 0)
+ {
+ set_mtr_build_thread_ports($opt_mtr_build_thread)
+ }
+ elsif ($ENV{'MTR_BUILD_THREAD'})
+ {
+ $opt_mtr_build_thread= $ENV{'MTR_BUILD_THREAD'};
+ }
+
+ # We require that we are in the "mysql-test" directory
+ # to run mysql-test-run
+ if (! -f $glob_scriptname)
+ {
+ mtr_error("Can't find the location for the mysql-test-run script\n" .
+ "Go to to the mysql-test directory and execute the script " .
+ "as follows:\n./$glob_scriptname");
+ }
+
+ if ( -d "../sql" )
+ {
+ $source_dist= 1;
+ }
+
+ # Find the absolute path to the test directory
+ $glob_mysql_test_dir= cwd();
+ if ( $glob_cygwin_perl )
+ {
+ # Windows programs like 'mysqld' needs Windows paths
+ $glob_mysql_test_dir= `cygpath -m "$glob_mysql_test_dir"`;
+ chomp($glob_mysql_test_dir);
+ }
+ $default_vardir= "$glob_mysql_test_dir/var";
+
+ # 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.
+ $glob_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 "$glob_basedir/bin" )
+ {
+ $glob_basedir= dirname($glob_basedir);
+ }
+
+ # Expect mysql-bench to be located adjacent to the source tree, by default
+ $glob_mysql_bench_dir= "$glob_basedir/../mysql-bench"
+ unless defined $glob_mysql_bench_dir;
+ $glob_mysql_bench_dir= undef
+ unless -d $glob_mysql_bench_dir;
+
+ $path_my_basedir=
+ $source_dist ? $glob_mysql_test_dir : $glob_basedir;
+
+ $glob_timers= mtr_init_timers();
+
+ # --------------------------------------------------------------------------
+ # Embedded server flag
+ # --------------------------------------------------------------------------
+ if ( $opt_embedded_server )
+ {
+ $glob_use_embedded_server= 1;
+ # Add the location for libmysqld.dll to the path.
+ if ( $glob_win32 )
+ {
+ my $lib_mysqld=
+ mtr_path_exists(vs_config_dirs('libmysqld',''));
+ $lib_mysqld= $glob_cygwin_perl ? ":".`cygpath "$lib_mysqld"`
+ : ";".$lib_mysqld;
+ chomp($lib_mysqld);
+ $ENV{'PATH'}="$ENV{'PATH'}".$lib_mysqld;
+ }
+
+ push(@glob_test_mode, "embedded");
+ $opt_skip_rpl= 1; # We never run replication with embedded
+ $opt_skip_ndbcluster= 1; # Turn off use of NDB cluster
+ $opt_skip_ssl= 1; # Turn off use of SSL
+
+ # Turn off use of bin log
+ push(@opt_extra_mysqld_opt, "--skip-log-bin");
+
+ if ( $opt_extern )
+ {
+ mtr_error("Can't use --extern with --embedded-server");
+ }
+ }
+
+ #
+ # Find the mysqld executable to be able to find the mysqld version
+ # number as early as possible
+ #
+
+ # 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("$glob_basedir/client_release",
+ "$glob_basedir/client_debug",
+ vs_config_dirs('client', ''),
+ "$glob_basedir/client",
+ "$glob_basedir/bin");
+ }
+
+ # Look for language files and charsetsdir, use same share
+ $path_share= mtr_path_exists("$glob_basedir/share/mysql",
+ "$glob_basedir/sql/share",
+ "$glob_basedir/share");
+
+ $path_language= mtr_path_exists("$path_share/english");
+ $path_charsetsdir= mtr_path_exists("$path_share/charsets");
+
+
+ if (!$opt_extern)
+ {
+ $exe_mysqld= mtr_exe_exists (vs_config_dirs('sql', 'mysqld'),
+ vs_config_dirs('sql', 'mysqld-debug'),
+ "$glob_basedir/sql/mysqld",
+ "$path_client_bindir/mysqld-max-nt",
+ "$path_client_bindir/mysqld-max",
+ "$path_client_bindir/mysqld-nt",
+ "$path_client_bindir/mysqld",
+ "$path_client_bindir/mysqld-debug",
+ "$path_client_bindir/mysqld-max",
+ "$glob_basedir/libexec/mysqld",
+ "$glob_basedir/bin/mysqld",
+ "$glob_basedir/sbin/mysqld");
+
+ # Use the mysqld found above to find out what features are available
+ collect_mysqld_features();
+ }
+ else
+ {
+ $mysqld_variables{'port'}= 3306;
+ $mysqld_variables{'master-port'}= 3306;
+ }
+
+ if ( $opt_comment )
+ {
+ print "\n";
+ print '#' x 78, "\n";
+ print "# $opt_comment\n";
+ print '#' x 78, "\n\n";
+ }
+
+ 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);
+ }
+ }
+
+ # --------------------------------------------------------------------------
+ # Find out type of logging that are being used
+ # --------------------------------------------------------------------------
+ if (!$opt_extern && $mysql_version_id >= 50100 )
+ {
+ foreach my $arg ( @opt_extra_mysqld_opt )
+ {
+ if ( $arg =~ /binlog[-_]format=(\S+)/ )
+ {
+ $used_binlog_format= $1;
+ }
+ }
+ if (defined $used_binlog_format)
+ {
+ mtr_report("Using binlog format '$used_binlog_format'");
+ }
+ else
+ {
+ mtr_report("Using dynamic switching of binlog format");
+ }
+ }
+
+
+ # --------------------------------------------------------------------------
+ # Find out default storage engine being used(if any)
+ # --------------------------------------------------------------------------
+ if ( $opt_with_ndbcluster )
+ {
+ # --ndb or --with-ndbcluster turns on --default-storage-engine=ndbcluster
+ push(@opt_extra_mysqld_opt, "--default-storage-engine=ndbcluster");
+ }
+
+ foreach my $arg ( @opt_extra_mysqld_opt )
+ {
+ if ( $arg =~ /default-storage-engine=(\S+)/ )
+ {
+ $used_default_engine= $1;
+ }
+ }
+ mtr_report("Using default engine '$used_default_engine'")
+ if defined $used_default_engine;
+
+ # --------------------------------------------------------------------------
+ # 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 list to find a suitable location
+ # Use --mem=<dir> as first location to look.
+ my @tmpfs_locations= ($opt_mem, "/dev/shm", "/tmp");
+
+ foreach my $fs (@tmpfs_locations)
+ {
+ if ( -d $fs )
+ {
+ mtr_report("Using tmpfs in $fs");
+ $opt_mem= "$fs/var";
+ $opt_mem .= $opt_mtr_build_thread if $opt_mtr_build_thread;
+ last;
+ }
+ }
+ }
+
+ # --------------------------------------------------------------------------
+ # Set the "var/" directory, as it is the base for everything else
+ # --------------------------------------------------------------------------
+ if ( ! $opt_vardir )
+ {
+ $opt_vardir= $default_vardir;
+ }
+ elsif ( $mysql_version_id < 50000 and
+ $opt_vardir ne $default_vardir)
+ {
+ # Version 4.1 and --vardir was specified
+ # Only supported as a symlink from var/
+ # by setting up $opt_mem that symlink will be created
+ if ( ! $glob_win32 )
+ {
+ # Only platforms that have native symlinks can use the vardir trick
+ $opt_mem= $opt_vardir;
+ mtr_report("Using 4.1 vardir trick");
+ }
+
+ $opt_vardir= $default_vardir;
+ }
+
+ $path_vardir_trace= $opt_vardir;
+ # Chop off any "c:", DBUG likes a unix path ex: c:/src/... => /src/...
+ $path_vardir_trace=~ s/^\w://;
+
+ # We make the path absolute, as the server will do a chdir() before usage
+ unless ( $opt_vardir =~ m,^/, or
+ ($glob_win32 and $opt_vardir =~ m,^[a-z]:/,i) )
+ {
+ # Make absolute path, relative test dir
+ $opt_vardir= "$glob_mysql_test_dir/$opt_vardir";
+ }
+
+ # --------------------------------------------------------------------------
+ # Set tmpdir
+ # --------------------------------------------------------------------------
+ $opt_tmpdir= "$opt_vardir/tmp" unless $opt_tmpdir;
+ $opt_tmpdir =~ s,/+$,,; # Remove ending slash if any
+
+ # --------------------------------------------------------------------------
+ # Check im suport
+ # --------------------------------------------------------------------------
+ if ($opt_extern)
+ {
+ mtr_report("Disable instance manager when running with extern mysqld");
+ $opt_skip_im= 1;
+ }
+ elsif ( $mysql_version_id < 50000 )
+ {
+ # Instance manager is not supported until 5.0
+ $opt_skip_im= 1;
+ }
+ elsif ( $glob_win32 )
+ {
+ mtr_report("Disable Instance manager - testing not supported on Windows");
+ $opt_skip_im= 1;
+ }
+
+ # --------------------------------------------------------------------------
+ # Record flag
+ # --------------------------------------------------------------------------
+ if ( $opt_record and ! @opt_cases )
+ {
+ mtr_error("Will not run in record mode without a specific test case");
+ }
+
+ if ( $opt_record )
+ {
+ $opt_skip_combination = 1;
+ }
+
+ # --------------------------------------------------------------------------
+ # ps protcol flag
+ # --------------------------------------------------------------------------
+ if ( $opt_ps_protocol )
+ {
+ push(@glob_test_mode, "ps-protocol");
+ }
+
+ # --------------------------------------------------------------------------
+ # Bench flags
+ # --------------------------------------------------------------------------
+ if ( $opt_small_bench )
+ {
+ $opt_bench= 1;
+ }
+
+ # --------------------------------------------------------------------------
+ # Big test flags
+ # --------------------------------------------------------------------------
+ if ( $opt_big_test )
+ {
+ $ENV{'BIG_TEST'}= 1;
+ }
+
+ # --------------------------------------------------------------------------
+ # Gcov flag
+ # --------------------------------------------------------------------------
+ if ( $opt_gcov and ! $source_dist )
+ {
+ mtr_error("Coverage test needs the source - please use source dist");
+ }
+
+ # --------------------------------------------------------------------------
+ # Check debug related options
+ # --------------------------------------------------------------------------
+ if ( $opt_gdb || $opt_client_gdb || $opt_ddd || $opt_client_ddd ||
+ $opt_manual_gdb || $opt_manual_ddd || $opt_manual_debug ||
+ $opt_debugger || $opt_client_debugger )
+ {
+ # Indicate that we are using debugger
+ $glob_debugger= 1;
+ if ( $opt_extern )
+ {
+ mtr_error("Can't use --extern when using debugger");
+ }
+ }
+
+ # --------------------------------------------------------------------------
+ # Check if special exe was selected for master or slave
+ # --------------------------------------------------------------------------
+ $exe_master_mysqld= $exe_master_mysqld || $exe_mysqld;
+ $exe_slave_mysqld= $exe_slave_mysqld || $exe_mysqld;
+
+ # --------------------------------------------------------------------------
+ # Check valgrind arguments
+ # --------------------------------------------------------------------------
+ if ( $opt_valgrind or $opt_valgrind_path or @valgrind_args)
+ {
+ mtr_report("Turning on valgrind for all executables");
+ $opt_valgrind= 1;
+ $opt_valgrind_mysqld= 1;
+ $opt_valgrind_mysqltest= 1;
+ }
+ elsif ( $opt_valgrind_mysqld )
+ {
+ mtr_report("Turning on valgrind for mysqld(s) only");
+ $opt_valgrind= 1;
+ }
+ elsif ( $opt_valgrind_mysqltest )
+ {
+ mtr_report("Turning on valgrind for mysqltest and mysql_client_test only");
+ $opt_valgrind= 1;
+ }
+
+ if ( $opt_callgrind )
+ {
+ mtr_report("Turning on valgrind with callgrind for mysqld(s)");
+ $opt_valgrind= 1;
+ $opt_valgrind_mysqld= 1;
+
+ # Set special valgrind options unless options passed on command line
+ push(@valgrind_args, "--trace-children=yes")
+ unless @valgrind_args;
+ }
+
+ if ( $opt_valgrind )
+ {
+ # Set valgrind_options to default unless already defined
+ push(@valgrind_args, @default_valgrind_args)
+ unless @valgrind_args;
+
+ mtr_report("Running valgrind with options \"",
+ join(" ", @valgrind_args), "\"");
+ }
+
+ if ( ! $opt_testcase_timeout )
+ {
+ $opt_testcase_timeout= $default_testcase_timeout;
+ $opt_testcase_timeout*= 10 if $opt_valgrind;
+ $opt_testcase_timeout*= 10 if ($opt_debug and $glob_win32);
+ }
+
+ if ( ! $opt_suite_timeout )
+ {
+ $opt_suite_timeout= $default_suite_timeout;
+ $opt_suite_timeout*= 6 if $opt_valgrind;
+ $opt_suite_timeout*= 6 if ($opt_debug and $glob_win32);
+ }
+
+ if ( ! $opt_user )
+ {
+ if ( $opt_extern )
+ {
+ $opt_user= "test";
+ }
+ else
+ {
+ $opt_user= "root"; # We want to do FLUSH xxx commands
+ }
+ }
+
+ # On QNX, /tmp/dir/master.sock and /tmp/dir//master.sock seem to be
+ # considered different, so avoid the extra slash (/) in the socket
+ # paths.
+ my $sockdir = $opt_tmpdir;
+ $sockdir =~ s|/+$||;
+
+ # On some operating systems, there is a limit to the length of a
+ # UNIX domain socket's path far below PATH_MAX, so try to avoid long
+ # socket path names.
+ $sockdir = tempdir(CLEANUP => 0) if ( length($sockdir) >= 70 );
+
+ $master->[0]=
+ {
+ pid => 0,
+ type => "master",
+ idx => 0,
+ path_myddir => "$opt_vardir/master-data",
+ path_myerr => "$opt_vardir/log/master.err",
+ path_pid => "$opt_vardir/run/master.pid",
+ path_sock => "$sockdir/master.sock",
+ port => $opt_master_myport,
+ start_timeout => 400, # enough time create innodb tables
+ cluster => 0, # index in clusters list
+ start_opts => [],
+ };
+
+ $master->[1]=
+ {
+ pid => 0,
+ type => "master",
+ idx => 1,
+ path_myddir => "$opt_vardir/master1-data",
+ path_myerr => "$opt_vardir/log/master1.err",
+ path_pid => "$opt_vardir/run/master1.pid",
+ path_sock => "$sockdir/master1.sock",
+ port => $opt_master_myport + 1,
+ start_timeout => 400, # enough time create innodb tables
+ cluster => 0, # index in clusters list
+ start_opts => [],
+ };
+
+ $slave->[0]=
+ {
+ pid => 0,
+ type => "slave",
+ idx => 0,
+ path_myddir => "$opt_vardir/slave-data",
+ path_myerr => "$opt_vardir/log/slave.err",
+ path_pid => "$opt_vardir/run/slave.pid",
+ path_sock => "$sockdir/slave.sock",
+ port => $opt_slave_myport,
+ start_timeout => 400,
+
+ cluster => 1, # index in clusters list
+ start_opts => [],
+ };
+
+ $slave->[1]=
+ {
+ pid => 0,
+ type => "slave",
+ idx => 1,
+ path_myddir => "$opt_vardir/slave1-data",
+ path_myerr => "$opt_vardir/log/slave1.err",
+ path_pid => "$opt_vardir/run/slave1.pid",
+ path_sock => "$sockdir/slave1.sock",
+ port => $opt_slave_myport + 1,
+ start_timeout => 300,
+ cluster => -1, # index in clusters list
+ start_opts => [],
+ };
+
+ $slave->[2]=
+ {
+ pid => 0,
+ type => "slave",
+ idx => 2,
+ path_myddir => "$opt_vardir/slave2-data",
+ path_myerr => "$opt_vardir/log/slave2.err",
+ path_pid => "$opt_vardir/run/slave2.pid",
+ path_sock => "$sockdir/slave2.sock",
+ port => $opt_slave_myport + 2,
+ start_timeout => 300,
+ cluster => -1, # index in clusters list
+ start_opts => [],
+ };
+
+ $instance_manager=
+ {
+ path_err => "$opt_vardir/log/im.err",
+ path_log => "$opt_vardir/log/im.log",
+ path_pid => "$opt_vardir/run/im.pid",
+ path_angel_pid => "$opt_vardir/run/im.angel.pid",
+ path_sock => "$sockdir/im.sock",
+ port => $im_port,
+ start_timeout => $master->[0]->{'start_timeout'},
+ admin_login => 'im_admin',
+ admin_password => 'im_admin_secret',
+ admin_sha1 => '*598D51AD2DFF7792045D6DF3DDF9AA1AF737B295',
+ password_file => "$opt_vardir/im.passwd",
+ defaults_file => "$opt_vardir/im.cnf",
+ };
+
+ $instance_manager->{'instances'}->[0]=
+ {
+ server_id => 1,
+ port => $im_mysqld1_port,
+ path_datadir => "$opt_vardir/im_mysqld_1.data",
+ path_sock => "$sockdir/mysqld_1.sock",
+ path_pid => "$opt_vardir/run/mysqld_1.pid",
+ start_timeout => 400, # enough time create innodb tables
+ old_log_format => 1
+ };
+
+ $instance_manager->{'instances'}->[1]=
+ {
+ server_id => 2,
+ port => $im_mysqld2_port,
+ path_datadir => "$opt_vardir/im_mysqld_2.data",
+ path_sock => "$sockdir/mysqld_2.sock",
+ path_pid => "$opt_vardir/run/mysqld_2.pid",
+ nonguarded => 1,
+ start_timeout => 400, # enough time create innodb tables
+ old_log_format => 1
+ };
+
+ my $data_dir= "$opt_vardir/ndbcluster-$opt_ndbcluster_port";
+ $clusters->[0]=
+ {
+ name => "Master",
+ nodes => 2,
+ port => "$opt_ndbcluster_port",
+ data_dir => "$data_dir",
+ connect_string => "host=localhost:$opt_ndbcluster_port",
+ path_pid => "$data_dir/ndb_3.pid", # Nodes + 1
+ pid => 0, # pid of ndb_mgmd
+ installed_ok => 0,
+ };
+
+ $data_dir= "$opt_vardir/ndbcluster-$opt_ndbcluster_port_slave";
+ $clusters->[1]=
+ {
+ name => "Slave",
+ nodes => 1,
+ port => "$opt_ndbcluster_port_slave",
+ data_dir => "$data_dir",
+ connect_string => "host=localhost:$opt_ndbcluster_port_slave",
+ path_pid => "$data_dir/ndb_2.pid", # Nodes + 1
+ pid => 0, # pid of ndb_mgmd
+ installed_ok => 0,
+ };
+
+ # Init pids of ndbd's
+ foreach my $cluster ( @{$clusters} )
+ {
+ for ( my $idx= 0; $idx < $cluster->{'nodes'}; $idx++ )
+ {
+ my $nodeid= $idx+1;
+ $cluster->{'ndbds'}->[$idx]=
+ {
+ pid => 0,
+ nodeid => $nodeid,
+ path_pid => "$cluster->{'data_dir'}/ndb_${nodeid}.pid",
+ path_fs => "$cluster->{'data_dir'}/ndb_${nodeid}_fs",
+ };
+ }
+ }
+
+ # --------------------------------------------------------------------------
+ # extern
+ # --------------------------------------------------------------------------
+ if ( $opt_extern )
+ {
+ # Turn off features not supported when running with extern server
+ $opt_skip_rpl= 1;
+ $opt_skip_ndbcluster= 1;
+
+ # Setup master->[0] with the settings for the extern server
+ $master->[0]->{'path_sock'}= $opt_socket ? $opt_socket : "/tmp/mysql.sock";
+ mtr_report("Using extern server at '$master->[0]->{path_sock}'");
+ }
+ else
+ {
+ mtr_error("--socket can only be used in combination with --extern")
+ if $opt_socket;
+ }
+
+
+ # --------------------------------------------------------------------------
+ # ndbconnectstring and ndbconnectstring_slave
+ # --------------------------------------------------------------------------
+ if ( $opt_ndbconnectstring )
+ {
+ # ndbconnectstring was supplied by user, the tests shoudl be run
+ # against an already started cluster, change settings
+ my $cluster= $clusters->[0]; # Master cluster
+ $cluster->{'connect_string'}= $opt_ndbconnectstring;
+ $cluster->{'use_running'}= 1;
+
+ mtr_error("Can't specify --ndb-connectstring and --skip-ndbcluster")
+ if $opt_skip_ndbcluster;
+ }
+ $ENV{'NDB_CONNECTSTRING'}= $clusters->[0]->{'connect_string'};
+
+
+ if ( $opt_ndbconnectstring_slave )
+ {
+ # ndbconnectstring-slave was supplied by user, the tests should be run
+ # agains an already started slave cluster, change settings
+ my $cluster= $clusters->[1]; # Slave cluster
+ $cluster->{'connect_string'}= $opt_ndbconnectstring_slave;
+ $cluster->{'use_running'}= 1;
+
+ mtr_error("Can't specify ndb-connectstring_slave and " .
+ "--skip-ndbcluster-slave")
+ if $opt_skip_ndbcluster_slave;
+ }
+
+
+ $path_timefile= "$opt_vardir/log/mysqltest-time";
+ $path_mysqltest_log= "$opt_vardir/log/mysqltest.log";
+ $path_current_test_log= "$opt_vardir/log/current_test";
+ $path_ndb_testrun_log= "$opt_vardir/log/ndb_testrun.log";
+
+ $path_snapshot= "$opt_tmpdir/snapshot_$opt_master_myport/";
+
+ 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
+ foreach my $mysqld (@{$master}, @{$slave})
+ {
+ my $sidx= $mysqld->{idx} ? "$mysqld->{idx}" : "";
+ $mysqld->{path_myerr}=
+ "$opt_vardir/log/" . $mysqld->{type} . "$sidx.trace";
+ }
+ }
+}
+
+#
+# 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_mtr_build_thread_ports($) {
+ my $mtr_build_thread= shift;
+
+ if ( lc($mtr_build_thread) eq 'auto' ) {
+ print "Requesting build thread... ";
+ $ENV{'MTR_BUILD_THREAD'} = $mtr_build_thread = mtr_require_unique_id_and_wait("/tmp/mysql-test-ports", 200, 299);
+ print "got ".$mtr_build_thread."\n";
+ }
+
+ # Up to two masters, up to three slaves
+ # A magic value in command_line_setup depends on these equations.
+ $opt_master_myport= $mtr_build_thread * 10 + 10000; # and 1
+ $opt_slave_myport= $opt_master_myport + 2; # and 3 4
+ $opt_ndbcluster_port= $opt_master_myport + 5;
+ $opt_ndbcluster_port_slave= $opt_master_myport + 6;
+ $im_port= $opt_master_myport + 7;
+ $im_mysqld1_port= $opt_master_myport + 8;
+ $im_mysqld2_port= $opt_master_myport + 9;
+
+ if ( $opt_master_myport < 5001 or $opt_master_myport + 10 >= 32767 )
+ {
+ mtr_error("MTR_BUILD_THREAD number results in a port",
+ "outside 5001 - 32767",
+ "($opt_master_myport - $opt_master_myport + 10)");
+ }
+}
+
+
+sub datadir_list_setup () {
+
+ # Make a list of all data_dirs
+ for (my $idx= 0; $idx < $max_master_num; $idx++)
+ {
+ push(@data_dir_lst, $master->[$idx]->{'path_myddir'});
+ }
+
+ for (my $idx= 0; $idx < $max_slave_num; $idx++)
+ {
+ push(@data_dir_lst, $slave->[$idx]->{'path_myddir'});
+ }
+
+ unless ($opt_skip_im)
+ {
+ foreach my $instance (@{$instance_manager->{'instances'}})
+ {
+ push(@data_dir_lst, $instance->{'path_datadir'});
+ }
+ }
+}
+
+
+##############################################################################
+#
+# Set paths to various executable programs
+#
+##############################################################################
+
+
+sub collect_mysqld_features () {
+ my $found_variable_list_start= 0;
+ my $tmpdir;
+ if ( $opt_tmpdir ) {
+ # Use the requested tmpdir
+ mkpath($opt_tmpdir) if (! -d $opt_tmpdir);
+ $tmpdir= $opt_tmpdir;
+ }
+ else {
+ $tmpdir= tempdir(CLEANUP => 0); # Directory removed by this function
+ }
+
+ #
+ # Execute "mysqld --help --verbose" to get a list
+ # 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 $list= `$exe_mysqld --no-defaults --datadir=$tmpdir --language=$path_language --skip-grant-tables --verbose --help`;
+
+ foreach my $line (split('\n', $list))
+ {
+ # First look for version
+ if ( !$mysql_version_id )
+ {
+ # Look for version
+ my $exe_name= basename($exe_mysqld);
+ mtr_verbose("exe_name: $exe_name");
+ if ( $line =~ /^\S*$exe_name\s\sVer\s([0-9]*)\.([0-9]*)\.([0-9]*)/ )
+ {
+ #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("MySQL Version $1.$2.$3");
+ }
+ }
+ else
+ {
+ if (!$found_variable_list_start)
+ {
+ # Look for start of variables list
+ if ( $line =~ /[\-]+\s[\-]+/ )
+ {
+ $found_variable_list_start= 1;
+ }
+ }
+ else
+ {
+ # Put variables into hash
+ if ( $line =~ /^([\S]+)[ \t]+(.*?)\r?$/ )
+ {
+ # print "$1=\"$2\"\n";
+ $mysqld_variables{$1}= $2;
+ }
+ else
+ {
+ # The variable list is ended with a blank line
+ if ( $line =~ /^[\s]*$/ )
+ {
+ last;
+ }
+ else
+ {
+ # Send out a warning, we should fix the variables that has no
+ # space between variable name and it's value
+ # or should it be fixed width column parsing? It does not
+ # look like that in function my_print_variables in my_getopt.c
+ mtr_warning("Could not parse variable list line : $line");
+ }
+ }
+ }
+ }
+ }
+ rmtree($tmpdir) if (!$opt_tmpdir);
+ mtr_error("Could not find version of MySQL") unless $mysql_version_id;
+ mtr_error("Could not find variabes list") unless $found_variable_list_start;
+
+}
+
+
+sub run_query($$) {
+ my ($mysqld, $query)= @_;
+
+ my $args;
+ mtr_init_args(\$args);
+
+ mtr_add_arg($args, "--no-defaults");
+ mtr_add_arg($args, "--user=%s", $opt_user);
+ mtr_add_arg($args, "--port=%d", $mysqld->{'port'});
+ mtr_add_arg($args, "--socket=%s", $mysqld->{'path_sock'});
+ mtr_add_arg($args, "--silent"); # Tab separated output
+ mtr_add_arg($args, "-e '%s'", $query);
+
+ my $cmd= "$exe_mysql " . join(' ', @$args);
+ mtr_verbose("cmd: $cmd");
+ return `$cmd`;
+}
+
+
+sub collect_mysqld_features_from_running_server ()
+{
+ my $list= run_query($master->[0], "use mysql; SHOW VARIABLES");
+
+ foreach my $line (split('\n', $list))
+ {
+ # Put variables into hash
+ if ( $line =~ /^([\S]+)[ \t]+(.*?)\r?$/ )
+ {
+ print "$1=\"$2\"\n";
+ $mysqld_variables{$1}= $2;
+ }
+ }
+}
+
+sub executable_setup_im () {
+
+ # Look for instance manager binary - mysqlmanager
+ $exe_im=
+ mtr_exe_maybe_exists(
+ "$glob_basedir/server-tools/instance-manager/mysqlmanager",
+ "$glob_basedir/libexec/mysqlmanager",
+ "$glob_basedir/bin/mysqlmanager",
+ "$glob_basedir/sbin/mysqlmanager");
+
+ return ($exe_im eq "");
+}
+
+sub executable_setup_ndb () {
+
+ # Look for ndb tols and binaries
+ my $ndb_path= mtr_file_exists("$glob_basedir/ndb",
+ "$glob_basedir/storage/ndb",
+ "$glob_basedir/bin");
+
+ $exe_ndbd=
+ mtr_exe_maybe_exists("$ndb_path/src/kernel/ndbd",
+ "$ndb_path/ndbd",
+ "$glob_basedir/libexec/ndbd");
+ $exe_ndb_mgm=
+ mtr_exe_maybe_exists("$ndb_path/src/mgmclient/ndb_mgm",
+ "$ndb_path/ndb_mgm");
+ $exe_ndb_mgmd=
+ mtr_exe_maybe_exists("$ndb_path/src/mgmsrv/ndb_mgmd",
+ "$ndb_path/ndb_mgmd",
+ "$glob_basedir/libexec/ndb_mgmd");
+ $exe_ndb_waiter=
+ mtr_exe_maybe_exists("$ndb_path/tools/ndb_waiter",
+ "$ndb_path/ndb_waiter");
+
+ # May not exist
+ $path_ndb_tools_dir= mtr_file_exists("$ndb_path/tools",
+ "$ndb_path");
+ # May not exist
+ $path_ndb_examples_dir=
+ mtr_file_exists("$ndb_path/ndbapi-examples",
+ "$ndb_path/examples");
+ # May not exist
+ $exe_ndb_example=
+ mtr_file_exists("$path_ndb_examples_dir/ndbapi_simple/ndbapi_simple");
+
+ return ( $exe_ndbd eq "" or
+ $exe_ndb_mgm eq "" or
+ $exe_ndb_mgmd eq "" or
+ $exe_ndb_waiter eq "");
+}
+
+sub executable_setup () {
+
+ #
+ # Check if libtool is available in this distribution/clone
+ # we need it when valgrinding or debugging non installed binary
+ # Otherwise valgrind will valgrind the libtool wrapper or bash
+ # and gdb will not find the real executable to debug
+ #
+ if ( -x "../libtool")
+ {
+ $exe_libtool= "../libtool";
+ if ($opt_valgrind or $glob_debugger)
+ {
+ mtr_report("Using \"$exe_libtool\" when running valgrind or debugger");
+ }
+ }
+
+ # Look for my_print_defaults
+ $exe_my_print_defaults=
+ mtr_exe_exists(vs_config_dirs('extra', 'my_print_defaults'),
+ "$path_client_bindir/my_print_defaults",
+ "$glob_basedir/extra/my_print_defaults");
+
+ # Look for perror
+ $exe_perror= mtr_exe_exists(vs_config_dirs('extra', 'perror'),
+ "$glob_basedir/extra/perror",
+ "$path_client_bindir/perror");
+
+ # Look for the client binaries
+ $exe_mysqlcheck= mtr_exe_exists("$path_client_bindir/mysqlcheck");
+ $exe_mysqldump= mtr_exe_exists("$path_client_bindir/mysqldump");
+ $exe_mysqlimport= mtr_exe_exists("$path_client_bindir/mysqlimport");
+ $exe_mysqlshow= mtr_exe_exists("$path_client_bindir/mysqlshow");
+ $exe_mysqlbinlog= mtr_exe_exists("$path_client_bindir/mysqlbinlog");
+ $exe_mysqladmin= mtr_exe_exists("$path_client_bindir/mysqladmin");
+ $exe_mysql= mtr_exe_exists("$path_client_bindir/mysql");
+
+ if (!$opt_extern)
+ {
+ # Look for SQL scripts directory
+ if ( mtr_file_exists("$path_share/mysql_system_tables.sql") ne "")
+ {
+ # The SQL scripts are in path_share
+ $path_sql_dir= $path_share;
+ }
+ else
+ {
+ $path_sql_dir= mtr_path_exists("$glob_basedir/share",
+ "$glob_basedir/scripts");
+ }
+
+ if ( $mysql_version_id >= 50100 )
+ {
+ $exe_mysqlslap= mtr_exe_exists("$path_client_bindir/mysqlslap");
+ }
+ if ( $mysql_version_id >= 50000 and !$glob_use_embedded_server )
+ {
+ $exe_mysql_upgrade= mtr_exe_exists("$path_client_bindir/mysql_upgrade")
+ }
+ else
+ {
+ $exe_mysql_upgrade= "";
+ }
+
+ if ( ! $glob_win32 )
+ {
+ # Look for mysql_fix_system_table script
+ $exe_mysql_fix_system_tables=
+ mtr_script_exists("$glob_basedir/scripts/mysql_fix_privilege_tables",
+ "$path_client_bindir/mysql_fix_privilege_tables");
+ }
+
+ # Look for mysql_fix_privilege_tables.sql script
+ $file_mysql_fix_privilege_tables=
+ mtr_file_exists("$glob_basedir/scripts/mysql_fix_privilege_tables.sql",
+ "$glob_basedir/share/mysql_fix_privilege_tables.sql",
+ "$glob_basedir/share/mysql/mysql_fix_privilege_tables.sql");
+
+ if ( ! $opt_skip_ndbcluster and executable_setup_ndb())
+ {
+ mtr_warning("Could not find all required ndb binaries, " .
+ "all ndb tests will fail, use --skip-ndbcluster to " .
+ "skip testing it.");
+
+ foreach my $cluster (@{$clusters})
+ {
+ $cluster->{"executable_setup_failed"}= 1;
+ }
+ }
+
+ if ( ! $opt_skip_im and executable_setup_im())
+ {
+ mtr_warning("Could not find all required instance manager binaries, " .
+ "all im tests will fail, use --skip-im to " .
+ "continue without instance manager");
+ $instance_manager->{"executable_setup_failed"}= 1;
+ }
+
+ # Look for the udf_example library
+ $lib_udf_example=
+ mtr_file_exists(vs_config_dirs('sql', 'udf_example.dll'),
+ "$glob_basedir/sql/.libs/udf_example.so",);
+
+ # Look for the ha_example library
+ $lib_example_plugin=
+ mtr_file_exists(vs_config_dirs('storage/example', 'ha_example.dll'),
+ "$glob_basedir/storage/example/.libs/ha_example.so",);
+
+ }
+
+ # Look for mysqltest executable
+ if ( $glob_use_embedded_server )
+ {
+ $exe_mysqltest=
+ mtr_exe_exists(vs_config_dirs('libmysqld/examples','mysqltest_embedded'),
+ "$glob_basedir/libmysqld/examples/mysqltest_embedded",
+ "$path_client_bindir/mysqltest_embedded");
+ }
+ else
+ {
+ $exe_mysqltest= mtr_exe_exists("$path_client_bindir/mysqltest");
+ }
+
+ # Look for mysql_client_test executable which may _not_ exist in
+ # some versions, test using it should be skipped
+ if ( $glob_use_embedded_server )
+ {
+ $exe_mysql_client_test=
+ mtr_exe_maybe_exists(
+ vs_config_dirs('libmysqld/examples', 'mysql_client_test_embedded'),
+ "$glob_basedir/libmysqld/examples/mysql_client_test_embedded");
+ }
+ else
+ {
+ $exe_mysql_client_test=
+ mtr_exe_maybe_exists(vs_config_dirs('tests', 'mysql_client_test'),
+ "$glob_basedir/tests/mysql_client_test",
+ "$glob_basedir/bin/mysql_client_test");
+ }
+
+ # Look for bug25714 executable which may _not_ exist in
+ # some versions, test using it should be skipped
+ $exe_bug25714=
+ mtr_exe_maybe_exists(vs_config_dirs('tests', 'bug25714'),
+ "$glob_basedir/tests/bug25714");
+}
+
+
+sub generate_cmdline_mysqldump ($) {
+ my($mysqld) = @_;
+ return
+ mtr_native_path($exe_mysqldump) .
+ " --no-defaults -uroot --debug-check " .
+ "--port=$mysqld->{'port'} " .
+ "--socket=$mysqld->{'path_sock'} --password=";
+}
+
+
+##############################################################################
+#
+# Set environment to be used by childs of this process for
+# things that are constant duting the whole lifetime of mysql-test-run.pl
+#
+##############################################################################
+
+sub mysql_client_test_arguments()
+{
+ my $exe= $exe_mysql_client_test;
+
+ my $args;
+ mtr_init_args(\$args);
+ if ( $opt_valgrind_mysqltest )
+ {
+ valgrind_arguments($args, \$exe);
+ }
+
+ mtr_add_arg($args, "--no-defaults");
+ mtr_add_arg($args, "--testcase");
+ mtr_add_arg($args, "--user=root");
+ mtr_add_arg($args, "--port=$master->[0]->{'port'}");
+ mtr_add_arg($args, "--socket=$master->[0]->{'path_sock'}");
+
+ if ( $opt_extern || $mysql_version_id >= 50000 )
+ {
+ mtr_add_arg($args, "--vardir=$opt_vardir")
+ }
+
+ if ( $opt_debug )
+ {
+ mtr_add_arg($args,
+ "--debug=d:t:A,$path_vardir_trace/log/mysql_client_test.trace");
+ }
+
+ if ( $glob_use_embedded_server )
+ {
+ mtr_add_arg($args,
+ " -A --language=$path_language");
+ mtr_add_arg($args,
+ " -A --datadir=$slave->[0]->{'path_myddir'}");
+ mtr_add_arg($args,
+ " -A --character-sets-dir=$path_charsetsdir");
+ }
+
+ return join(" ", $exe, @$args);
+}
+
+sub mysql_upgrade_arguments()
+{
+ my $exe= $exe_mysql_upgrade;
+
+ my $args;
+ mtr_init_args(\$args);
+# if ( $opt_valgrind_mysql_ugrade )
+# {
+# valgrind_arguments($args, \$exe);
+# }
+
+ mtr_add_arg($args, "--no-defaults");
+ mtr_add_arg($args, "--user=root");
+ mtr_add_arg($args, "--port=$master->[0]->{'port'}");
+ mtr_add_arg($args, "--socket=$master->[0]->{'path_sock'}");
+ mtr_add_arg($args, "--datadir=$master->[0]->{'path_myddir'}");
+ mtr_add_arg($args, "--basedir=$glob_basedir");
+ mtr_add_arg($args, "--tmpdir=$opt_tmpdir");
+
+ if ( $opt_debug )
+ {
+ mtr_add_arg($args,
+ "--debug=d:t:A,$path_vardir_trace/log/mysql_upgrade.trace");
+ }
+
+ return join(" ", $exe, @$args);
+}
+
+# Note that some env is setup in spawn/run, in "mtr_process.pl"
+
+sub environment_setup () {
+
+ umask(022);
+
+ 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, "$glob_basedir/libmysql/.libs/",
+ "$glob_basedir/libmysql_r/.libs/",
+ "$glob_basedir/zlib.libs/");
+ }
+ else
+ {
+ push(@ld_library_paths, "$glob_basedir/lib");
+ }
+ }
+
+ # --------------------------------------------------------------------------
+ # Add the path where libndbclient can be found
+ # --------------------------------------------------------------------------
+ if ( $glob_ndbcluster_supported )
+ {
+ push(@ld_library_paths, "$glob_basedir/storage/ndb/src/.libs");
+ }
+
+ # --------------------------------------------------------------------------
+ # Valgrind need to be run with debug libraries otherwise it's almost
+ # impossible to add correct supressions, that means if "/usr/lib/debug"
+ # is available, it should be added to
+ # LD_LIBRARY_PATH
+ #
+ # But pthread is broken in libc6-dbg on Debian <= 3.1 (see Debian
+ # bug 399035, http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=399035),
+ # so don't change LD_LIBRARY_PATH on that platform.
+ # --------------------------------------------------------------------------
+ my $debug_libraries_path= "/usr/lib/debug";
+ my $deb_version;
+ if ( $opt_valgrind and -d $debug_libraries_path and
+ (! -e '/etc/debian_version' or
+ ($deb_version= mtr_grab_file('/etc/debian_version')) !~ /^[0-9]+\.[0-9]$/ or
+ $deb_version > 3.1 ) )
+ {
+ push(@ld_library_paths, $debug_libraries_path);
+ }
+
+ $ENV{'LD_LIBRARY_PATH'}= join(":", @ld_library_paths,
+ $ENV{'LD_LIBRARY_PATH'} ?
+ split(':', $ENV{'LD_LIBRARY_PATH'}) : ());
+ 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'}");
+
+ # --------------------------------------------------------------------------
+ # Also command lines in .opt files may contain env vars
+ # --------------------------------------------------------------------------
+
+ $ENV{'CHARSETSDIR'}= $path_charsetsdir;
+ $ENV{'UMASK'}= "0660"; # The octal *string*
+ $ENV{'UMASK_DIR'}= "0770"; # The octal *string*
+
+ #
+ # MySQL 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{'USE_RUNNING_SERVER'}= $opt_extern;
+ $ENV{'MYSQL_TEST_DIR'}= $glob_mysql_test_dir;
+ $ENV{'MYSQLTEST_VARDIR'}= $opt_vardir;
+ $ENV{'MYSQL_TMP_DIR'}= $opt_tmpdir;
+ $ENV{'MASTER_MYSOCK'}= $master->[0]->{'path_sock'};
+ $ENV{'MASTER_MYSOCK1'}= $master->[1]->{'path_sock'};
+ $ENV{'MASTER_MYPORT'}= $master->[0]->{'port'};
+ $ENV{'MASTER_MYPORT1'}= $master->[1]->{'port'};
+ $ENV{'SLAVE_MYSOCK'}= $slave->[0]->{'path_sock'};
+ $ENV{'SLAVE_MYPORT'}= $slave->[0]->{'port'};
+ $ENV{'SLAVE_MYPORT1'}= $slave->[1]->{'port'};
+ $ENV{'SLAVE_MYPORT2'}= $slave->[2]->{'port'};
+ $ENV{'MYSQL_TCP_PORT'}= $mysqld_variables{'port'};
+ $ENV{'DEFAULT_MASTER_PORT'}= $mysqld_variables{'master-port'};
+
+ $ENV{'IM_PATH_SOCK'}= $instance_manager->{path_sock};
+ $ENV{'IM_USERNAME'}= $instance_manager->{admin_login};
+ $ENV{'IM_PASSWORD'}= $instance_manager->{admin_password};
+ $ENV{MTR_BUILD_THREAD}= $opt_mtr_build_thread;
+
+ $ENV{'EXE_MYSQL'}= $exe_mysql;
+
+
+ # ----------------------------------------------------
+ # Setup env for NDB
+ # ----------------------------------------------------
+ if ( ! $opt_skip_ndbcluster )
+ {
+ $ENV{'NDB_MGM'}= $exe_ndb_mgm;
+
+ $ENV{'NDBCLUSTER_PORT'}= $opt_ndbcluster_port;
+ $ENV{'NDBCLUSTER_PORT_SLAVE'}= $opt_ndbcluster_port_slave;
+
+ $ENV{'NDB_EXTRA_TEST'}= $opt_ndb_extra_test;
+
+ $ENV{'NDB_BACKUP_DIR'}= $clusters->[0]->{'data_dir'};
+ $ENV{'NDB_DATA_DIR'}= $clusters->[0]->{'data_dir'};
+ $ENV{'NDB_TOOLS_DIR'}= $path_ndb_tools_dir;
+ $ENV{'NDB_TOOLS_OUTPUT'}= $path_ndb_testrun_log;
+
+ if ( $mysql_version_id >= 50000 )
+ {
+ $ENV{'NDB_EXAMPLES_DIR'}= $path_ndb_examples_dir;
+ $ENV{'MY_NDB_EXAMPLES_BINARY'}= $exe_ndb_example;
+ }
+ $ENV{'NDB_EXAMPLES_OUTPUT'}= $path_ndb_testrun_log;
+ }
+
+ # ----------------------------------------------------
+ # Setup env for IM
+ # ----------------------------------------------------
+ if ( ! $opt_skip_im )
+ {
+ $ENV{'IM_EXE'}= $exe_im;
+ $ENV{'IM_PATH_PID'}= $instance_manager->{path_pid};
+ $ENV{'IM_PATH_ANGEL_PID'}= $instance_manager->{path_angel_pid};
+ $ENV{'IM_PORT'}= $instance_manager->{port};
+ $ENV{'IM_DEFAULTS_PATH'}= $instance_manager->{defaults_file};
+ $ENV{'IM_PASSWORD_PATH'}= $instance_manager->{password_file};
+
+ $ENV{'IM_MYSQLD1_SOCK'}=
+ $instance_manager->{instances}->[0]->{path_sock};
+ $ENV{'IM_MYSQLD1_PORT'}=
+ $instance_manager->{instances}->[0]->{port};
+ $ENV{'IM_MYSQLD1_PATH_PID'}=
+ $instance_manager->{instances}->[0]->{path_pid};
+ $ENV{'IM_MYSQLD2_SOCK'}=
+ $instance_manager->{instances}->[1]->{path_sock};
+ $ENV{'IM_MYSQLD2_PORT'}=
+ $instance_manager->{instances}->[1]->{port};
+ $ENV{'IM_MYSQLD2_PATH_PID'}=
+ $instance_manager->{instances}->[1]->{path_pid};
+ }
+
+ # ----------------------------------------------------
+ # Setup env so childs can execute mysqlcheck
+ # ----------------------------------------------------
+ my $cmdline_mysqlcheck=
+ mtr_native_path($exe_mysqlcheck) .
+ " --no-defaults --debug-check -uroot " .
+ "--port=$master->[0]->{'port'} " .
+ "--socket=$master->[0]->{'path_sock'} --password=";
+
+ if ( $opt_debug )
+ {
+ $cmdline_mysqlcheck .=
+ " --debug=d:t:A,$path_vardir_trace/log/mysqlcheck.trace";
+ }
+ $ENV{'MYSQL_CHECK'}= $cmdline_mysqlcheck;
+
+ # ----------------------------------------------------
+ # Setup env to childs can execute myqldump
+ # ----------------------------------------------------
+ my $cmdline_mysqldump= generate_cmdline_mysqldump($master->[0]);
+ my $cmdline_mysqldumpslave= generate_cmdline_mysqldump($slave->[0]);
+
+ if ( $opt_debug )
+ {
+ $cmdline_mysqldump .=
+ " --debug=d:t:A,$path_vardir_trace/log/mysqldump-master.trace";
+ $cmdline_mysqldumpslave .=
+ " --debug=d:t:A,$path_vardir_trace/log/mysqldump-slave.trace";
+ }
+ $ENV{'MYSQL_DUMP'}= $cmdline_mysqldump;
+ $ENV{'MYSQL_DUMP_SLAVE'}= $cmdline_mysqldumpslave;
+
+
+ # ----------------------------------------------------
+ # Setup env so childs can execute mysqlslap
+ # ----------------------------------------------------
+ if ( $exe_mysqlslap )
+ {
+ my $cmdline_mysqlslap=
+ mtr_native_path($exe_mysqlslap) .
+ " -uroot " .
+ "--port=$master->[0]->{'port'} " .
+ "--socket=$master->[0]->{'path_sock'} --password= ";
+
+ if ( $opt_debug )
+ {
+ $cmdline_mysqlslap .=
+ " --debug=d:t:A,$path_vardir_trace/log/mysqlslap.trace";
+ }
+ $ENV{'MYSQL_SLAP'}= $cmdline_mysqlslap;
+ }
+
+ # ----------------------------------------------------
+ # Setup env so childs can execute mysqlimport
+ # ----------------------------------------------------
+ my $cmdline_mysqlimport=
+ mtr_native_path($exe_mysqlimport) .
+ " -uroot --debug-check " .
+ "--port=$master->[0]->{'port'} " .
+ "--socket=$master->[0]->{'path_sock'} --password=";
+
+ if ( $opt_debug )
+ {
+ $cmdline_mysqlimport .=
+ " --debug=d:t:A,$path_vardir_trace/log/mysqlimport.trace";
+ }
+ $ENV{'MYSQL_IMPORT'}= $cmdline_mysqlimport;
+
+
+ # ----------------------------------------------------
+ # Setup env so childs can execute mysqlshow
+ # ----------------------------------------------------
+ my $cmdline_mysqlshow=
+ mtr_native_path($exe_mysqlshow) .
+ " -uroot --debug-check " .
+ "--port=$master->[0]->{'port'} " .
+ "--socket=$master->[0]->{'path_sock'} --password=";
+
+ if ( $opt_debug )
+ {
+ $cmdline_mysqlshow .=
+ " --debug=d:t:A,$path_vardir_trace/log/mysqlshow.trace";
+ }
+ $ENV{'MYSQL_SHOW'}= $cmdline_mysqlshow;
+
+ # ----------------------------------------------------
+ # Setup env so childs can execute mysqlbinlog
+ # ----------------------------------------------------
+ my $cmdline_mysqlbinlog=
+ mtr_native_path($exe_mysqlbinlog) .
+ " --no-defaults --disable-force-if-open --debug-check";
+ if ( !$opt_extern && $mysql_version_id >= 50000 )
+ {
+ $cmdline_mysqlbinlog .=" --character-sets-dir=$path_charsetsdir";
+ }
+ # Always use the given tmpdir for the LOAD files created
+ # by mysqlbinlog
+ $cmdline_mysqlbinlog .=" --local-load=$opt_tmpdir";
+
+ if ( $opt_debug )
+ {
+ $cmdline_mysqlbinlog .=
+ " --debug=d:t:A,$path_vardir_trace/log/mysqlbinlog.trace";
+ }
+ $ENV{'MYSQL_BINLOG'}= $cmdline_mysqlbinlog;
+
+ # ----------------------------------------------------
+ # Setup env so childs can execute mysql
+ # ----------------------------------------------------
+ my $cmdline_mysql=
+ mtr_native_path($exe_mysql) .
+ " --no-defaults --debug-check --host=localhost --user=root --password= " .
+ "--port=$master->[0]->{'port'} " .
+ "--socket=$master->[0]->{'path_sock'} ".
+ "--character-sets-dir=$path_charsetsdir";
+
+ $ENV{'MYSQL'}= $cmdline_mysql;
+
+ # ----------------------------------------------------
+ # Setup env so childs can execute bug25714
+ # ----------------------------------------------------
+ $ENV{'MYSQL_BUG25714'}= $exe_bug25714;
+
+ # ----------------------------------------------------
+ # Setup env so childs can execute mysql_client_test
+ # ----------------------------------------------------
+ $ENV{'MYSQL_CLIENT_TEST'}= mysql_client_test_arguments();
+
+ # ----------------------------------------------------
+ # Setup env so childs can execute mysql_upgrade
+ # ----------------------------------------------------
+ if ( !$opt_extern && $mysql_version_id >= 50000 )
+ {
+ $ENV{'MYSQL_UPGRADE'}= mysql_upgrade_arguments();
+ }
+
+ # ----------------------------------------------------
+ # Setup env so childs can execute mysql_fix_system_tables
+ # ----------------------------------------------------
+ if ( !$opt_extern && ! $glob_win32 )
+ {
+ my $cmdline_mysql_fix_system_tables=
+ "$exe_mysql_fix_system_tables --no-defaults --host=localhost " .
+ "--user=root --password= " .
+ "--basedir=$glob_basedir --bindir=$path_client_bindir --verbose " .
+ "--port=$master->[0]->{'port'} " .
+ "--socket=$master->[0]->{'path_sock'}";
+ $ENV{'MYSQL_FIX_SYSTEM_TABLES'}= $cmdline_mysql_fix_system_tables;
+
+ }
+ $ENV{'MYSQL_FIX_PRIVILEGE_TABLES'}= $file_mysql_fix_privilege_tables;
+
+ # ----------------------------------------------------
+ # Setup env so childs can execute my_print_defaults
+ # ----------------------------------------------------
+ $ENV{'MYSQL_MY_PRINT_DEFAULTS'}= mtr_native_path($exe_my_print_defaults);
+
+ # ----------------------------------------------------
+ # Setup env so childs can execute mysqladmin
+ # ----------------------------------------------------
+ $ENV{'MYSQLADMIN'}= mtr_native_path($exe_mysqladmin);
+
+ # ----------------------------------------------------
+ # Setup env so childs can execute perror
+ # ----------------------------------------------------
+ $ENV{'MY_PERROR'}= mtr_native_path($exe_perror);
+
+ # ----------------------------------------------------
+ # Add the path where mysqld will find udf_example.so
+ # ----------------------------------------------------
+ $ENV{'UDF_EXAMPLE_LIB'}=
+ ($lib_udf_example ? basename($lib_udf_example) : "");
+ $ENV{'UDF_EXAMPLE_LIB_OPT'}=
+ ($lib_udf_example ? "--plugin_dir=" . dirname($lib_udf_example) : "");
+
+ # ----------------------------------------------------
+ # Add the path where mysqld will find ha_example.so
+ # ----------------------------------------------------
+ $ENV{'EXAMPLE_PLUGIN'}=
+ ($lib_example_plugin ? basename($lib_example_plugin) : "");
+ $ENV{'EXAMPLE_PLUGIN_OPT'}=
+ ($lib_example_plugin ? "--plugin_dir=" . dirname($lib_example_plugin) : "");
+
+ # ----------------------------------------------------
+ # Setup env so childs can execute myisampack and myisamchk
+ # ----------------------------------------------------
+ $ENV{'MYISAMCHK'}= mtr_native_path(mtr_exe_exists(
+ vs_config_dirs('storage/myisam', 'myisamchk'),
+ vs_config_dirs('myisam', 'myisamchk'),
+ "$path_client_bindir/myisamchk",
+ "$glob_basedir/storage/myisam/myisamchk",
+ "$glob_basedir/myisam/myisamchk"));
+ $ENV{'MYISAMPACK'}= mtr_native_path(mtr_exe_exists(
+ vs_config_dirs('storage/myisam', 'myisampack'),
+ vs_config_dirs('myisam', 'myisampack'),
+ "$path_client_bindir/myisampack",
+ "$glob_basedir/storage/myisam/myisampack",
+ "$glob_basedir/myisam/myisampack"));
+
+ # ----------------------------------------------------
+ # We are nice and report a bit about our settings
+ # ----------------------------------------------------
+ if (!$opt_extern)
+ {
+ print "Using MTR_BUILD_THREAD = $ENV{MTR_BUILD_THREAD}\n";
+ print "Using MASTER_MYPORT = $ENV{MASTER_MYPORT}\n";
+ print "Using MASTER_MYPORT1 = $ENV{MASTER_MYPORT1}\n";
+ print "Using SLAVE_MYPORT = $ENV{SLAVE_MYPORT}\n";
+ print "Using SLAVE_MYPORT1 = $ENV{SLAVE_MYPORT1}\n";
+ print "Using SLAVE_MYPORT2 = $ENV{SLAVE_MYPORT2}\n";
+ if ( ! $opt_skip_ndbcluster )
+ {
+ print "Using NDBCLUSTER_PORT = $ENV{NDBCLUSTER_PORT}\n";
+ if ( ! $opt_skip_ndbcluster_slave )
+ {
+ print "Using NDBCLUSTER_PORT_SLAVE = $ENV{NDBCLUSTER_PORT_SLAVE}\n";
+ }
+ }
+ if ( ! $opt_skip_im )
+ {
+ print "Using IM_PORT = $ENV{IM_PORT}\n";
+ print "Using IM_MYSQLD1_PORT = $ENV{IM_MYSQLD1_PORT}\n";
+ print "Using IM_MYSQLD2_PORT = $ENV{IM_MYSQLD2_PORT}\n";
+ }
+ }
+
+ # Create an environment variable to make it possible
+ # to detect that valgrind is being used from test cases
+ $ENV{'VALGRIND_TEST'}= $opt_valgrind;
+
+}
+
+
+##############################################################################
+#
+# If we get a ^C, we try to clean up before termination
+#
+##############################################################################
+# FIXME check restrictions what to do in a signal handler
+
+sub signal_setup () {
+ $SIG{INT}= \&handle_int_signal;
+}
+
+
+sub handle_int_signal () {
+ $SIG{INT}= 'DEFAULT'; # If we get a ^C again, we die...
+ mtr_warning("got INT signal, cleaning up.....");
+ stop_all_servers();
+ mtr_error("We die from ^C signal from user");
+}
+
+
+##############################################################################
+#
+# Handle left overs from previous runs
+#
+##############################################################################
+
+sub kill_running_servers () {
+
+ if ( $opt_fast or $glob_use_embedded_server )
+ {
+ # FIXME is embedded server really using PID files?!
+ unlink($master->[0]->{'path_pid'});
+ unlink($master->[1]->{'path_pid'});
+ unlink($slave->[0]->{'path_pid'});
+ unlink($slave->[1]->{'path_pid'});
+ unlink($slave->[2]->{'path_pid'});
+ }
+ else
+ {
+ # Ensure that no old mysqld test servers are running
+ # This is different from terminating processes we have
+ # started from this run of the script, this is terminating
+ # leftovers from previous runs.
+ mtr_kill_leftovers();
+ }
+}
+
+#
+# Remove var and any directories in var/ created by previous
+# tests
+#
+sub remove_stale_vardir () {
+
+ mtr_report("Removing Stale Files");
+
+ # Safety!
+ mtr_error("No, don't remove the vardir when running with --extern")
+ if $opt_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 and readlink($opt_vardir) eq $opt_mem )
+ {
+ # Remove the directory which the link points at
+ mtr_verbose("Removing " . readlink($opt_vardir));
+ mtr_rmtree(readlink($opt_vardir));
+
+ # Remove the "var" symlink
+ mtr_verbose("unlink($opt_vardir)");
+ unlink($opt_vardir);
+ }
+ elsif ( $opt_mem )
+ {
+ # Just remove the "var" symlink
+ mtr_report("WARNING: Removing '$opt_vardir' symlink it's wrong");
+
+ 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
+ mtr_error("The destination for symlink $opt_vardir does not exist")
+ if ! -d readlink($opt_vardir);
+
+ foreach my $bin ( glob("$opt_vardir/*") )
+ {
+ mtr_verbose("Removing bin $bin");
+ mtr_rmtree($bin);
+ }
+ }
+ }
+ else
+ {
+ # Remove the entire "var" dir
+ mtr_verbose("Removing $opt_vardir/");
+ mtr_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");
+ mtr_rmtree($opt_mem);
+ }
+
+ }
+ else
+ {
+ #
+ # Running with "var" in some other place
+ #
+
+ # Remove the var/ dir in mysql-test dir if any
+ # this could be an old symlink that shouldn't be there
+ mtr_verbose("Removing $default_vardir");
+ mtr_rmtree($default_vardir);
+
+ # Remove the "var" dir
+ mtr_verbose("Removing $opt_vardir/");
+ mtr_rmtree("$opt_vardir/");
+ }
+}
+
+#
+# Create var and the directories needed in var
+#
+sub setup_vardir() {
+ mtr_report("Creating Directories");
+
+ 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
+ mtr_error("The destination for symlink $opt_vardir does not exist")
+ if ! -d readlink($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");
+ mkpath("$opt_vardir/tmp");
+ mkpath($opt_tmpdir) if $opt_tmpdir ne "$opt_vardir/tmp";
+
+ # Create new data dirs
+ foreach my $data_dir (@data_dir_lst)
+ {
+ mkpath("$data_dir/mysql");
+ mkpath("$data_dir/test");
+ }
+
+ # Make a link std_data_ln in var/ that points to std_data
+ if ( ! $glob_win32 )
+ {
+ symlink("$glob_mysql_test_dir/std_data", "$opt_vardir/std_data_ln");
+ }
+ else
+ {
+ # on windows, copy all files from std_data into var/std_data_ln
+ mkpath("$opt_vardir/std_data_ln");
+ mtr_copy_dir("$glob_mysql_test_dir/std_data", "$opt_vardir/std_data_ln");
+ }
+
+ # Remove old log files
+ foreach my $name (glob("r/*.progress r/*.log r/*.warnings"))
+ {
+ unlink($name);
+ }
+}
+
+
+sub check_running_as_root () {
+ # Check if running as root
+ # i.e a file can be read regardless what mode we set it to
+ 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;
+
+ $ENV{'MYSQL_TEST_ROOT'}= "NO";
+ 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'}= "YES";
+ }
+
+ chmod(oct("0755"), $test_file);
+ unlink($test_file);
+
+}
+
+
+sub check_ssl_support ($) {
+ my $mysqld_variables= shift;
+
+ if ($opt_skip_ssl || $opt_extern)
+ {
+ if (!$opt_extern)
+ {
+ 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("Setting mysqld to support SSL connections");
+ $opt_ssl_supported= 1;
+}
+
+
+sub check_debug_support ($) {
+ my $mysqld_variables= shift;
+
+ if ( ! $mysqld_variables->{'debug'} )
+ {
+ #mtr_report("Binaries are not debug compiled");
+ $debug_compiled_binaries= 0;
+
+ if ( $opt_debug )
+ {
+ mtr_error("Can't use --debug, binaries does not support it");
+ }
+ return;
+ }
+ mtr_report("Binaries are debug compiled");
+ $debug_compiled_binaries= 1;
+}
+
+##############################################################################
+#
+# Helper function to handle configuration-based subdirectories which Visual
+# Studio uses for storing binaries. If opt_vs_config 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;
+
+ if ($opt_vs_config)
+ {
+ return ("$glob_basedir/$path_part/$opt_vs_config/$exe");
+ }
+
+ return ("$glob_basedir/$path_part/release/$exe",
+ "$glob_basedir/$path_part/relwithdebinfo/$exe",
+ "$glob_basedir/$path_part/debug/$exe");
+}
+
+##############################################################################
+#
+# Start the ndb cluster
+#
+##############################################################################
+
+sub check_ndbcluster_support ($) {
+ my $mysqld_variables= shift;
+
+ if ($opt_skip_ndbcluster || $opt_extern)
+ {
+ if (!$opt_extern)
+ {
+ mtr_report("Skipping ndbcluster");
+ }
+ $opt_skip_ndbcluster_slave= 1;
+ return;
+ }
+
+ if ( ! $mysqld_variables->{'ndb-connectstring'} )
+ {
+ mtr_report("Skipping ndbcluster, mysqld not compiled with ndbcluster");
+ $opt_skip_ndbcluster= 1;
+ $opt_skip_ndbcluster_slave= 1;
+ return;
+ }
+ $glob_ndbcluster_supported= 1;
+ mtr_report("Using ndbcluster when necessary, mysqld supports it");
+
+ if ( $mysql_version_id < 50100 )
+ {
+ # Slave cluster is not supported until 5.1
+ $opt_skip_ndbcluster_slave= 1;
+
+ }
+
+ return;
+}
+
+
+sub ndbcluster_start_install ($) {
+ my $cluster= shift;
+
+ mtr_report("Installing $cluster->{'name'} Cluster");
+
+ mkdir($cluster->{'data_dir'});
+
+ # Create a config file from template
+ my $ndb_no_ord=512;
+ my $ndb_no_attr=2048;
+ my $ndb_con_op=105000;
+ my $ndb_dmem="80M";
+ my $ndb_imem="24M";
+ my $ndb_pbmem="32M";
+ my $nodes= $cluster->{'nodes'};
+ my $ndb_host= "localhost";
+ my $ndb_diskless= 0;
+
+ if (!$opt_bench)
+ {
+ # Use a smaller configuration
+ if ( $mysql_version_id < 50100 )
+ {
+ # 4.1 and 5.0 is using a "larger" --small configuration
+ $ndb_no_ord=128;
+ $ndb_con_op=10000;
+ $ndb_dmem="40M";
+ $ndb_imem="12M";
+ }
+ else
+ {
+ $ndb_no_ord=32;
+ $ndb_con_op=10000;
+ $ndb_dmem="20M";
+ $ndb_imem="1M";
+ $ndb_pbmem="4M";
+ }
+ }
+
+ my $config_file_template= "lib/v1/ndb_config_${nodes}_node.ini";
+ my $config_file= "$cluster->{'data_dir'}/config.ini";
+
+ open(IN, $config_file_template)
+ or mtr_error("Can't open $config_file_template: $!");
+ open(OUT, ">", $config_file)
+ or mtr_error("Can't write to $config_file: $!");
+ while (<IN>)
+ {
+ chomp;
+
+ s/CHOOSE_MaxNoOfAttributes/$ndb_no_attr/;
+ s/CHOOSE_MaxNoOfOrderedIndexes/$ndb_no_ord/;
+ s/CHOOSE_MaxNoOfConcurrentOperations/$ndb_con_op/;
+ s/CHOOSE_DataMemory/$ndb_dmem/;
+ s/CHOOSE_IndexMemory/$ndb_imem/;
+ s/CHOOSE_Diskless/$ndb_diskless/;
+ s/CHOOSE_HOSTNAME_.*/$ndb_host/;
+ s/CHOOSE_FILESYSTEM/$cluster->{'data_dir'}/;
+ s/CHOOSE_PORT_MGM/$cluster->{'port'}/;
+ if ( $mysql_version_id < 50000 )
+ {
+ my $base_port= $cluster->{'port'} + 1;
+ s/CHOOSE_PORT_TRANSPORTER/$base_port/;
+ }
+ s/CHOOSE_DiskPageBufferMemory/$ndb_pbmem/;
+
+ print OUT "$_ \n";
+ }
+ close OUT;
+ close IN;
+
+
+ # Start cluster with "--initial"
+
+ ndbcluster_start($cluster, "--initial");
+
+ return 0;
+}
+
+
+sub ndbcluster_wait_started($$){
+ my $cluster= shift;
+ my $ndb_waiter_extra_opt= shift;
+ my $path_waiter_log= "$cluster->{'data_dir'}/ndb_waiter.log";
+ my $args;
+
+ mtr_init_args(\$args);
+
+ mtr_add_arg($args, "--no-defaults");
+ mtr_add_arg($args, "--core");
+ mtr_add_arg($args, "--ndb-connectstring=%s", $cluster->{'connect_string'});
+ mtr_add_arg($args, "--timeout=60");
+
+ if ($ndb_waiter_extra_opt)
+ {
+ mtr_add_arg($args, "$ndb_waiter_extra_opt");
+ }
+
+ # Start the ndb_waiter which will connect to the ndb_mgmd
+ # and poll it for state of the ndbd's, will return when
+ # all nodes in the cluster is started
+ my $res= mtr_run($exe_ndb_waiter, $args,
+ "", $path_waiter_log, $path_waiter_log, "");
+ mtr_verbose("ndbcluster_wait_started, returns: $res") if $res;
+ return $res;
+}
+
+
+
+sub mysqld_wait_started($){
+ my $mysqld= shift;
+
+ if (sleep_until_file_created($mysqld->{'path_pid'},
+ $mysqld->{'start_timeout'},
+ $mysqld->{'pid'}) == 0)
+ {
+ # Failed to wait for pid file
+ return 1;
+ }
+
+ # Get the "real pid" of the process, it will be used for killing
+ # the process in ActiveState's perl on windows
+ $mysqld->{'real_pid'}= mtr_get_pid_from_file($mysqld->{'path_pid'});
+
+ return 0;
+}
+
+
+sub ndb_mgmd_wait_started($) {
+ my ($cluster)= @_;
+
+ my $retries= 100;
+ while (ndbcluster_wait_started($cluster, "--no-contact") and
+ $retries)
+ {
+ # Millisceond sleep emulated with select
+ select(undef, undef, undef, (0.1));
+
+ $retries--;
+ }
+
+ return $retries == 0;
+
+}
+
+sub ndb_mgmd_start ($) {
+ my $cluster= shift;
+
+ my $args; # Arg vector
+ my $pid= -1;
+
+ mtr_init_args(\$args);
+ mtr_add_arg($args, "--no-defaults");
+ mtr_add_arg($args, "--core");
+ mtr_add_arg($args, "--nodaemon");
+ mtr_add_arg($args, "--config-file=%s", "$cluster->{'data_dir'}/config.ini");
+
+
+ my $path_ndb_mgmd_log= "$cluster->{'data_dir'}/\l$cluster->{'name'}_ndb_mgmd.log";
+ $pid= mtr_spawn($exe_ndb_mgmd, $args, "",
+ $path_ndb_mgmd_log,
+ $path_ndb_mgmd_log,
+ "",
+ { append_log_file => 1 });
+
+ # FIXME Should not be needed
+ # Unfortunately the cluster nodes will fail to start
+ # if ndb_mgmd has not started properly
+ if (ndb_mgmd_wait_started($cluster))
+ {
+ mtr_error("Failed to wait for start of ndb_mgmd");
+ }
+
+ # Remember pid of ndb_mgmd
+ $cluster->{'pid'}= $pid;
+
+ mtr_verbose("ndb_mgmd_start, pid: $pid");
+
+ return $pid;
+}
+
+
+sub ndbd_start ($$$) {
+ my $cluster= shift;
+ my $idx= shift;
+ my $extra_args= shift;
+
+ my $args; # Arg vector
+ my $pid= -1;
+
+ mtr_init_args(\$args);
+ mtr_add_arg($args, "--no-defaults");
+ mtr_add_arg($args, "--core");
+ mtr_add_arg($args, "--ndb-connectstring=%s", "$cluster->{'connect_string'}");
+ if ( $mysql_version_id >= 50000)
+ {
+ mtr_add_arg($args, "--character-sets-dir=%s", "$path_charsetsdir");
+ }
+ mtr_add_arg($args, "--nodaemon");
+ mtr_add_arg($args, "$extra_args");
+
+ my $nodeid= $cluster->{'ndbds'}->[$idx]->{'nodeid'};
+ my $path_ndbd_log= "$cluster->{'data_dir'}/ndb_${nodeid}.log";
+ $pid= mtr_spawn($exe_ndbd, $args, "",
+ $path_ndbd_log,
+ $path_ndbd_log,
+ "",
+ { append_log_file => 1 });
+
+ # Add pid to list of pids for this cluster
+ $cluster->{'ndbds'}->[$idx]->{'pid'}= $pid;
+
+ # Rememeber options used when starting
+ $cluster->{'ndbds'}->[$idx]->{'start_extra_args'}= $extra_args;
+ $cluster->{'ndbds'}->[$idx]->{'idx'}= $idx;
+
+ mtr_verbose("ndbd_start, pid: $pid");
+
+ return $pid;
+}
+
+
+sub ndbcluster_start ($$) {
+ my $cluster= shift;
+ my $extra_args= shift;
+
+ mtr_verbose("ndbcluster_start '$cluster->{'name'}'");
+
+ if ( $cluster->{'use_running'} )
+ {
+ return 0;
+ }
+
+ if ( $cluster->{'pid'} )
+ {
+ mtr_error("Cluster '$cluster->{'name'}' already started");
+ }
+
+ ndb_mgmd_start($cluster);
+
+ for ( my $idx= 0; $idx < $cluster->{'nodes'}; $idx++ )
+ {
+ ndbd_start($cluster, $idx, $extra_args);
+ }
+
+ return 0;
+}
+
+
+sub rm_ndbcluster_tables ($) {
+ my $dir= shift;
+ foreach my $bin ( glob("$dir/mysql/ndb_apply_status*"),
+ glob("$dir/mysql/ndb_schema*"))
+ {
+ unlink($bin);
+ }
+}
+
+
+##############################################################################
+#
+# Run the benchmark suite
+#
+##############################################################################
+
+sub run_benchmarks ($) {
+ my $benchmark= shift;
+
+ my $args;
+
+ if ( ! $glob_use_embedded_server )
+ {
+ mysqld_start($master->[0],[],[]);
+ if ( ! $master->[0]->{'pid'} )
+ {
+ mtr_error("Can't start the mysqld server");
+ }
+ }
+
+ mtr_init_args(\$args);
+
+ mtr_add_arg($args, "--socket=%s", $master->[0]->{'path_sock'});
+ mtr_add_arg($args, "--user=%s", $opt_user);
+
+ if ( $opt_small_bench )
+ {
+ mtr_add_arg($args, "--small-test");
+ mtr_add_arg($args, "--small-tables");
+ }
+
+ if ( $opt_with_ndbcluster )
+ {
+ mtr_add_arg($args, "--create-options=TYPE=ndb");
+ }
+
+ chdir($glob_mysql_bench_dir)
+ or mtr_error("Couldn't chdir to '$glob_mysql_bench_dir': $!");
+
+ if ( ! $benchmark )
+ {
+ mtr_add_arg($args, "--log");
+ mtr_run("$glob_mysql_bench_dir/run-all-tests", $args, "", "", "", "");
+ # FIXME check result code?!
+ }
+ elsif ( -x $benchmark )
+ {
+ mtr_run("$glob_mysql_bench_dir/$benchmark", $args, "", "", "", "");
+ # FIXME check result code?!
+ }
+ else
+ {
+ mtr_error("Benchmark $benchmark not found");
+ }
+
+ chdir($glob_mysql_test_dir); # Go back
+
+ if ( ! $glob_use_embedded_server )
+ {
+ stop_masters();
+ }
+}
+
+
+##############################################################################
+#
+# Run the tests
+#
+##############################################################################
+
+sub run_tests () {
+ my ($tests)= @_;
+
+ mtr_print_thick_line();
+
+ mtr_timer_start($glob_timers,"suite", 60 * $opt_suite_timeout);
+
+ mtr_report_tests_not_skipped_though_disabled($tests);
+
+ mtr_print_header();
+
+ foreach my $tinfo ( @$tests )
+ {
+ if (run_testcase_check_skip_test($tinfo))
+ {
+ next;
+ }
+
+ mtr_timer_start($glob_timers,"testcase", 60 * $opt_testcase_timeout);
+ run_testcase($tinfo);
+ mtr_timer_stop($glob_timers,"testcase");
+ }
+
+ mtr_print_line();
+
+ if ( ! $glob_debugger and
+ ! $opt_extern and
+ ! $glob_use_embedded_server )
+ {
+ stop_all_servers();
+ }
+
+ if ( $opt_gcov )
+ {
+ gcov_collect(); # collect coverage information
+ }
+ if ( $opt_gprof )
+ {
+ gprof_collect(); # collect coverage information
+ }
+
+ mtr_report_stats($tests);
+
+ mtr_timer_stop($glob_timers,"suite");
+}
+
+
+##############################################################################
+#
+# Initiate the test databases
+#
+##############################################################################
+
+sub initialize_servers () {
+
+ datadir_list_setup();
+
+ if ( $opt_extern )
+ {
+ # Running against an already started server, if the specified
+ # vardir does not already exist it should be created
+ if ( ! -d $opt_vardir )
+ {
+ mtr_report("Creating '$opt_vardir'");
+ setup_vardir();
+ }
+ else
+ {
+ mtr_verbose("No need to create '$opt_vardir' it already exists");
+ }
+ }
+ else
+ {
+ kill_running_servers();
+
+ if ( ! $opt_start_dirty )
+ {
+ remove_stale_vardir();
+ setup_vardir();
+
+ mysql_install_db();
+ if ( $opt_force )
+ {
+ # Save a snapshot of the freshly installed db
+ # to make it possible to restore to a known point in time
+ save_installed_db();
+ }
+ }
+ }
+ check_running_as_root();
+
+ mtr_log_init("$opt_vardir/log/mysql-test-run.log");
+
+}
+
+sub mysql_install_db () {
+
+ install_db('master', $master->[0]->{'path_myddir'});
+
+ if ($max_master_num > 1)
+ {
+ copy_install_db('master', $master->[1]->{'path_myddir'});
+ }
+
+ # Install the number of slave databses needed
+ for (my $idx= 0; $idx < $max_slave_num; $idx++)
+ {
+ copy_install_db("slave".($idx+1), $slave->[$idx]->{'path_myddir'});
+ }
+
+ if ( ! $opt_skip_im )
+ {
+ im_prepare_env($instance_manager);
+ }
+
+ my $cluster_started_ok= 1; # Assume it can be started
+
+ my $cluster= $clusters->[0]; # Master cluster
+ if ($opt_skip_ndbcluster ||
+ $cluster->{'use_running'} ||
+ $cluster->{executable_setup_failed})
+ {
+ # Don't install master cluster
+ }
+ elsif (ndbcluster_start_install($cluster))
+ {
+ mtr_warning("Failed to start install of $cluster->{name}");
+ $cluster_started_ok= 0;
+ }
+
+ $cluster= $clusters->[1]; # Slave cluster
+ if ($max_slave_num == 0 ||
+ $opt_skip_ndbcluster_slave ||
+ $cluster->{'use_running'} ||
+ $cluster->{executable_setup_failed})
+ {
+ # Don't install slave cluster
+ }
+ elsif (ndbcluster_start_install($cluster))
+ {
+ mtr_warning("Failed to start install of $cluster->{name}");
+ $cluster_started_ok= 0;
+ }
+
+ foreach $cluster (@{$clusters})
+ {
+
+ next if !$cluster->{'pid'};
+
+ $cluster->{'installed_ok'}= 1; # Assume install suceeds
+
+ if (ndbcluster_wait_started($cluster, ""))
+ {
+ # failed to install, disable usage and flag that its no ok
+ mtr_report("ndbcluster_install of $cluster->{'name'} failed");
+ $cluster->{"installed_ok"}= 0;
+
+ $cluster_started_ok= 0;
+ }
+ }
+
+ if ( ! $cluster_started_ok )
+ {
+ if ( $opt_force)
+ {
+ # Continue without cluster
+ }
+ else
+ {
+ mtr_error("To continue, re-run with '--force'.");
+ }
+ }
+
+ return 0;
+}
+
+
+sub copy_install_db ($$) {
+ my $type= shift;
+ my $data_dir= shift;
+
+ mtr_report("Installing \u$type Database");
+
+ # Just copy the installed db from first master
+ mtr_copy_dir($master->[0]->{'path_myddir'}, $data_dir);
+
+}
+
+
+sub install_db ($$) {
+ my $type= shift;
+ my $data_dir= shift;
+
+ mtr_report("Installing \u$type Database");
+
+
+ my $args;
+ mtr_init_args(\$args);
+ mtr_add_arg($args, "--no-defaults");
+ mtr_add_arg($args, "--bootstrap");
+ mtr_add_arg($args, "--basedir=%s", $path_my_basedir);
+ mtr_add_arg($args, "--datadir=%s", $data_dir);
+ mtr_add_arg($args, "--loose-skip-innodb");
+ mtr_add_arg($args, "--loose-skip-ndbcluster");
+ mtr_add_arg($args, "--tmpdir=.");
+ mtr_add_arg($args, "--core-file");
+
+ if ( $opt_debug )
+ {
+ mtr_add_arg($args, "--debug=d:t:i:A,%s/log/bootstrap_%s.trace",
+ $path_vardir_trace, $type);
+ }
+
+ if ( ! $glob_netware )
+ {
+ mtr_add_arg($args, "--language=%s", $path_language);
+ mtr_add_arg($args, "--character-sets-dir=%s", $path_charsetsdir);
+ }
+
+ # 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'} || $exe_mysqld;
+
+ # ----------------------------------------------------------------------
+ # export MYSQLD_BOOTSTRAP_CMD variable containing <path>/mysqld <args>
+ # ----------------------------------------------------------------------
+ $ENV{'MYSQLD_BOOTSTRAP_CMD'}= "$exe_mysqld_bootstrap " . join(" ", @$args);
+
+ # ----------------------------------------------------------------------
+ # Create the bootstrap.sql file
+ # ----------------------------------------------------------------------
+ my $bootstrap_sql_file= "$opt_vardir/tmp/bootstrap.sql";
+
+ # Use the mysql database for system tables
+ mtr_tofile($bootstrap_sql_file, "use mysql");
+
+ # Add the offical mysql system tables
+ # for a production system
+ mtr_appendfile_to_file("$path_sql_dir/mysql_system_tables.sql",
+ $bootstrap_sql_file);
+
+ # Add the mysql system tables initial data
+ # for a production system
+ mtr_appendfile_to_file("$path_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("$path_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("$path_sql_dir/fill_help_tables.sql",
+ $bootstrap_sql_file);
+
+ # Remove anonymous users
+ mtr_tofile($bootstrap_sql_file,
+ "DELETE FROM mysql.user where user= '';");
+
+ # Log bootstrap command
+ my $path_bootstrap_log= "$opt_vardir/log/bootstrap.log";
+ mtr_tofile($path_bootstrap_log,
+ "$exe_mysqld_bootstrap " . join(" ", @$args) . "\n");
+
+
+ if ( mtr_run($exe_mysqld_bootstrap, $args, $bootstrap_sql_file,
+ $path_bootstrap_log, $path_bootstrap_log,
+ "", { append_log_file => 1 }) != 0 )
+
+ {
+ mtr_error("Error executing mysqld --bootstrap\n" .
+ "Could not install system database from $bootstrap_sql_file\n" .
+ "see $path_bootstrap_log for errors");
+ }
+}
+
+
+sub im_prepare_env($) {
+ my $instance_manager = shift;
+
+ im_create_passwd_file($instance_manager);
+ im_prepare_data_dir($instance_manager);
+}
+
+
+sub im_create_passwd_file($) {
+ my $instance_manager = shift;
+
+ my $pwd_file_path = $instance_manager->{'password_file'};
+
+ mtr_report("Creating IM password file ($pwd_file_path)");
+
+ open(OUT, ">", $pwd_file_path)
+ or mtr_error("Can't write to $pwd_file_path: $!");
+
+ print OUT $instance_manager->{'admin_login'}, ":",
+ $instance_manager->{'admin_sha1'}, "\n";
+
+ close(OUT);
+}
+
+
+sub im_create_defaults_file($) {
+ my $instance_manager = shift;
+
+ my $defaults_file = $instance_manager->{'defaults_file'};
+
+ open(OUT, ">", $defaults_file)
+ or mtr_error("Can't write to $defaults_file: $!");
+
+ print OUT <<EOF
+[mysql]
+
+[manager]
+pid-file = $instance_manager->{path_pid}
+angel-pid-file = $instance_manager->{path_angel_pid}
+socket = $instance_manager->{path_sock}
+port = $instance_manager->{port}
+password-file = $instance_manager->{password_file}
+default-mysqld-path = $exe_mysqld
+
+EOF
+;
+
+ foreach my $instance (@{$instance_manager->{'instances'}})
+ {
+ my $server_id = $instance->{'server_id'};
+
+ print OUT <<EOF
+[mysqld$server_id]
+socket = $instance->{path_sock}
+pid-file = $instance->{path_pid}
+port = $instance->{port}
+datadir = $instance->{path_datadir}
+log = $instance->{path_datadir}/mysqld$server_id.log
+log-error = $instance->{path_datadir}/mysqld$server_id.err.log
+log-slow-queries = $instance->{path_datadir}/mysqld$server_id.slow.log
+language = $path_language
+character-sets-dir = $path_charsetsdir
+basedir = $path_my_basedir
+server_id = $server_id
+shutdown-delay = 10
+skip-stack-trace
+loose-skip-innodb
+loose-skip-ndbcluster
+EOF
+;
+ if ( $mysql_version_id < 50100 )
+ {
+ print OUT "skip-bdb\n";
+ }
+ print OUT "nonguarded\n" if $instance->{'nonguarded'};
+ if ( $mysql_version_id >= 50100 )
+ {
+ print OUT "log-output=FILE\n" if $instance->{'old_log_format'};
+ }
+ print OUT "\n";
+ }
+
+ close(OUT);
+}
+
+
+sub im_prepare_data_dir($) {
+ my $instance_manager = shift;
+
+ foreach my $instance (@{$instance_manager->{'instances'}})
+ {
+ copy_install_db(
+ 'im_mysqld_' . $instance->{'server_id'},
+ $instance->{'path_datadir'});
+ }
+}
+
+
+
+#
+# Restore snapshot of the installed slave databases
+# if the snapshot exists
+#
+sub restore_slave_databases ($) {
+ my ($num_slaves)= @_;
+
+ if ( -d $path_snapshot)
+ {
+ for (my $idx= 0; $idx < $num_slaves; $idx++)
+ {
+ my $data_dir= $slave->[$idx]->{'path_myddir'};
+ my $name= basename($data_dir);
+ mtr_rmtree($data_dir);
+ mtr_copy_dir("$path_snapshot/$name", $data_dir);
+ }
+ }
+}
+
+
+sub run_testcase_check_skip_test($)
+{
+ my ($tinfo)= @_;
+
+ # ----------------------------------------------------------------------
+ # 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_name($tinfo);
+ mtr_report_test_skipped($tinfo);
+ return 1;
+ }
+
+ if ($tinfo->{'ndb_test'})
+ {
+ foreach my $cluster (@{$clusters})
+ {
+ # Slave cluster is skipped and thus not
+ # installed, no need to perform checks
+ last if ($opt_skip_ndbcluster_slave and
+ $cluster->{'name'} eq 'Slave');
+
+ # Using running cluster - no need
+ # to check if test should be skipped
+ # will be done by test itself
+ last if ($cluster->{'use_running'});
+
+ # If test needs this cluster, check binaries was found ok
+ if ( $cluster->{'executable_setup_failed'} )
+ {
+ mtr_report_test_name($tinfo);
+ $tinfo->{comment}=
+ "Failed to find cluster binaries";
+ mtr_report_test_failed($tinfo);
+ return 1;
+ }
+
+ # If test needs this cluster, check it was installed ok
+ if ( !$cluster->{'installed_ok'} )
+ {
+ mtr_report_test_name($tinfo);
+ $tinfo->{comment}=
+ "Cluster $cluster->{'name'} was not installed ok";
+ mtr_report_test_failed($tinfo);
+ return 1;
+ }
+
+ }
+ }
+
+ if ( $tinfo->{'component_id'} eq 'im' )
+ {
+ # If test needs im, check binaries was found ok
+ if ( $instance_manager->{'executable_setup_failed'} )
+ {
+ mtr_report_test_name($tinfo);
+ $tinfo->{comment}=
+ "Failed to find MySQL manager binaries";
+ mtr_report_test_failed($tinfo);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+sub do_before_run_mysqltest($)
+{
+ my $tinfo= shift;
+ my $args;
+
+ # Remove old files produced by mysqltest
+ my $base_file= mtr_match_extension($tinfo->{'result_file'},
+ "result"); # Trim extension
+ unlink("$base_file.reject");
+ unlink("$base_file.progress");
+ unlink("$base_file.log");
+ unlink("$base_file.warnings");
+
+ if (!$opt_extern)
+ {
+ if ( $mysql_version_id < 50000 ) {
+ # Set environment variable NDB_STATUS_OK to 1
+ # if script decided to run mysqltest cluster _is_ installed ok
+ $ENV{'NDB_STATUS_OK'} = "1";
+ } elsif ( $mysql_version_id < 50100 ) {
+ # Set environment variable NDB_STATUS_OK to YES
+ # if script decided to run mysqltest cluster _is_ installed ok
+ $ENV{'NDB_STATUS_OK'} = "YES";
+ }
+ if (defined $tinfo->{binlog_format} and $mysql_version_id > 50100 )
+ {
+ # Dynamically switch binlog format of
+ # master, slave is always restarted
+ foreach my $server ( @$master )
+ {
+ next unless ($server->{'pid'});
+
+ mtr_init_args(\$args);
+ mtr_add_arg($args, "--no-defaults");
+ mtr_add_arg($args, "--user=root");
+ mtr_add_arg($args, "--port=$server->{'port'}");
+ mtr_add_arg($args, "--socket=$server->{'path_sock'}");
+
+ my $sql= "include/set_binlog_format_".$tinfo->{binlog_format}.".sql";
+ mtr_verbose("Setting binlog format:", $tinfo->{binlog_format});
+ if (mtr_run($exe_mysql, $args, $sql, "", "", "") != 0)
+ {
+ mtr_error("Failed to switch binlog format");
+ }
+ }
+ }
+ }
+}
+
+sub do_after_run_mysqltest($)
+{
+ my $tinfo= shift;
+
+ # Save info from this testcase run to mysqltest.log
+ mtr_appendfile_to_file($path_current_test_log, $path_mysqltest_log)
+ if -f $path_current_test_log;
+ mtr_appendfile_to_file($path_timefile, $path_mysqltest_log)
+ if -f $path_timefile;
+}
+
+
+sub run_testcase_mark_logs($$)
+{
+ my ($tinfo, $log_msg)= @_;
+
+ # Write a marker to all log files
+
+ # The file indicating current test name
+ mtr_tonewfile($path_current_test_log, $log_msg);
+
+ # each mysqld's .err file
+ foreach my $mysqld (@{$master}, @{$slave})
+ {
+ mtr_tofile($mysqld->{path_myerr}, $log_msg);
+ }
+
+ if ( $tinfo->{'component_id'} eq 'im')
+ {
+ mtr_tofile($instance_manager->{path_err}, $log_msg);
+ mtr_tofile($instance_manager->{path_log}, $log_msg);
+ }
+
+ # ndbcluster log file
+ mtr_tofile($path_ndb_testrun_log, $log_msg);
+
+}
+
+sub find_testcase_skipped_reason($)
+{
+ my ($tinfo)= @_;
+
+ # Set default message
+ $tinfo->{'comment'}= "Detected by testcase(no log file)";
+
+ # Open mysqltest-time(the mysqltest log file)
+ my $F= IO::File->new($path_timefile)
+ 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_timefile");
+ $reason= "Detected by testcase(reason unknown) ";
+ }
+ $tinfo->{'comment'}= $reason;
+}
+
+
+##############################################################################
+#
+# Run a single test case
+#
+##############################################################################
+
+# When we get here, we have already filtered out test cases that doesn't
+# apply to the current setup, for example if we use a running server, test
+# cases that restart the server are dropped. So this function should mostly
+# be about doing things, not a lot of logic.
+
+# We don't start and kill the servers for each testcase. But some
+# testcases needs a restart, because they specify options to start
+# mysqld with. After that testcase, we need to restart again, to set
+# back the normal options.
+
+sub run_testcase ($) {
+ my $tinfo= shift;
+
+ # -------------------------------------------------------
+ # Init variables that can change between each test case
+ # -------------------------------------------------------
+
+ $ENV{'TZ'}= $tinfo->{'timezone'};
+ mtr_verbose("Setting timezone: $tinfo->{'timezone'}");
+
+ my $master_restart= run_testcase_need_master_restart($tinfo);
+ my $slave_restart= run_testcase_need_slave_restart($tinfo);
+
+ if ($master_restart or $slave_restart)
+ {
+ # Can't restart a running server that may be in use
+ if ( $opt_extern )
+ {
+ mtr_report_test_name($tinfo);
+ $tinfo->{comment}= "Can't restart a running server";
+ mtr_report_test_skipped($tinfo);
+ return;
+ }
+
+ run_testcase_stop_servers($tinfo, $master_restart, $slave_restart);
+ }
+
+ # Write to all log files to indicate start of testcase
+ run_testcase_mark_logs($tinfo, "CURRENT_TEST: $tinfo->{name}\n");
+
+ my $died= mtr_record_dead_children();
+ if ($died or $master_restart or $slave_restart)
+ {
+ if (run_testcase_start_servers($tinfo))
+ {
+ mtr_report_test_name($tinfo);
+ report_failure_and_restart($tinfo);
+ return 1;
+ }
+ }
+ elsif ($glob_use_embedded_server)
+ {
+ run_master_init_script($tinfo);
+ }
+
+ # ----------------------------------------------------------------------
+ # If --start-and-exit or --start-dirty given, stop here to let user manually
+ # run tests
+ # ----------------------------------------------------------------------
+ if ( $opt_start_and_exit or $opt_start_dirty )
+ {
+ mtr_timer_stop_all($glob_timers);
+ mtr_report("\nServers started, exiting");
+ if ($glob_win32_perl)
+ {
+ #ActiveState perl hangs when using normal exit, use POSIX::_exit instead
+ use POSIX qw[ _exit ];
+ POSIX::_exit(0);
+ }
+ else
+ {
+ exit(0);
+ }
+ }
+
+ {
+ do_before_run_mysqltest($tinfo);
+
+ my $res= run_mysqltest($tinfo);
+ mtr_report_test_name($tinfo);
+
+ do_after_run_mysqltest($tinfo);
+
+ if ( $res == 0 )
+ {
+ mtr_report_test_passed($tinfo);
+ }
+ elsif ( $res == 62 )
+ {
+ # Testcase itself tell us to skip this one
+
+ # Try to get reason from mysqltest.log
+ find_testcase_skipped_reason($tinfo);
+ mtr_report_test_skipped($tinfo);
+ }
+ elsif ( $res == 63 )
+ {
+ $tinfo->{'timeout'}= 1; # Mark as timeout
+ report_failure_and_restart($tinfo);
+ }
+ elsif ( $res == 1 )
+ {
+ # Test case failure reported by mysqltest
+ report_failure_and_restart($tinfo);
+ }
+ else
+ {
+ # mysqltest failed, probably crashed
+ $tinfo->{comment}=
+ "mysqltest returned unexpected code $res, it has probably crashed";
+ report_failure_and_restart($tinfo);
+ }
+ }
+
+ # Remove the file that mysqltest writes info to
+ unlink($path_timefile);
+
+ # ----------------------------------------------------------------------
+ # Stop Instance Manager if we are processing an IM-test case.
+ # ----------------------------------------------------------------------
+ if ( $tinfo->{'component_id'} eq 'im' and
+ !mtr_im_stop($instance_manager, $tinfo->{'name'}))
+ {
+ mtr_error("Failed to stop Instance Manager.")
+ }
+}
+
+
+#
+# Save a snapshot of the installed test db(s)
+# I.e take a snapshot of the var/ dir
+#
+sub save_installed_db () {
+
+ mtr_report("Saving snapshot of installed databases");
+ mtr_rmtree($path_snapshot);
+
+ foreach my $data_dir (@data_dir_lst)
+ {
+ my $name= basename($data_dir);
+ mtr_copy_dir("$data_dir", "$path_snapshot/$name");
+ }
+}
+
+
+#
+# Save any interesting files in the data_dir
+# before the data dir is removed.
+#
+sub save_files_before_restore($$) {
+ my $test_name= shift;
+ my $data_dir= shift;
+ my $save_name= "$opt_vardir/log/$test_name";
+
+ # Look for core files
+ foreach my $core_file ( glob("$data_dir/core*") )
+ {
+ last if $opt_max_save_core > 0 && $num_saved_cores >= $opt_max_save_core;
+ my $core_name= basename($core_file);
+ mtr_report("Saving $core_name");
+ mkdir($save_name) if ! -d $save_name;
+ rename("$core_file", "$save_name/$core_name");
+ ++$num_saved_cores;
+ }
+}
+
+
+#
+# Restore snapshot of the installed test db(s)
+# if the snapshot exists
+#
+sub restore_installed_db ($) {
+ my $test_name= shift;
+
+ if ( -d $path_snapshot)
+ {
+ mtr_report("Restoring snapshot of databases");
+
+ foreach my $data_dir (@data_dir_lst)
+ {
+ my $name= basename($data_dir);
+ save_files_before_restore($test_name, $data_dir);
+ mtr_rmtree("$data_dir");
+ mtr_copy_dir("$path_snapshot/$name", "$data_dir");
+ }
+
+ # Remove the ndb_*_fs dirs for all ndbd nodes
+ # forcing a clean start of ndb
+ foreach my $cluster (@{$clusters})
+ {
+ foreach my $ndbd (@{$cluster->{'ndbds'}})
+ {
+ mtr_rmtree("$ndbd->{'path_fs'}" );
+ }
+ }
+ }
+ else
+ {
+ # No snapshot existed
+ mtr_error("No snapshot existed");
+ }
+}
+
+sub report_failure_and_restart ($) {
+ my $tinfo= shift;
+
+ mtr_report_test_failed($tinfo);
+ print "\n";
+ if ( $opt_force )
+ {
+ # Stop all servers that are known to be running
+ stop_all_servers();
+
+ # Restore the snapshot of the installed test db
+ restore_installed_db($tinfo->{'name'});
+ mtr_report("Resuming Tests\n");
+ return;
+ }
+
+ my $test_mode= join(" ", @::glob_test_mode) || "default";
+ mtr_report("Aborting: $tinfo->{'name'} failed in $test_mode mode. ");
+ mtr_report("To continue, re-run with '--force'.");
+ if ( ! $glob_debugger and
+ ! $opt_extern and
+ ! $glob_use_embedded_server )
+ {
+ stop_all_servers();
+ }
+ mtr_exit(1);
+
+}
+
+
+sub run_master_init_script ($) {
+ my ($tinfo)= @_;
+ my $init_script= $tinfo->{'master_sh'};
+
+ # Run master initialization shell script if one exists
+ if ( $init_script )
+ {
+ my $ret= mtr_run("/bin/sh", [$init_script], "", "", "", "");
+ if ( $ret != 0 )
+ {
+ # FIXME rewrite those scripts to return 0 if successful
+ # mtr_warning("$init_script exited with code $ret");
+ }
+ }
+}
+
+
+##############################################################################
+#
+# Start and stop servers
+#
+##############################################################################
+
+
+sub do_before_start_master ($) {
+ my ($tinfo)= @_;
+
+ my $tname= $tinfo->{'name'};
+
+ # FIXME what about second master.....
+
+ # Don't delete anything if starting dirty
+ return if ($opt_start_dirty);
+
+ foreach my $bin ( glob("$opt_vardir/log/master*-bin*") )
+ {
+ unlink($bin);
+ }
+
+ # FIXME only remove the ones that are tied to this master
+ # Remove old master.info and relay-log.info files
+ unlink("$master->[0]->{'path_myddir'}/master.info");
+ unlink("$master->[0]->{'path_myddir'}/relay-log.info");
+ unlink("$master->[1]->{'path_myddir'}/master.info");
+ unlink("$master->[1]->{'path_myddir'}/relay-log.info");
+
+ run_master_init_script($tinfo);
+}
+
+
+sub do_before_start_slave ($) {
+ my ($tinfo)= @_;
+
+ my $tname= $tinfo->{'name'};
+ my $init_script= $tinfo->{'master_sh'};
+
+ # Don't delete anything if starting dirty
+ return if ($opt_start_dirty);
+
+ foreach my $bin ( glob("$opt_vardir/log/slave*-bin*") )
+ {
+ unlink($bin);
+ }
+
+ unlink("$slave->[0]->{'path_myddir'}/master.info");
+ unlink("$slave->[0]->{'path_myddir'}/relay-log.info");
+
+ # Run slave initialization shell script if one exists
+ if ( $init_script )
+ {
+ my $ret= mtr_run("/bin/sh", [$init_script], "", "", "", "");
+ if ( $ret != 0 )
+ {
+ # FIXME rewrite those scripts to return 0 if successful
+ # mtr_warning("$init_script exited with code $ret");
+ }
+ }
+
+ foreach my $bin ( glob("$slave->[0]->{'path_myddir'}/log.*") )
+ {
+ unlink($bin);
+ }
+}
+
+
+sub mysqld_arguments ($$$$) {
+ my $args= shift;
+ my $mysqld= shift;
+ my $extra_opt= shift;
+ my $slave_master_info= shift;
+
+ my $idx= $mysqld->{'idx'};
+ my $sidx= ""; # Index as string, 0 is empty string
+ if ( $idx> 0 )
+ {
+ $sidx= $idx;
+ }
+
+ my $prefix= ""; # If mysqltest server arg
+ if ( $glob_use_embedded_server )
+ {
+ $prefix= "--server-arg=";
+ }
+
+ mtr_add_arg($args, "%s--no-defaults", $prefix);
+
+ mtr_add_arg($args, "%s--basedir=%s", $prefix, $path_my_basedir);
+ mtr_add_arg($args, "%s--character-sets-dir=%s", $prefix, $path_charsetsdir);
+
+ if ( $mysql_version_id >= 50036)
+ {
+ # By default, prevent the started mysqld to access files outside of vardir
+ mtr_add_arg($args, "%s--secure-file-priv=%s", $prefix, $opt_vardir);
+ }
+
+ if ( $mysql_version_id >= 50000 )
+ {
+ mtr_add_arg($args, "%s--log-bin-trust-function-creators", $prefix);
+ }
+
+ mtr_add_arg($args, "%s--default-character-set=latin1", $prefix);
+ mtr_add_arg($args, "%s--language=%s", $prefix, $path_language);
+ mtr_add_arg($args, "%s--tmpdir=$opt_tmpdir", $prefix);
+
+ # Increase default connect_timeout to avoid intermittent
+ # disconnects when test servers are put under load
+ # see BUG#28359
+ mtr_add_arg($args, "%s--connect-timeout=60", $prefix);
+
+
+ # 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 (!$glob_win32 and $euid == 0 and
+ grep(/^--user/, @$extra_opt, @opt_extra_mysqld_opt) == 0) {
+ mtr_add_arg($args, "%s--user=root", $prefix);
+ }
+
+ if ( $opt_valgrind_mysqld )
+ {
+ mtr_add_arg($args, "%s--skip-safemalloc", $prefix);
+
+ if ( $mysql_version_id < 50100 )
+ {
+ mtr_add_arg($args, "%s--skip-bdb", $prefix);
+ }
+ }
+
+ mtr_add_arg($args, "%s--pid-file=%s", $prefix,
+ $mysqld->{'path_pid'});
+
+ mtr_add_arg($args, "%s--port=%d", $prefix,
+ $mysqld->{'port'});
+
+ mtr_add_arg($args, "%s--socket=%s", $prefix,
+ $mysqld->{'path_sock'});
+
+ mtr_add_arg($args, "%s--datadir=%s", $prefix,
+ $mysqld->{'path_myddir'});
+
+
+ if ( $mysql_version_id >= 50106 )
+ {
+ # Turn on logging to bothe tables and file
+ mtr_add_arg($args, "%s--log-output=table,file", $prefix);
+ }
+
+ my $log_base_path= "$opt_vardir/log/$mysqld->{'type'}$sidx";
+ mtr_add_arg($args, "%s--log=%s.log", $prefix, $log_base_path);
+ mtr_add_arg($args,
+ "%s--log-slow-queries=%s-slow.log", $prefix, $log_base_path);
+
+ # Check if "extra_opt" contains --skip-log-bin
+ my $skip_binlog= grep(/^--skip-log-bin/, @$extra_opt, @opt_extra_mysqld_opt);
+ if ( $mysqld->{'type'} eq 'master' )
+ {
+ if (! ($opt_skip_master_binlog || $skip_binlog) )
+ {
+ mtr_add_arg($args, "%s--log-bin=%s/log/master-bin%s", $prefix,
+ $opt_vardir, $sidx);
+ }
+
+ mtr_add_arg($args, "%s--server-id=%d", $prefix,
+ $idx > 0 ? $idx + 101 : 1);
+
+ mtr_add_arg($args, "%s--loose-innodb_data_file_path=ibdata1:10M:autoextend",
+ $prefix);
+
+ mtr_add_arg($args, "%s--local-infile", $prefix);
+
+ if ( $idx > 0 or !$use_innodb)
+ {
+ mtr_add_arg($args, "%s--loose-skip-innodb", $prefix);
+ }
+
+ my $cluster= $clusters->[$mysqld->{'cluster'}];
+ if ( $cluster->{'pid'} || # Cluster is started
+ $cluster->{'use_running'} ) # Using running cluster
+ {
+ mtr_add_arg($args, "%s--ndbcluster", $prefix);
+ mtr_add_arg($args, "%s--ndb-connectstring=%s", $prefix,
+ $cluster->{'connect_string'});
+ if ( $mysql_version_id >= 50100 )
+ {
+ mtr_add_arg($args, "%s--ndb-extra-logging", $prefix);
+ }
+ }
+ else
+ {
+ mtr_add_arg($args, "%s--loose-skip-ndbcluster", $prefix);
+ }
+ }
+ else
+ {
+ mtr_error("unknown mysqld type")
+ unless $mysqld->{'type'} eq 'slave';
+
+ mtr_add_arg($args, "%s--init-rpl-role=slave", $prefix);
+ if (! ( $opt_skip_slave_binlog || $skip_binlog ))
+ {
+ mtr_add_arg($args, "%s--log-bin=%s/log/slave%s-bin", $prefix,
+ $opt_vardir, $sidx); # FIXME use own dir for binlogs
+ mtr_add_arg($args, "%s--log-slave-updates", $prefix);
+ }
+
+ mtr_add_arg($args, "%s--master-retry-count=10", $prefix);
+
+ mtr_add_arg($args, "%s--relay-log=%s/log/slave%s-relay-bin", $prefix,
+ $opt_vardir, $sidx);
+ mtr_add_arg($args, "%s--report-host=127.0.0.1", $prefix);
+ mtr_add_arg($args, "%s--report-port=%d", $prefix,
+ $mysqld->{'port'});
+ mtr_add_arg($args, "%s--report-user=root", $prefix);
+ mtr_add_arg($args, "%s--loose-skip-innodb", $prefix);
+ mtr_add_arg($args, "%s--skip-slave-start", $prefix);
+
+ # Directory where slaves find the dumps generated by "load data"
+ # on the server. The path need to have constant length otherwise
+ # test results will vary, thus a relative path is used.
+ my $slave_load_path= "../tmp";
+ mtr_add_arg($args, "%s--slave-load-tmpdir=%s", $prefix,
+ $slave_load_path);
+ mtr_add_arg($args, "%s--set-variable=slave_net_timeout=120", $prefix);
+
+ if ( @$slave_master_info )
+ {
+ foreach my $arg ( @$slave_master_info )
+ {
+ mtr_add_arg($args, "%s%s", $prefix, $arg);
+ }
+ }
+ else
+ {
+ if ($mysql_version_id < 50200)
+ {
+ mtr_add_arg($args, "%s--master-user=root", $prefix);
+ mtr_add_arg($args, "%s--master-connect-retry=1", $prefix);
+ mtr_add_arg($args, "%s--master-host=127.0.0.1", $prefix);
+ mtr_add_arg($args, "%s--master-password=", $prefix);
+ mtr_add_arg($args, "%s--master-port=%d", $prefix,
+ $master->[0]->{'port'}); # First master
+ }
+ my $slave_server_id= 2 + $idx;
+ my $slave_rpl_rank= $slave_server_id;
+ mtr_add_arg($args, "%s--server-id=%d", $prefix, $slave_server_id);
+ mtr_add_arg($args, "%s--rpl-recovery-rank=%d", $prefix, $slave_rpl_rank);
+ }
+
+ my $cluster= $clusters->[$mysqld->{'cluster'}];
+ if ( $cluster->{'pid'} || # Slave cluster is started
+ $cluster->{'use_running'} ) # Using running slave cluster
+ {
+ mtr_add_arg($args, "%s--ndbcluster", $prefix);
+ mtr_add_arg($args, "%s--ndb-connectstring=%s", $prefix,
+ $cluster->{'connect_string'});
+
+ if ( $mysql_version_id >= 50100 )
+ {
+ mtr_add_arg($args, "%s--ndb-extra-logging", $prefix);
+ }
+ }
+ else
+ {
+ mtr_add_arg($args, "%s--loose-skip-ndbcluster", $prefix);
+ }
+
+ } # end slave
+
+ if ( $opt_debug )
+ {
+ mtr_add_arg($args, "%s--debug=d:t:i:A,%s/log/%s%s.trace",
+ $prefix, $path_vardir_trace, $mysqld->{'type'}, $sidx);
+ }
+
+ mtr_add_arg($args, "%s--key_buffer_size=1M", $prefix);
+ mtr_add_arg($args, "%s--sort_buffer=256K", $prefix);
+ mtr_add_arg($args, "%s--max_heap_table_size=1M", $prefix);
+
+ if ( $opt_ssl_supported )
+ {
+ mtr_add_arg($args, "%s--ssl-ca=%s/std_data/cacert.pem", $prefix,
+ $glob_mysql_test_dir);
+ mtr_add_arg($args, "%s--ssl-cert=%s/std_data/server-cert.pem", $prefix,
+ $glob_mysql_test_dir);
+ mtr_add_arg($args, "%s--ssl-key=%s/std_data/server-key.pem", $prefix,
+ $glob_mysql_test_dir);
+ }
+
+ if ( $opt_warnings )
+ {
+ mtr_add_arg($args, "%s--log-warnings", $prefix);
+ }
+
+ # Indicate to "mysqld" it will be debugged in debugger
+ if ( $glob_debugger )
+ {
+ mtr_add_arg($args, "%s--gdb", $prefix);
+ }
+
+ my $found_skip_core= 0;
+ foreach my $arg ( @opt_extra_mysqld_opt, @$extra_opt )
+ {
+ # 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
+ }
+ else
+ {
+ mtr_add_arg($args, "%s%s", $prefix, $arg);
+ }
+ }
+ if ( !$found_skip_core )
+ {
+ mtr_add_arg($args, "%s%s", $prefix, "--core-file");
+ }
+
+ if ( $opt_bench )
+ {
+ mtr_add_arg($args, "%s--rpl-recovery-rank=1", $prefix);
+ mtr_add_arg($args, "%s--init-rpl-role=master", $prefix);
+ }
+ elsif ( $mysqld->{'type'} eq 'master' )
+ {
+ mtr_add_arg($args, "%s--open-files-limit=1024", $prefix);
+ }
+
+ return $args;
+}
+
+
+##############################################################################
+#
+# Start mysqld and return the PID
+#
+##############################################################################
+
+sub mysqld_start ($$$) {
+ my $mysqld= shift;
+ my $extra_opt= shift;
+ my $slave_master_info= shift;
+
+ my $args; # Arg vector
+ my $exe;
+ my $pid= -1;
+ my $wait_for_pid_file= 1;
+
+ my $type= $mysqld->{'type'};
+ my $idx= $mysqld->{'idx'};
+
+ mtr_error("Internal error: mysqld should never be started for embedded")
+ if $glob_use_embedded_server;
+
+ if ( $type eq 'master' )
+ {
+ $exe= $exe_master_mysqld;
+ }
+ elsif ( $type eq 'slave' )
+ {
+ $exe= $exe_slave_mysqld;
+ }
+ else
+ {
+ mtr_error("Unknown 'type' \"$type\" passed to mysqld_start");
+ }
+
+ mtr_init_args(\$args);
+
+ if ( $opt_valgrind_mysqld )
+ {
+ valgrind_arguments($args, \$exe);
+ }
+
+ mysqld_arguments($args,$mysqld,$extra_opt,$slave_master_info);
+
+ if ( $opt_gdb || $opt_manual_gdb)
+ {
+ gdb_arguments(\$args, \$exe, "$type"."_$idx");
+ }
+ elsif ( $opt_ddd || $opt_manual_ddd )
+ {
+ ddd_arguments(\$args, \$exe, "$type"."_$idx");
+ }
+ elsif ( $opt_debugger )
+ {
+ debugger_arguments(\$args, \$exe, "$type"."_$idx");
+ }
+ elsif ( $opt_manual_debug )
+ {
+ print "\nStart $type in your debugger\n" .
+ "dir: $glob_mysql_test_dir\n" .
+ "exe: $exe\n" .
+ "args: " . join(" ", @$args) . "\n\n" .
+ "Waiting ....\n";
+
+ # Indicate the exe should not be started
+ $exe= undef;
+ }
+ else
+ {
+ # Default to not wait until pid file has been created
+ $wait_for_pid_file= 0;
+ }
+
+ # Remove the pidfile
+ unlink($mysqld->{'path_pid'});
+
+ if ( defined $exe )
+ {
+ $pid= mtr_spawn($exe, $args, "",
+ $mysqld->{'path_myerr'},
+ $mysqld->{'path_myerr'},
+ "",
+ { append_log_file => 1 });
+ }
+
+
+ if ( $wait_for_pid_file && !sleep_until_file_created($mysqld->{'path_pid'},
+ $mysqld->{'start_timeout'},
+ $pid))
+ {
+
+ mtr_error("Failed to start mysqld $mysqld->{'type'}");
+ }
+
+
+ # Remember pid of the started process
+ $mysqld->{'pid'}= $pid;
+
+ # Remember options used when starting
+ $mysqld->{'start_opts'}= $extra_opt;
+ $mysqld->{'start_slave_master_info'}= $slave_master_info;
+
+ mtr_verbose("mysqld pid: $pid");
+ return $pid;
+}
+
+
+sub stop_all_servers () {
+
+ mtr_report("Stopping All Servers");
+
+ if ( ! $opt_skip_im )
+ {
+ mtr_report("Shutting-down Instance Manager");
+ unless (mtr_im_stop($instance_manager, "stop_all_servers"))
+ {
+ mtr_error("Failed to stop Instance Manager.")
+ }
+ }
+
+ my %admin_pids; # hash of admin processes that requests shutdown
+ my @kill_pids; # list of processes to shutdown/kill
+ my $pid;
+
+ # Start shutdown of all started masters
+ foreach my $mysqld (@{$slave}, @{$master})
+ {
+ if ( $mysqld->{'pid'} )
+ {
+ $pid= mtr_mysqladmin_start($mysqld, "shutdown", 70);
+ $admin_pids{$pid}= 1;
+
+ push(@kill_pids,{
+ pid => $mysqld->{'pid'},
+ real_pid => $mysqld->{'real_pid'},
+ pidfile => $mysqld->{'path_pid'},
+ sockfile => $mysqld->{'path_sock'},
+ port => $mysqld->{'port'},
+ errfile => $mysqld->{'path_myerr'},
+ });
+
+ $mysqld->{'pid'}= 0; # Assume we are done with it
+ }
+ }
+
+ # Start shutdown of clusters
+ foreach my $cluster (@{$clusters})
+ {
+ if ( $cluster->{'pid'} )
+ {
+ $pid= mtr_ndbmgm_start($cluster, "shutdown");
+ $admin_pids{$pid}= 1;
+
+ push(@kill_pids,{
+ pid => $cluster->{'pid'},
+ pidfile => $cluster->{'path_pid'}
+ });
+
+ $cluster->{'pid'}= 0; # Assume we are done with it
+
+ foreach my $ndbd (@{$cluster->{'ndbds'}})
+ {
+ if ( $ndbd->{'pid'} )
+ {
+ push(@kill_pids,{
+ pid => $ndbd->{'pid'},
+ pidfile => $ndbd->{'path_pid'},
+ });
+ $ndbd->{'pid'}= 0;
+ }
+ }
+ }
+ }
+
+ # Wait blocking until all shutdown processes has completed
+ mtr_wait_blocking(\%admin_pids);
+
+ # Make sure that process has shutdown else try to kill them
+ mtr_check_stop_servers(\@kill_pids);
+
+ foreach my $mysqld (@{$master}, @{$slave})
+ {
+ rm_ndbcluster_tables($mysqld->{'path_myddir'});
+ }
+}
+
+
+sub run_testcase_need_master_restart($)
+{
+ my ($tinfo)= @_;
+
+ # We try to find out if we are to restart the master(s)
+ my $do_restart= 0; # Assumes we don't have to
+
+ if ( $glob_use_embedded_server )
+ {
+ mtr_verbose("Never start or restart for embedded server");
+ return $do_restart;
+ }
+ elsif ( $tinfo->{'master_sh'} )
+ {
+ $do_restart= 1; # Always restart if script to run
+ mtr_verbose("Restart master: Always restart if script to run");
+ }
+ if ( $tinfo->{'force_restart'} )
+ {
+ $do_restart= 1; # Always restart if --force-restart in -opt file
+ mtr_verbose("Restart master: Restart forced with --force-restart");
+ }
+ elsif ( ! $opt_skip_ndbcluster and
+ !$tinfo->{'ndb_test'} and
+ $clusters->[0]->{'pid'} != 0 )
+ {
+ $do_restart= 1; # Restart without cluster
+ mtr_verbose("Restart master: Test does not need cluster");
+ }
+ elsif ( ! $opt_skip_ndbcluster and
+ $tinfo->{'ndb_test'} and
+ $clusters->[0]->{'pid'} == 0 )
+ {
+ $do_restart= 1; # Restart with cluster
+ mtr_verbose("Restart master: Test need cluster");
+ }
+ elsif( $tinfo->{'component_id'} eq 'im' )
+ {
+ $do_restart= 1;
+ mtr_verbose("Restart master: Always restart for im tests");
+ }
+ elsif ( $master->[0]->{'running_master_options'} and
+ $master->[0]->{'running_master_options'}->{'timezone'} ne
+ $tinfo->{'timezone'})
+ {
+ $do_restart= 1;
+ mtr_verbose("Restart master: Different timezone");
+ }
+ # Check that running master was started with same options
+ # as the current test requires
+ elsif (! mtr_same_opts($master->[0]->{'start_opts'},
+ $tinfo->{'master_opt'}) )
+ {
+ $do_restart= 1;
+ mtr_verbose("Restart master: running with different options '" .
+ join(" ", @{$tinfo->{'master_opt'}}) . "' != '" .
+ join(" ", @{$master->[0]->{'start_opts'}}) . "'" );
+ }
+ elsif( ! $master->[0]->{'pid'} )
+ {
+ if ( $opt_extern )
+ {
+ $do_restart= 0;
+ mtr_verbose("No restart: using extern master");
+ }
+ else
+ {
+ $do_restart= 1;
+ mtr_verbose("Restart master: master is not started");
+ }
+ }
+ return $do_restart;
+}
+
+sub run_testcase_need_slave_restart($)
+{
+ my ($tinfo)= @_;
+
+ # We try to find out if we are to restart the slaves
+ my $do_slave_restart= 0; # Assumes we don't have to
+
+ if ( $glob_use_embedded_server )
+ {
+ mtr_verbose("Never start or restart for embedded server");
+ return $do_slave_restart;
+ }
+ elsif ( $max_slave_num == 0)
+ {
+ mtr_verbose("Skip slave restart: No testcase use slaves");
+ }
+ else
+ {
+
+ # Check if any slave is currently started
+ my $any_slave_started= 0;
+ foreach my $mysqld (@{$slave})
+ {
+ if ( $mysqld->{'pid'} )
+ {
+ $any_slave_started= 1;
+ last;
+ }
+ }
+
+ if ($any_slave_started)
+ {
+ mtr_verbose("Restart slave: Slave is started, always restart");
+ $do_slave_restart= 1;
+ }
+ elsif ( $tinfo->{'slave_num'} )
+ {
+ mtr_verbose("Restart slave: Test need slave");
+ $do_slave_restart= 1;
+ }
+ }
+
+ return $do_slave_restart;
+
+}
+
+# ----------------------------------------------------------------------
+# If not using a running servers we may need to stop and restart.
+# We restart in the case we have initiation scripts, server options
+# etc to run. But we also restart again after the test first restart
+# and test is run, to get back to normal server settings.
+#
+# To make the code a bit more clean, we actually only stop servers
+# here, and mark this to be done. Then a generic "start" part will
+# start up the needed servers again.
+# ----------------------------------------------------------------------
+
+sub run_testcase_stop_servers($$$) {
+ my ($tinfo, $do_restart, $do_slave_restart)= @_;
+ my $pid;
+ my %admin_pids; # hash of admin processes that requests shutdown
+ my @kill_pids; # list of processes to shutdown/kill
+
+ # Remember if we restarted for this test case (count restarts)
+ $tinfo->{'restarted'}= $do_restart;
+
+ if ( $do_restart )
+ {
+ delete $master->[0]->{'running_master_options'}; # Forget history
+
+ # Start shutdown of all started masters
+ foreach my $mysqld (@{$master})
+ {
+ if ( $mysqld->{'pid'} )
+ {
+ $pid= mtr_mysqladmin_start($mysqld, "shutdown", 20);
+
+ $admin_pids{$pid}= 1;
+
+ push(@kill_pids,{
+ pid => $mysqld->{'pid'},
+ real_pid => $mysqld->{'real_pid'},
+ pidfile => $mysqld->{'path_pid'},
+ sockfile => $mysqld->{'path_sock'},
+ port => $mysqld->{'port'},
+ errfile => $mysqld->{'path_myerr'},
+ });
+
+ $mysqld->{'pid'}= 0; # Assume we are done with it
+ }
+ }
+
+ # Start shutdown of master cluster
+ my $cluster= $clusters->[0];
+ if ( $cluster->{'pid'} )
+ {
+ $pid= mtr_ndbmgm_start($cluster, "shutdown");
+ $admin_pids{$pid}= 1;
+
+ push(@kill_pids,{
+ pid => $cluster->{'pid'},
+ pidfile => $cluster->{'path_pid'}
+ });
+
+ $cluster->{'pid'}= 0; # Assume we are done with it
+
+ foreach my $ndbd (@{$cluster->{'ndbds'}})
+ {
+ push(@kill_pids,{
+ pid => $ndbd->{'pid'},
+ pidfile => $ndbd->{'path_pid'},
+ });
+ $ndbd->{'pid'}= 0; # Assume we are done with it
+ }
+ }
+ }
+
+ if ( $do_restart || $do_slave_restart )
+ {
+
+ delete $slave->[0]->{'running_slave_options'}; # Forget history
+
+ # Start shutdown of all started slaves
+ foreach my $mysqld (@{$slave})
+ {
+ if ( $mysqld->{'pid'} )
+ {
+ $pid= mtr_mysqladmin_start($mysqld, "shutdown", 20);
+
+ $admin_pids{$pid}= 1;
+
+ push(@kill_pids,{
+ pid => $mysqld->{'pid'},
+ real_pid => $mysqld->{'real_pid'},
+ pidfile => $mysqld->{'path_pid'},
+ sockfile => $mysqld->{'path_sock'},
+ port => $mysqld->{'port'},
+ errfile => $mysqld->{'path_myerr'},
+ });
+
+
+ $mysqld->{'pid'}= 0; # Assume we are done with it
+ }
+ }
+
+ # Start shutdown of slave cluster
+ my $cluster= $clusters->[1];
+ if ( $cluster->{'pid'} )
+ {
+ $pid= mtr_ndbmgm_start($cluster, "shutdown");
+
+ $admin_pids{$pid}= 1;
+
+ push(@kill_pids,{
+ pid => $cluster->{'pid'},
+ pidfile => $cluster->{'path_pid'}
+ });
+
+ $cluster->{'pid'}= 0; # Assume we are done with it
+
+ foreach my $ndbd (@{$cluster->{'ndbds'}} )
+ {
+ push(@kill_pids,{
+ pid => $ndbd->{'pid'},
+ pidfile => $ndbd->{'path_pid'},
+ });
+ $ndbd->{'pid'}= 0; # Assume we are done with it
+ }
+ }
+ }
+
+ # ----------------------------------------------------------------------
+ # Shutdown has now been started and lists for the shutdown processes
+ # and the processes to be killed has been created
+ # ----------------------------------------------------------------------
+
+ # Wait blocking until all shutdown processes has completed
+ mtr_wait_blocking(\%admin_pids);
+
+
+ # Make sure that process has shutdown else try to kill them
+ mtr_check_stop_servers(\@kill_pids);
+
+ foreach my $mysqld (@{$master}, @{$slave})
+ {
+ if ( ! $mysqld->{'pid'} )
+ {
+ # Remove ndbcluster tables if server is stopped
+ rm_ndbcluster_tables($mysqld->{'path_myddir'});
+ }
+ }
+}
+
+
+#
+# run_testcase_start_servers
+#
+# Start the servers needed by this test case
+#
+# RETURN
+# 0 OK
+# 1 Start failed
+#
+
+sub run_testcase_start_servers($) {
+ my $tinfo= shift;
+ my $tname= $tinfo->{'name'};
+
+ if ( $tinfo->{'component_id'} eq 'mysqld' )
+ {
+ if ( ! $opt_skip_ndbcluster and
+ !$clusters->[0]->{'pid'} and
+ $tinfo->{'ndb_test'} )
+ {
+ # Test need cluster, cluster is not started, start it
+ ndbcluster_start($clusters->[0], "");
+ }
+
+ if ( !$master->[0]->{'pid'} )
+ {
+ # Master mysqld is not started
+ do_before_start_master($tinfo);
+
+ mysqld_start($master->[0],$tinfo->{'master_opt'},[]);
+
+ }
+
+ if ( $clusters->[0]->{'pid'} || $clusters->[0]->{'use_running'}
+ and ! $master->[1]->{'pid'} and
+ $tinfo->{'master_num'} > 1 )
+ {
+ # Test needs cluster, start an extra mysqld connected to cluster
+
+ if ( $mysql_version_id >= 50100 )
+ {
+ # First wait for first mysql server to have created ndb system
+ # tables ok FIXME This is a workaround so that only one mysqld
+ # create the tables
+ if ( ! sleep_until_file_created(
+ "$master->[0]->{'path_myddir'}/mysql/ndb_apply_status.ndb",
+ $master->[0]->{'start_timeout'},
+ $master->[0]->{'pid'}))
+ {
+
+ $tinfo->{'comment'}= "Failed to create 'mysql/ndb_apply_status' table";
+ return 1;
+ }
+ }
+ mysqld_start($master->[1],$tinfo->{'master_opt'},[]);
+ }
+
+ # Save this test case information, so next can examine it
+ $master->[0]->{'running_master_options'}= $tinfo;
+ }
+ elsif ( ! $opt_skip_im and $tinfo->{'component_id'} eq 'im' )
+ {
+ # We have to create defaults file every time, in order to ensure that it
+ # will be the same for each test. The problem is that test can change the
+ # file (by SET/UNSET commands), so w/o recreating the file, execution of
+ # one test can affect the other.
+
+ im_create_defaults_file($instance_manager);
+
+ if ( ! mtr_im_start($instance_manager, $tinfo->{im_opts}) )
+ {
+ $tinfo->{'comment'}= "Failed to start Instance Manager. ";
+ return 1;
+ }
+ }
+
+ # ----------------------------------------------------------------------
+ # Start slaves - if needed
+ # ----------------------------------------------------------------------
+ if ( $tinfo->{'slave_num'} )
+ {
+ restore_slave_databases($tinfo->{'slave_num'});
+
+ do_before_start_slave($tinfo);
+
+ if ( ! $opt_skip_ndbcluster_slave and
+ !$clusters->[1]->{'pid'} and
+ $tinfo->{'ndb_test'} )
+ {
+ # Test need slave cluster, cluster is not started, start it
+ ndbcluster_start($clusters->[1], "");
+ }
+
+ for ( my $idx= 0; $idx < $tinfo->{'slave_num'}; $idx++ )
+ {
+ if ( ! $slave->[$idx]->{'pid'} )
+ {
+ mysqld_start($slave->[$idx],$tinfo->{'slave_opt'},
+ $tinfo->{'slave_mi'});
+
+ }
+ }
+
+ # Save this test case information, so next can examine it
+ $slave->[0]->{'running_slave_options'}= $tinfo;
+ }
+
+ # Wait for clusters to start
+ foreach my $cluster (@{$clusters})
+ {
+
+ next if !$cluster->{'pid'};
+
+ if (ndbcluster_wait_started($cluster, ""))
+ {
+ # failed to start
+ $tinfo->{'comment'}= "Start of $cluster->{'name'} cluster failed";
+ return 1;
+ }
+ }
+
+ # Wait for mysqld's to start
+ foreach my $mysqld (@{$master},@{$slave})
+ {
+
+ next if !$mysqld->{'pid'};
+
+ if (mysqld_wait_started($mysqld))
+ {
+ # failed to start
+ $tinfo->{'comment'}=
+ "Failed to start $mysqld->{'type'} mysqld $mysqld->{'idx'}";
+ return 1;
+ }
+ }
+ return 0;
+}
+
+#
+# Run include/check-testcase.test
+# Before a testcase, run in record mode, save result file to var
+# After testcase, run and compare with the recorded file, they should be equal!
+#
+# RETURN VALUE
+# 0 OK
+# 1 Check failed
+#
+sub run_check_testcase ($$) {
+
+ my $mode= shift;
+ my $mysqld= shift;
+
+ my $name= "check-" . $mysqld->{'type'} . $mysqld->{'idx'};
+
+ my $args;
+ mtr_init_args(\$args);
+
+ mtr_add_arg($args, "--no-defaults");
+ mtr_add_arg($args, "--silent");
+ mtr_add_arg($args, "--skip-safemalloc");
+ mtr_add_arg($args, "--tmpdir=%s", $opt_tmpdir);
+ mtr_add_arg($args, "--character-sets-dir=%s", $path_charsetsdir);
+
+ mtr_add_arg($args, "--socket=%s", $mysqld->{'path_sock'});
+ mtr_add_arg($args, "--port=%d", $mysqld->{'port'});
+ mtr_add_arg($args, "--database=test");
+ mtr_add_arg($args, "--user=%s", $opt_user);
+ mtr_add_arg($args, "--password=");
+
+ mtr_add_arg($args, "-R");
+ mtr_add_arg($args, "$opt_vardir/tmp/$name.result");
+
+ if ( $mode eq "before" )
+ {
+ mtr_add_arg($args, "--record");
+ }
+
+ my $res = mtr_run_test($exe_mysqltest,$args,
+ "include/check-testcase.test", "", "", "");
+
+ if ( $res == 1 and $mode eq "after")
+ {
+ mtr_run("diff",["-u",
+ "$opt_vardir/tmp/$name.result",
+ "$opt_vardir/tmp/$name.reject"],
+ "", "", "", "");
+ }
+ elsif ( $res )
+ {
+ mtr_error("Could not execute 'check-testcase' $mode testcase");
+ }
+ return $res;
+}
+
+##############################################################################
+#
+# Report the features that were compiled in
+#
+##############################################################################
+
+sub run_report_features () {
+ my $args;
+
+ if ( ! $glob_use_embedded_server )
+ {
+ mysqld_start($master->[0],[],[]);
+ if ( ! $master->[0]->{'pid'} )
+ {
+ mtr_error("Can't start the mysqld server");
+ }
+ mysqld_wait_started($master->[0]);
+ }
+
+ my $tinfo = {};
+ $tinfo->{'name'} = 'report features';
+ $tinfo->{'result_file'} = undef;
+ $tinfo->{'component_id'} = 'mysqld';
+ $tinfo->{'path'} = 'include/report-features.test';
+ $tinfo->{'timezone'}= "GMT-3";
+ $tinfo->{'slave_num'} = 0;
+ $tinfo->{'master_opt'} = [];
+ $tinfo->{'slave_opt'} = [];
+ $tinfo->{'slave_mi'} = [];
+ $tinfo->{'comment'} = 'report server features';
+ run_mysqltest($tinfo);
+
+ if ( ! $glob_use_embedded_server )
+ {
+ stop_all_servers();
+ }
+}
+
+
+sub run_mysqltest ($) {
+ my ($tinfo)= @_;
+ my $exe= $exe_mysqltest;
+ my $args;
+
+ mtr_init_args(\$args);
+
+ mtr_add_arg($args, "--no-defaults");
+ mtr_add_arg($args, "--silent");
+ mtr_add_arg($args, "--skip-safemalloc");
+ 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;
+
+ if ($tinfo->{'component_id'} eq 'im')
+ {
+ mtr_add_arg($args, "--socket=%s", $instance_manager->{'path_sock'});
+ mtr_add_arg($args, "--port=%d", $instance_manager->{'port'});
+ mtr_add_arg($args, "--user=%s", $instance_manager->{'admin_login'});
+ mtr_add_arg($args, "--password=%s", $instance_manager->{'admin_password'});
+ }
+ else # component_id == mysqld
+ {
+ mtr_add_arg($args, "--socket=%s", $master->[0]->{'path_sock'});
+ mtr_add_arg($args, "--port=%d", $master->[0]->{'port'});
+ mtr_add_arg($args, "--database=test");
+ mtr_add_arg($args, "--user=%s", $opt_user);
+ mtr_add_arg($args, "--password=");
+ }
+
+ 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_strace_client )
+ {
+ $exe= "strace"; # FIXME there are ktrace, ....
+ mtr_add_arg($args, "-o");
+ mtr_add_arg($args, "%s/log/mysqltest.strace", $opt_vardir);
+ mtr_add_arg($args, "$exe_mysqltest");
+ }
+
+ if ( $opt_timer )
+ {
+ 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_debug )
+ {
+ mtr_add_arg($args, "--debug=d:t:A,%s/log/mysqltest.trace",
+ $path_vardir_trace);
+ }
+
+ if ( $opt_ssl_supported )
+ {
+ mtr_add_arg($args, "--ssl-ca=%s/std_data/cacert.pem",
+ $glob_mysql_test_dir);
+ mtr_add_arg($args, "--ssl-cert=%s/std_data/client-cert.pem",
+ $glob_mysql_test_dir);
+ mtr_add_arg($args, "--ssl-key=%s/std_data/client-key.pem",
+ $glob_mysql_test_dir);
+ }
+
+ if ( $opt_ssl )
+ {
+ # Turn on SSL for _all_ test cases if option --ssl was used
+ mtr_add_arg($args, "--ssl");
+ }
+ elsif ( $opt_ssl_supported )
+ {
+ mtr_add_arg($args, "--skip-ssl");
+ }
+
+ # ----------------------------------------------------------------------
+ # If embedded server, we create server args to give mysqltest to pass on
+ # ----------------------------------------------------------------------
+
+ if ( $glob_use_embedded_server )
+ {
+ mysqld_arguments($args,$master->[0],$tinfo->{'master_opt'},[]);
+ }
+
+ # ----------------------------------------------------------------------
+ # export MYSQL_TEST variable containing <path>/mysqltest <args>
+ # ----------------------------------------------------------------------
+ $ENV{'MYSQL_TEST'}=
+ mtr_native_path($exe_mysqltest) . " " . join(" ", @$args);
+
+ # ----------------------------------------------------------------------
+ # Add arguments that should not go into the MYSQL_TEST env var
+ # ----------------------------------------------------------------------
+
+ if ( $opt_valgrind_mysqltest )
+ {
+ # Prefix the Valgrind options to the argument list.
+ # We do this here, since we do not want to Valgrind the nested invocations
+ # of mysqltest; that would mess up the stderr output causing test failure.
+ my @args_saved = @$args;
+ mtr_init_args(\$args);
+ valgrind_arguments($args, \$exe);
+ mtr_add_arg($args, "%s", $_) for @args_saved;
+ }
+
+ 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=20");
+
+ if ( defined $tinfo->{'result_file'} ) {
+ mtr_add_arg($args, "--result-file=%s", $tinfo->{'result_file'});
+ }
+
+ if ( $opt_record )
+ {
+ mtr_add_arg($args, "--record");
+ }
+
+ if ( $opt_client_gdb )
+ {
+ gdb_arguments(\$args, \$exe, "client");
+ }
+ elsif ( $opt_client_ddd )
+ {
+ ddd_arguments(\$args, \$exe, "client");
+ }
+ elsif ( $opt_client_debugger )
+ {
+ debugger_arguments(\$args, \$exe, "client");
+ }
+
+ if ( $opt_check_testcases )
+ {
+ foreach my $mysqld (@{$master}, @{$slave})
+ {
+ if ($mysqld->{'pid'})
+ {
+ run_check_testcase("before", $mysqld);
+ }
+ }
+ }
+
+ my $res = mtr_run_test($exe,$args,"","",$path_timefile,"");
+
+ if ( $opt_check_testcases )
+ {
+ foreach my $mysqld (@{$master}, @{$slave})
+ {
+ if ($mysqld->{'pid'})
+ {
+ if (run_check_testcase("after", $mysqld))
+ {
+ # Check failed, mark the test case with that info
+ $tinfo->{'check_testcase_failed'}= 1;
+ }
+ }
+ }
+ }
+
+ return $res;
+
+}
+
+
+#
+# Modify the exe and args so that program is run in gdb in xterm
+#
+sub gdb_arguments {
+ my $args= shift;
+ my $exe= shift;
+ my $type= shift;
+
+ # Write $args to gdb init file
+ my $str= join(" ", @$$args);
+ my $gdb_init_file= "$opt_tmpdir/gdbinit.$type";
+
+ # Remove the old gdbinit file
+ unlink($gdb_init_file);
+
+ if ( $type eq "client" )
+ {
+ # write init file for client
+ mtr_tofile($gdb_init_file,
+ "set args $str\n" .
+ "break main\n");
+ }
+ else
+ {
+ # write init file for mysqld
+ mtr_tofile($gdb_init_file,
+ "set args $str\n" .
+ "break mysql_parse\n" .
+ "commands 1\n" .
+ "disable 1\n" .
+ "end\n" .
+ "run");
+ }
+
+ if ( $opt_manual_gdb )
+ {
+ print "\nTo start gdb for $type, type in another window:\n";
+ print "gdb -cd $glob_mysql_test_dir -x $gdb_init_file $$exe\n";
+
+ # Indicate the exe should not be started
+ $$exe= undef;
+ return;
+ }
+
+ $$args= [];
+ mtr_add_arg($$args, "-title");
+ mtr_add_arg($$args, "$type");
+ mtr_add_arg($$args, "-e");
+
+ if ( $exe_libtool )
+ {
+ mtr_add_arg($$args, $exe_libtool);
+ mtr_add_arg($$args, "--mode=execute");
+ }
+
+ mtr_add_arg($$args, "gdb");
+ mtr_add_arg($$args, "-x");
+ mtr_add_arg($$args, "$gdb_init_file");
+ mtr_add_arg($$args, "$$exe");
+
+ $$exe= "xterm";
+}
+
+
+#
+# Modify the exe and args so that program is run in ddd
+#
+sub ddd_arguments {
+ my $args= shift;
+ my $exe= shift;
+ my $type= shift;
+
+ # Write $args to ddd init file
+ my $str= join(" ", @$$args);
+ my $gdb_init_file= "$opt_tmpdir/gdbinit.$type";
+
+ # Remove the old gdbinit file
+ unlink($gdb_init_file);
+
+ if ( $type eq "client" )
+ {
+ # write init file for client
+ mtr_tofile($gdb_init_file,
+ "set args $str\n" .
+ "break main\n");
+ }
+ else
+ {
+ # write init file for mysqld
+ mtr_tofile($gdb_init_file,
+ "file $$exe\n" .
+ "set args $str\n" .
+ "break mysql_parse\n" .
+ "commands 1\n" .
+ "disable 1\n" .
+ "end");
+ }
+
+ if ( $opt_manual_ddd )
+ {
+ print "\nTo start ddd for $type, type in another window:\n";
+ print "ddd -cd $glob_mysql_test_dir -x $gdb_init_file $$exe\n";
+
+ # Indicate the exe should not be started
+ $$exe= undef;
+ return;
+ }
+
+ my $save_exe= $$exe;
+ $$args= [];
+ if ( $exe_libtool )
+ {
+ $$exe= $exe_libtool;
+ mtr_add_arg($$args, "--mode=execute");
+ mtr_add_arg($$args, "ddd");
+ }
+ else
+ {
+ $$exe= "ddd";
+ }
+ mtr_add_arg($$args, "--command=$gdb_init_file");
+ mtr_add_arg($$args, "$save_exe");
+}
+
+
+#
+# Modify the exe and args so that program is run in the selected debugger
+#
+sub debugger_arguments {
+ my $args= shift;
+ my $exe= shift;
+ my $debugger= $opt_debugger || $opt_client_debugger;
+
+ if ( $debugger =~ /vcexpress|vc|devenv/ )
+ {
+ # vc[express] /debugexe exe arg1 .. argn
+
+ # Add /debugexe and name of the exe before args
+ unshift(@$$args, "/debugexe");
+ unshift(@$$args, "$$exe");
+
+ # Set exe to debuggername
+ $$exe= $debugger;
+
+ }
+ elsif ( $debugger =~ /windbg/ )
+ {
+ # windbg exe arg1 .. argn
+
+ # Add name of the exe before args
+ unshift(@$$args, "$$exe");
+
+ # Set exe to debuggername
+ $$exe= $debugger;
+
+ }
+ elsif ( $debugger eq "dbx" )
+ {
+ # xterm -e dbx -r exe arg1 .. argn
+
+ unshift(@$$args, $$exe);
+ unshift(@$$args, "-r");
+ unshift(@$$args, $debugger);
+ unshift(@$$args, "-e");
+
+ $$exe= "xterm";
+
+ }
+ else
+ {
+ mtr_error("Unknown argument \"$debugger\" passed to --debugger");
+ }
+}
+
+
+#
+# Modify the exe and args so that program is run in valgrind
+#
+sub valgrind_arguments {
+ my $args= shift;
+ my $exe= shift;
+
+ if ( $opt_callgrind)
+ {
+ mtr_add_arg($args, "--tool=callgrind");
+ mtr_add_arg($args, "--base=$opt_vardir/log");
+ }
+ else
+ {
+ mtr_add_arg($args, "--tool=memcheck"); # From >= 2.1.2 needs this option
+ mtr_add_arg($args, "--alignment=8");
+ mtr_add_arg($args, "--leak-check=yes");
+ mtr_add_arg($args, "--num-callers=16");
+ mtr_add_arg($args, "--suppressions=%s/valgrind.supp", $glob_mysql_test_dir)
+ if -f "$glob_mysql_test_dir/valgrind.supp";
+ }
+
+ # Add valgrind options, can be overriden by user
+ mtr_add_arg($args, '%s', $_) for (@valgrind_args);
+
+ mtr_add_arg($args, $$exe);
+
+ $$exe= $opt_valgrind_path || "valgrind";
+
+ if ($exe_libtool)
+ {
+ # Add "libtool --mode-execute" before the test to execute
+ # if running in valgrind(to avoid valgrinding bash)
+ unshift(@$args, "--mode=execute", $$exe);
+ $$exe= $exe_libtool;
+ }
+}
+
+
+##############################################################################
+#
+# Usage
+#
+##############################################################################
+
+sub usage ($) {
+ my $message= shift;
+
+ if ( $message )
+ {
+ print STDERR "$message\n";
+ }
+
+ print <<HERE;
+
+$0 [ OPTIONS ] [ TESTCASE ]
+
+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
+ compress Use the compressed protocol between client and server
+ ssl Use ssl protocol between client and server
+ skip-ssl Dont start server with support for ssl connections
+ bench Run the benchmark suite
+ small-bench Run the benchmarks with --small-tests --small-tables
+ ndb|with-ndbcluster Use cluster as default table type
+ vs-config Visual Studio configuration used to create executables
+ (default: MTR_VS_CONFIG environment variable)
+
+Options to control directories to use
+ benchdir=DIR The directory where the benchmark suite is stored
+ (default: ../../mysql-bench)
+ 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 Run testsuite in "memory" using tmpfs or ramdisk
+ Attempts to find a suitable location
+ using a builtin list of standard locations
+ for tmpfs (/dev/shm)
+ The option can also be set using environment
+ variable MTR_MEM=[DIR]
+
+Options to control what test suites or cases to run
+
+ force Continue to run the suite after failure
+ with-ndbcluster-only Run only tests that include "ndb" in the filename
+ skip-ndb[cluster] Skip all tests that need cluster
+ skip-ndb[cluster]-slave Skip all tests that need a slave cluster
+ ndb-extra Run extra tests from ndb directory
+ 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 from test prefixed with PREFIX
+ suite[s]=NAME1,..,NAMEN Collect tests in suites from the comma separated
+ list of suite names.
+ The default is: "$opt_suites_default"
+ skip-rpl Skip the replication test cases.
+ skip-im Don't start IM, and skip the IM test cases
+ big-test Set the environment variable BIG_TEST, which can be
+ checked from test cases.
+ combination="ARG1 .. ARG2" Specify a set of "mysqld" arguments for one
+ combination.
+ skip-combination Skip any combination options and combinations files
+
+Options that specify ports
+
+ master_port=PORT Specify the port number used by the first master
+ slave_port=PORT Specify the port number used by the first slave
+ ndbcluster-port=PORT Specify the port number used by cluster
+ ndbcluster-port-slave=PORT Specify the port number used by slave cluster
+ mtr-build-thread=# Specify unique collection of ports. Can also be set by
+ setting the environment variable MTR_BUILD_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
+
+ mysqld=ARGS Specify additional arguments to "mysqld"
+
+Options to run test on running server
+
+ extern Use running server for tests
+ ndb-connectstring=STR Use running cluster, and connect using STR
+ ndb-connectstring-slave=STR Use running slave cluster, and connect using STR
+ user=USER User for connection to extern server
+ socket=PATH Socket for connection to extern server
+
+Options for debugging the product
+
+ client-ddd Start mysqltest client in ddd
+ client-debugger=NAME Start mysqltest in the selected debugger
+ client-gdb Start mysqltest client in gdb
+ ddd Start mysqld in ddd
+ debug Dump trace output for all servers and client programs
+ debugger=NAME Start mysqld in the selected debugger
+ gdb Start the mysqld(s) in gdb
+ manual-debug Let user manually start mysqld in debugger, before
+ running test(s)
+ manual-gdb Let user manually start mysqld in gdb, before running
+ test(s)
+ manual-ddd Let user manually start mysqld in ddd, before running
+ test(s)
+ master-binary=PATH Specify the master "mysqld" to use
+ slave-binary=PATH Specify the slave "mysqld" to use
+ strace-client Create strace output for mysqltest client
+ 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 to 0 for no limit.
+
+Options for coverage, profiling etc
+
+ gcov FIXME
+ gprof FIXME
+ valgrind Run the "mysqltest" and "mysqld" executables using
+ valgrind with default options
+ valgrind-all Synonym for --valgrind
+ valgrind-mysqltest Run the "mysqltest" and "mysql_client_test" executable
+ with valgrind
+ valgrind-mysqld Run the "mysqld" executable with valgrind
+ valgrind-options=ARGS Deprecated, use --valgrind-option
+ valgrind-option=ARGS Option to give valgrind, replaces default option(s),
+ can be specified more then once
+ valgrind-path=[EXE] Path to the valgrind executable
+ callgrind Instruct valgrind to use callgrind
+
+Misc options
+
+ comment=STR Write STR to the output
+ notimer Don't show test case execution time
+ script-debug Debug this script itself
+ verbose More verbose output
+ start-and-exit Only initialize and start the servers, using the
+ startup settings for the specified test case (if any)
+ start-dirty Only start the servers (without initialization) for
+ the specified test case (if any)
+ fast Don't try to clean up from earlier runs
+ reorder Reorder tests to get fewer server restarts
+ help Get this help text
+
+ testcase-timeout=MINUTES Max test case run time (default $default_testcase_timeout)
+ suite-timeout=MINUTES Max test suite run time (default $default_suite_timeout)
+ warnings | log-warnings Pass --log-warnings to mysqld
+
+ sleep=SECONDS Passed to mysqltest, will be used as fixed sleep time
+ client-bindir=PATH Path to the directory where client binaries are located
+ client-libdir=PATH Path to the directory where client libraries are located
+
+Deprecated options
+ with-openssl Deprecated option for ssl
+
+
+HERE
+ mtr_exit(1);
+
+}
diff --git a/mysql-test/lib/v1/ndb_config_1_node.ini b/mysql-test/lib/v1/ndb_config_1_node.ini
new file mode 100644
index 00000000000..4e0be7796dc
--- /dev/null
+++ b/mysql-test/lib/v1/ndb_config_1_node.ini
@@ -0,0 +1,47 @@
+[ndbd default]
+NoOfReplicas= 1
+MaxNoOfConcurrentTransactions= 64
+MaxNoOfConcurrentOperations= CHOOSE_MaxNoOfConcurrentOperations
+DataMemory= CHOOSE_DataMemory
+IndexMemory= CHOOSE_IndexMemory
+Diskless= CHOOSE_Diskless
+TimeBetweenWatchDogCheck= 30000
+DataDir= CHOOSE_FILESYSTEM
+MaxNoOfOrderedIndexes= CHOOSE_MaxNoOfOrderedIndexes
+MaxNoOfAttributes= CHOOSE_MaxNoOfAttributes
+TimeBetweenGlobalCheckpoints= 500
+NoOfFragmentLogFiles= 8
+FragmentLogFileSize= 6M
+DiskPageBufferMemory= CHOOSE_DiskPageBufferMemory
+
+#
+# Increase timeouts to cater for slow test-machines
+# (possibly running several tests in parallell)
+#
+HeartbeatIntervalDbDb= 30000
+HeartbeatIntervalDbApi= 30000
+#TransactionDeadlockDetectionTimeout= 7500
+
+[ndbd]
+HostName= CHOOSE_HOSTNAME_1 # hostname is a valid network adress
+
+[ndb_mgmd]
+HostName= CHOOSE_HOSTNAME_1 # hostname is a valid network adress
+DataDir= CHOOSE_FILESYSTEM #
+PortNumber= CHOOSE_PORT_MGM
+
+[mysqld]
+
+[mysqld]
+
+[mysqld]
+
+[mysqld]
+
+[mysqld]
+
+[mysqld]
+
+[mysqld]
+
+[mysqld]
diff --git a/mysql-test/lib/v1/ndb_config_2_node.ini b/mysql-test/lib/v1/ndb_config_2_node.ini
new file mode 100644
index 00000000000..57e4d049ad6
--- /dev/null
+++ b/mysql-test/lib/v1/ndb_config_2_node.ini
@@ -0,0 +1,55 @@
+[ndbd default]
+NoOfReplicas= 2
+MaxNoOfConcurrentTransactions= 64
+MaxNoOfConcurrentOperations= CHOOSE_MaxNoOfConcurrentOperations
+DataMemory= CHOOSE_DataMemory
+IndexMemory= CHOOSE_IndexMemory
+Diskless= CHOOSE_Diskless
+TimeBetweenWatchDogCheck= 30000
+DataDir= CHOOSE_FILESYSTEM
+MaxNoOfOrderedIndexes= CHOOSE_MaxNoOfOrderedIndexes
+MaxNoOfAttributes= CHOOSE_MaxNoOfAttributes
+TimeBetweenGlobalCheckpoints= 500
+NoOfFragmentLogFiles= 4
+FragmentLogFileSize=12M
+DiskPageBufferMemory= CHOOSE_DiskPageBufferMemory
+# O_DIRECT has issues on 2.4 whach have not been handled, Bug #29612
+#ODirect= 1
+# the following parametes just function as a small regression
+# test that the parameter exists
+InitialNoOfOpenFiles= 27
+
+#
+# Increase timeouts to cater for slow test-machines
+# (possibly running several tests in parallell)
+#
+HeartbeatIntervalDbDb= 30000
+HeartbeatIntervalDbApi= 30000
+#TransactionDeadlockDetectionTimeout= 7500
+
+[ndbd]
+HostName= CHOOSE_HOSTNAME_1 # hostname is a valid network adress
+
+[ndbd]
+HostName= CHOOSE_HOSTNAME_2 # hostname is a valid network adress
+
+[ndb_mgmd]
+HostName= CHOOSE_HOSTNAME_1 # hostname is a valid network adress
+DataDir= CHOOSE_FILESYSTEM #
+PortNumber= CHOOSE_PORT_MGM
+
+[mysqld]
+
+[mysqld]
+
+[mysqld]
+
+[mysqld]
+
+[mysqld]
+
+[mysqld]
+
+[mysqld]
+
+[mysqld]