1# -*- cperl -*-
2# Copyright (c) 2005, 2021, Oracle and/or its affiliates.
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License, version 2.0,
6# as published by the Free Software Foundation.
7#
8# This program is also distributed with certain software (including
9# but not limited to OpenSSL) that is licensed under separate terms,
10# as designated in a particular file or component or in included license
11# documentation.  The authors of MySQL hereby grant you an additional
12# permission to link the program and your derivative works with the
13# separately licensed software that they have included with MySQL.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18# GNU General Public License, version 2.0, for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program; if not, write to the Free Software
22# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
23
24# This is a library file used by the Perl version of mysql-test-run,
25# and is part of the translation of the Bourne shell script with the
26# same name.
27
28package mtr_cases;
29use strict;
30
31use base qw(Exporter);
32our @EXPORT= qw(collect_option collect_test_cases);
33
34use mtr_report;
35use mtr_match;
36
37# Options used for the collect phase
38our $start_from;
39our $print_testcases;
40our $skip_rpl;
41our $do_test;
42our $skip_test;
43our $skip_combinations;
44our $binlog_format;
45our $enable_disabled;
46our $default_storage_engine;
47our $opt_with_ndbcluster_only;
48our $defaults_file;
49our $defaults_extra_file;
50our $quick_collect;
51# Set to 1 if you want the tests to override
52# default storage engine settings, and use MyISAM
53# as default.  (temporary option used in connection
54# with the change of default storage engine to InnoDB)
55our $default_myisam= 0;
56
57
58sub collect_option {
59  my ($opt, $value)= @_;
60
61  # Evaluate $opt as string to use "Getopt::Long::Callback legacy API"
62  my $opt_name = "$opt";
63
64  # Convert - to _ in option name
65  $opt_name =~ s/-/_/g;
66  no strict 'refs';
67  ${$opt_name}= $value;
68}
69
70use File::Basename;
71use File::Spec::Functions qw / splitdir /;
72use IO::File();
73use My::Config;
74use My::Platform;
75use My::Test;
76use My::Find;
77
78require "mtr_misc.pl";
79
80# Precompiled regex's for tests to do or skip
81my $do_test_reg;
82my $skip_test_reg;
83
84# Related to adding InnoDB plugin combinations
85my $lib_innodb_plugin;
86my $do_innodb_plugin;
87
88# If "Quick collect", set to 1 once a test to run has been found.
89my $some_test_found;
90
91sub init_pattern {
92  my ($from, $what)= @_;
93  return undef unless defined $from;
94  if ( $from =~ /^[a-z0-9\.]*$/ ) {
95    # Does not contain any regex (except . that we allow as
96    # separator betwen suite and testname), make the pattern match
97    # beginning of string
98    $from= "^$from";
99    mtr_verbose("$what='$from'");
100  }
101  # Check that pattern is a valid regex
102  eval { "" =~/$from/; 1 } or
103    mtr_error("Invalid regex '$from' passed to $what\nPerl says: $@");
104  return $from;
105}
106
107
108##############################################################################
109#
110#  Collect information about test cases to be run
111#
112##############################################################################
113
114sub collect_test_cases ($$$$) {
115  my $opt_reorder= shift; # True if we're reordering tests
116  my $suites= shift; # Semicolon separated list of test suites
117  my $opt_cases= shift;
118  my $opt_skip_test_list= shift;
119  my $cases= []; # Array of hash(one hash for each testcase)
120
121  # Unit tests off by default also if using --do-test or --start-from
122  $::opt_ctest= 0 if $::opt_ctest == -1 && ($do_test || $start_from);
123
124  $do_test_reg= init_pattern($do_test, "--do-test");
125  $skip_test_reg= init_pattern($skip_test, "--skip-test");
126
127  $lib_innodb_plugin=
128    my_find_file($::basedir,
129		 ["storage/innodb_plugin", "storage/innodb_plugin/.libs",
130		  "lib/mysql/plugin", "lib/plugin"],
131		 ["ha_innodb_plugin.dll", "ha_innodb_plugin.so",
132		  "ha_innodb_plugin.sl"],
133		 NOT_REQUIRED);
134  $do_innodb_plugin= ($::mysql_version_id >= 50100 &&
135		      !(IS_WINDOWS && $::opt_embedded_server) &&
136		      $lib_innodb_plugin);
137
138  # If not reordering, we also shouldn't group by suites, unless
139  # no test cases were named.
140  # This also effects some logic in the loop following this.
141  if ($opt_reorder or !@$opt_cases)
142  {
143    foreach my $suite (split(",", $suites))
144    {
145      push(@$cases, collect_one_suite($suite, $opt_cases, $opt_skip_test_list));
146      last if $some_test_found;
147      push(@$cases, collect_one_suite("i_".$suite, $opt_cases, $opt_skip_test_list));
148    }
149  }
150
151  if ( @$opt_cases )
152  {
153    # A list of tests was specified on the command line.
154    # Among those, the tests which are not already collected will be
155    # collected and stored temporarily in an array of hashes pointed
156    # by the below reference. This array is eventually appeneded to
157    # the one having all collected test cases.
158    my $cmdline_cases;
159
160    # Check that the tests specified was found
161    # in at least one suite
162    foreach my $test_name_spec ( @$opt_cases )
163    {
164      my $found= 0;
165      my ($sname, $tname, $extension)= split_testname($test_name_spec);
166      foreach my $test ( @$cases )
167      {
168	last unless $opt_reorder;
169	# test->{name} is always in suite.name format
170	if ( $test->{name} =~ /^$sname.*\.$tname$/ )
171	{
172	  $found= 1;
173	  last;
174	}
175      }
176      if ( not $found )
177      {
178        if ( $sname )
179        {
180	  # If suite was part of name, find it there, may come with combinations
181	  my @this_case = collect_one_suite($sname, [ $tname ]);
182
183          # If a test is specified multiple times on the command line, all
184          # instances of the test need to be picked. Hence, such tests are
185          # stored in the temporary array instead of adding them to $cases
186          # directly so that repeated tests are not run only once
187	  if (@this_case)
188          {
189	    push (@$cmdline_cases, @this_case);
190	  }
191	  else
192	  {
193	    mtr_error("Could not find '$tname' in '$sname' suite");
194          }
195        }
196        else
197        {
198          if ( !$opt_reorder )
199          {
200            # If --no-reorder is passed and if suite was not part of name,
201            # search in all the suites
202            foreach my $suite (split(",", $suites))
203            {
204              my @this_case = collect_one_suite($suite, [ $tname ]);
205              if ( @this_case )
206              {
207                push (@$cmdline_cases, @this_case);
208                $found= 1;
209              }
210              @this_case= collect_one_suite("i_".$suite, [ $tname ]);
211              if ( @this_case )
212              {
213                push (@$cmdline_cases, @this_case);
214                $found= 1;
215              }
216            }
217          }
218          if ( !$found )
219          {
220            mtr_error("Could not find '$tname' in '$suites' suite(s)");
221          }
222        }
223      }
224    }
225    # Add test cases collected in the temporary array to the one
226    # containing all previously collected test cases
227    push (@$cases, @$cmdline_cases) if $cmdline_cases;
228  }
229
230  if ( $opt_reorder && !$quick_collect)
231  {
232    # Reorder the test cases in an order that will make them faster to run
233    # Make a mapping of test name to a string that represents how that test
234    # should be sorted among the other tests.  Put the most important criterion
235    # first, then a sub-criterion, then sub-sub-criterion, etc.
236    foreach my $tinfo (@$cases)
237    {
238      my @criteria = ();
239
240      #
241      # Append the criteria for sorting, in order of importance.
242      #
243      push(@criteria, "ndb=" . ($tinfo->{'ndb_test'} ? "A" : "B"));
244      push(@criteria, $tinfo->{template_path});
245      # Group test with equal options together.
246      # Ending with "~" makes empty sort later than filled
247      my $opts= $tinfo->{'master_opt'} ? $tinfo->{'master_opt'} : [];
248      push(@criteria, join("!", sort @{$opts}) . "~");
249      # Add slave opts if any
250      if ($tinfo->{'slave_opt'})
251      {
252	push(@criteria, join("!", sort @{$tinfo->{'slave_opt'}}));
253      }
254      # This sorts tests with force-restart *before* identical tests
255      push(@criteria, $tinfo->{force_restart} ? "force-restart" : "no-restart");
256
257      $tinfo->{criteria}= join(" ", @criteria);
258    }
259
260    @$cases = sort {$a->{criteria} cmp $b->{criteria}; } @$cases;
261
262    # For debugging the sort-order
263    # foreach my $tinfo (@$cases)
264    # {
265    #   my $tname= $tinfo->{name} . ' ' . $tinfo->{combination};
266    #   my $crit= $tinfo->{criteria};
267    #   print("$tname\n\t$crit\n");
268    # }
269  }
270
271  if (defined $print_testcases){
272    print_testcases(@$cases);
273    exit(1);
274  }
275
276  return $cases;
277
278}
279
280
281# Returns (suitename, testname, extension)
282sub split_testname {
283  my ($test_name)= @_;
284
285  # If .test file name is used, get rid of directory part
286  $test_name= basename($test_name) if $test_name =~ /\.test$/;
287
288  # Now split name on .'s
289  my @parts= split(/\./, $test_name);
290
291  if (@parts == 1){
292    # Only testname given, ex: alias
293    return (undef , $parts[0], undef);
294  } elsif (@parts == 2) {
295    # Either testname.test or suite.testname given
296    # Ex. main.alias or alias.test
297
298    if ($parts[1] eq "test")
299    {
300      return (undef , $parts[0], $parts[1]);
301    }
302    else
303    {
304      return ($parts[0], $parts[1], undef);
305    }
306
307  } elsif (@parts == 3) {
308    # Fully specified suitename.testname.test
309    # ex main.alias.test
310    return ( $parts[0], $parts[1], $parts[2]);
311  }
312
313  mtr_error("Illegal format of test name: $test_name");
314}
315
316
317sub collect_one_suite($)
318{
319  my $suite= shift;  # Test suite name
320  my $opt_cases= shift;
321  my $opt_skip_test_list= shift;
322  my @cases; # Array of hash
323
324  mtr_verbose("Collecting: $suite");
325
326  my $suitedir= "$::glob_mysql_test_dir"; # Default
327  if ( $suite ne "main" )
328  {
329    # Allow suite to be path to "some dir" if $suite has at least
330    # one directory part
331    if ( -d $suite and splitdir($suite) > 1 ){
332      $suitedir= $suite;
333      mtr_report(" - from '$suitedir'");
334
335    }
336    else
337    {
338      $suitedir= my_find_dir($::basedir,
339			     ["share/mysql-test/suite",
340			      "mysql-test/suite",
341                              "lib/mysql-test/suite",
342			      "internal/mysql-test/suite",
343			      "mysql-test",
344			      # Look in storage engine specific suite dirs
345			      "storage/*/mtr",
346			      # Look in plugin specific suite dir
347			      "plugin/$suite/tests",
348			      "internal/plugin/$suite/tests",
349			      "rapid/plugin/$suite/tests",
350			      "rapid/mysql-test/suite",
351			     ],
352			     [$suite, "mtr"], ($suite =~ /^i_/));
353      return unless $suitedir;
354    }
355    mtr_verbose("suitedir: $suitedir");
356  }
357
358  my $testdir= "$suitedir/t";
359  my $resdir=  "$suitedir/r";
360
361  # Check if t/ exists
362  if (-d $testdir){
363    # t/ exists
364
365    if ( -d $resdir )
366    {
367      # r/exists
368    }
369    else
370    {
371      # No r/, use t/ as result dir
372      $resdir= $testdir;
373    }
374
375  }
376  else {
377    # No t/ dir => there can' be any r/ dir
378    mtr_error("Can't have r/ dir without t/") if -d $resdir;
379
380    # No t/ or r/ => use suitedir
381    $resdir= $testdir= $suitedir;
382  }
383
384  mtr_verbose("testdir: $testdir");
385  mtr_verbose("resdir: $resdir");
386
387  # ----------------------------------------------------------------------
388  # Build a hash of disabled testcases for this suite
389  # ----------------------------------------------------------------------
390  my %disabled;
391  my @disabled_collection= @{$opt_skip_test_list} if $opt_skip_test_list;
392  unshift (@disabled_collection, "$testdir/disabled.def");
393
394  # Check for the tests to be skipped in a sanitizer which are listed
395  # in "mysql-test/collections/disabled-<sanitizer>.list" file.
396  if ($::opt_sanitize) {
397    # Check for disabled-asan.list
398    if ($::mysql_version_extra =~ /asan/i &&
399        !grep (/disabled-asan\.list$/, @{$opt_skip_test_list})) {
400      push(@disabled_collection, "collections/disabled-asan.list");
401    }
402  }
403
404  for my $skip (@disabled_collection)
405    {
406      if ( open(DISABLED, $skip ) )
407	{
408	  # $^O on Windows considered not generic enough
409	  my $plat= (IS_WINDOWS) ? 'windows' : $^O;
410
411	  while ( <DISABLED> )
412	    {
413	      chomp;
414	      #diasble the test case if platform matches
415	      if ( /\@/ )
416		{
417		  if ( /\@$plat/ )
418		    {
419		      /^\s*(\S+)\s*\@$plat.*:\s*(.*?)\s*$/ ;
420		      $disabled{$1}= $2 if not exists $disabled{$1};
421		    }
422		  elsif ( /\@!(\S*)/ )
423		    {
424		      if ( $1 ne $plat)
425			{
426			  /^\s*(\S+)\s*\@!.*:\s*(.*?)\s*$/ ;
427			  $disabled{$1}= $2 if not exists $disabled{$1};
428			}
429		    }
430		}
431	      elsif ( /^\s*(\S+)\s*:\s*(.*?)\s*$/ )
432		{
433		  chomp;
434		  if ( /^\s*(\S+)\s*:\s*(.*?)\s*$/ )
435		    {
436		      $disabled{$1}= $2 if not exists $disabled{$1};
437		    }
438		}
439	    }
440	  close DISABLED;
441	}
442    }
443
444  # Read suite.opt file
445  my $suite_opt_file=  "$testdir/suite.opt";
446
447  if ( $::opt_suite_opt )
448  {
449    $suite_opt_file= "$testdir/$::opt_suite_opt";
450  }
451
452  my $suite_opts= [];
453  if ( -f $suite_opt_file )
454  {
455    $suite_opts= opts_from_file($suite_opt_file);
456  }
457
458  if ( @$opt_cases )
459  {
460    # Collect in specified order
461    foreach my $test_name_spec ( @$opt_cases )
462    {
463      my ($sname, $tname, $extension)= split_testname($test_name_spec);
464
465      # The test name parts have now been defined
466      #print "  suite_name: $sname\n";
467      #print "  tname:      $tname\n";
468      #print "  extension:  $extension\n";
469
470      # Check cirrect suite if suitename is defined
471      next if (defined $sname and $suite ne $sname);
472
473      if ( defined $extension )
474      {
475	my $full_name= "$testdir/$tname.$extension";
476	# Extension was specified, check if the test exists
477        if ( ! -f $full_name)
478        {
479	  # This is only an error if suite was specified, otherwise it
480	  # could exist in another suite
481          mtr_error("Test '$full_name' was not found in suite '$sname'")
482	    if $sname;
483
484	  next;
485        }
486      }
487      else
488      {
489	# No extension was specified, use default
490	$extension= "test";
491	my $full_name= "$testdir/$tname.$extension";
492
493	# Test not found here, could exist in other suite
494	next if ( ! -f $full_name );
495      }
496
497      push(@cases,
498	   collect_one_test_case($suitedir,
499				 $testdir,
500				 $resdir,
501				 $suite,
502				 $tname,
503				 "$tname.$extension",
504				 \%disabled,
505				 $suite_opts));
506    }
507  }
508  else
509  {
510    opendir(TESTDIR, $testdir) or mtr_error("Can't open dir \"$testdir\": $!");
511
512    foreach my $elem ( sort readdir(TESTDIR) )
513    {
514      my $tname= mtr_match_extension($elem, 'test');
515
516      next unless defined $tname;
517
518      # Skip tests that does not match the --do-test= filter
519      next if ($do_test_reg and not $tname =~ /$do_test_reg/o);
520
521      push(@cases,
522	   collect_one_test_case($suitedir,
523				 $testdir,
524				 $resdir,
525				 $suite,
526				 $tname,
527				 $elem,
528				 \%disabled,
529				 $suite_opts));
530    }
531    closedir TESTDIR;
532  }
533
534  #  Return empty list if no testcases found
535  return if (@cases == 0);
536
537  # ----------------------------------------------------------------------
538  # Read combinations for this suite and build testcases x combinations
539  # if any combinations exists
540  # ----------------------------------------------------------------------
541  if ( ! $skip_combinations && ! $quick_collect )
542  {
543    my @combinations;
544    my $combination_file= "$suitedir/combinations";
545    #print "combination_file: $combination_file\n";
546    if (@::opt_combinations)
547    {
548      # take the combination from command-line
549      mtr_verbose("Take the combination from command line");
550      foreach my $combination (@::opt_combinations) {
551	my $comb= {};
552	$comb->{name}= $combination;
553	push(@{$comb->{comb_opt}}, $combination);
554	push(@combinations, $comb);
555      }
556    }
557    elsif (-f $combination_file )
558    {
559      # Read combinations file in my.cnf format
560      mtr_verbose("Read combinations file");
561      my $config= My::Config->new($combination_file);
562      foreach my $group ($config->groups()) {
563	my $comb= {};
564	$comb->{name}= $group->name();
565        foreach my $option ( $group->options() ) {
566	  push(@{$comb->{comb_opt}}, $option->option());
567	}
568	push(@combinations, $comb);
569      }
570    }
571
572    if (@combinations)
573    {
574      print " - adding combinations for $suite\n";
575      #print_testcases(@cases);
576
577      my @new_cases;
578      foreach my $comb (@combinations)
579      {
580	foreach my $test (@cases)
581	{
582
583	  next if ( $test->{'skip'} );
584
585	  # Skip this combination if the values it provides
586	  # already are set in master_opt or slave_opt
587	  if (My::Options::is_set($test->{master_opt}, $comb->{comb_opt}) &&
588	      My::Options::is_set($test->{slave_opt}, $comb->{comb_opt}) ){
589	    next;
590	  }
591
592	  # Copy test options
593	  my $new_test= My::Test->new();
594	  while (my ($key, $value) = each(%$test)) {
595	    if (ref $value eq "ARRAY") {
596	      push(@{$new_test->{$key}}, @$value);
597	    } else {
598	      $new_test->{$key}= $value;
599	    }
600	  }
601
602	  # Append the combination options to master_opt and slave_opt
603	  push(@{$new_test->{master_opt}}, @{$comb->{comb_opt}});
604	  push(@{$new_test->{slave_opt}}, @{$comb->{comb_opt}});
605
606	  # Add combination name short name
607	  $new_test->{combination}= $comb->{name};
608
609	  # Add the new test to new test cases list
610	  push(@new_cases, $new_test);
611	}
612      }
613
614      # Add the plain test if it was not already added
615      # as part of a combination
616      my %added;
617      foreach my $new_test (@new_cases){
618	$added{$new_test->{name}}= 1;
619      }
620      foreach my $test (@cases){
621	push(@new_cases, $test) unless $added{$test->{name}};
622      }
623
624
625      #print_testcases(@new_cases);
626      @cases= @new_cases;
627      #print_testcases(@cases);
628    }
629  }
630
631  optimize_cases(\@cases);
632  #print_testcases(@cases);
633
634  return @cases;
635}
636
637
638
639#
640# Loop through all test cases
641# - optimize which test to run by skipping unnecessary ones
642# - update settings if necessary
643#
644sub optimize_cases {
645  my ($cases)= @_;
646
647  foreach my $tinfo ( @$cases )
648  {
649    # Skip processing if already marked as skipped
650    next if $tinfo->{skip};
651
652    # =======================================================
653    # If a special binlog format was selected with
654    # --mysqld=--binlog-format=x, skip all test that does not
655    # support it
656    # =======================================================
657    #print "binlog_format: $binlog_format\n";
658    if (defined $binlog_format )
659    {
660      # =======================================================
661      # Fixed --binlog-format=x specified on command line
662      # =======================================================
663      if ( defined $tinfo->{'binlog_formats'} )
664      {
665	#print "binlog_formats: ". join(", ", @{$tinfo->{binlog_formats}})."\n";
666
667	# The test supports different binlog formats
668	# check if the selected one is ok
669	my $supported=
670	  grep { $_ eq lc $binlog_format } @{$tinfo->{'binlog_formats'}};
671	if ( !$supported )
672	{
673	  $tinfo->{'skip'}= 1;
674	  $tinfo->{'comment'}=
675	    "Doesn't support --binlog-format='$binlog_format'";
676	}
677      }
678    }
679    else
680    {
681      # =======================================================
682      # Use dynamic switching of binlog format
683      # =======================================================
684
685      # Get binlog-format used by this test from master_opt
686      my $test_binlog_format;
687      foreach my $opt ( @{$tinfo->{master_opt}} ) {
688       (my $dash_opt = $opt) =~ s/_/-/g;
689	$test_binlog_format=
690	  mtr_match_prefix($dash_opt, "--binlog-format=") || $test_binlog_format;
691      }
692
693      if (defined $test_binlog_format and
694	  defined $tinfo->{binlog_formats} )
695      {
696	my $supported=
697	  grep { My::Options::option_equals($_, lc $test_binlog_format) }
698            @{$tinfo->{'binlog_formats'}};
699	if ( !$supported )
700	{
701	  $tinfo->{'skip'}= 1;
702	  $tinfo->{'comment'}=
703	    "Doesn't support --binlog-format='$test_binlog_format'";
704	  next;
705	}
706      }
707    }
708
709    # =======================================================
710    # Check that engine selected by
711    # --default-storage-engine=<engine> is supported
712    # =======================================================
713    my %builtin_engines = ('myisam' => 1, 'memory' => 1, 'csv' => 1);
714
715    foreach my $opt ( @{$tinfo->{master_opt}} ) {
716     (my $dash_opt = $opt) =~ s/_/-/g;
717      my $default_engine=
718	mtr_match_prefix($dash_opt, "--default-storage-engine=");
719      my $default_tmp_engine=
720	mtr_match_prefix($dash_opt, "--default-tmp-storage-engine=");
721
722      # Allow use of uppercase, convert to all lower case
723      $default_engine =~ tr/A-Z/a-z/;
724      $default_tmp_engine =~ tr/A-Z/a-z/;
725
726      if (defined $default_engine){
727
728	#print " $tinfo->{name}\n";
729	#print " - The test asked to use '$default_engine'\n";
730
731	#my $engine_value= $::mysqld_variables{$default_engine};
732	#print " - The mysqld_variables says '$engine_value'\n";
733
734	if ( ! exists $::mysqld_variables{$default_engine} and
735	     ! exists $builtin_engines{$default_engine} )
736	{
737	  $tinfo->{'skip'}= 1;
738	  $tinfo->{'comment'}=
739	    "'$default_engine' not supported";
740	}
741
742	$tinfo->{'ndb_test'}= 1
743	  if ( $default_engine =~ /^ndb/i );
744	$tinfo->{'myisam_test'}= 1
745	  if ( $default_engine =~ /^myisam/i );
746      }
747      if (defined $default_tmp_engine){
748
749	#print " $tinfo->{name}\n";
750	#print " - The test asked to use '$default_tmp_engine' as temp engine\n";
751
752	#my $engine_value= $::mysqld_variables{$default_tmp_engine};
753	#print " - The mysqld_variables says '$engine_value'\n";
754
755	if ( ! exists $::mysqld_variables{$default_tmp_engine} and
756	     ! exists $builtin_engines{$default_tmp_engine} )
757	{
758	  $tinfo->{'skip'}= 1;
759	  $tinfo->{'comment'}=
760	    "'$default_tmp_engine' not supported";
761	}
762
763	$tinfo->{'ndb_test'}= 1
764	  if ( $default_tmp_engine =~ /^ndb/i );
765	$tinfo->{'myisam_test'}= 1
766	  if ( $default_tmp_engine =~ /^myisam/i );
767      }
768    }
769
770    if ($quick_collect && ! $tinfo->{'skip'})
771    {
772      $some_test_found= 1;
773      return;
774    }
775  }
776}
777
778
779#
780# Read options from the given opt file and append them as an array
781# to $tinfo->{$opt_name}
782#
783sub process_opts_file {
784  my ($tinfo, $opt_file, $opt_name)= @_;
785
786  if ( -f $opt_file )
787  {
788    my $opts= opts_from_file($opt_file);
789
790    foreach my $opt ( @$opts )
791    {
792      my $value;
793
794      # The opt file is used both to send special options to the mysqld
795      # as well as pass special test case specific options to this
796      # script
797
798      $value= mtr_match_prefix($opt, "--timezone=");
799      if ( defined $value )
800      {
801	$tinfo->{'timezone'}= $value;
802	next;
803      }
804
805      $value= mtr_match_prefix($opt, "--result-file=");
806      if ( defined $value )
807      {
808	# Specifies the file mysqltest should compare
809	# output against
810	$tinfo->{'result_file'}= "r/$value.result";
811	next;
812      }
813
814      $value= mtr_match_prefix($opt, "--config-file-template=");
815      if ( defined $value)
816      {
817	# Specifies the configuration file to use for this test
818	$tinfo->{'template_path'}= dirname($tinfo->{path})."/$value";
819	next;
820      }
821
822      # If we set default time zone, remove the one we have
823      $value= mtr_match_prefix($opt, "--default-time-zone=");
824      if ( defined $value )
825      {
826	# Set timezone for this test case to something different
827	$tinfo->{'timezone'}= "GMT-8";
828	# Fallthrough, add the --default-time-zone option
829      }
830
831      # The --restart option forces a restart even if no special
832      # option is set. If the options are the same as next testcase
833      # there is no need to restart after the testcase
834      # has completed
835      if ( $opt eq "--force-restart" )
836      {
837	$tinfo->{'force_restart'}= 1;
838	next;
839      }
840
841      $value= mtr_match_prefix($opt, "--testcase-timeout=");
842      if ( defined $value ) {
843	# Overrides test case timeout for this test
844	$tinfo->{'case-timeout'}= $value;
845	next;
846      }
847
848      # Ok, this was a real option, add it
849      push(@{$tinfo->{$opt_name}}, $opt);
850    }
851  }
852}
853
854##############################################################################
855#
856#  Collect information about a single test case
857#
858##############################################################################
859
860sub collect_one_test_case {
861  my $suitedir=   shift;
862  my $testdir=    shift;
863  my $resdir=     shift;
864  my $suitename=  shift;
865  my $tname=      shift;
866  my $filename=   shift;
867  my $disabled=   shift;
868  my $suite_opts= shift;
869
870  #print "collect_one_test_case\n";
871  #print " suitedir: $suitedir\n";
872  #print " testdir: $testdir\n";
873  #print " resdir: $resdir\n";
874  #print " suitename: $suitename\n";
875  #print " tname: $tname\n";
876  #print " filename: $filename\n";
877
878  # ----------------------------------------------------------------------
879  # Check --start-from
880  # ----------------------------------------------------------------------
881  if ( $start_from )
882  {
883    # start_from can be specified as [suite.].testname_prefix
884    my ($suite, $test, $ext)= split_testname($start_from);
885
886    if ( $suite and $suitename lt $suite){
887      return; # Skip silently
888    }
889    if ( $tname lt $test ){
890      return; # Skip silently
891    }
892  }
893
894  # ----------------------------------------------------------------------
895  # Set defaults
896  # ----------------------------------------------------------------------
897  my $tinfo= My::Test->new
898    (
899     name          => "$suitename.$tname",
900     shortname     => $tname,
901     path          => "$testdir/$filename",
902
903    );
904
905  my $result_file= "$resdir/$tname.result";
906  if (-f $result_file) {
907    # Allow nonexistsing result file
908    # in that case .test must issue "exit" otherwise test
909    # should fail by default
910    $tinfo->{result_file}= $result_file;
911  }
912  else {
913    # No .result file exist
914    # Remember the path  where it should be
915    # saved in case of --record
916    $tinfo->{record_file}= $result_file;
917  }
918
919  # ----------------------------------------------------------------------
920  # Skip some tests but include in list, just mark them as skipped
921  # ----------------------------------------------------------------------
922  if ( $skip_test_reg and $tname =~ /$skip_test_reg/o )
923  {
924    $tinfo->{'skip'}= 1;
925    return $tinfo;
926  }
927
928  # ----------------------------------------------------------------------
929  # Check for replicaton tests
930  # ----------------------------------------------------------------------
931  $tinfo->{'rpl_test'}= 1 if ($suitename =~ 'rpl');
932  $tinfo->{'grp_rpl_test'}= 1 if ($suitename =~ 'group_replication');
933
934  # ----------------------------------------------------------------------
935  # Check for disabled tests
936  # ----------------------------------------------------------------------
937  my $marked_as_disabled= 0;
938  if ( $disabled->{$tname} or $disabled->{"$suitename.$tname"} )
939  {
940    # Test was marked as disabled in suites disabled.def file
941    $marked_as_disabled= 1;
942    # Test name may have been disabled with or without suite name part
943    $tinfo->{'comment'}= $disabled->{$tname} ||
944                         $disabled->{"$suitename.$tname"};
945  }
946
947  my $disabled_file= "$testdir/$tname.disabled";
948  if ( -f $disabled_file )
949  {
950    $marked_as_disabled= 1;
951    $tinfo->{'comment'}= mtr_fromfile($disabled_file);
952  }
953
954  if ( $marked_as_disabled )
955  {
956    if ( $enable_disabled )
957    {
958      # User has selected to run all disabled tests
959      mtr_report(" - $tinfo->{name} wil be run although it's been disabled\n",
960		 "  due to '$tinfo->{comment}'");
961    }
962    else
963    {
964      $tinfo->{'skip'}= 1;
965      $tinfo->{'disable'}= 1;   # Sub type of 'skip'
966      return $tinfo;
967    }
968  }
969
970  # ----------------------------------------------------------------------
971  # Append suite extra options to both master and slave
972  # ----------------------------------------------------------------------
973  push(@{$tinfo->{'master_opt'}}, @$suite_opts);
974  push(@{$tinfo->{'slave_opt'}}, @$suite_opts);
975
976  #-----------------------------------------------------------------------
977  # Check for test specific config file
978  #-----------------------------------------------------------------------
979  my $test_cnf_file= "$testdir/$tname.cnf";
980  if ( -f $test_cnf_file) {
981    # Specifies the configuration file to use for this test
982    $tinfo->{'template_path'}= $test_cnf_file;
983  }
984
985  # ----------------------------------------------------------------------
986  # Check for test specific config file
987  # ----------------------------------------------------------------------
988  my $test_cnf_file= "$testdir/$tname.cnf";
989  if ( -f $test_cnf_file ) {
990    # Specifies the configuration file to use for this test
991    $tinfo->{'template_path'}= $test_cnf_file;
992  }
993
994  # ----------------------------------------------------------------------
995  # master sh
996  # ----------------------------------------------------------------------
997  my $master_sh= "$testdir/$tname-master.sh";
998  if ( -f $master_sh )
999  {
1000    if ( IS_WIN32PERL )
1001    {
1002      $tinfo->{'skip'}= 1;
1003      $tinfo->{'comment'}= "No tests with sh scripts on Windows";
1004      return $tinfo;
1005    }
1006    else
1007    {
1008      $tinfo->{'master_sh'}= $master_sh;
1009    }
1010  }
1011
1012  # ----------------------------------------------------------------------
1013  # slave sh
1014  # ----------------------------------------------------------------------
1015  my $slave_sh= "$testdir/$tname-slave.sh";
1016  if ( -f $slave_sh )
1017  {
1018    if ( IS_WIN32PERL )
1019    {
1020      $tinfo->{'skip'}= 1;
1021      $tinfo->{'comment'}= "No tests with sh scripts on Windows";
1022      return $tinfo;
1023    }
1024    else
1025    {
1026      $tinfo->{'slave_sh'}= $slave_sh;
1027    }
1028  }
1029
1030  # ----------------------------------------------------------------------
1031  # <tname>.slave-mi
1032  # ----------------------------------------------------------------------
1033  mtr_error("$tname: slave-mi not supported anymore")
1034    if ( -f "$testdir/$tname.slave-mi");
1035
1036
1037  tags_from_test_file($tinfo,"$testdir/${tname}.test");
1038
1039  if ( defined $default_storage_engine )
1040  {
1041    # Different default engine is used
1042    # tag test to require that engine
1043    $tinfo->{'ndb_test'}= 1
1044      if ( $default_storage_engine =~ /^ndb/i );
1045
1046    $tinfo->{'mysiam_test'}= 1
1047      if ( $default_storage_engine =~ /^mysiam/i );
1048
1049  }
1050
1051  if ( $tinfo->{'big_test'} and ! $::opt_big_test )
1052  {
1053    $tinfo->{'skip'}= 1;
1054    $tinfo->{'comment'}= "Test needs 'big-test' option";
1055    return $tinfo
1056  }
1057
1058  if ( $tinfo->{'need_debug'} && ! $::debug_compiled_binaries )
1059  {
1060    $tinfo->{'skip'}= 1;
1061    $tinfo->{'comment'}= "Test needs debug binaries";
1062    return $tinfo
1063  }
1064
1065  if ( $tinfo->{'ndb_test'} )
1066  {
1067    # This is a NDB test
1068    if ( $::ndbcluster_enabled == 0)
1069    {
1070      # ndbcluster is disabled
1071      $tinfo->{'skip'}= 1;
1072      $tinfo->{'comment'}= "ndbcluster disabled";
1073      return $tinfo;
1074    }
1075  }
1076  else
1077  {
1078    # This is not a ndb test
1079    if ( $opt_with_ndbcluster_only )
1080    {
1081      # Only the ndb test should be run, all other should be skipped
1082      $tinfo->{'skip'}= 1;
1083      $tinfo->{'comment'}= "Only ndbcluster tests";
1084      return $tinfo;
1085    }
1086  }
1087
1088  if ($tinfo->{'federated_test'})
1089  {
1090    # This is a test that needs federated, enable it
1091    push(@{$tinfo->{'master_opt'}}, "--loose-federated");
1092    push(@{$tinfo->{'slave_opt'}}, "--loose-federated");
1093  }
1094  if ( $tinfo->{'myisam_test'})
1095  {
1096    # This is a temporary fix to allow non-innodb tests to run even if
1097    # the default storage engine is innodb.
1098    push(@{$tinfo->{'master_opt'}}, "--default-storage-engine=MyISAM");
1099    push(@{$tinfo->{'slave_opt'}}, "--default-storage-engine=MyISAM");
1100    push(@{$tinfo->{'master_opt'}}, "--default-tmp-storage-engine=MyISAM");
1101    push(@{$tinfo->{'slave_opt'}}, "--default-tmp-storage-engine=MyISAM");
1102  }
1103  if ( $tinfo->{'need_binlog'} )
1104  {
1105    if (grep(/^--skip[-_]log[-_]bin/,  @::opt_extra_mysqld_opt) )
1106    {
1107      $tinfo->{'skip'}= 1;
1108      $tinfo->{'comment'}= "Test needs binlog";
1109      return $tinfo;
1110    }
1111  }
1112  else
1113  {
1114    # Test does not need binlog, add --skip-binlog to
1115    # the options used when starting
1116    push(@{$tinfo->{'master_opt'}}, "--loose-skip-log-bin");
1117    push(@{$tinfo->{'slave_opt'}}, "--loose-skip-log-bin");
1118  }
1119
1120  if ( $tinfo->{'rpl_test'} or $tinfo->{'grp_rpl_test'} )
1121  {
1122    if ( $skip_rpl )
1123    {
1124      $tinfo->{'skip'}= 1;
1125      $tinfo->{'comment'}= "No replication tests(--skip-rpl)";
1126      return $tinfo;
1127    }
1128  }
1129
1130  if ( $::opt_embedded_server )
1131  {
1132    if ( $tinfo->{'not_embedded'} )
1133    {
1134      $tinfo->{'skip'}= 1;
1135      $tinfo->{'comment'}= "Not run for embedded server";
1136      return $tinfo;
1137    }
1138#Setting the default storage engine to InnoDB for embedded tests as the default
1139#storage engine for mysqld in embedded mode is still MyISAM.
1140#To be removed after completion of WL #6911.
1141    if ( !$tinfo->{'myisam_test'} && !defined $default_storage_engine)
1142    {
1143      push(@{$tinfo->{'master_opt'}}, "--default-storage-engine=InnoDB");
1144      push(@{$tinfo->{'master_opt'}}, "--default-tmp-storage-engine=InnoDB");
1145    }
1146  }
1147
1148  if ( $tinfo->{'need_ssl'} )
1149  {
1150    # This is a test that needs ssl
1151    if ( ! $::opt_ssl_supported ) {
1152      # SSL is not supported, skip it
1153      $tinfo->{'skip'}= 1;
1154      $tinfo->{'comment'}= "No SSL support";
1155      return $tinfo;
1156    }
1157  }
1158
1159  # Check for group replication tests
1160  if ( $tinfo->{'grp_rpl_test'} )
1161  {
1162    $::group_replication= 1;
1163  }
1164
1165  # Check for xplugin tests
1166  if ( $tinfo->{'xplugin_test'} )
1167  {
1168    $::xplugin= 1;
1169  }
1170
1171  if ( $tinfo->{'not_windows'} && IS_WINDOWS )
1172  {
1173    $tinfo->{'skip'}= 1;
1174    $tinfo->{'comment'}= "Test not supported on Windows";
1175    return $tinfo;
1176  }
1177
1178  # ----------------------------------------------------------------------
1179  # Find config file to use if not already selected in <testname>.opt file
1180  # ----------------------------------------------------------------------
1181  if (defined $defaults_file) {
1182    # Using same config file for all tests
1183    $tinfo->{template_path}= $defaults_file;
1184  }
1185  elsif (! $tinfo->{template_path} )
1186  {
1187    my $config= "$suitedir/my.cnf";
1188    if (! -f $config )
1189    {
1190      # assume default.cnf will be used
1191      $config= "include/default_my.cnf";
1192
1193      # Suite has no config, autodetect which one to use
1194      if ( $tinfo->{rpl_test} ){
1195	$config= "suite/rpl/my.cnf";
1196	if ( $tinfo->{ndb_test} ){
1197	  $config= "suite/rpl_ndb/my.cnf";
1198	}
1199      }
1200      elsif ( $tinfo->{ndb_test} ){
1201	$config= "suite/ndb/my.cnf";
1202      }
1203    }
1204    $tinfo->{template_path}= $config;
1205  }
1206
1207  # Set extra config file to use
1208  if (defined $defaults_extra_file) {
1209    $tinfo->{extra_template_path}= $defaults_extra_file;
1210  }
1211
1212  # ----------------------------------------------------------------------
1213  # Append mysqld extra options to both master and slave
1214  # ----------------------------------------------------------------------
1215  push(@{$tinfo->{'master_opt'}}, @::opt_extra_mysqld_opt);
1216  push(@{$tinfo->{'slave_opt'}}, @::opt_extra_mysqld_opt);
1217
1218  # ----------------------------------------------------------------------
1219  # Add master opts, extra options only for master
1220  # ----------------------------------------------------------------------
1221  process_opts_file($tinfo, "$testdir/$tname-master.opt", 'master_opt');
1222
1223  # ----------------------------------------------------------------------
1224  # Add slave opts, list of extra option only for slave
1225  # ----------------------------------------------------------------------
1226  process_opts_file($tinfo, "$testdir/$tname-slave.opt", 'slave_opt');
1227
1228  return $tinfo;
1229}
1230
1231
1232# List of tags in the .test files that if found should set
1233# the specified value in "tinfo"
1234my @tags=
1235(
1236 ["include/have_binlog_format_row.inc", "binlog_formats", ["row"]],
1237 ["include/have_binlog_format_statement.inc", "binlog_formats", ["statement"]],
1238 ["include/have_binlog_format_mixed.inc", "binlog_formats", ["mixed", "mix"]],
1239 ["include/have_binlog_format_mixed_or_row.inc",
1240  "binlog_formats", ["mixed", "mix", "row"]],
1241 ["include/have_binlog_format_mixed_or_statement.inc",
1242  "binlog_formats", ["mixed", "mix", "statement"]],
1243 ["include/have_binlog_format_row_or_statement.inc",
1244  "binlog_formats", ["row", "statement"]],
1245
1246 ["include/have_log_bin.inc", "need_binlog", 1],
1247# an empty file to use test that needs myisam engine.
1248 ["include/force_myisam_default.inc", "myisam_test", 1],
1249 ["include/big_test.inc", "big_test", 1],
1250 ["include/have_debug.inc", "need_debug", 1],
1251 ["include/have_ndb.inc", "ndb_test", 1],
1252 ["include/have_multi_ndb.inc", "ndb_test", 1],
1253 ["include/master-slave.inc", "rpl_test", 1],
1254 ["include/ndb_master-slave.inc", "rpl_test", 1],
1255 ["include/ndb_master-slave.inc", "ndb_test", 1],
1256 ["federated.inc", "federated_test", 1],
1257 ["include/not_embedded.inc", "not_embedded", 1],
1258 ["include/have_ssl.inc", "need_ssl", 1],
1259 ["include/have_ssl_communication.inc", "need_ssl", 1],
1260 ["include/not_windows.inc", "not_windows", 1],
1261
1262 # Tests with below .inc file are considered to be group replication tests
1263 ["have_group_replication_plugin_base.inc", "grp_rpl_test", 1],
1264
1265 # Tests with below .inc file are considered to be xplugin tests
1266 ["include/have_mysqlx_plugin.inc", "xplugin_test", 1],
1267);
1268
1269
1270sub tags_from_test_file {
1271  my $tinfo= shift;
1272  my $file= shift;
1273  #mtr_verbose("$file");
1274  my $F= IO::File->new($file) or mtr_error("can't open file \"$file\": $!");
1275
1276  while ( my $line= <$F> )
1277  {
1278
1279    # Skip line if it start's with #
1280    next if ( $line =~ /^#/ );
1281
1282    # Match this line against tag in "tags" array
1283    foreach my $tag (@tags)
1284    {
1285      if ( index($line, $tag->[0]) >= 0 )
1286      {
1287	# Tag matched, assign value to "tinfo"
1288	$tinfo->{"$tag->[1]"}= $tag->[2];
1289      }
1290    }
1291
1292    # If test sources another file, open it as well
1293    if ( $line =~ /^\-\-([[:space:]]*)source(.*)$/ or
1294	 $line =~ /^([[:space:]]*)source(.*);$/ )
1295    {
1296      my $value= $2;
1297      $value =~ s/^\s+//;  # Remove leading space
1298      $value =~ s/[[:space:]]+$//;  # Remove ending space
1299
1300      # Sourced file may exist relative to test or
1301      # in global location
1302      foreach my $sourced_file (dirname($file). "/$value",
1303				"$::glob_mysql_test_dir/$value")
1304      {
1305	if ( -f $sourced_file )
1306	{
1307	  # Only source the file if it exists, we may get
1308	  # false positives in the regexes above if someone
1309	  # writes "source nnnn;" in a test case(such as mysqltest.test)
1310	  tags_from_test_file($tinfo, $sourced_file);
1311	  last;
1312	}
1313      }
1314    }
1315
1316  }
1317}
1318
1319sub unspace {
1320  my $string= shift;
1321  my $quote=  shift;
1322  $string =~ s/[ \t]/\x11/g;
1323  return "$quote$string$quote";
1324}
1325
1326
1327sub opts_from_file ($) {
1328  my $file=  shift;
1329
1330  open(FILE,"<",$file) or mtr_error("can't open file \"$file\": $!");
1331  my @args;
1332  while ( <FILE> )
1333  {
1334    chomp;
1335
1336    #    --init_connect=set @a='a\\0c'
1337    s/^\s+//;                           # Remove leading space
1338    s/\s+$//;                           # Remove ending space
1339
1340    # This is strange, but we need to fill whitespace inside
1341    # quotes with something, to remove later. We do this to
1342    # be able to split on space. Else, we have trouble with
1343    # options like
1344    #
1345    #   --someopt="--insideopt1 --insideopt2"
1346    #
1347    # But still with this, we are not 100% sure it is right,
1348    # we need a shell to do it right.
1349
1350    s/\'([^\'\"]*)\'/unspace($1,"\x0a")/ge;
1351    s/\"([^\'\"]*)\"/unspace($1,"\x0b")/ge;
1352    s/\'([^\'\"]*)\'/unspace($1,"\x0a")/ge;
1353    s/\"([^\'\"]*)\"/unspace($1,"\x0b")/ge;
1354
1355    foreach my $arg (split(/[ \t]+/))
1356    {
1357      $arg =~ tr/\x11\x0a\x0b/ \'\"/;     # Put back real chars
1358      # The outermost quotes has to go
1359      $arg =~ s/^([^\'\"]*)\'(.*)\'([^\'\"]*)$/$1$2$3/
1360        or $arg =~ s/^([^\'\"]*)\"(.*)\"([^\'\"]*)$/$1$2$3/;
1361      $arg =~ s/\\\\/\\/g;
1362
1363      # Do not pass empty string since my_getopt is not capable to handle it.
1364      if (length($arg)) {
1365	push(@args, $arg);
1366      }
1367    }
1368  }
1369  close FILE;
1370  return \@args;
1371}
1372
1373sub print_testcases {
1374  my (@cases)= @_;
1375
1376  print "=" x 60, "\n";
1377  foreach my $test (@cases){
1378    $test->print_test();
1379  }
1380  print "=" x 60, "\n";
1381}
1382
1383
13841;
1385