1
# This program is copyright 2009-2011 Percona Inc.
2
# Feedback and improvements are welcome.
4
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
5
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
6
# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
8
# This program is free software; you can redistribute it and/or modify it under
9
# the terms of the GNU General Public License as published by the Free Software
10
# Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
11
# systems, you can issue `man perlgpl' or `man perlartistic' to read these
14
# You should have received a copy of the GNU General Public License along with
15
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
16
# Place, Suite 330, Boston, MA 02111-1307 USA.
17
# ###########################################################################
18
# MaatkitTest package $Revision: 7096 $
19
# ###########################################################################
21
# Package: MaatkitTest
22
# MaatkitTest is a collection of helper-subs for the Maatkit tests.
23
# Any file arguments (like no_diff() $expected_output) are relative to
24
# PERCONA_TOOLKIT_BRANCH. So passing "commont/t/samples/foo" means
25
# "PERCONA_TOOLKIT_BRANCH/common/t/samples/foo". Do not BAIL_OUT() because
26
# this terminates the *entire* test process; die instead. All
27
# subs are exported by default, so is the variable $trunk, so there's
28
# no need to import() in the test scripts.
33
use warnings FATAL => 'all';
34
use English qw(-no_match_vars);
35
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
38
use Time::HiRes qw(usleep);
39
use POSIX qw(signal_h);
41
$Data::Dumper::Indent = 1;
42
$Data::Dumper::Sortkeys = 1;
43
$Data::Dumper::Quotekeys = 0;
46
our @ISA = qw(Exporter);
47
our %EXPORT_TAGS = ();
48
our @EXPORT_OK = qw();
67
our $trunk = $ENV{PERCONA_TOOLKIT_BRANCH};
69
our $sandbox_version = '';
71
chomp(my $v = `$trunk/sandbox/test-env version`);
72
$sandbox_version = $v if $v;
78
desc => 'Default character set',
84
desc => 'Database to use',
90
desc => 'Only read default options from the given file',
91
dsn => 'mysql_read_default_file',
96
desc => 'Connect to host',
102
desc => 'Password to use when connecting',
108
desc => 'Port number to use for connection',
114
desc => 'Socket file to use for connection',
115
dsn => 'mysql_socket',
126
desc => 'User for login if not current user',
132
# Runs code, captures and returns its output.
133
# Optional arguments:
134
# * file scalar: capture output to this file (default none)
135
# * stderr scalar: capture STDERR (default no)
136
# * die scalar: die if code dies (default no)
137
# * trf coderef: pass output to this coderef (default none)
139
my ( $code, %args ) = @_;
140
die "I need a code argument" unless $code;
141
my ($file, $stderr, $die, $trf) = @args{qw(file stderr die trf)};
145
open *output_fh, '>', $file
146
or die "Cannot open file $file: $OS_ERROR";
149
open *output_fh, '>', \$output
150
or die "Cannot capture output to variable: $OS_ERROR";
152
local *STDOUT = *output_fh;
154
# If capturing STDERR we must dynamically scope (local) STDERR
155
# in the outer scope of the sub. If we did,
156
# if ( $args{stderr} ) { local *STDERR; ... }
157
# then STDERR would revert to its original value outside the if
159
local *STDERR if $args{stderr}; # do in outer scope of this sub
160
*STDERR = *STDOUT if $args{stderr};
165
die $EVAL_ERROR if $die;
169
# Possible transform output before returning it. This doesn't work
170
# if output was captured to a file.
171
$output = $trf->($output) if $trf;
176
# Load data from file and removes spaces. Used to load tcpdump dumps.
179
$file = "$trunk/$file";
180
open my $fh, '<', $file or die "Cannot open $file: $OS_ERROR";
181
my $contents = do { local $/ = undef; <$fh> };
183
(my $data = join('', $contents =~ m/(.*)/g)) =~ s/\s+//g;
187
# Slurp file and return its entire contents.
189
my ( $file, %args ) = @_;
190
$file = "$trunk/$file";
191
open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
192
my $contents = do { local $/ = undef; <$fh> };
194
chomp $contents if $args{chomp_contents};
199
my ( $file, $p, $ea ) = @_;
200
$file = "$trunk/$file";
203
open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
205
next_event => sub { return <$fh>; },
206
tell => sub { return tell $fh; },
209
while ( my $e = $p->parse_event(%args) ) {
211
$ea->aggregate($e) if $ea;
215
die $EVAL_ERROR if $EVAL_ERROR;
219
# Wait until code returns true.
221
my ( $code, $t, $max_t ) = @_;
223
my $sleep_int = $t || .5;
227
while ( $slept <= $max_t ) {
230
$slept += $sleep_int;
235
# Wait t seconds for code to return.
237
my ( $code, $t ) = @_;
239
my $mask = POSIX::SigSet->new(&POSIX::SIGALRM);
240
my $action = POSIX::SigAction->new(
244
my $oldaction = POSIX::SigAction->new();
245
sigaction(&POSIX::SIGALRM, $action, $oldaction);
263
sub test_log_parser {
265
foreach my $arg ( qw(parser file) ) {
266
die "I need a $arg argument" unless $args{$arg};
268
my $p = $args{parser};
270
# Make sure caller isn't giving us something we don't understand.
271
# We could ignore it, but then caller might not get the results
273
map { die "What is $_ for?"; }
274
grep { $_ !~ m/^(?:parser|misc|file|result|num_events|oktorun)$/ }
277
my $file = "$trunk/$args{file}";
280
open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
282
next_event => sub { return _read($fh); },
283
tell => sub { return tell($fh); },
286
oktorun => $args{oktorun},
288
while ( my $e = $p->parse_event(%parser_args) ) {
297
"No error on $args{file}"
300
if ( defined $args{result} ) {
305
) or print "Got: ", Dumper(\@e);
308
if ( defined $args{num_events} ) {
312
"$args{file} num_events"
319
sub test_protocol_parser {
321
foreach my $arg ( qw(parser protocol file) ) {
322
die "I need a $arg argument" unless $args{$arg};
324
my $parser = $args{parser};
325
my $protocol = $args{protocol};
327
# Make sure caller isn't giving us something we don't understand.
328
# We could ignore it, but then caller might not get the results
330
map { die "What is $_ for?"; }
331
grep { $_ !~ m/^(?:parser|protocol|misc|file|result|num_events|desc)$/ }
334
my $file = "$trunk/$args{file}";
337
open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
339
next_event => sub { return _read($fh); },
340
tell => sub { return tell($fh); },
343
while ( my $p = $parser->parse_event(%parser_args) ) {
344
my $e = $protocol->parse_event(%parser_args, event => $p);
353
"No error on $args{file}"
356
if ( defined $args{result} ) {
360
$args{file} . ($args{desc} ? ": $args{desc}" : '')
361
) or print "Got: ", Dumper(\@e);
364
if ( defined $args{num_events} ) {
368
"$args{file} num_events"
375
sub test_packet_parser {
377
foreach my $arg ( qw(parser file) ) {
378
die "I need a $arg argument" unless $args{$arg};
380
my $parser = $args{parser};
382
# Make sure caller isn't giving us something we don't understand.
383
# We could ignore it, but then caller might not get the results
385
map { die "What is $_ for?"; }
386
grep { $_ !~ m/^(?:parser|misc|file|result|desc|oktorun)$/ }
389
my $file = "$trunk/$args{file}";
391
open my $fh, '<', $file or die "Cannot open $file: $OS_ERROR";
393
next_event => sub { return _read($fh); },
394
tell => sub { return tell($fh); },
396
oktorun => $args{oktorun},
398
while ( my $packet = $parser->parse_event(%parser_args) ) {
399
push @packets, $packet;
402
# raw_packet is the actual dump text from the file. It's used
403
# in MySQLProtocolParser but I don't think we need to double-check
404
# it here. It will make the results very long.
405
foreach my $packet ( @packets ) {
406
delete $packet->{raw_packet};
412
"$args{file}" . ($args{desc} ? ": $args{desc}" : '')
414
print Dumper(\@packets);
420
# no_diff() compares the STDOUT output of a cmd or code to expected output.
421
# Returns true if there are no differences between the two outputs,
422
# else returns false. Dies if the cmd/code dies. Does not capture STDERR.
424
# * cmd scalar or coderef: if cmd is a scalar then the
425
# cmd is ran via the shell. if it's a coderef then
426
# the code is ran. the latter is preferred because
427
# it generates test coverage.
428
# * expected_output scalar: file name relative to PERCONA_TOOLKIT_BRANCH
429
# * args hash: (optional) may include
430
# update_sample overwrite expected_output with cmd/code output
431
# keep_output keep last cmd/code output file
432
# * trf transform cmd/code output before diff
433
# The sub dies if cmd or code dies. STDERR is not captured.
435
my ( $cmd, $expected_output, %args ) = @_;
436
die "I need a cmd argument" unless $cmd;
437
die "I need an expected_output argument" unless $expected_output;
439
$expected_output = "$trunk/$expected_output";
440
die "$expected_output does not exist" unless -f $expected_output;
442
my $tmp_file = '/tmp/maatkit-test-output.txt';
443
my $tmp_file_orig = '/tmp/maatkit-test-output-original.txt';
445
# Determine cmd type and run it.
446
if ( ref $cmd eq 'CODE' ) {
447
output($cmd, file => $tmp_file);
449
elsif ( $args{cmd_output} ) {
450
# Copy cmd output to tmp file so we don't with the original.
451
open my $tmp_fh, '>', $tmp_file or die "Cannot open $tmp_file: $OS_ERROR";
459
# Do optional arg stuff.
460
`cp $tmp_file $tmp_file_orig`;
461
if ( my $trf = $args{trf} ) {
462
`$trf $tmp_file_orig > $tmp_file`;
464
if ( my $sed = $args{sed} ) {
465
foreach my $sed_args ( @{$args{sed}} ) {
466
`sed $sed_args $tmp_file`;
471
my $retval = system("diff $tmp_file $expected_output");
473
# diff returns 0 if there were no differences,
474
# so !0 = 1 = no diff in our testing parlance.
475
$retval = $retval >> 8;
478
if ( $ENV{UPDATE_SAMPLES} || $args{update_sample} ) {
479
`cat $tmp_file > $expected_output`;
480
print STDERR "Updated $expected_output\n";
484
# Remove our tmp files.
485
`rm -f $tmp_file $tmp_file_orig`
486
unless $ENV{KEEP_OUTPUT} || $args{keep_output};
492
my ( $code, $pat, $msg ) = @_;
494
like ( $EVAL_ERROR, $pat, $msg );
497
# Remove /*maatkit ...*/ trace comments from the given SQL statement(s).
498
# Traces are added in ChangeHandler::process_rows().
501
my $trace_pat = qr/ \/\*maatkit .+?\*\//;
502
if ( ref $sql && ref $sql eq 'ARRAY' ) {
503
map { $_ =~ s/$trace_pat//gm } @$sql;
506
$sql =~ s/$trace_pat//gm;
513
# ###########################################################################
514
# End MaatkitTest package
515
# ###########################################################################