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