1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
|
use strict; use warnings;
use Test::More tests => 92;
use lib 't/lib';
use Hook::Guard;
use DBIx::Connector;
my $CLASS = 'DBIx::Connector';
ok my $conn = $CLASS->new( 'dbi:ExampleP:dummy', '', '' ),
'Get a connection';
# Test with no existing dbh.
my $connect_meth = Hook::Guard->new( \*DBIx::Connector::_connect )->prepend(sub {
pass '_connect should be called';
});
ok my $dbh = $conn->dbh, 'Fetch the database handle';
ok $dbh->{AutoCommit}, 'We should not be in a txn';
ok !$conn->in_txn, 'in_txn() should know that, too';
ok !$conn->{_in_run}, '_in_run should be false';
# Set up a DBI mocker.
my $ping = 0;
my $dbh_ping_meth = Hook::Guard->new( \*DBI::db::ping )->replace( sub { ++$ping } );
is $conn->{_dbh}, $dbh, 'The dbh should be stored';
is $ping, 0, 'No pings yet';
ok $conn->connected, 'We should be connected';
is $ping, 1, 'Ping should have been called';
ok $conn->txn( fixup => sub {
is $ping, 1, 'Ping should not have been called before the txn';
ok !shift->{AutoCommit}, 'Inside, we should be in a transaction';
ok $conn->in_txn, 'in_txn() should know it';
ok $conn->{_in_run}, '_in_run should be true';
is $conn->dbh, $dbh, 'Should get same dbh from dbh()';
is $ping, 1, 'ping should not have been called again';
}), 'Do something with no existing handle';
$connect_meth->restore;
ok !$conn->{_in_run}, '_in_run should be false again';
ok $dbh->{AutoCommit}, 'Transaction should be committed';
ok !$conn->in_txn, 'And in_txn() should know it';
# Test with instantiated dbh.
is $conn->{_dbh}, $dbh, 'The dbh should be stored';
ok $conn->connected, 'We should be connected';
ok $conn->txn( fixup => sub {
my $dbha = shift;
is $dbha, $dbh, 'The handle should have been passed';
is $_, $dbh, 'It should also be in $_';
is $_, $dbh, 'Should have dbh in $_';
$ping = 0;
is $conn->dbh, $dbh, 'Should get same dbh from dbh()';
is $ping, 0, 'Should have been no ping';
ok !$dbha->{AutoCommit}, 'We should be in a transaction';
ok $conn->in_txn, 'in_txn() should know about that';
}), 'Do something with stored handle';
ok $dbh->{AutoCommit}, 'New transaction should be committed';
ok !$conn->in_txn, 'And in_txn() should know it';
# Test the return value.
ok my $foo = $conn->txn( fixup => sub {
return (2, 3, 5);
}), 'Do in scalar context';
is $foo, 5, 'The return value should be the last value';
ok my @foo = $conn->txn( fixup => sub {
return (2, 3, 5);
}), 'Do in array context';
is_deeply \@foo, [2, 3, 5], 'The return value should be the list';
# Test an exception.
eval { $conn->txn( fixup => sub { die 'WTF?' }) };
ok $@, 'We should have died';
ok $dbh->{AutoCommit}, 'New transaction should rolled back';
ok !$conn->in_txn, 'And in_txn() should know it';
# Test a disconnect.
my $die = 1;
my $calls;
$conn->txn( fixup => sub {
my $dbha = shift;
ok !$dbha->{AutoCommit}, 'We should be in a transaction';
ok $conn->in_txn, 'in_txn() should know it';
$calls++;
if ($die) {
is $dbha, $dbh, 'Should have the stored dbh';
is $_, $dbh, 'It should also be in $_';
$ping = 0;
is $conn->dbh, $dbh, 'Should get same dbh from dbh()';
is $ping, 0, 'Should have been no ping';
$die = 0;
$dbha->{Active} = 0;
ok !$dbha->{Active}, 'Disconnect';
die 'WTF?';
}
isnt $dbha, $dbh, 'Should have new dbh';
});
ok $dbh = $conn->dbh, 'Get the new handle';
ok $dbh->{AutoCommit}, 'New transaction should be committed';
ok !$conn->in_txn, 'And in_txn() should know it';
is $calls, 2, 'Sub should have been called twice';
# Test disconnect and die.
$calls = 0;
eval {
$conn->txn( fixup => sub {
my $dbha = shift;
ok !$dbha->{AutoCommit}, 'We should be in a transaction';
ok $conn->in_txn, 'in_txn() should know it';
$dbha->{Active} = 0;
if ($calls++) {
die 'OMGWTF?';
} else {
is $dbha, $dbh, 'Should have the stored dbh again';
is $_, $dbh, 'It should also be in $_';
die 'Disconnected';
}
});
};
ok my $err = $@, 'We should have died';
like $@, qr/OMGWTF[?]/, 'We should have killed ourselves';
is $calls, 2, 'Sub should have been called twice';
# Make sure nested calls work.
$conn->txn( fixup => sub {
my $dbh = shift;
ok !$dbh->{AutoCommit}, 'We should be in a txn';
ok $conn->in_txn, 'in_txn() should know it';
local $dbh->{Active} = 0;
$conn->txn( fixup => sub {
isnt shift, $dbh, 'Nested txn_fixup_run should not get inactive dbh';
ok !$dbh->{AutoCommit}, 'Nested txn_fixup_run should be in the txn';
ok $conn->in_txn, 'in_txn() should know it';
});
});
# Make sure that it does nothing transactional if we've started the
# transaction.
$dbh = $conn->dbh;
my $driver = $conn->driver;
$driver->begin_work($dbh);
ok !$dbh->{AutoCommit}, 'Transaction should be started';
ok $conn->in_txn, 'And in_txn() should know it';
$conn->txn( fixup => sub {
my $dbha = shift;
is $dbha, $dbh, 'We should have the same database handle';
is $_, $dbh, 'It should also be in $_';
$ping = 0;
is $conn->dbh, $dbh, 'Should get same dbh from dbh()';
is $ping, 0, 'Should have been no ping';
ok !$dbha->{AutoCommit}, 'Transaction should still be going';
ok $conn->in_txn, 'in_txn() should know that';
});
ok !$dbh->{AutoCommit}, 'Transaction should stil be live after txn_fixup_run';
$driver->rollback($dbh);
# Make sure nested calls when ping returns false.
$conn->txn( fixup => sub {
my $dbh = shift;
ok !$dbh->{AutoCommit}, 'We should be in a txn';
ok $conn->in_txn, 'in_txn() should know it';
$dbh_ping_meth->replace( sub { 0 } );
$conn->txn( fixup => sub {
is shift, $dbh, 'Nested txn_fixup_run should get same dbh, even though inactive';
ok !$dbh->{AutoCommit}, 'Nested txn_fixup_run should be in the txn';
ok $conn->in_txn, 'in_txn() should know it';
});
});
# Have the rollback die.
my $dbh_begin_work_meth = Hook::Guard->new( \*DBI::db::begin_work )->replace( sub { return } );
my $dbh_rollback_meth = Hook::Guard->new( \*DBI::db::rollback )->replace( sub { die 'Rollback WTF' } );
eval { $conn->txn(sub {
die 'Transaction WTF';
}) };
ok $err = $@, 'We should have died';
isa_ok $err, 'DBIx::Connector::TxnRollbackError', 'The exception';
like $err, qr/Transaction aborted: Transaction WTF/, 'Should have the transaction error';
like $err, qr/Transaction rollback failed: Rollback WTF/, 'Should have the rollback error';
like $err->rollback_error, qr/Rollback WTF/, 'Should have rollback error';
like $err->error, qr/Transaction WTF/, 'Should have transaction error';
# Try a nested transaction.
eval { $conn->txn(sub {
local $_->{AutoCommit} = 0;
$conn->txn(sub { die 'Nested WTF' });
}) };
ok $err = $@, 'We should have died again';
isa_ok $err, 'DBIx::Connector::TxnRollbackError', 'The exception';
like $err->rollback_error, qr/Rollback WTF/, 'Should have rollback error';
like $err->error, qr/Nested WTF/, 'Should have nested transaction error';
ok !ref $err->error, 'The nested error should not be an object';
|