#!/usr/bin/perl

# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library 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
# Library 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 perl script checks for availability of the Perl modules DBI and
# DBD::mysql using the "current" perl interpreter.
#
# Useful for test environment checking before testing executable perl scripts
# in the MySQL Server distribution.
#
# NOTE: The "shebang" on the first line of this script should always point to
#       /usr/bin/perl, so that we can use this script to check whether or not we
#       support running perl scripts with such a shebang without specifying the
#       perl interpreter on the command line.
#
#       When run as "checkDBI_DBD-mysql.pl" the shebang line will be evaluated
#       and used. When run as "perl checkDBI_DBD-mysql.pl" the shebang line is
#       not used.
#
# NOTE: This script will create a temporary file in MTR's tmp dir.
#       If modules are found, a mysql-test statement which sets a special
#       variable is written to this file. If one of the modules is not found
#       (or cannot be loaded), the file will remain empty.
#       A test (or include file) which sources that file can then easily do
#       an if-check on the special variable to determine success or failure.
#
#       Example:
#
#         --let $perlChecker= $MYSQLTEST_VARDIR/std_data/checkDBI_DBD-mysql.pl
#         --let $resultFile= $MYSQL_TMP_DIR/dbidbd-mysql.txt
#         --chmod 0755 $perlChecker
#         --exec $perlChecker
#         --source $resultFile
#         if (!$dbidbd) {
#             --skip Test needs Perl modules DBI and DBD::mysql
#         }
#
#       The calling script is also responsible for cleaning up after use:
#
#         --remove_file $resultFile
#
# Windows notes:
#   - shebangs may work differently - call this script with "perl " in front.
#
# See mysql-test/include/have_dbi_dbd-mysql.inc for example use of this script.
# This script should be executable for the user running MTR.
#
################################################################################

BEGIN {
    # By using eval inside BEGIN we can suppress warnings and continue after.
    # We need to catch "Can't locate" as well as "Can't load" errors.
    eval{
        $FOUND_DBI=0;
        $FOUND_DBD_MYSQL=0;

        # Check for DBI module:
        $FOUND_DBI=1 if require DBI;

        # Check for DBD::mysql module
        $FOUND_DBD_MYSQL=1 if require DBD::mysql;
    };
};

# Open a file to be used for transfer of result back to mysql-test.
# The file must be created whether we write to it or not, otherwise mysql-test
# will complain if trying to source it.
# An empty file indicates failure to load modules.
open(FILE, ">", $ENV{'MYSQL_TMP_DIR'}.'/dbidbd-mysql.txt');

if ($FOUND_DBI && $FOUND_DBD_MYSQL) {
    # write a mysql-test command setting a variable to indicate success
    print(FILE 'let $dbidbd= FOUND_DBI_DBD-MYSQL;'."\n");
}

# close the file.
close(FILE);

1;


#!/usr/bin/perl
################################################################################
#
# This script runs the transactional stress test "stress_tx_rr" against the
# transactional storage engine and looks for errors in two log files:
#   var/stress/<timestamp>/mysql-stress-test.log
#   var/log/master.err
#
# The script assumes current working dir is mysql-test/.
#
# Regarding the server error log, currently only error lines containing the
# string "Error:" will be reported as a critical error, in addition to signs
# of crashes.
#
# In the stress test log, all lines matching the regex "S\d:" (denoting an
# error with a specified severity) will be reported as errors.
#
# Error information including the full server log in the case of server crash
# is output to standard out.
#
# This script is and should be silent if no errors are detected.
#
################################################################################

use File::Find;
use File::Spec;
use Cwd;
use Cwd 'abs_path';
use Getopt::Long;

# Checking script is run from the correct location
if (! -f "mysql-test-run.pl") {
   print("\nERROR: This script should be run from the \'\<INSTALL_DIR\>/mysql-test\' directory.\n");
   error(1);
}

$runlog="rr_trx.log";

my $errorFound;

my $installdir=abs_path(File::Spec->updir());

my $f=abs_path($0);
my ($v,$d,$f)=File::Spec->splitpath($f);
my $testsuitedir=$v.$d;

################################################################################
# Run stress test, redirect output to tmp file.
# Duration is specified in seconds. Some nice values:
#   5 minutes =   300
#  30 minutes =  1800
#   1 hour    =  3600
#   2 hours   =  7200
#   5 hours   = 18000
#  12 hours   = 43200
#
################################################################################
$opt_duration=600;

