File: txn.t

package info (click to toggle)
libdbix-connector-perl 0.60-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 284 kB
  • sloc: perl: 582; makefile: 2
file content (206 lines) | stat: -rw-r--r-- 7,235 bytes parent folder | download
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
199
200
201
202
203
204
205
206
use strict; use warnings;

use Test::More tests => 93;
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(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, 'We should be in a txn';
    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, '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(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()';
    $ping = 1;
    ok !$dbha->{AutoCommit}, 'We should be in a transaction';
    ok $conn->in_txn, 'in_txn() should know about it';
}), 'Do something with stored handle';
ok $dbh->{AutoCommit}, 'New transaction should be committed';
ok !$conn->in_txn, 'in_txn() should know it, too';

# Test the return value.
ok my $foo = $conn->txn(sub {
    return (2, 3, 5);
}), 'Do in scalar context';
is $foo, 5, 'The return value should be the last value';

ok $foo = $conn->txn(sub {
    return wantarray ?  (2, 3, 5) : 'scalar';
}), 'Do in scalar context';
is $foo, 'scalar', 'Callback should know when its context is scalar';

ok my @foo = $conn->txn(sub {
    return (2, 3, 5);
}), 'Do in array context';
is_deeply \@foo, [2, 3, 5], 'The return value should be the list';

ok @foo = $conn->txn(sub {
    return wantarray ?  (2, 3, 5) : 'scalar';
}), 'Do in scalar context';
is_deeply \@foo, [2, 3, 5], 'Callback should know when its context is list';

# Test an exception.
eval {  $conn->txn(sub { die 'WTF?' }) };
ok $@, 'We should have died';
ok $dbh->{AutoCommit}, 'New transaction should rolled back';
ok !$conn->in_txn, 'in_txn() should know that';

# Make sure nested calls work.
$conn->txn(sub {
    my $dbh = shift;
    ok !$dbh->{AutoCommit}, 'We should be in a txn';
    ok $conn->in_txn, 'in_txn() should know about it';
    local $dbh->{Active} = 0;
    $conn->txn(sub {
        isnt shift, $dbh, 'Nested txn should not get inactive dbh';
        ok !$dbh->{AutoCommit}, 'Nested txn 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, 'in_txn() should know it';
$conn->txn(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()';
    $ping = 1;
    ok !$dbha->{AutoCommit}, 'Transaction should still be going';
    ok $conn->in_txn, 'in_txn() should know it';
});
ok !$dbh->{AutoCommit}, 'Transaction should stil be live after txn';
ok $conn->in_txn, 'in_txn() should know it';
$driver->rollback($dbh);

# Make sure nested calls when ping returns false.
$conn->txn(sub {
    my $dbh = shift;
    ok !$dbh->{AutoCommit}, 'We should be in a txn';
    ok $conn->in_txn, 'in_txn() should know that, too';
    $dbh_ping_meth->replace( sub { 0 } );
    $conn->txn(sub {
        is shift, $dbh, 'Nested txn should get same dbh, even though inactive';
        ok !$dbh->{AutoCommit}, 'Nested txn should be in the txn';
    ok $conn->in_txn, 'in_txn() should know that, too';
    });
});

# Test mode.
$conn->txn(sub {
    is $conn->mode, 'no_ping', 'Default mode should be no_ping';
});

$conn->txn(ping => sub {
    is $conn->mode, 'ping', 'Mode should be "ping" inside ping txn'
});
is $conn->mode, 'no_ping', 'Back outside, should be "no_ping" again';

$conn->txn(fixup => sub {
    is $conn->mode, 'fixup', 'Mode should be "fixup" inside fixup txn'
});
is $conn->mode, 'no_ping', 'Back outside, should be "no_ping" again';

ok $conn->mode('ping'), 'Se mode to "ping"';
$conn->txn(sub {
    is $conn->mode, 'ping', 'Mode should implicitly be "ping"'
});

ok $conn->mode('fixup'), 'Se mode to "fixup"';
$conn->txn(sub {
    is $conn->mode, 'fixup', 'Mode should implicitly be "fixup"'
});

NOEXIT: {
    no warnings;

    my $begin_work_meth = Hook::Guard->new( \*DBIx::Connector::Driver::begin_work )->replace( sub { shift } );
    my $keyword;
    my $commit_meth = Hook::Guard->new( \*DBIx::Connector::Driver::commit )->replace( sub {
        pass "Commit should be called when returning via $keyword"
    });

    # Make sure we don't exit the app via `next` or `last`.
    for my $mode (qw(ping no_ping fixup)) {
        $conn->mode($mode);

        $keyword = 'next';
        ok !$conn->txn(sub { next }), "Return via $keyword should fail";

        $keyword = 'last';
        ok !$conn->txn(sub { last }), "Return via $keyword should fail";
    }
}

# 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 my $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';