Commit | Line | Data |
70350518 |
1 | use strict; |
d7ded411 |
2 | use warnings; |
70350518 |
3 | |
4 | use Test::More; |
d7ded411 |
5 | use Test::Warn; |
3b7f3eac |
6 | use Test::Exception; |
70350518 |
7 | use lib qw(t/lib); |
8 | use DBICTest; |
9 | |
a47e1233 |
10 | my $schema = DBICTest->init_schema(); |
70350518 |
11 | |
a62cf8d4 |
12 | my $code = sub { |
13 | my ($artist, @cd_titles) = @_; |
d7ded411 |
14 | |
a62cf8d4 |
15 | $artist->create_related('cds', { |
16 | title => $_, |
17 | year => 2006, |
18 | }) foreach (@cd_titles); |
d7ded411 |
19 | |
a62cf8d4 |
20 | return $artist->cds->all; |
21 | }; |
22 | |
171dadd7 |
23 | # Test checking of parameters |
24 | { |
dd7d4b43 |
25 | throws_ok (sub { |
171dadd7 |
26 | (ref $schema)->txn_do(sub{}); |
dd7d4b43 |
27 | }, qr/storage/, "can't call txn_do without storage"); |
28 | |
29 | throws_ok ( sub { |
171dadd7 |
30 | $schema->txn_do(''); |
dd7d4b43 |
31 | }, qr/must be a CODE reference/, '$coderef parameter check ok'); |
171dadd7 |
32 | } |
33 | |
a62cf8d4 |
34 | # Test successful txn_do() - scalar context |
35 | { |
57c18b65 |
36 | is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0'); |
37 | |
a62cf8d4 |
38 | my @titles = map {'txn_do test CD ' . $_} (1..5); |
39 | my $artist = $schema->resultset('Artist')->find(1); |
40 | my $count_before = $artist->cds->count; |
41 | my $count_after = $schema->txn_do($code, $artist, @titles); |
42 | is($count_after, $count_before+5, 'successful txn added 5 cds'); |
43 | is($artist->cds({ |
44 | title => "txn_do test CD $_", |
45 | })->first->year, 2006, "new CD $_ year correct") for (1..5); |
57c18b65 |
46 | |
47 | is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset'); |
a62cf8d4 |
48 | } |
49 | |
50 | # Test successful txn_do() - list context |
51 | { |
57c18b65 |
52 | is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0'); |
53 | |
a62cf8d4 |
54 | my @titles = map {'txn_do test CD ' . $_} (6..10); |
55 | my $artist = $schema->resultset('Artist')->find(1); |
56 | my $count_before = $artist->cds->count; |
57 | my @cds = $schema->txn_do($code, $artist, @titles); |
58 | is(scalar @cds, $count_before+5, 'added 5 CDs and returned in list context'); |
59 | is($artist->cds({ |
60 | title => "txn_do test CD $_", |
61 | })->first->year, 2006, "new CD $_ year correct") for (6..10); |
57c18b65 |
62 | |
63 | is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset'); |
a62cf8d4 |
64 | } |
65 | |
66 | # Test nested successful txn_do() |
67 | { |
57c18b65 |
68 | is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0'); |
69 | |
a62cf8d4 |
70 | my $nested_code = sub { |
71 | my ($schema, $artist, $code) = @_; |
72 | |
73 | my @titles1 = map {'nested txn_do test CD ' . $_} (1..5); |
74 | my @titles2 = map {'nested txn_do test CD ' . $_} (6..10); |
75 | |
76 | $schema->txn_do($code, $artist, @titles1); |
77 | $schema->txn_do($code, $artist, @titles2); |
78 | }; |
79 | |
80 | my $artist = $schema->resultset('Artist')->find(2); |
81 | my $count_before = $artist->cds->count; |
82 | |
dd7d4b43 |
83 | lives_ok (sub { |
a62cf8d4 |
84 | $schema->txn_do($nested_code, $schema, $artist, $code); |
dd7d4b43 |
85 | }, 'nested txn_do succeeded'); |
a62cf8d4 |
86 | |
a62cf8d4 |
87 | is($artist->cds({ |
88 | title => 'nested txn_do test CD '.$_, |
89 | })->first->year, 2006, qq{nested txn_do CD$_ year ok}) for (1..10); |
90 | is($artist->cds->count, $count_before+10, 'nested txn_do added all CDs'); |
57c18b65 |
91 | |
92 | is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset'); |
a62cf8d4 |
93 | } |
94 | |
95 | my $fail_code = sub { |
96 | my ($artist) = @_; |
97 | $artist->create_related('cds', { |
98 | title => 'this should not exist', |
99 | year => 2005, |
100 | }); |
101 | die "the sky is falling"; |
102 | }; |
103 | |
104 | # Test failed txn_do() |
105 | { |
57c18b65 |
106 | |
107 | is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0'); |
108 | |
109 | my $artist = $schema->resultset('Artist')->find(3); |
110 | |
dd7d4b43 |
111 | throws_ok (sub { |
57c18b65 |
112 | $schema->txn_do($fail_code, $artist); |
dd7d4b43 |
113 | }, qr/the sky is falling/, 'failed txn_do threw an exception'); |
57c18b65 |
114 | |
57c18b65 |
115 | my $cd = $artist->cds({ |
116 | title => 'this should not exist', |
117 | year => 2005, |
118 | })->first; |
119 | ok(!defined($cd), q{failed txn_do didn't change the cds table}); |
120 | |
121 | is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset'); |
122 | } |
123 | |
124 | # do the same transaction again |
125 | { |
126 | is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0'); |
127 | |
a62cf8d4 |
128 | my $artist = $schema->resultset('Artist')->find(3); |
129 | |
dd7d4b43 |
130 | throws_ok (sub { |
a62cf8d4 |
131 | $schema->txn_do($fail_code, $artist); |
dd7d4b43 |
132 | }, qr/the sky is falling/, 'failed txn_do threw an exception'); |
a62cf8d4 |
133 | |
a62cf8d4 |
134 | my $cd = $artist->cds({ |
135 | title => 'this should not exist', |
136 | year => 2005, |
137 | })->first; |
138 | ok(!defined($cd), q{failed txn_do didn't change the cds table}); |
57c18b65 |
139 | |
140 | is( $schema->storage->{transaction_depth}, 0, 'txn depth has been reset'); |
a62cf8d4 |
141 | } |
142 | |
143 | # Test failed txn_do() with failed rollback |
144 | { |
57c18b65 |
145 | is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0'); |
146 | |
a62cf8d4 |
147 | my $artist = $schema->resultset('Artist')->find(3); |
148 | |
149 | # Force txn_rollback() to throw an exception |
150 | no warnings 'redefine'; |
58d387fe |
151 | no strict 'refs'; |
57c18b65 |
152 | |
fb61e30c |
153 | # die in rollback |
57c18b65 |
154 | local *{"DBIx::Class::Storage::DBI::SQLite::txn_rollback"} = sub{ |
155 | my $storage = shift; |
57c18b65 |
156 | die 'FAILED'; |
157 | }; |
a62cf8d4 |
158 | |
dd7d4b43 |
159 | throws_ok ( |
160 | sub { |
161 | $schema->txn_do($fail_code, $artist); |
162 | }, |
163 | qr/the sky is falling.+Rollback failed/s, |
164 | 'txn_rollback threw a rollback exception (and included the original exception' |
165 | ); |
a62cf8d4 |
166 | |
167 | my $cd = $artist->cds({ |
168 | title => 'this should not exist', |
169 | year => 2005, |
170 | })->first; |
171 | isa_ok($cd, 'DBICTest::CD', q{failed txn_do with a failed txn_rollback }. |
172 | q{changed the cds table}); |
173 | $cd->delete; # Rollback failed |
174 | $cd = $artist->cds({ |
175 | title => 'this should not exist', |
176 | year => 2005, |
177 | })->first; |
178 | ok(!defined($cd), q{deleted the failed txn's cd}); |
57c18b65 |
179 | $schema->storage->_dbh->rollback; |
a62cf8d4 |
180 | } |
181 | |
fb61e30c |
182 | # reset schema object (the txn_rollback meddling screws it up) |
183 | $schema = DBICTest->init_schema(); |
184 | |
a62cf8d4 |
185 | # Test nested failed txn_do() |
186 | { |
57c18b65 |
187 | is( $schema->storage->{transaction_depth}, 0, 'txn depth starts at 0'); |
188 | |
a62cf8d4 |
189 | my $nested_fail_code = sub { |
190 | my ($schema, $artist, $code1, $code2) = @_; |
191 | |
192 | my @titles = map {'nested txn_do test CD ' . $_} (1..5); |
193 | |
194 | $schema->txn_do($code1, $artist, @titles); # successful txn |
195 | $schema->txn_do($code2, $artist); # failed txn |
196 | }; |
197 | |
198 | my $artist = $schema->resultset('Artist')->find(3); |
199 | |
dd7d4b43 |
200 | throws_ok ( sub { |
a62cf8d4 |
201 | $schema->txn_do($nested_fail_code, $schema, $artist, $code, $fail_code); |
dd7d4b43 |
202 | }, qr/the sky is falling/, 'nested failed txn_do threw exception'); |
a62cf8d4 |
203 | |
a62cf8d4 |
204 | ok(!defined($artist->cds({ |
205 | title => 'nested txn_do test CD '.$_, |
206 | year => 2006, |
207 | })->first), qq{failed txn_do didn't add first txn's cd $_}) for (1..5); |
208 | my $cd = $artist->cds({ |
209 | title => 'this should not exist', |
210 | year => 2005, |
211 | })->first; |
212 | ok(!defined($cd), q{failed txn_do didn't add failed txn's cd}); |
213 | } |
a62cf8d4 |
214 | |
291bf95f |
215 | # Grab a new schema to test txn before connect |
216 | { |
217 | my $schema2 = DBICTest->init_schema(no_deploy => 1); |
dd7d4b43 |
218 | lives_ok (sub { |
291bf95f |
219 | $schema2->txn_begin(); |
220 | $schema2->txn_begin(); |
dd7d4b43 |
221 | }, 'Pre-connection nested transactions.'); |
c52c3466 |
222 | |
223 | # although not connected DBI would still warn about rolling back at disconnect |
224 | $schema2->txn_rollback; |
225 | $schema2->txn_rollback; |
a211cb63 |
226 | $schema2->storage->disconnect; |
291bf95f |
227 | } |
a211cb63 |
228 | $schema->storage->disconnect; |
3b7f3eac |
229 | |
3b7f3eac |
230 | # Test txn_scope_guard |
231 | { |
3b7f3eac |
232 | my $schema = DBICTest->init_schema(); |
233 | |
234 | is($schema->storage->transaction_depth, 0, "Correct transaction depth"); |
235 | my $artist_rs = $schema->resultset('Artist'); |
236 | throws_ok { |
237 | my $guard = $schema->txn_scope_guard; |
238 | |
239 | |
240 | $artist_rs->create({ |
241 | name => 'Death Cab for Cutie', |
242 | made_up_column => 1, |
243 | }); |
d7ded411 |
244 | |
3b7f3eac |
245 | $guard->commit; |
5ee678c8 |
246 | } qr/No such column made_up_column .*? at .*?81transactions.t line \d+/s, "Error propogated okay"; |
3b7f3eac |
247 | |
248 | ok(!$artist_rs->find({name => 'Death Cab for Cutie'}), "Artist not created"); |
249 | |
dd7d4b43 |
250 | my $inner_exception = ''; # set in inner() below |
251 | throws_ok (sub { |
3b7f3eac |
252 | outer($schema, 1); |
dd7d4b43 |
253 | }, qr/$inner_exception/, "Nested exceptions propogated"); |
3b7f3eac |
254 | |
255 | ok(!$artist_rs->find({name => 'Death Cab for Cutie'}), "Artist not created"); |
256 | |
aab0d3b7 |
257 | lives_ok (sub { |
d7ded411 |
258 | warnings_exist ( sub { |
257a1d3b |
259 | # The 0 arg says don't die, just let the scope guard go out of scope |
d7ded411 |
260 | # forcing a txn_rollback to happen |
261 | outer($schema, 0); |
771298cf |
262 | }, qr/A DBIx::Class::Storage::TxnScopeGuard went out of scope without explicit commit or error. Rolling back./, 'Out of scope warning detected'); |
aab0d3b7 |
263 | ok(!$artist_rs->find({name => 'Death Cab for Cutie'}), "Artist not created"); |
264 | }, 'rollback successful withot exception'); |
3b7f3eac |
265 | |
266 | sub outer { |
267 | my ($schema) = @_; |
aab0d3b7 |
268 | |
3b7f3eac |
269 | my $guard = $schema->txn_scope_guard; |
270 | $schema->resultset('Artist')->create({ |
271 | name => 'Death Cab for Cutie', |
272 | }); |
273 | inner(@_); |
3b7f3eac |
274 | } |
275 | |
276 | sub inner { |
277 | my ($schema, $fatal) = @_; |
aab0d3b7 |
278 | |
279 | my $inner_guard = $schema->txn_scope_guard; |
280 | is($schema->storage->transaction_depth, 2, "Correct transaction depth"); |
3b7f3eac |
281 | |
282 | my $artist = $artist_rs->find({ name => 'Death Cab for Cutie' }); |
283 | |
3b7f3eac |
284 | eval { |
257a1d3b |
285 | $artist->cds->create({ |
3b7f3eac |
286 | title => 'Plans', |
257a1d3b |
287 | year => 2005, |
3b7f3eac |
288 | $fatal ? ( foo => 'bar' ) : () |
289 | }); |
290 | }; |
291 | if ($@) { |
292 | # Record what got thrown so we can test it propgates out properly. |
293 | $inner_exception = $@; |
294 | die $@; |
295 | } |
296 | |
aab0d3b7 |
297 | # inner guard should commit without consequences |
298 | $inner_guard->commit; |
3b7f3eac |
299 | } |
300 | } |
d7ded411 |
301 | |
302 | # make sure the guard does not eat exceptions |
303 | { |
304 | my $schema = DBICTest->init_schema(); |
305 | throws_ok (sub { |
306 | my $guard = $schema->txn_scope_guard; |
307 | $schema->resultset ('Artist')->create ({ name => 'bohhoo'}); |
308 | |
309 | $schema->storage->disconnect; # this should freak out the guard rollback |
310 | |
311 | die 'Deliberate exception'; |
312 | }, qr/Deliberate exception.+Rollback failed/s); |
313 | } |
314 | |
83f26263 |
315 | # make sure it warns *big* on failed rollbacks |
316 | { |
c6e27318 |
317 | my $schema = DBICTest->init_schema(); |
c6e27318 |
318 | |
36099e8c |
319 | # something is really confusing Test::Warn here, no time to debug |
320 | =begin |
321 | warnings_exist ( |
322 | sub { |
771298cf |
323 | my $guard = $schema->txn_scope_guard; |
324 | $schema->resultset ('Artist')->create ({ name => 'bohhoo'}); |
325 | |
326 | $schema->storage->disconnect; # this should freak out the guard rollback |
771298cf |
327 | }, |
83f26263 |
328 | [ |
329 | qr/A DBIx::Class::Storage::TxnScopeGuard went out of scope without explicit commit or error. Rolling back./, |
330 | qr/\*+ ROLLBACK FAILED\!\!\! \*+/, |
331 | ], |
332 | 'proper warnings generated on out-of-scope+rollback failure' |
333 | ); |
36099e8c |
334 | =cut |
335 | |
336 | my @want = ( |
337 | qr/A DBIx::Class::Storage::TxnScopeGuard went out of scope without explicit commit or error. Rolling back./, |
338 | qr/\*+ ROLLBACK FAILED\!\!\! \*+/, |
339 | ); |
340 | |
341 | my @w; |
342 | local $SIG{__WARN__} = sub { |
343 | if (grep {$_[0] =~ $_} (@want)) { |
344 | push @w, $_[0]; |
345 | } |
346 | else { |
347 | warn $_[0]; |
348 | } |
349 | }; |
350 | { |
351 | my $guard = $schema->txn_scope_guard; |
352 | $schema->resultset ('Artist')->create ({ name => 'bohhoo'}); |
353 | |
354 | $schema->storage->disconnect; # this should freak out the guard rollback |
355 | } |
356 | |
357 | is (@w, 2, 'Both expected warnings found'); |
c6e27318 |
358 | } |
359 | |
257a1d3b |
360 | # make sure AutoCommit => 0 on external handles behaves correctly with scope_guard |
361 | { |
362 | my $factory = DBICTest->init_schema (AutoCommit => 0); |
363 | cmp_ok ($factory->resultset('CD')->count, '>', 0, 'Something to delete'); |
364 | my $dbh = $factory->storage->dbh; |
365 | |
366 | ok (!$dbh->{AutoCommit}, 'AutoCommit is off on $dbh'); |
367 | my $schema = DBICTest::Schema->connect (sub { $dbh }); |
368 | |
369 | |
370 | lives_ok ( sub { |
371 | my $guard = $schema->txn_scope_guard; |
372 | $schema->resultset('CD')->delete; |
373 | $guard->commit; |
374 | }, 'No attempt to start a transaction with scope guard'); |
375 | |
376 | is ($schema->resultset('CD')->count, 0, 'Deletion successful'); |
377 | } |
378 | |
379 | # make sure AutoCommit => 0 on external handles behaves correctly with txn_do |
380 | { |
381 | my $factory = DBICTest->init_schema (AutoCommit => 0); |
382 | cmp_ok ($factory->resultset('CD')->count, '>', 0, 'Something to delete'); |
383 | my $dbh = $factory->storage->dbh; |
384 | |
385 | ok (!$dbh->{AutoCommit}, 'AutoCommit is off on $dbh'); |
386 | my $schema = DBICTest::Schema->connect (sub { $dbh }); |
387 | |
388 | |
389 | lives_ok ( sub { |
390 | $schema->txn_do (sub { $schema->resultset ('CD')->delete }); |
391 | }, 'No attempt to start a atransaction with txn_do'); |
392 | |
393 | is ($schema->resultset('CD')->count, 0, 'Deletion successful'); |
394 | } |
395 | |
d7ded411 |
396 | done_testing; |