# Special handling for the InnoDB plugin
$plugin_params="\"--plugin-load=innodb=ha_innodb_plugin.so;innodb_trx=ha_innodb_plugin.so;innodb_locks=ha_innodb_plugin.so;innodb_cmp=ha_innodb_plugin.so;innodb_cmp_reset=ha_innodb_plugin.so;innodb_cmpmem=ha_innodb_plugin.so;innodb_cmpmem_reset=ha_innodb_plugin.so\"";
$plugin_params=~s/so/dll/g if (windows());

$opt_help="";
$opt_try="";
$opt_engine="";
$opt_threads=10;

# Collection command line options
GetOptions("engine:s"   => \$opt_engine,
           "duration=i" => \$opt_duration,
           "threads=i"  => \$opt_treads,
           "try", "help") || usage();

if ($opt_help) { usage(); }
if (!$opt_engine) {
   print("\nERROR: --engine=\<engine\> argument is required!!!\n");
   usage();
}

# setting specific engine parameters
$engine_options="";
  # for innodb engine
  if ($opt_engine eq "InnoDB") {
     $engine_options=
           "--mysqld=--innodb " .
           "--mysqld=--innodb-lock-wait-timeout=2 " .
           " ";
     }
  elsif ($opt_engine eq "InnoDB_plugin") {
        $engine_options=
           "--mysqld=--innodb " .
           "--mysqld=--ignore-builtin-innodb " .
           #"--mysqld=--plugin_dir=".$installdir."/lib " .
           "--mysqld=--plugin_dir=".$installdir."/storage/innodb_plugin/.libs " .
           "--mysqld=--innodb-lock-wait-timeout=2 " .
           "--mysqld=".$plugin_params." " .
           " ";
     }
  # add parameters for a new engine by modifying the 'elsif' section below
  elsif ($opt_engine eq "zz") {
        $engine_options=
           " ";
     }
  else  {
        print("\nERROR: '".$opt_engine."' - unknown engine\n");
        add_engine_help();
     }

# From this point forward there is no difference between the build in InnDB and the plugin
$opt_engine='InnoDB' if ($opt_engine eq 'InnoDB_plugin');

# checking that custom files for that engine exist
$engine_lower= lc($opt_engine);
$missing=0;
if (!-f $testsuitedir.'init_'.$engine_lower.'.txt') {
   print("\nERROR: config file 'init_".$engine_lower.".txt' missing.");
   $missing=1;
}
if (!-f $testsuitedir.'t/init_'.$engine_lower.'.test') {
   print("\nERROR: config file 'init_".$engine_lower.".test' missing.");
   $missing=1;
}
if (!-f $testsuitedir.'r/init_'.$engine_lower.'.result') {
   print("\nERROR: config file 'init_".$engine_lower.".result' missing.");
   $missing=1;
}
add_engine_help() if ($missing);

# bilding test command line
$cmd="MTR_VERSION=1 " .
    "perl ./mysql-test-run.pl " .
    "--comment=stress_tx_rr_".$opt_engine." " .
    "--stress " .
    "--stress-init-file=init_".$engine_lower.".txt " .
    "--stress-test-file=run.txt " .
    "--stress-suite=engines/rr_trx " .
    "--stress-test-duration=".$opt_duration." " .
    "--stress-threads=".$opt_threads."  " .
    "--mysqld=--log-output=file " .
    "--mysqld=--sql-mode=no_engine_substitution " .
    "--skip-im " .
    "--skip-ndb " .
    $engine_options .
    " > ".$runlog." 2>&1";

# running the test
print("\n   Running \'rr_trx\' test with ".$opt_threads." clients\n");
print("   for ".$opt_duration." seconds using the ".$opt_engine." storag engine.\n");
print("\n   Log file: ".$runlog."\n");
if ($opt_try) {
   print("\nThe following command will execute:\n");
   print("$cmd\n\n");
   exit(0);
}
system $cmd;

################################################################################
# Check for crash and other severe errors in the server log.
#
################################################################################

# Open log file. If MTR_VERSION=1 this is in var/log/master.err.
# Otherwise, it is in ?... [stress_tx_rr not yet runnable with MTR_VERSION=2]
# Assuming current directory mysql-test/
my $serverlog=getcwd() . "/var/log/master.err";

open(SERVERLOG, $serverlog)
    or die "Unable to open $serverlog. Test not run?";
my @servererrors = ();  # Lines with "Severe" errors in server error log
my @crash = ();    # Empty if no stack trace detected, non-empty otherwise.

# Grep for errors and crashes. Going line-by-line since the file can be large.
while (<SERVERLOG>) {
    $line = $_;
    push @crash, $line if /This could be because you hit a bug/;
    push @servererrors, $line if /Error:/;
}
close(SERVERLOG);

