mirror of
https://github.com/percona/percona-toolkit.git
synced 2025-09-11 05:29:30 +00:00
Fix pt-fifo-split.t. Remove db and tbl progress from pt-index-usage until those can be implemented in SchemaIterator. Use new Schema and SchemaIterator in pt-index-usage. Add PerconaTest.pm (copy of MaatkitTest.pm).
This commit is contained in:
@@ -3679,6 +3679,202 @@ sub _d {
|
|||||||
# End MySQLDump package
|
# End MySQLDump package
|
||||||
# ###########################################################################
|
# ###########################################################################
|
||||||
|
|
||||||
|
# ###########################################################################
|
||||||
|
# Schema package
|
||||||
|
# This package is a copy without comments from the original. The original
|
||||||
|
# with comments and its test file can be found in the Bazaar repository at,
|
||||||
|
# lib/Schema.pm
|
||||||
|
# t/lib/Schema.t
|
||||||
|
# See https://launchpad.net/percona-toolkit for more information.
|
||||||
|
# ###########################################################################
|
||||||
|
{
|
||||||
|
package Schema;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings FATAL => 'all';
|
||||||
|
use English qw(-no_match_vars);
|
||||||
|
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
|
||||||
|
|
||||||
|
sub new {
|
||||||
|
my ( $class, %args ) = @_;
|
||||||
|
my @required_args = qw();
|
||||||
|
foreach my $arg ( @required_args ) {
|
||||||
|
die "I need a $arg argument" unless $args{$arg};
|
||||||
|
}
|
||||||
|
|
||||||
|
my $self = {
|
||||||
|
%args,
|
||||||
|
schema => {}, # keyed on db->tbl
|
||||||
|
};
|
||||||
|
return bless $self, $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_schema {
|
||||||
|
my ( $self ) = @_;
|
||||||
|
return $self->{schema};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_table {
|
||||||
|
my ( $self, $db_name, $tbl_name ) = @_;
|
||||||
|
if ( exists $self->{schema}->{$db_name}
|
||||||
|
&& exists $self->{schema}->{$db_name}->{$tbl_name} ) {
|
||||||
|
return $self->{schema}->{$db_name}->{$tbl_name};
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
sub add_schema_object {
|
||||||
|
my ( $self, $schema_object ) = @_;
|
||||||
|
die "I need a schema_object argument" unless $schema_object;
|
||||||
|
|
||||||
|
my ($db, $tbl) = @{$schema_object}{qw(db tbl)};
|
||||||
|
if ( !$db || !$tbl ) {
|
||||||
|
warn "No database or table for schema object";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $tbl_struct = $schema_object->{tbl_struct};
|
||||||
|
if ( !$tbl_struct ) {
|
||||||
|
warn "No table structure for $db.$tbl";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->{schema}->{lc $db}->{lc $tbl} = $schema_object;
|
||||||
|
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub find_column {
|
||||||
|
my ( $self, %args ) = @_;
|
||||||
|
my $ignore = $args{ignore};
|
||||||
|
my $schema = $self->{schema};
|
||||||
|
|
||||||
|
my ($col, $tbl, $db);
|
||||||
|
if ( my $col_name = $args{col_name} ) {
|
||||||
|
($col, $tbl, $db) = reverse map { s/`//g; $_ } split /[.]/, $col_name;
|
||||||
|
MKDEBUG && _d('Column', $col_name, 'has db', $db, 'tbl', $tbl,
|
||||||
|
'col', $col);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
($col, $tbl, $db) = @args{qw(col tbl db)};
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = lc $db;
|
||||||
|
$tbl = lc $tbl;
|
||||||
|
$col = lc $col;
|
||||||
|
|
||||||
|
if ( !$col ) {
|
||||||
|
MKDEBUG && _d('No column specified or parsed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MKDEBUG && _d('Finding column', $col, 'in', $db, $tbl);
|
||||||
|
|
||||||
|
if ( $db && !$schema->{$db} ) {
|
||||||
|
MKDEBUG && _d('Database', $db, 'does not exist');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $db && $tbl && !$schema->{$db}->{$tbl} ) {
|
||||||
|
MKDEBUG && _d('Table', $tbl, 'does not exist in database', $db);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my @tbls;
|
||||||
|
my @search_dbs = $db ? ($db) : keys %$schema;
|
||||||
|
DATABASE:
|
||||||
|
foreach my $search_db ( @search_dbs ) {
|
||||||
|
my @search_tbls = $tbl ? ($tbl) : keys %{$schema->{$search_db}};
|
||||||
|
|
||||||
|
TABLE:
|
||||||
|
foreach my $search_tbl ( @search_tbls ) {
|
||||||
|
next DATABASE unless exists $schema->{$search_db}->{$search_tbl};
|
||||||
|
|
||||||
|
if ( $ignore
|
||||||
|
&& grep { $_->{db} eq $search_db && $_->{tbl} eq $search_tbl }
|
||||||
|
@$ignore ) {
|
||||||
|
MKDEBUG && _d('Ignoring', $search_db, $search_tbl, $col);
|
||||||
|
next TABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $tbl = $schema->{$search_db}->{$search_tbl};
|
||||||
|
if ( $tbl->{tbl_struct}->{is_col}->{$col} ) {
|
||||||
|
MKDEBUG && _d('Column', $col, 'exists in', $tbl->{db}, $tbl->{tbl});
|
||||||
|
push @tbls, $tbl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return \@tbls;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub find_table {
|
||||||
|
my ( $self, %args ) = @_;
|
||||||
|
my $ignore = $args{ignore};
|
||||||
|
my $schema = $self->{schema};
|
||||||
|
|
||||||
|
my ($tbl, $db);
|
||||||
|
if ( my $tbl_name = $args{tbl_name} ) {
|
||||||
|
($tbl, $db) = reverse map { s/`//g; $_ } split /[.]/, $tbl_name;
|
||||||
|
MKDEBUG && _d('Table', $tbl_name, 'has db', $db, 'tbl', $tbl);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
($tbl, $db) = @args{qw(tbl db)};
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = lc $db;
|
||||||
|
$tbl = lc $tbl;
|
||||||
|
|
||||||
|
if ( !$tbl ) {
|
||||||
|
MKDEBUG && _d('No table specified or parsed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MKDEBUG && _d('Finding table', $tbl, 'in', $db);
|
||||||
|
|
||||||
|
if ( $db && !$schema->{$db} ) {
|
||||||
|
MKDEBUG && _d('Database', $db, 'does not exist');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $db && $tbl && !$schema->{$db}->{$tbl} ) {
|
||||||
|
MKDEBUG && _d('Table', $tbl, 'does not exist in database', $db);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my @dbs;
|
||||||
|
my @search_dbs = $db ? ($db) : keys %$schema;
|
||||||
|
DATABASE:
|
||||||
|
foreach my $search_db ( @search_dbs ) {
|
||||||
|
if ( $ignore && grep { $_->{db} eq $search_db } @$ignore ) {
|
||||||
|
MKDEBUG && _d('Ignoring', $search_db);
|
||||||
|
next DATABASE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( exists $schema->{$search_db}->{$tbl} ) {
|
||||||
|
MKDEBUG && _d('Table', $tbl, 'exists in', $search_db);
|
||||||
|
push @dbs, $search_db;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return \@dbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _d {
|
||||||
|
my ($package, undef, $line) = caller 0;
|
||||||
|
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
|
||||||
|
map { defined $_ ? $_ : 'undef' }
|
||||||
|
@_;
|
||||||
|
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
}
|
||||||
|
# ###########################################################################
|
||||||
|
# End Schema package
|
||||||
|
# ###########################################################################
|
||||||
|
|
||||||
# ###########################################################################
|
# ###########################################################################
|
||||||
# SchemaIterator package
|
# SchemaIterator package
|
||||||
# This package is a copy without comments from the original. The original
|
# This package is a copy without comments from the original. The original
|
||||||
@@ -4855,7 +5051,6 @@ sub main {
|
|||||||
my $parser = new SlowLogParser();
|
my $parser = new SlowLogParser();
|
||||||
my $fi = new FileIterator();
|
my $fi = new FileIterator();
|
||||||
my $du = new MySQLDump();
|
my $du = new MySQLDump();
|
||||||
my $si = new SchemaIterator(Quoter => $q);
|
|
||||||
my $iu = new IndexUsage(
|
my $iu = new IndexUsage(
|
||||||
QueryRewriter => $qr,
|
QueryRewriter => $qr,
|
||||||
);
|
);
|
||||||
@@ -4876,7 +5071,6 @@ sub main {
|
|||||||
ExplainAnalyzer => $exa,
|
ExplainAnalyzer => $exa,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
# ########################################################################
|
# ########################################################################
|
||||||
# Ready the save results database and its tables.
|
# Ready the save results database and its tables.
|
||||||
# ########################################################################
|
# ########################################################################
|
||||||
@@ -4939,49 +5133,28 @@ sub main {
|
|||||||
# guess which database to USE for EXPLAIN-ing it. This code block doesn't
|
# guess which database to USE for EXPLAIN-ing it. This code block doesn't
|
||||||
# read query logs, it's just inventorying the tables and indexes.
|
# read query logs, it's just inventorying the tables and indexes.
|
||||||
# ########################################################################
|
# ########################################################################
|
||||||
$si->set_filter($si->make_filter($o));
|
|
||||||
my $version = $vp->parse($dbh->selectrow_array('SELECT VERSION()'));
|
my $version = $vp->parse($dbh->selectrow_array('SELECT VERSION()'));
|
||||||
my ($next_db, $db_count) = $si->get_db_itr(dbh => $si_dbh);
|
|
||||||
my ($db_pr, $dbs_done);
|
my $schema = new Schema();
|
||||||
if ( $o->get('progress') ) {
|
my $schema_itr = new SchemaIterator(
|
||||||
$db_pr = new Progress(
|
dbh => $dbh,
|
||||||
jobsize => $db_count,
|
OptionParser => $o,
|
||||||
spec => $o->get('progress'),
|
Quoter => $q,
|
||||||
name => 'schema inventory',
|
MySQLDump => $du,
|
||||||
|
TableParser => $tp,
|
||||||
|
Schema => $schema,
|
||||||
|
keep_ddl => 1,
|
||||||
);
|
);
|
||||||
}
|
TALBE:
|
||||||
DATABASE:
|
while ( my $tbl = $schema_itr->next_schema_object() ) {
|
||||||
while ( my $database = $next_db->() ) {
|
|
||||||
MKDEBUG && _d('Getting tables from', $database);
|
|
||||||
my ($next_tbl, $tbl_count) = $si->get_tbl_itr(
|
|
||||||
dbh => $si_dbh,
|
|
||||||
db => $database,
|
|
||||||
views => 0,
|
|
||||||
);
|
|
||||||
my ($tbl_pr, $tbls_done);
|
|
||||||
if ( $o->get('progress') ) {
|
|
||||||
$tbl_pr = new Progress(
|
|
||||||
jobsize => $tbl_count,
|
|
||||||
spec => $o->get('progress'),
|
|
||||||
name => "table inventory for $database",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
TABLE:
|
|
||||||
while ( my $table = $next_tbl->() ) {
|
|
||||||
MKDEBUG && _d('Got table', $table);
|
|
||||||
eval {
|
eval {
|
||||||
my $ddl = $du->get_create_table(
|
my ($indexes) = $tp->get_keys($tbl->{ddl}, {version => $version});
|
||||||
$si_dbh, $q, $database, $table)->[1];
|
$iu->add_indexes(%$tbl, indexes=>$indexes);
|
||||||
my ($indexes) = $tp->get_keys($ddl, {version => $version });
|
|
||||||
$iu->add_indexes(db=>$database, tbl=>$table, indexes=>$indexes);
|
|
||||||
};
|
};
|
||||||
if ( $EVAL_ERROR ) {
|
if ( $EVAL_ERROR ) {
|
||||||
warn $EVAL_ERROR unless $o->get('q');
|
warn $EVAL_ERROR unless $o->get('q');
|
||||||
MKDEBUG && _d($EVAL_ERROR);
|
MKDEBUG && _d($EVAL_ERROR);
|
||||||
}
|
}
|
||||||
$tbl_pr->update(sub { ++$tbls_done }) if $tbl_pr;
|
|
||||||
}
|
|
||||||
$db_pr->update(sub { ++$dbs_done }) if $db_pr;
|
|
||||||
}
|
}
|
||||||
$si_dbh->disconnect();
|
$si_dbh->disconnect();
|
||||||
|
|
||||||
|
525
lib/PerconaTest.pm
Normal file
525
lib/PerconaTest.pm
Normal file
@@ -0,0 +1,525 @@
|
|||||||
|
# This program is copyright 2009-2011 Percona Inc.
|
||||||
|
# Feedback and improvements are welcome.
|
||||||
|
#
|
||||||
|
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
|
||||||
|
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
|
||||||
|
# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
#
|
||||||
|
# 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; OR the Perl Artistic License. On UNIX and similar
|
||||||
|
# systems, you can issue `man perlgpl' or `man perlartistic' to read these
|
||||||
|
# licenses.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
# ###########################################################################
|
||||||
|
# PerconaTest package
|
||||||
|
# ###########################################################################
|
||||||
|
{
|
||||||
|
# Package: PerconaTest
|
||||||
|
# PerconaTest is a collection of helper-subs for Percona Toolkit tests.
|
||||||
|
# Any file arguments (like no_diff() $expected_output) are relative to
|
||||||
|
# PERCONA_TOOLKIT_BRANCH. So passing "commont/t/samples/foo" means
|
||||||
|
# "PERCONA_TOOLKIT_BRANCH/common/t/samples/foo". Do not BAIL_OUT() because
|
||||||
|
# this terminates the *entire* test process; die instead. All
|
||||||
|
# subs are exported by default, so is the variable $trunk, so there's
|
||||||
|
# no need to import() in the test scripts.
|
||||||
|
package PerconaTest;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings FATAL => 'all';
|
||||||
|
use English qw(-no_match_vars);
|
||||||
|
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
|
||||||
|
|
||||||
|
use Test::More;
|
||||||
|
use Time::HiRes qw(usleep);
|
||||||
|
use POSIX qw(signal_h);
|
||||||
|
use Data::Dumper;
|
||||||
|
$Data::Dumper::Indent = 1;
|
||||||
|
$Data::Dumper::Sortkeys = 1;
|
||||||
|
$Data::Dumper::Quotekeys = 0;
|
||||||
|
|
||||||
|
require Exporter;
|
||||||
|
our @ISA = qw(Exporter);
|
||||||
|
our %EXPORT_TAGS = ();
|
||||||
|
our @EXPORT_OK = qw();
|
||||||
|
our @EXPORT = qw(
|
||||||
|
output
|
||||||
|
load_data
|
||||||
|
load_file
|
||||||
|
parse_file
|
||||||
|
wait_until
|
||||||
|
wait_for
|
||||||
|
test_log_parser
|
||||||
|
test_protocol_parser
|
||||||
|
test_packet_parser
|
||||||
|
no_diff
|
||||||
|
throws_ok
|
||||||
|
remove_traces
|
||||||
|
test_bash_tool
|
||||||
|
$trunk
|
||||||
|
$dsn_opts
|
||||||
|
$sandbox_version
|
||||||
|
);
|
||||||
|
|
||||||
|
our $trunk = $ENV{PERCONA_TOOLKIT_BRANCH};
|
||||||
|
|
||||||
|
our $sandbox_version = '';
|
||||||
|
eval {
|
||||||
|
chomp(my $v = `$trunk/sandbox/test-env version`);
|
||||||
|
$sandbox_version = $v if $v;
|
||||||
|
};
|
||||||
|
|
||||||
|
our $dsn_opts = [
|
||||||
|
{
|
||||||
|
key => 'A',
|
||||||
|
desc => 'Default character set',
|
||||||
|
dsn => 'charset',
|
||||||
|
copy => 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key => 'D',
|
||||||
|
desc => 'Database to use',
|
||||||
|
dsn => 'database',
|
||||||
|
copy => 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key => 'F',
|
||||||
|
desc => 'Only read default options from the given file',
|
||||||
|
dsn => 'mysql_read_default_file',
|
||||||
|
copy => 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key => 'h',
|
||||||
|
desc => 'Connect to host',
|
||||||
|
dsn => 'host',
|
||||||
|
copy => 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key => 'p',
|
||||||
|
desc => 'Password to use when connecting',
|
||||||
|
dsn => 'password',
|
||||||
|
copy => 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key => 'P',
|
||||||
|
desc => 'Port number to use for connection',
|
||||||
|
dsn => 'port',
|
||||||
|
copy => 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key => 'S',
|
||||||
|
desc => 'Socket file to use for connection',
|
||||||
|
dsn => 'mysql_socket',
|
||||||
|
copy => 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key => 't',
|
||||||
|
desc => 'Table',
|
||||||
|
dsn => undef,
|
||||||
|
copy => 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key => 'u',
|
||||||
|
desc => 'User for login if not current user',
|
||||||
|
dsn => 'user',
|
||||||
|
copy => 1,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
# Runs code, captures and returns its output.
|
||||||
|
# Optional arguments:
|
||||||
|
# * file scalar: capture output to this file (default none)
|
||||||
|
# * stderr scalar: capture STDERR (default no)
|
||||||
|
# * die scalar: die if code dies (default no)
|
||||||
|
# * trf coderef: pass output to this coderef (default none)
|
||||||
|
sub output {
|
||||||
|
my ( $code, %args ) = @_;
|
||||||
|
die "I need a code argument" unless $code;
|
||||||
|
my ($file, $stderr, $die, $trf) = @args{qw(file stderr die trf)};
|
||||||
|
|
||||||
|
my $output = '';
|
||||||
|
if ( $file ) {
|
||||||
|
open *output_fh, '>', $file
|
||||||
|
or die "Cannot open file $file: $OS_ERROR";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
open *output_fh, '>', \$output
|
||||||
|
or die "Cannot capture output to variable: $OS_ERROR";
|
||||||
|
}
|
||||||
|
local *STDOUT = *output_fh;
|
||||||
|
|
||||||
|
# If capturing STDERR we must dynamically scope (local) STDERR
|
||||||
|
# in the outer scope of the sub. If we did,
|
||||||
|
# if ( $args{stderr} ) { local *STDERR; ... }
|
||||||
|
# then STDERR would revert to its original value outside the if
|
||||||
|
# block.
|
||||||
|
local *STDERR if $args{stderr}; # do in outer scope of this sub
|
||||||
|
*STDERR = *STDOUT if $args{stderr};
|
||||||
|
|
||||||
|
eval { $code->() };
|
||||||
|
close *output_fh;
|
||||||
|
if ( $EVAL_ERROR ) {
|
||||||
|
die $EVAL_ERROR if $die;
|
||||||
|
return $EVAL_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Possible transform output before returning it. This doesn't work
|
||||||
|
# if output was captured to a file.
|
||||||
|
$output = $trf->($output) if $trf;
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load data from file and removes spaces. Used to load tcpdump dumps.
|
||||||
|
sub load_data {
|
||||||
|
my ( $file ) = @_;
|
||||||
|
$file = "$trunk/$file";
|
||||||
|
open my $fh, '<', $file or die "Cannot open $file: $OS_ERROR";
|
||||||
|
my $contents = do { local $/ = undef; <$fh> };
|
||||||
|
close $fh;
|
||||||
|
(my $data = join('', $contents =~ m/(.*)/g)) =~ s/\s+//g;
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Slurp file and return its entire contents.
|
||||||
|
sub load_file {
|
||||||
|
my ( $file, %args ) = @_;
|
||||||
|
$file = "$trunk/$file";
|
||||||
|
open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
|
||||||
|
my $contents = do { local $/ = undef; <$fh> };
|
||||||
|
close $fh;
|
||||||
|
chomp $contents if $args{chomp_contents};
|
||||||
|
return $contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub parse_file {
|
||||||
|
my ( $file, $p, $ea ) = @_;
|
||||||
|
$file = "$trunk/$file";
|
||||||
|
my @e;
|
||||||
|
eval {
|
||||||
|
open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
|
||||||
|
my %args = (
|
||||||
|
next_event => sub { return <$fh>; },
|
||||||
|
tell => sub { return tell $fh; },
|
||||||
|
fh => $fh,
|
||||||
|
);
|
||||||
|
while ( my $e = $p->parse_event(%args) ) {
|
||||||
|
push @e, $e;
|
||||||
|
$ea->aggregate($e) if $ea;
|
||||||
|
}
|
||||||
|
close $fh;
|
||||||
|
};
|
||||||
|
die $EVAL_ERROR if $EVAL_ERROR;
|
||||||
|
return \@e;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wait until code returns true.
|
||||||
|
sub wait_until {
|
||||||
|
my ( $code, $t, $max_t ) = @_;
|
||||||
|
my $slept = 0;
|
||||||
|
my $sleep_int = $t || .5;
|
||||||
|
$t ||= .5;
|
||||||
|
$max_t ||= 5;
|
||||||
|
$t *= 1_000_000;
|
||||||
|
while ( $slept <= $max_t ) {
|
||||||
|
return if $code->();
|
||||||
|
usleep($t);
|
||||||
|
$slept += $sleep_int;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wait t seconds for code to return.
|
||||||
|
sub wait_for {
|
||||||
|
my ( $code, $t ) = @_;
|
||||||
|
$t ||= 0;
|
||||||
|
my $mask = POSIX::SigSet->new(&POSIX::SIGALRM);
|
||||||
|
my $action = POSIX::SigAction->new(
|
||||||
|
sub { die },
|
||||||
|
$mask,
|
||||||
|
);
|
||||||
|
my $oldaction = POSIX::SigAction->new();
|
||||||
|
sigaction(&POSIX::SIGALRM, $action, $oldaction);
|
||||||
|
eval {
|
||||||
|
alarm $t;
|
||||||
|
$code->();
|
||||||
|
alarm 0;
|
||||||
|
};
|
||||||
|
if ( $EVAL_ERROR ) {
|
||||||
|
# alarm was raised
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _read {
|
||||||
|
my ( $fh ) = @_;
|
||||||
|
return <$fh>;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub test_log_parser {
|
||||||
|
my ( %args ) = @_;
|
||||||
|
foreach my $arg ( qw(parser file) ) {
|
||||||
|
die "I need a $arg argument" unless $args{$arg};
|
||||||
|
}
|
||||||
|
my $p = $args{parser};
|
||||||
|
|
||||||
|
# Make sure caller isn't giving us something we don't understand.
|
||||||
|
# We could ignore it, but then caller might not get the results
|
||||||
|
# they expected.
|
||||||
|
map { die "What is $_ for?"; }
|
||||||
|
grep { $_ !~ m/^(?:parser|misc|file|result|num_events|oktorun)$/ }
|
||||||
|
keys %args;
|
||||||
|
|
||||||
|
my $file = "$trunk/$args{file}";
|
||||||
|
my @e;
|
||||||
|
eval {
|
||||||
|
open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
|
||||||
|
my %parser_args = (
|
||||||
|
next_event => sub { return _read($fh); },
|
||||||
|
tell => sub { return tell($fh); },
|
||||||
|
fh => $fh,
|
||||||
|
misc => $args{misc},
|
||||||
|
oktorun => $args{oktorun},
|
||||||
|
);
|
||||||
|
while ( my $e = $p->parse_event(%parser_args) ) {
|
||||||
|
push @e, $e;
|
||||||
|
}
|
||||||
|
close $fh;
|
||||||
|
};
|
||||||
|
|
||||||
|
is(
|
||||||
|
$EVAL_ERROR,
|
||||||
|
'',
|
||||||
|
"No error on $args{file}"
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( defined $args{result} ) {
|
||||||
|
is_deeply(
|
||||||
|
\@e,
|
||||||
|
$args{result},
|
||||||
|
$args{file}
|
||||||
|
) or print "Got: ", Dumper(\@e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( defined $args{num_events} ) {
|
||||||
|
is(
|
||||||
|
scalar @e,
|
||||||
|
$args{num_events},
|
||||||
|
"$args{file} num_events"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return \@e;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub test_protocol_parser {
|
||||||
|
my ( %args ) = @_;
|
||||||
|
foreach my $arg ( qw(parser protocol file) ) {
|
||||||
|
die "I need a $arg argument" unless $args{$arg};
|
||||||
|
}
|
||||||
|
my $parser = $args{parser};
|
||||||
|
my $protocol = $args{protocol};
|
||||||
|
|
||||||
|
# Make sure caller isn't giving us something we don't understand.
|
||||||
|
# We could ignore it, but then caller might not get the results
|
||||||
|
# they expected.
|
||||||
|
map { die "What is $_ for?"; }
|
||||||
|
grep { $_ !~ m/^(?:parser|protocol|misc|file|result|num_events|desc)$/ }
|
||||||
|
keys %args;
|
||||||
|
|
||||||
|
my $file = "$trunk/$args{file}";
|
||||||
|
my @e;
|
||||||
|
eval {
|
||||||
|
open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
|
||||||
|
my %parser_args = (
|
||||||
|
next_event => sub { return _read($fh); },
|
||||||
|
tell => sub { return tell($fh); },
|
||||||
|
misc => $args{misc},
|
||||||
|
);
|
||||||
|
while ( my $p = $parser->parse_event(%parser_args) ) {
|
||||||
|
my $e = $protocol->parse_event(%parser_args, event => $p);
|
||||||
|
push @e, $e if $e;
|
||||||
|
}
|
||||||
|
close $fh;
|
||||||
|
};
|
||||||
|
|
||||||
|
is(
|
||||||
|
$EVAL_ERROR,
|
||||||
|
'',
|
||||||
|
"No error on $args{file}"
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( defined $args{result} ) {
|
||||||
|
is_deeply(
|
||||||
|
\@e,
|
||||||
|
$args{result},
|
||||||
|
$args{file} . ($args{desc} ? ": $args{desc}" : '')
|
||||||
|
) or print "Got: ", Dumper(\@e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( defined $args{num_events} ) {
|
||||||
|
is(
|
||||||
|
scalar @e,
|
||||||
|
$args{num_events},
|
||||||
|
"$args{file} num_events"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return \@e;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub test_packet_parser {
|
||||||
|
my ( %args ) = @_;
|
||||||
|
foreach my $arg ( qw(parser file) ) {
|
||||||
|
die "I need a $arg argument" unless $args{$arg};
|
||||||
|
}
|
||||||
|
my $parser = $args{parser};
|
||||||
|
|
||||||
|
# Make sure caller isn't giving us something we don't understand.
|
||||||
|
# We could ignore it, but then caller might not get the results
|
||||||
|
# they expected.
|
||||||
|
map { die "What is $_ for?"; }
|
||||||
|
grep { $_ !~ m/^(?:parser|misc|file|result|desc|oktorun)$/ }
|
||||||
|
keys %args;
|
||||||
|
|
||||||
|
my $file = "$trunk/$args{file}";
|
||||||
|
my @packets;
|
||||||
|
open my $fh, '<', $file or die "Cannot open $file: $OS_ERROR";
|
||||||
|
my %parser_args = (
|
||||||
|
next_event => sub { return _read($fh); },
|
||||||
|
tell => sub { return tell($fh); },
|
||||||
|
misc => $args{misc},
|
||||||
|
oktorun => $args{oktorun},
|
||||||
|
);
|
||||||
|
while ( my $packet = $parser->parse_event(%parser_args) ) {
|
||||||
|
push @packets, $packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
# raw_packet is the actual dump text from the file. It's used
|
||||||
|
# in MySQLProtocolParser but I don't think we need to double-check
|
||||||
|
# it here. It will make the results very long.
|
||||||
|
foreach my $packet ( @packets ) {
|
||||||
|
delete $packet->{raw_packet};
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !is_deeply(
|
||||||
|
\@packets,
|
||||||
|
$args{result},
|
||||||
|
"$args{file}" . ($args{desc} ? ": $args{desc}" : '')
|
||||||
|
) ) {
|
||||||
|
print Dumper(\@packets);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# no_diff() compares the STDOUT output of a cmd or code to expected output.
|
||||||
|
# Returns true if there are no differences between the two outputs,
|
||||||
|
# else returns false. Dies if the cmd/code dies. Does not capture STDERR.
|
||||||
|
# Args:
|
||||||
|
# * cmd scalar or coderef: if cmd is a scalar then the
|
||||||
|
# cmd is ran via the shell. if it's a coderef then
|
||||||
|
# the code is ran. the latter is preferred because
|
||||||
|
# it generates test coverage.
|
||||||
|
# * expected_output scalar: file name relative to PERCONA_TOOLKIT_BRANCH
|
||||||
|
# * args hash: (optional) may include
|
||||||
|
# update_sample overwrite expected_output with cmd/code output
|
||||||
|
# keep_output keep last cmd/code output file
|
||||||
|
# * trf transform cmd/code output before diff
|
||||||
|
# The sub dies if cmd or code dies. STDERR is not captured.
|
||||||
|
sub no_diff {
|
||||||
|
my ( $cmd, $expected_output, %args ) = @_;
|
||||||
|
die "I need a cmd argument" unless $cmd;
|
||||||
|
die "I need an expected_output argument" unless $expected_output;
|
||||||
|
|
||||||
|
$expected_output = "$trunk/$expected_output";
|
||||||
|
die "$expected_output does not exist" unless -f $expected_output;
|
||||||
|
|
||||||
|
my $tmp_file = '/tmp/percona-toolkit-test-output.txt';
|
||||||
|
my $tmp_file_orig = '/tmp/percona-toolkit-test-output-original.txt';
|
||||||
|
|
||||||
|
# Determine cmd type and run it.
|
||||||
|
if ( ref $cmd eq 'CODE' ) {
|
||||||
|
output($cmd, file => $tmp_file);
|
||||||
|
}
|
||||||
|
elsif ( $args{cmd_output} ) {
|
||||||
|
# Copy cmd output to tmp file so we don't with the original.
|
||||||
|
open my $tmp_fh, '>', $tmp_file or die "Cannot open $tmp_file: $OS_ERROR";
|
||||||
|
print $tmp_fh $cmd;
|
||||||
|
close $tmp_fh;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
`$cmd > $tmp_file`;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Do optional arg stuff.
|
||||||
|
`cp $tmp_file $tmp_file_orig`;
|
||||||
|
if ( my $trf = $args{trf} ) {
|
||||||
|
`$trf $tmp_file_orig > $tmp_file`;
|
||||||
|
}
|
||||||
|
if ( my $sed = $args{sed} ) {
|
||||||
|
foreach my $sed_args ( @{$args{sed}} ) {
|
||||||
|
`sed $sed_args $tmp_file`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# diff the outputs.
|
||||||
|
my $retval = system("diff $tmp_file $expected_output");
|
||||||
|
|
||||||
|
# diff returns 0 if there were no differences,
|
||||||
|
# so !0 = 1 = no diff in our testing parlance.
|
||||||
|
$retval = $retval >> 8;
|
||||||
|
|
||||||
|
if ( $retval ) {
|
||||||
|
if ( $ENV{UPDATE_SAMPLES} || $args{update_sample} ) {
|
||||||
|
`cat $tmp_file > $expected_output`;
|
||||||
|
print STDERR "Updated $expected_output\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove our tmp files.
|
||||||
|
`rm -f $tmp_file $tmp_file_orig`
|
||||||
|
unless $ENV{KEEP_OUTPUT} || $args{keep_output};
|
||||||
|
|
||||||
|
return !$retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub throws_ok {
|
||||||
|
my ( $code, $pat, $msg ) = @_;
|
||||||
|
eval { $code->(); };
|
||||||
|
like ( $EVAL_ERROR, $pat, $msg );
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove /*percona-toolkit ...*/ trace comments from the given SQL statement(s).
|
||||||
|
# Traces are added in ChangeHandler::process_rows().
|
||||||
|
sub remove_traces {
|
||||||
|
my ( $sql ) = @_;
|
||||||
|
my $trace_pat = qr/ \/\*percona-toolkit .+?\*\//;
|
||||||
|
if ( ref $sql && ref $sql eq 'ARRAY' ) {
|
||||||
|
map { $_ =~ s/$trace_pat//gm } @$sql;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$sql =~ s/$trace_pat//gm;
|
||||||
|
}
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub test_bash_tool {
|
||||||
|
my ( $tool ) = @_;
|
||||||
|
die "I need a tool argument" unless $tool;
|
||||||
|
my $outfile = "/tmp/$tool-test-results.txt";
|
||||||
|
`rm -rf $outfile >/dev/null`;
|
||||||
|
`$trunk/util/test-bash-tool $tool > $outfile`;
|
||||||
|
print `cat $outfile`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
}
|
||||||
|
# ###########################################################################
|
||||||
|
# End PerconaTest package
|
||||||
|
# ###########################################################################
|
@@ -14,7 +14,7 @@ use Test::More tests => 4;
|
|||||||
use PerconaTest;
|
use PerconaTest;
|
||||||
require "$trunk/bin/pt-fifo-split";
|
require "$trunk/bin/pt-fifo-split";
|
||||||
|
|
||||||
unlink('/tmp/mk-fifo-split');
|
unlink('/tmp/pt-fifo-split');
|
||||||
|
|
||||||
my $cmd = "$trunk/bin/pt-fifo-split";
|
my $cmd = "$trunk/bin/pt-fifo-split";
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ like($output, qr/Options and values/, 'It lives');
|
|||||||
system("($cmd --lines 10000 $trunk/bin/pt-fifo-split > /dev/null 2>&1 < /dev/null)&");
|
system("($cmd --lines 10000 $trunk/bin/pt-fifo-split > /dev/null 2>&1 < /dev/null)&");
|
||||||
sleep(1);
|
sleep(1);
|
||||||
|
|
||||||
open my $fh, '<', '/tmp/mk-fifo-split' or die $OS_ERROR;
|
open my $fh, '<', '/tmp/pt-fifo-split' or die $OS_ERROR;
|
||||||
my $contents = do { local $INPUT_RECORD_SEPARATOR; <$fh>; };
|
my $contents = do { local $INPUT_RECORD_SEPARATOR; <$fh>; };
|
||||||
close $fh;
|
close $fh;
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ ok($contents eq $contents2, 'I read the file');
|
|||||||
system("($cmd $trunk/t/pt-fifo-split/samples/file_with_lines --offset 2 > /dev/null 2>&1 < /dev/null)&");
|
system("($cmd $trunk/t/pt-fifo-split/samples/file_with_lines --offset 2 > /dev/null 2>&1 < /dev/null)&");
|
||||||
sleep(1);
|
sleep(1);
|
||||||
|
|
||||||
open $fh, '<', '/tmp/mk-fifo-split' or die $OS_ERROR;
|
open $fh, '<', '/tmp/pt-fifo-split' or die $OS_ERROR;
|
||||||
$contents = do { local $INPUT_RECORD_SEPARATOR; <$fh>; };
|
$contents = do { local $INPUT_RECORD_SEPARATOR; <$fh>; };
|
||||||
close $fh;
|
close $fh;
|
||||||
|
|
||||||
@@ -53,14 +53,14 @@ EOF
|
|||||||
# #########################################################################
|
# #########################################################################
|
||||||
# Issue 391: Add --pid option to all scripts
|
# Issue 391: Add --pid option to all scripts
|
||||||
# #########################################################################
|
# #########################################################################
|
||||||
`touch /tmp/mk-script.pid`;
|
`touch /tmp/pt-script.pid`;
|
||||||
$output = `$cmd --pid /tmp/mk-script.pid 2>&1`;
|
$output = `$cmd --pid /tmp/pt-script.pid 2>&1`;
|
||||||
like(
|
like(
|
||||||
$output,
|
$output,
|
||||||
qr{PID file /tmp/mk-script.pid already exists},
|
qr{PID file /tmp/pt-script.pid already exists},
|
||||||
'Dies if PID file already exists (issue 391)'
|
'Dies if PID file already exists (issue 391)'
|
||||||
);
|
);
|
||||||
`rm -rf /tmp/mk-script.pid`;
|
`rm -rf /tmp/pt-script.pid`;
|
||||||
|
|
||||||
# #############################################################################
|
# #############################################################################
|
||||||
# Done.
|
# Done.
|
||||||
|
Reference in New Issue
Block a user