Commit | Line | Data |
70350518 |
1 | use strict; |
2 | use warnings; |
3 | |
4 | use Test::More; |
3b7f3eac |
5 | use Test::Exception; |
70350518 |
6 | use lib qw(t/lib); |
7 | use DBICTest; |
8 | |
a47e1233 |
9 | my $schema = DBICTest->init_schema(); |
70350518 |
10 | |
c52c3466 |
11 | plan tests => 64; |
a62cf8d4 |
12 | |
13 | my $code = sub { |
14 | my ($artist, @cd_titles) = @_; |
15 | |
16 | $artist->create_related('cds', { |
17 | title => $_, |
18 | year => 2006, |
19 | }) foreach (@cd_titles); |
20 | |
21 | return $artist->cds->all; |
22 | }; |
23 | |
171dadd7 |
24 | # Test checking of parameters |
25 | { |
171dadd7 |
26 | eval { |
27 | (ref $schema)->txn_do(sub{}); |
28 | }; |
f32eb113 |
29 | like($@, qr/storage/, "can't call txn_do without storage"); |
171dadd7 |
30 | eval { |
31 | $schema->txn_do(''); |
32 | }; |
33 | like($@, qr/must be a CODE reference/, '$coderef parameter check ok'); |
34 | } |
35 | |
a62cf8d4 |
36 | # Test successful txn_do() - scalar context |
37 | { |
57c18b65 |
38 | is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0'); |
39 | |
a62cf8d4 |
40 | my @titles = map {'txn_do test CD ' . $_} (1..5); |
41 | my $artist = $schema->resultset('Artist')->find(1); |
42 | my $count_before = $artist->cds->count; |
43 | my $count_after = $schema->txn_do($code, $artist, @titles); |
44 | is($count_after, $count_before+5, 'successful txn added 5 cds'); |
45 | is($artist->cds({ |
46 | title => "txn_do test CD $_", |
47 | })->first->year, 2006, "new CD $_ year correct") for (1..5); |
57c18b65 |
48 | |
49 | is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset'); |
a62cf8d4 |
50 | } |
51 | |
52 | # Test successful txn_do() - list context |
53 | { |
57c18b65 |
54 | is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0'); |
55 | |
a62cf8d4 |
56 | my @titles = map {'txn_do test CD ' . $_} (6..10); |
57 | my $artist = $schema->resultset('Artist')->find(1); |
58 | my $count_before = $artist->cds->count; |
59 | my @cds = $schema->txn_do($code, $artist, @titles); |
60 | is(scalar @cds, $count_before+5, 'added 5 CDs and returned in list context'); |
61 | is($artist->cds({ |
62 | title => "txn_do test CD $_", |
63 | })->first->year, 2006, "new CD $_ year correct") for (6..10); |
57c18b65 |
64 | |
65 | is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset'); |
a62cf8d4 |
66 | } |
67 | |
68 | # Test nested successful txn_do() |
69 | { |
57c18b65 |
70 | is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0'); |
71 | |
a62cf8d4 |
72 | my $nested_code = sub { |
73 | my ($schema, $artist, $code) = @_; |
74 | |
75 | my @titles1 = map {'nested txn_do test CD ' . $_} (1..5); |
76 | my @titles2 = map {'nested txn_do test CD ' . $_} (6..10); |
77 | |
78 | $schema->txn_do($code, $artist, @titles1); |
79 | $schema->txn_do($code, $artist, @titles2); |
80 | }; |
81 | |
82 | my $artist = $schema->resultset('Artist')->find(2); |
83 | my $count_before = $artist->cds->count; |
84 | |
85 | eval { |
86 | $schema->txn_do($nested_code, $schema, $artist, $code); |
87 | }; |
88 | |
89 | my $error = $@; |
90 | |
91 | ok(!$error, 'nested txn_do succeeded'); |
92 | is($artist->cds({ |
93 | title => 'nested txn_do test CD '.$_, |
94 | })->first->year, 2006, qq{nested txn_do CD$_ year ok}) for (1..10); |
95 | is($artist->cds->count, $count_before+10, 'nested txn_do added all CDs'); |
57c18b65 |
96 | |
97 | is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset'); |
a62cf8d4 |
98 | } |
99 | |
100 | my $fail_code = sub { |
101 | my ($artist) = @_; |
102 | $artist->create_related('cds', { |
103 | title => 'this should not exist', |
104 | year => 2005, |
105 | }); |
106 | die "the sky is falling"; |
107 | }; |
108 | |
109 | # Test failed txn_do() |
110 | { |
57c18b65 |
111 | |
112 | is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0'); |
113 | |
114 | my $artist = $schema->resultset('Artist')->find(3); |
115 | |
116 | eval { |
117 | $schema->txn_do($fail_code, $artist); |
118 | }; |
119 | |
120 | my $error = $@; |
121 | |
122 | like($error, qr/the sky is falling/, 'failed txn_do threw an exception'); |
123 | my $cd = $artist->cds({ |
124 | title => 'this should not exist', |
125 | year => 2005, |
126 | })->first; |
127 | ok(!defined($cd), q{failed txn_do didn't change the cds table}); |
128 | |
129 | is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset'); |
130 | } |
131 | |
132 | # do the same transaction again |
133 | { |
134 | is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0'); |
135 | |
a62cf8d4 |
136 | my $artist = $schema->resultset('Artist')->find(3); |
137 | |
138 | eval { |
139 | $schema->txn_do($fail_code, $artist); |
140 | }; |
141 | |
142 | my $error = $@; |
143 | |
144 | like($error, qr/the sky is falling/, 'failed txn_do threw an exception'); |
145 | my $cd = $artist->cds({ |
146 | title => 'this should not exist', |
147 | year => 2005, |
148 | })->first; |
149 | ok(!defined($cd), q{failed txn_do didn't change the cds table}); |
57c18b65 |
150 | |
151 | is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset'); |
a62cf8d4 |
152 | } |
153 | |
154 | # Test failed txn_do() with failed rollback |
155 | { |
57c18b65 |
156 | is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0'); |
157 | |
a62cf8d4 |
158 | my $artist = $schema->resultset('Artist')->find(3); |
159 | |
160 | # Force txn_rollback() to throw an exception |
161 | no warnings 'redefine'; |
58d387fe |
162 | no strict 'refs'; |
57c18b65 |
163 | |
164 | # die in rollback, but maintain sanity for further tests ... |
165 | local *{"DBIx::Class::Storage::DBI::SQLite::txn_rollback"} = sub{ |
166 | my $storage = shift; |
167 | $storage->{transaction_depth}--; |
168 | die 'FAILED'; |
169 | }; |
a62cf8d4 |
170 | |
171 | eval { |
172 | $schema->txn_do($fail_code, $artist); |
173 | }; |
174 | |
175 | my $error = $@; |
176 | |
177 | like($error, qr/Rollback failed/, 'failed txn_do with a failed '. |
178 | 'txn_rollback threw a rollback exception'); |
179 | like($error, qr/the sky is falling/, 'failed txn_do with a failed '. |
180 | 'txn_rollback included the original exception'); |
181 | |
182 | my $cd = $artist->cds({ |
183 | title => 'this should not exist', |
184 | year => 2005, |
185 | })->first; |
186 | isa_ok($cd, 'DBICTest::CD', q{failed txn_do with a failed txn_rollback }. |
187 | q{changed the cds table}); |
188 | $cd->delete; # Rollback failed |
189 | $cd = $artist->cds({ |
190 | title => 'this should not exist', |
191 | year => 2005, |
192 | })->first; |
193 | ok(!defined($cd), q{deleted the failed txn's cd}); |
57c18b65 |
194 | $schema->storage->_dbh->rollback; |
a62cf8d4 |
195 | } |
196 | |
197 | # Test nested failed txn_do() |
198 | { |
57c18b65 |
199 | is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0'); |
200 | |
a62cf8d4 |
201 | my $nested_fail_code = sub { |
202 | my ($schema, $artist, $code1, $code2) = @_; |
203 | |
204 | my @titles = map {'nested txn_do test CD ' . $_} (1..5); |
205 | |
206 | $schema->txn_do($code1, $artist, @titles); # successful txn |
207 | $schema->txn_do($code2, $artist); # failed txn |
208 | }; |
209 | |
210 | my $artist = $schema->resultset('Artist')->find(3); |
211 | |
212 | eval { |
213 | $schema->txn_do($nested_fail_code, $schema, $artist, $code, $fail_code); |
214 | }; |
215 | |
216 | my $error = $@; |
217 | |
218 | like($error, qr/the sky is falling/, 'nested failed txn_do threw exception'); |
219 | ok(!defined($artist->cds({ |
220 | title => 'nested txn_do test CD '.$_, |
221 | year => 2006, |
222 | })->first), qq{failed txn_do didn't add first txn's cd $_}) for (1..5); |
223 | my $cd = $artist->cds({ |
224 | title => 'this should not exist', |
225 | year => 2005, |
226 | })->first; |
227 | ok(!defined($cd), q{failed txn_do didn't add failed txn's cd}); |
228 | } |
a62cf8d4 |
229 | |
291bf95f |
230 | # Grab a new schema to test txn before connect |
231 | { |
232 | my $schema2 = DBICTest->init_schema(no_deploy => 1); |
233 | eval { |
234 | $schema2->txn_begin(); |
235 | $schema2->txn_begin(); |
236 | }; |
237 | my $err = $@; |
c52c3466 |
238 | ok(! $err, 'Pre-connection nested transactions.'); |
239 | |
240 | # although not connected DBI would still warn about rolling back at disconnect |
241 | $schema2->txn_rollback; |
242 | $schema2->txn_rollback; |
a211cb63 |
243 | $schema2->storage->disconnect; |
291bf95f |
244 | } |
a211cb63 |
245 | $schema->storage->disconnect; |
3b7f3eac |
246 | |
3b7f3eac |
247 | # Test txn_scope_guard |
248 | { |
3b7f3eac |
249 | my $schema = DBICTest->init_schema(); |
250 | |
251 | is($schema->storage->transaction_depth, 0, "Correct transaction depth"); |
252 | my $artist_rs = $schema->resultset('Artist'); |
253 | throws_ok { |
254 | my $guard = $schema->txn_scope_guard; |
255 | |
256 | |
257 | $artist_rs->create({ |
258 | name => 'Death Cab for Cutie', |
259 | made_up_column => 1, |
260 | }); |
261 | |
262 | $guard->commit; |
5ee678c8 |
263 | } qr/No such column made_up_column .*? at .*?81transactions.t line \d+/s, "Error propogated okay"; |
3b7f3eac |
264 | |
265 | ok(!$artist_rs->find({name => 'Death Cab for Cutie'}), "Artist not created"); |
266 | |
267 | my $inner_exception; |
268 | eval { |
269 | outer($schema, 1); |
270 | }; |
271 | is($@, $inner_exception, "Nested exceptions propogated"); |
272 | |
273 | ok(!$artist_rs->find({name => 'Death Cab for Cutie'}), "Artist not created"); |
274 | |
aab0d3b7 |
275 | lives_ok (sub { |
c52c3466 |
276 | my $w; |
277 | local $SIG{__WARN__} = sub { $w = shift }; |
278 | |
279 | # The 0 arg says don't die, just let the scope guard go out of scope |
3b7f3eac |
280 | # forcing a txn_rollback to happen |
281 | outer($schema, 0); |
c52c3466 |
282 | |
283 | like ($w, qr/A DBIx::Class::Storage::TxnScopeGuard went out of scope without explicit commit or an error/, 'Out of scope warning detected'); |
aab0d3b7 |
284 | ok(!$artist_rs->find({name => 'Death Cab for Cutie'}), "Artist not created"); |
285 | }, 'rollback successful withot exception'); |
3b7f3eac |
286 | |
287 | sub outer { |
288 | my ($schema) = @_; |
aab0d3b7 |
289 | |
3b7f3eac |
290 | my $guard = $schema->txn_scope_guard; |
291 | $schema->resultset('Artist')->create({ |
292 | name => 'Death Cab for Cutie', |
293 | }); |
294 | inner(@_); |
3b7f3eac |
295 | } |
296 | |
297 | sub inner { |
298 | my ($schema, $fatal) = @_; |
aab0d3b7 |
299 | |
300 | my $inner_guard = $schema->txn_scope_guard; |
301 | is($schema->storage->transaction_depth, 2, "Correct transaction depth"); |
3b7f3eac |
302 | |
303 | my $artist = $artist_rs->find({ name => 'Death Cab for Cutie' }); |
304 | |
3b7f3eac |
305 | eval { |
306 | $artist->cds->create({ |
307 | title => 'Plans', |
308 | year => 2005, |
309 | $fatal ? ( foo => 'bar' ) : () |
310 | }); |
311 | }; |
312 | if ($@) { |
313 | # Record what got thrown so we can test it propgates out properly. |
314 | $inner_exception = $@; |
315 | die $@; |
316 | } |
317 | |
aab0d3b7 |
318 | # inner guard should commit without consequences |
319 | $inner_guard->commit; |
3b7f3eac |
320 | } |
321 | } |