if (@crash) {
    # Crash (stack trace) detected in server log.
    print "Transactional stress test stress_tx_rr:\n\n";
    print "SERVER CRASH DETECTED!\n";
    print "Server log: $serverlog printed at the bottom of this log.\n\n";
    print "########################################################\n\n";
}
if (@servererrors) {
    # "Severe" errors detected. Print error lines to std out
    print "CRITICAL ERRORS:\n\n";
    foreach $error (@servererrors) {
        print $error;
    }
    print "\n########################################################\n\n";
}

################################################################################
# Check for errors reported by mysql-stress-test.pl. Transactional consistency
# issues are shown as result diffs.
################################################################################

my $dir;
find(\&finddir, cwd);  # sets variable $dir

# Open log file
my $logfile="$dir/mysql-stress-test.log";
open(LOGFILE, $logfile)
    or die "Unable to open $logfile. Test not run?";
my @errors = ();
my @heading = ();

# Grep for errors. Going line-by-line since the file can be large.
while (<LOGFILE>) {
    #push @errors, $_ if ! /No Errors/;
    push @errors, $_ if /S\d:/;
    push @heading, $_ if /TestID|=====/;
}
close(LOGFILE);

# Print all errors, i.e. all lines that do not contain the string "No Errors"
if (@errors) {
    $errorFound = 1;
    print "Stress test main log file: $logfile\n";
    print "Errors follow:\n\n";
    # First print the heading
    foreach $header_line (@heading) {
        print $header_line;
    }
    foreach $error (@errors) {
        print $error;
    }
}

# If errors in server log, output the log and exit 1?
if (@servererrors or @crash) {
    $errorFound = 1;
    print "\n########################################################\n\n";
    print "Server error log (master.err):\n\n";

    open(SERVERLOG, $serverlog)
        or die "Unable to open $serverlog!";

    while (<SERVERLOG>) {
        print $_;
    }
    close(SERVERLOG);
}

if ($errorFound) {
    # Exit with error code != 0 if we found an error.
    print("\nTest Completed with errors. \n");
    print(" - See ".$runlog." for summary.\n");
    print(" - See files under var/stress for details.\n");
    exit 1;
}

print("\nTest Completed - See ".$runlog." for details\n");
################################################################################
# Helper routines etc.
#
################################################################################

sub finddir {
  my $file = $File::Find::name;       # complete path to the file

  return unless -d $file;             # process directories (-d), not files (-f)
  return unless $_ =~ m/^\d{14}$/;    # check if file matches timstamp regex,
                                      # must be 14 digits
  $dir=$file;
  #$dir= $_;                          # $_ = just the file name, no path
  return $_;
}

sub usage
{
  print <<EOF;

SYNTAX $0 --engine=<engine> [--duration=<nn>] [--thread=<nn>] [--try]

  --engine=<engine>
    The engine used to run the test. \<engine\> needs to be provided exactly as
    it is reprted in the SHOW ENGINES comand.
    EXCEPTION: In order to use the InnoDB plugin, specify 'InnoDB_plugin'
  Required option.

  --duration=nn
    The time the test should run for in seconds. Defaut value is 600 seconds (10 minutes).
  Optional parameter

  --threads=nn
    The number of clients used by the test driver. Defaut value is 10.
  Optional parameter

  --try
    Do not run the actual test but show what will be run
  Optional parameter

EOF

exit(0);
}

sub add_engine_help
{
  print <<EOF;

\nThis test is can be run against any transactional engine. However scripts need to be modifed in order
to support such engines (support to InnoDB is provided as an example).
In order to add support for a new engine, you will need to modify scripts as follows:
   1) cd to INSTALL_DIR/mysql-test/suite/engines/rr_trx
   2) Modify the 'run_stress_rr.pl' file by adding an 'elsif' section for your engine and have it
      include specifc values required to be passed as startup parameters to the MySQL server by
      specifying them using "--mysqld" options (see InnoDB example).
   3) Copy the 'init_innodb.txt' file to 'init_<engine>.txt file and change its content to be "init_<engine>".
   4) In the 't' directory copy the "init_innodb.test" file to "init_\<engine\>.test" and change the value of
      the '\$engine' variable to \<engine\>.
   5) In the 'r' directory copy "the init_innodb.result" file to "init_\<engine\>.result" and change refrences
      to 'InnoDB' to \<engine\>.

EOF

exit(0);
}

sub windows {
        if (
                ($^O eq 'MSWin32') ||
                ($^O eq 'MSWin64')
        ) {
                return 1;
        } else {
                return 0;
        }
}