#!/usr/bin/perl BEGIN { my $server_pid; eval { require DBIx::MyServer; require DBIx::MyServer::DBI; }; if ($@) { require Test::More; import Test::More skip_all => 'these tests require DBIx::MyServer'; exit 0; } } use List::Util qw( sum ); use Cwd qw( abs_path ); use File::Basename qw( dirname ); use AnyEvent::DBI; my $topdir = dirname(abs_path($0)); # fork off a child to be a mysql server my $server_pid = fork; if (! $server_pid ) { exec "$^X $topdir/fake-mysql --config $topdir/myserver.conf"; die 'exec failed'; } # the parent is the test script eval { require Test::More; import Test::More tests => 34; }; if ($@) { print 'ok 1 # skip this test requires Test::More'."\n"; exit 0; } # wait for server sleep 1; my $cv = AnyEvent->condvar; my $dbh = new AnyEvent::DBI( "dbi:mysql:database=database;host=127.0.0.1;port=23306",'','', PrintError => 0, timeout => 2, on_error => sub { }, on_connect => sub { if (! $_[0]) { $cv->send($@); } else { $cv->send(); } }, ); my $connect_error = $cv->recv(); is($connect_error,undef,'on_connect() called without error, fake mysql server is connected'); # issue a query $cv = AnyEvent->condvar; $dbh->exec ( "select a,b,c from rows14 where num=?", 10, sub { my ($dbh,$rows, $metadata) = @_; if (! $dbh) { $cv->send($@); } else { $cv->send(undef,$rows); } } ); my ($error, $rows) = $cv->recv(); #print "@$_\n" for @$rows; is($error,undef,'query returns no errors'); is(scalar @$rows,14,'query found 14 rows'); is(scalar @{$$rows[0]},3,'first row has 3 data'); # issue a query that returns an error $cv = AnyEvent->condvar; $dbh->exec ( "select a,b,c from nosuchtable", sub { my ($dbh,$rows, $metadata) = @_; if (! $dbh) { $cv->send($@); } else { $cv->send(undef,$rows); } } ); ($error, $rows) = $cv->recv(); is($error,qq{Table 'database.nosuchtable' doesn't exist},'SELECT on non-existant table returns NONFATAL error'); # good query after bad $cv = AnyEvent->condvar; $dbh->exec ( "select a,b,c from rows14 where num=?", 10, sub { my ($dbh,$rows, $metadata) = @_; if (! $dbh) { $cv->send($@); } else { $cv->send(undef,$rows); } } ); ($error, $rows) = $cv->recv(); #print "@$_\n" for @$rows; is($error,undef,'good query after bad returns no errors'); is(scalar @$rows,14,'query found 14 rows'); is(scalar @{$$rows[0]},3,'first row has 3 data'); ############################################################################ # enque a series of alternating good/bad queries $cv = AnyEvent->condvar; my @results = (); my $num_qry = 0; my $qrydone = sub { my ($dbh,$rows,$metadata) = @_; my $err = undef; if (! $dbh) { $err = $@; } push @results , [$err,$rows]; if (scalar @results == $num_qry) { $cv->send(); } }; $dbh->exec ("select a,b,c from nosuchtable1", $qrydone); $num_qry++; $dbh->exec ("select a,b,c from rows1" , $qrydone); $num_qry++; $dbh->exec ("select a,b,c from nosuchtable2", $qrydone); $num_qry++; $dbh->exec ("select a,b,c from rows2" , $qrydone); $num_qry++; $dbh->exec ("select a,b,c from nosuchtable3", $qrydone); $num_qry++; $dbh->exec ("select a,b,c from rows3" , $qrydone); $num_qry++; $dbh->exec ("select a,b,c from nosuchtable4", $qrydone); $num_qry++; $dbh->exec ("select a,b,c from rows4" , $qrydone); $num_qry++; $cv->recv(); for my $r (0..$num_qry-1) { my $offset = int($r / 2 )+1; if ($r % 2) { ok(! defined $results[$r]->[0],'Multi Query Queue: No error on good queries'); is(scalar @{$results[$r]->[1]},$offset,'Multi Query Queue: Good query got right number of rows'); } else { is( $results[$r]->[0], qq{Table 'database.nosuchtable$offset' doesn't exist},'Multi Query Queue: Bad query gets correct error' ); } } ############################################################################ # try to connect to a closed port # NOTE tcp port 9 is 'discard', hopefully not running $cv = AnyEvent->condvar; my $dbh2 = new AnyEvent::DBI( "dbi:mysql:database=test;host=127.0.0.1;port=9",'','', PrintError => 0, timeout => 3, on_error => sub { }, on_connect => sub { if (! $_[0]) { $cv->send($@); } else { $cv->send(); } }, ); $connect_error = $cv->recv(); like($connect_error,qr{can't connect}i,'mysql connect to localhost:9 refused'); # try to connect to a firewalled port $cv = AnyEvent->condvar; $dbh2 = new AnyEvent::DBI( "dbi:mysql:database=test;host=www.google.com;port=23306",'','', timeout => 3, on_error => sub { }, on_connect => sub { if (! $_[0]) { $cv->send($@); } else { $cv->send(); } }, ); $connect_error = $cv->recv(); is($connect_error,'TIMEOUT','mysql connect to google port 23306 times out'); undef $dbh2; # issue a query which times out $cv = AnyEvent->condvar; $dbh->exec ( "select a,b,c from delay10 where num=?", 10, sub { my ($dbh,$rows, $metadata) = @_; if (! $dbh) { $cv->send($@); } else { $cv->send(undef,$rows); } } ); ($error,$rows) = $cv->recv(); is($error,'TIMEOUT','timeout fires during long-running query'); # issue a query after a fatal timeout error $cv = AnyEvent->condvar; my $start = AnyEvent->now; my $run = 10; my $ran = 0; my $fin = 0; my $errs = []; while ($ran++ < $run) { $dbh->exec ( "select d,e,f,g from rows5 where num=?", 10, sub { my ($dbh,$rows, $metadata) = @_; if (! $dbh) { push @$errs, $@; } if (++$fin == $run) { $cv->send(); } } ); } $cv->recv(); ok(AnyEvent->now -$start < 0.0001,'invalid db handle returns from multiple queries immediately'); is (scalar @$errs, 10, 'invalid db handle returned error for all enqueued queries'); is($errs->[0],'NO DATABASE CONNECTION','invalid db handle returns correct error'); undef $dbh; # check for server process leakage eval { require Proc::Exists; import Proc::Exists qw(pexists); }; my $has_pe = ! $@; SKIP: { skip ( 'This test requires Proc::Exists',4) unless $has_pe; # connect three handles $cv = AnyEvent->condvar; my @handles; my @handle_errors; my $connected =0; for (0..2) { my $dbh3 = new AnyEvent::DBI( "dbi:mysql:database=database;host=127.0.0.1;port=23306",'','', PrintError => 0, timeout => 2, on_error => sub { }, on_connect => sub { if (! $_[0]) { push @handle_errors, $@; } if (++$connected == 3) { $cv->send(); } }, ); push @handles, $dbh3; } $cv->recv(); is(scalar @handles,3,'created three handles'); is(scalar @handle_errors,0,'no errors during handle creation'); my @pids = map {$_->_server_pid} @handles; ok( defined pexists(@pids, {all=>1}),'Found three start_serve processes'); undef @handles; $cv = AnyEvent->condvar; my $cleanup = AnyEvent->timer(after=>0.5,cb=>sub {$cv->send()}); $cv->recv(); ok(!defined pexists(@pids, {any=>1}),'All start_serve processes exited'); } # connect to the server again $cv = AnyEvent->condvar; $dbh = new AnyEvent::DBI( "dbi:mysql:database=database;host=127.0.0.1;port=23306",'','', PrintError => 0, timeout => 2, on_error => sub { }, on_connect => sub { if (! $_[0]) { $cv->send($@); } else { $cv->send(); } }, ); $connect_error = $cv->recv(); is($connect_error,undef,'on_connect() called without error, fake mysql server is re-connected'); # End the server and reap it $cv = AnyEvent->condvar; my $server_process_watcher = AnyEvent->child( pid => $server_pid , cb => sub { $cv->send(@_); } ); kill 2, $server_pid; my ($dead_pid,$dead_status)=$cv->recv(); is ($dead_pid,$server_pid,'MySQL Server processess exited'); is ($dead_status,2,'Server exited on our signal'); sleep 2; # give all mysql server children a chance to exit # try to make another query with a down MYSQL server # issue a query $cv = AnyEvent->condvar; $dbh->exec ( "select x from rows1 where num=?", 10, sub { my ($dbh,$rows, $metadata) = @_; if (! $dbh) { $cv->send($@); } else { $cv->send(undef,$rows); } } ); ($error, $rows) = $cv->recv(); is($error,'TIMEOUT','mysql query to dead server times out'); undef $dbh; END { if ($server_pid) { # shut down the fake_mysql server delete $SIG{CLD}; kill 15, $server_pid; waitpid $server_pid,0; } exit 0; }