Extra test for Replicated debug (forgotten in 8592e2d1)
[dbsrgits/DBIx-Class.git] / t / storage / replicated.t
1 use strict;
2 use warnings;
3
4 use Test::More;
5
6 BEGIN {
7     require DBIx::Class;
8     plan skip_all => 'Test needs ' . DBIx::Class::Optional::Dependencies->req_missing_for ('test_replicated')
9       unless DBIx::Class::Optional::Dependencies->req_ok_for ('test_replicated');
10 }
11
12 use Test::Moose;
13 use Test::Exception;
14 use List::Util 'first';
15 use Scalar::Util 'reftype';
16 use File::Spec;
17 use IO::Handle;
18 use Moose();
19 use MooseX::Types();
20 note "Using Moose version $Moose::VERSION and MooseX::Types version $MooseX::Types::VERSION";
21
22 use lib qw(t/lib);
23 use DBICTest;
24
25 my $var_dir = quotemeta ( File::Spec->catdir(qw/t var/) );
26
27 use_ok 'DBIx::Class::Storage::DBI::Replicated::Pool';
28 use_ok 'DBIx::Class::Storage::DBI::Replicated::Balancer';
29 use_ok 'DBIx::Class::Storage::DBI::Replicated::Replicant';
30 use_ok 'DBIx::Class::Storage::DBI::Replicated';
31
32
33 =head1 HOW TO USE
34
35     This is a test of the replicated storage system.  This will work in one of
36     two ways, either it was try to fake replication with a couple of SQLite DBs
37     and creative use of copy, or if you define a couple of %ENV vars correctly
38     will try to test those.  If you do that, it will assume the setup is properly
39     replicating.  Your results may vary, but I have demonstrated this to work with
40     mysql native replication.
41
42 =cut
43
44
45 ## ----------------------------------------------------------------------------
46 ## Build a class to hold all our required testing data and methods.
47 ## ----------------------------------------------------------------------------
48
49 TESTSCHEMACLASSES: {
50
51     ## --------------------------------------------------------------------- ##
52     ## Create an object to contain your replicated stuff.
53     ## --------------------------------------------------------------------- ##
54
55     package DBIx::Class::DBI::Replicated::TestReplication;
56
57     use DBICTest;
58     use base qw/Class::Accessor::Fast/;
59
60     __PACKAGE__->mk_accessors( qw/schema/ );
61
62     ## Initialize the object
63
64     sub new {
65         my ($class, $schema_method) = (shift, shift);
66         my $self = $class->SUPER::new(@_);
67
68         $self->schema( $self->init_schema($schema_method) );
69         return $self;
70     }
71
72     ## Get the Schema and set the replication storage type
73
74     sub init_schema {
75         # current SQLT SQLite producer does not handle DROP TABLE IF EXISTS, trap warnings here
76         local $SIG{__WARN__} = sub { warn @_ unless $_[0] =~ /no such table.+DROP TABLE/s };
77
78         my ($class, $schema_method) = @_;
79
80         my $method = "get_schema_$schema_method";
81         my $schema = $class->$method;
82
83         return $schema;
84     }
85
86     sub get_schema_by_storage_type {
87       DBICTest->init_schema(
88         sqlite_use_file => 1,
89         storage_type=>{
90           '::DBI::Replicated' => {
91             balancer_type=>'::Random',
92             balancer_args=>{
93               auto_validate_every=>100,
94               master_read_weight => 1
95             },
96           }
97         },
98         deploy_args=>{
99           add_drop_table => 1,
100         },
101       );
102     }
103
104     sub get_schema_by_connect_info {
105       DBICTest->init_schema(
106         sqlite_use_file => 1,
107         storage_type=> '::DBI::Replicated',
108         balancer_type=>'::Random',
109         balancer_args=> {
110             auto_validate_every=>100,
111             master_read_weight => 1
112         },
113         pool_args=>{
114             maximum_lag=>1,
115         },
116         deploy_args=>{
117           add_drop_table => 1,
118         },
119       );
120     }
121
122     sub generate_replicant_connect_info {}
123     sub replicate {}
124     sub cleanup {}
125
126     ## --------------------------------------------------------------------- ##
127     ## Add a connect_info option to test option merging.
128     ## --------------------------------------------------------------------- ##
129     {
130     package DBIx::Class::Storage::DBI::Replicated;
131
132     use Moose;
133
134     __PACKAGE__->meta->make_mutable;
135
136     around connect_info => sub {
137       my ($next, $self, $info) = @_;
138       $info->[3]{master_option} = 1;
139       $self->$next($info);
140     };
141
142     __PACKAGE__->meta->make_immutable;
143
144     no Moose;
145     }
146
147     ## --------------------------------------------------------------------- ##
148     ## Subclass for when you are using SQLite for testing, this provides a fake
149     ## replication support.
150     ## --------------------------------------------------------------------- ##
151
152     package DBIx::Class::DBI::Replicated::TestReplication::SQLite;
153
154     use DBICTest;
155     use File::Copy;
156     use base 'DBIx::Class::DBI::Replicated::TestReplication';
157
158     __PACKAGE__->mk_accessors(qw/master_path slave_paths/);
159
160     ## Set the master path from DBICTest
161
162     sub new {
163         my $class = shift @_;
164         my $self = $class->SUPER::new(@_);
165
166         $self->master_path( DBICTest->_sqlite_dbfilename );
167         $self->slave_paths([
168             File::Spec->catfile(qw/t var DBIxClass_slave1.db/),
169             File::Spec->catfile(qw/t var DBIxClass_slave2.db/),
170         ]);
171
172         return $self;
173     }
174
175     ## Return an Array of ArrayRefs where each ArrayRef is suitable to use for
176     ## $storage->connect_info to be used for connecting replicants.
177
178     sub generate_replicant_connect_info {
179         my $self = shift @_;
180         my @dsn = map {
181             "dbi:SQLite:${_}";
182         } @{$self->slave_paths};
183
184         my @connect_infos = map { [$_,'','',{AutoCommit=>1}] } @dsn;
185
186         ## Make sure nothing is left over from a failed test
187         $self->cleanup;
188
189         ## try a hashref too
190         my $c = $connect_infos[0];
191         $connect_infos[0] = {
192           dsn => $c->[0],
193           user => $c->[1],
194           password => $c->[2],
195           %{ $c->[3] }
196         };
197
198         @connect_infos
199     }
200
201     ## Do a 'good enough' replication by copying the master dbfile over each of
202     ## the slave dbfiles.  If the master is SQLite we do this, otherwise we
203     ## just do a one second pause to let the slaves catch up.
204
205     sub replicate {
206         my $self = shift @_;
207         foreach my $slave (@{$self->slave_paths}) {
208             copy($self->master_path, $slave);
209         }
210     }
211
212     ## Cleanup after ourselves.  Unlink all gthe slave paths.
213
214     sub cleanup {
215         my $self = shift @_;
216         foreach my $slave (@{$self->slave_paths}) {
217             if(-e $slave) {
218                 unlink $slave;
219             }
220         }
221     }
222
223     ## --------------------------------------------------------------------- ##
224     ## Subclass for when you are setting the databases via custom export vars
225     ## This is for when you have a replicating database setup that you are
226     ## going to test against.  You'll need to define the correct $ENV and have
227     ## two slave databases to test against, as well as a replication system
228     ## that will replicate in less than 1 second.
229     ## --------------------------------------------------------------------- ##
230
231     package DBIx::Class::DBI::Replicated::TestReplication::Custom;
232     use base 'DBIx::Class::DBI::Replicated::TestReplication';
233
234     ## Return an Array of ArrayRefs where each ArrayRef is suitable to use for
235     ## $storage->connect_info to be used for connecting replicants.
236
237     sub generate_replicant_connect_info {
238         return (
239             [$ENV{"DBICTEST_SLAVE0_DSN"}, $ENV{"DBICTEST_SLAVE0_DBUSER"}, $ENV{"DBICTEST_SLAVE0_DBPASS"}, {AutoCommit => 1}],
240             [$ENV{"DBICTEST_SLAVE1_DSN"}, $ENV{"DBICTEST_SLAVE1_DBUSER"}, $ENV{"DBICTEST_SLAVE1_DBPASS"}, {AutoCommit => 1}],
241         );
242     }
243
244     ## pause a bit to let the replication catch up
245
246     sub replicate {
247         sleep 1;
248     }
249 }
250
251 ## ----------------------------------------------------------------------------
252 ## Create an object and run some tests
253 ## ----------------------------------------------------------------------------
254
255 ## Thi first bunch of tests are basic, just make sure all the bits are behaving
256
257 my $replicated_class = DBICTest->has_custom_dsn ?
258     'DBIx::Class::DBI::Replicated::TestReplication::Custom' :
259     'DBIx::Class::DBI::Replicated::TestReplication::SQLite';
260
261 my $replicated;
262
263 for my $method (qw/by_connect_info by_storage_type/) {
264   undef $replicated;
265   ok $replicated = $replicated_class->new($method)
266       => "Created a replication object $method";
267
268   isa_ok $replicated->schema
269       => 'DBIx::Class::Schema';
270
271   isa_ok $replicated->schema->storage
272       => 'DBIx::Class::Storage::DBI::Replicated';
273
274   isa_ok $replicated->schema->storage->balancer
275       => 'DBIx::Class::Storage::DBI::Replicated::Balancer::Random'
276       => 'configured balancer_type';
277 }
278
279 ### check that all Storage::DBI methods are handled by ::Replicated
280 {
281   my @storage_dbi_methods = Class::MOP::Class
282     ->initialize('DBIx::Class::Storage::DBI')->get_all_method_names;
283
284   my @replicated_methods  = DBIx::Class::Storage::DBI::Replicated->meta
285     ->get_all_method_names;
286
287 # remove constants and OTHER_CRAP
288   @storage_dbi_methods = grep !/^[A-Z_]+\z/, @storage_dbi_methods;
289
290 # remove CAG accessors
291   @storage_dbi_methods = grep !/_accessor\z/, @storage_dbi_methods;
292
293 # remove DBIx::Class (the root parent, with CAG and stuff) methods
294   my @root_methods = Class::MOP::Class->initialize('DBIx::Class')
295     ->get_all_method_names;
296   my %count;
297   $count{$_}++ for (@storage_dbi_methods, @root_methods);
298
299   @storage_dbi_methods = grep $count{$_} != 2, @storage_dbi_methods;
300
301 # make hashes
302   my %storage_dbi_methods;
303   @storage_dbi_methods{@storage_dbi_methods} = ();
304   my %replicated_methods;
305   @replicated_methods{@replicated_methods} = ();
306
307 # remove ::Replicated-specific methods
308   for my $method (@replicated_methods) {
309     delete $replicated_methods{$method}
310       unless exists $storage_dbi_methods{$method};
311   }
312   @replicated_methods = keys %replicated_methods;
313
314 # check that what's left is implemented
315   %count = ();
316   $count{$_}++ for (@storage_dbi_methods, @replicated_methods);
317
318   if ((grep $count{$_} == 2, @storage_dbi_methods) == @storage_dbi_methods) {
319     pass 'all DBIx::Class::Storage::DBI methods implemented';
320   }
321   else {
322     my @unimplemented = grep $count{$_} == 1, @storage_dbi_methods;
323
324     fail 'the following DBIx::Class::Storage::DBI methods are unimplemented: '
325       . "@unimplemented";
326   }
327 }
328
329 ok $replicated->schema->storage->meta
330     => 'has a meta object';
331
332 isa_ok $replicated->schema->storage->master
333     => 'DBIx::Class::Storage::DBI';
334
335 isa_ok $replicated->schema->storage->pool
336     => 'DBIx::Class::Storage::DBI::Replicated::Pool';
337
338 does_ok $replicated->schema->storage->balancer
339     => 'DBIx::Class::Storage::DBI::Replicated::Balancer';
340
341 ok my @replicant_connects = $replicated->generate_replicant_connect_info
342     => 'got replication connect information';
343
344 ok my @replicated_storages = $replicated->schema->storage->connect_replicants(@replicant_connects)
345     => 'Created some storages suitable for replicants';
346
347 our %debug;
348 $replicated->schema->storage->debug(1);
349 $replicated->schema->storage->debugcb(sub {
350     my ($op, $info) = @_;
351     ##warn "\n$op, $info\n";
352     %debug = (
353         op => $op,
354         info => $info,
355         dsn => ($info=~m/\[(.+)\]/)[0],
356         storage_type => $info=~m/REPLICANT/ ? 'REPLICANT' : 'MASTER',
357     );
358 });
359
360 ok my @all_storages = $replicated->schema->storage->all_storages
361     => '->all_storages';
362
363 is scalar @all_storages,
364     3
365     => 'correct number of ->all_storages';
366
367 is ((grep $_->isa('DBIx::Class::Storage::DBI'), @all_storages),
368     3
369     => '->all_storages are correct type');
370
371 my @all_storage_opts =
372   grep { (reftype($_)||'') eq 'HASH' }
373     map @{ $_->_connect_info }, @all_storages;
374
375 is ((grep $_->{master_option}, @all_storage_opts),
376     3
377     => 'connect_info was merged from master to replicants');
378
379 my @replicant_names = keys %{ $replicated->schema->storage->replicants };
380
381 ok @replicant_names, "found replicant names @replicant_names";
382
383 ## Silence warning about not supporting the is_replicating method if using the
384 ## sqlite dbs.
385 $replicated->schema->storage->debugobj->silence(1)
386   if first { $_ =~ /$var_dir/ } @replicant_names;
387
388 isa_ok $replicated->schema->storage->balancer->current_replicant
389     => 'DBIx::Class::Storage::DBI';
390
391 $replicated->schema->storage->debugobj->silence(0);
392
393 ok $replicated->schema->storage->pool->has_replicants
394     => 'does have replicants';
395
396 is $replicated->schema->storage->pool->num_replicants => 2
397     => 'has two replicants';
398
399 does_ok $replicated_storages[0]
400     => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
401
402 does_ok $replicated_storages[1]
403     => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
404
405 does_ok $replicated->schema->storage->replicants->{$replicant_names[0]}
406     => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
407
408 does_ok $replicated->schema->storage->replicants->{$replicant_names[1]}
409     => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
410
411 ## Add some info to the database
412
413 $replicated
414     ->schema
415     ->populate('Artist', [
416         [ qw/artistid name/ ],
417         [ 4, "Ozric Tentacles"],
418     ]);
419
420     is $debug{storage_type}, 'MASTER',
421         "got last query from a master: $debug{dsn}";
422
423     like $debug{info}, qr/INSERT/, 'Last was an insert';
424
425 ## Make sure all the slaves have the table definitions
426
427 $replicated->replicate;
428 $replicated->schema->storage->replicants->{$replicant_names[0]}->active(1);
429 $replicated->schema->storage->replicants->{$replicant_names[1]}->active(1);
430
431 ## Silence warning about not supporting the is_replicating method if using the
432 ## sqlite dbs.
433 $replicated->schema->storage->debugobj->silence(1)
434   if first { $_ =~ /$var_dir/ } @replicant_names;
435
436 $replicated->schema->storage->pool->validate_replicants;
437
438 $replicated->schema->storage->debugobj->silence(0);
439
440 ## Make sure we can read the data.
441
442 ok my $artist1 = $replicated->schema->resultset('Artist')->find(4)
443     => 'Created Result';
444
445 ## We removed testing here since master read weight is on, so we can't tell in
446 ## advance what storage to expect.  We turn master read weight off a bit lower
447 ## is $debug{storage_type}, 'REPLICANT'
448 ##     => "got last query from a replicant: $debug{dsn}, $debug{info}";
449
450 isa_ok $artist1
451     => 'DBICTest::Artist';
452
453 is $artist1->name, 'Ozric Tentacles'
454     => 'Found expected name for first result';
455
456 ## Check that master_read_weight is honored
457 {
458     no warnings qw/once redefine/;
459
460     local
461     *DBIx::Class::Storage::DBI::Replicated::Balancer::Random::_random_number =
462     sub { 999 };
463
464     $replicated->schema->storage->balancer->increment_storage;
465
466     is $replicated->schema->storage->balancer->current_replicant,
467        $replicated->schema->storage->master
468        => 'master_read_weight is honored';
469
470     ## turn it off for the duration of the test
471     $replicated->schema->storage->balancer->master_read_weight(0);
472     $replicated->schema->storage->balancer->increment_storage;
473 }
474
475 ## Add some new rows that only the master will have  This is because
476 ## we overload any type of write operation so that is must hit the master
477 ## database.
478
479 $replicated
480     ->schema
481     ->populate('Artist', [
482         [ qw/artistid name/ ],
483         [ 5, "Doom's Children"],
484         [ 6, "Dead On Arrival"],
485         [ 7, "Watergate"],
486     ]);
487
488     is $debug{storage_type}, 'MASTER',
489         "got last query from a master: $debug{dsn}";
490
491     like $debug{info}, qr/INSERT/, 'Last was an insert';
492
493 ## Make sure all the slaves have the table definitions
494 $replicated->replicate;
495
496 ## Should find some data now
497
498 ok my $artist2 = $replicated->schema->resultset('Artist')->find(5)
499     => 'Sync succeed';
500
501 is $debug{storage_type}, 'REPLICANT'
502     => "got last query from a replicant: $debug{dsn}";
503
504 isa_ok $artist2
505     => 'DBICTest::Artist';
506
507 is $artist2->name, "Doom's Children"
508     => 'Found expected name for first result';
509
510 ## What happens when we disconnect all the replicants?
511
512 is $replicated->schema->storage->pool->connected_replicants => 2
513     => "both replicants are connected";
514
515 $replicated->schema->storage->replicants->{$replicant_names[0]}->disconnect;
516 $replicated->schema->storage->replicants->{$replicant_names[1]}->disconnect;
517
518 is $replicated->schema->storage->pool->connected_replicants => 0
519     => "both replicants are now disconnected";
520
521 ## All these should pass, since the database should automatically reconnect
522
523 ok my $artist3 = $replicated->schema->resultset('Artist')->find(6)
524     => 'Still finding stuff.';
525
526 is $debug{storage_type}, 'REPLICANT'
527     => "got last query from a replicant: $debug{dsn}";
528
529 isa_ok $artist3
530     => 'DBICTest::Artist';
531
532 is $artist3->name, "Dead On Arrival"
533     => 'Found expected name for first result';
534
535 is $replicated->schema->storage->pool->connected_replicants => 1
536     => "At Least One replicant reconnected to handle the job";
537
538 ## What happens when we try to select something that doesn't exist?
539
540 ok ! $replicated->schema->resultset('Artist')->find(666)
541     => 'Correctly failed to find something.';
542
543 is $debug{storage_type}, 'REPLICANT'
544     => "got last query from a replicant: $debug{dsn}";
545
546 ## test the reliable option
547
548 TESTRELIABLE: {
549
550     $replicated->schema->storage->set_reliable_storage;
551
552     ok $replicated->schema->resultset('Artist')->find(2)
553         => 'Read from master 1';
554
555     is $debug{storage_type}, 'MASTER',
556         "got last query from a master: $debug{dsn}";
557
558     ok $replicated->schema->resultset('Artist')->find(5)
559         => 'Read from master 2';
560
561     is $debug{storage_type}, 'MASTER',
562         "got last query from a master: $debug{dsn}";
563
564     $replicated->schema->storage->set_balanced_storage;
565
566     ok $replicated->schema->resultset('Artist')->find(3)
567         => 'Read from replicant';
568
569     is $debug{storage_type}, 'REPLICANT',
570         "got last query from a replicant: $debug{dsn}";
571 }
572
573 ## Make sure when reliable goes out of scope, we are using replicants again
574
575 ok $replicated->schema->resultset('Artist')->find(1)
576     => 'back to replicant 1.';
577
578     is $debug{storage_type}, 'REPLICANT',
579         "got last query from a replicant: $debug{dsn}";
580
581 ok $replicated->schema->resultset('Artist')->find(2)
582     => 'back to replicant 2.';
583
584     is $debug{storage_type}, 'REPLICANT',
585         "got last query from a replicant: $debug{dsn}";
586
587 ## set all the replicants to inactive, and make sure the balancer falls back to
588 ## the master.
589
590 $replicated->schema->storage->replicants->{$replicant_names[0]}->active(0);
591 $replicated->schema->storage->replicants->{$replicant_names[1]}->active(0);
592
593 {
594     ## catch the fallback to master warning
595     open my $debugfh, '>', \my $fallback_warning;
596     my $oldfh = $replicated->schema->storage->debugfh;
597     $replicated->schema->storage->debugfh($debugfh);
598
599     ok $replicated->schema->resultset('Artist')->find(2)
600         => 'Fallback to master';
601
602     is $debug{storage_type}, 'MASTER',
603         "got last query from a master: $debug{dsn}";
604
605     like $fallback_warning, qr/falling back to master/
606         => 'emits falling back to master debug';
607
608     $replicated->schema->storage->debugfh($oldfh);
609 }
610
611 $replicated->schema->storage->replicants->{$replicant_names[0]}->active(1);
612 $replicated->schema->storage->replicants->{$replicant_names[1]}->active(1);
613
614 ## Silence warning about not supporting the is_replicating method if using the
615 ## sqlite dbs.
616 $replicated->schema->storage->debugobj->silence(1)
617   if first { $_ =~ /$var_dir/ } @replicant_names;
618
619 $replicated->schema->storage->pool->validate_replicants;
620
621 $replicated->schema->storage->debugobj->silence(0);
622
623 {
624     ## catch the fallback to master warning
625     open my $debugfh, '>', \my $return_warning;
626     my $oldfh = $replicated->schema->storage->debugfh;
627     $replicated->schema->storage->debugfh($debugfh);
628
629     ok $replicated->schema->resultset('Artist')->find(2)
630         => 'Return to replicants';
631
632     is $debug{storage_type}, 'REPLICANT',
633       "got last query from a replicant: $debug{dsn}";
634
635     like $return_warning, qr/Moved back to slave/
636         => 'emits returning to slave debug';
637
638     $replicated->schema->storage->debugfh($oldfh);
639 }
640
641 ## Getting slave status tests
642
643 SKIP: {
644     ## We skip this tests unless you have a custom replicants, since the default
645     ## sqlite based replication tests don't support these functions.
646
647     skip 'Cannot Test Replicant Status on Non Replicating Database', 10
648      unless DBICTest->has_custom_dsn && $ENV{"DBICTEST_SLAVE0_DSN"};
649
650     $replicated->replicate; ## Give the slaves a chance to catchup.
651
652     ok $replicated->schema->storage->replicants->{$replicant_names[0]}->is_replicating
653         => 'Replicants are replicating';
654
655     is $replicated->schema->storage->replicants->{$replicant_names[0]}->lag_behind_master, 0
656         => 'Replicant is zero seconds behind master';
657
658     ## Test the validate replicants
659
660     $replicated->schema->storage->pool->validate_replicants;
661
662     is $replicated->schema->storage->pool->active_replicants, 2
663         => 'Still have 2 replicants after validation';
664
665     ## Force the replicants to fail the validate test by required their lag to
666     ## be negative (ie ahead of the master!)
667
668     $replicated->schema->storage->pool->maximum_lag(-10);
669     $replicated->schema->storage->pool->validate_replicants;
670
671     is $replicated->schema->storage->pool->active_replicants, 0
672         => 'No way a replicant be be ahead of the master';
673
674     ## Let's be fair to the replicants again.  Let them lag up to 5
675
676     $replicated->schema->storage->pool->maximum_lag(5);
677     $replicated->schema->storage->pool->validate_replicants;
678
679     is $replicated->schema->storage->pool->active_replicants, 2
680         => 'Both replicants in good standing again';
681
682     ## Check auto validate
683
684     is $replicated->schema->storage->balancer->auto_validate_every, 100
685         => "Got the expected value for auto validate";
686
687         ## This will make sure we auto validatge everytime
688         $replicated->schema->storage->balancer->auto_validate_every(0);
689
690         ## set all the replicants to inactive, and make sure the balancer falls back to
691         ## the master.
692
693         $replicated->schema->storage->replicants->{$replicant_names[0]}->active(0);
694         $replicated->schema->storage->replicants->{$replicant_names[1]}->active(0);
695
696         ## Ok, now when we go to run a query, autovalidate SHOULD reconnect
697
698     is $replicated->schema->storage->pool->active_replicants => 0
699         => "both replicants turned off";
700
701     ok $replicated->schema->resultset('Artist')->find(5)
702         => 'replicant reactivated';
703
704     is $debug{storage_type}, 'REPLICANT',
705         "got last query from a replicant: $debug{dsn}";
706
707     is $replicated->schema->storage->pool->active_replicants => 2
708         => "both replicants reactivated";
709 }
710
711 ## Test the reliably callback
712
713 ok my $reliably = sub {
714
715     ok $replicated->schema->resultset('Artist')->find(5)
716         => 'replicant reactivated';
717
718     is $debug{storage_type}, 'MASTER',
719         "got last query from a master: $debug{dsn}";
720
721 } => 'created coderef properly';
722
723 $replicated->schema->storage->execute_reliably($reliably);
724
725 ## Try something with an error
726
727 ok my $unreliably = sub {
728
729     ok $replicated->schema->resultset('ArtistXX')->find(5)
730         => 'replicant reactivated';
731
732 } => 'created coderef properly';
733
734 throws_ok {$replicated->schema->storage->execute_reliably($unreliably)}
735     qr/Can't find source for ArtistXX/
736     => 'Bad coderef throws proper error';
737
738 ## Make sure replication came back
739
740 ok $replicated->schema->resultset('Artist')->find(3)
741     => 'replicant reactivated';
742
743 is $debug{storage_type}, 'REPLICANT', "got last query from a replicant: $debug{dsn}";
744
745 ## make sure transactions are set to execute_reliably
746
747 ok my $transaction = sub {
748
749     my $id = shift @_;
750
751     $replicated
752         ->schema
753         ->populate('Artist', [
754             [ qw/artistid name/ ],
755             [ $id, "Children of the Grave $id"],
756         ]);
757
758     ok my $result = $replicated->schema->resultset('Artist')->find($id)
759         => "Found expected artist for $id";
760
761     is $debug{storage_type}, 'MASTER',
762         "got last query from a master: $debug{dsn}";
763
764     ok my $more = $replicated->schema->resultset('Artist')->find(1)
765         => 'Found expected artist again for 1';
766
767     is $debug{storage_type}, 'MASTER',
768         "got last query from a master: $debug{dsn}";
769
770    return ($result, $more);
771
772 } => 'Created a coderef properly';
773
774 ## Test the transaction with multi return
775 {
776     ok my @return = $replicated->schema->txn_do($transaction, 666)
777         => 'did transaction';
778
779         is $return[0]->id, 666
780             => 'first returned value is correct';
781
782         is $debug{storage_type}, 'MASTER',
783             "got last query from a master: $debug{dsn}";
784
785         is $return[1]->id, 1
786             => 'second returned value is correct';
787
788         is $debug{storage_type}, 'MASTER',
789              "got last query from a master: $debug{dsn}";
790
791 }
792
793 ## Test that asking for single return works
794 {
795     ok my @return = $replicated->schema->txn_do($transaction, 777)
796         => 'did transaction';
797
798         is $return[0]->id, 777
799             => 'first returned value is correct';
800
801         is $return[1]->id, 1
802             => 'second returned value is correct';
803 }
804
805 ## Test transaction returning a single value
806
807 {
808     ok my $result = $replicated->schema->txn_do(sub {
809         ok my $more = $replicated->schema->resultset('Artist')->find(1)
810         => 'found inside a transaction';
811         is $debug{storage_type}, 'MASTER', "got last query from a master: $debug{dsn}";
812         return $more;
813     }) => 'successfully processed transaction';
814
815     is $result->id, 1
816        => 'Got expected single result from transaction';
817 }
818
819 ## Make sure replication came back
820
821 ok $replicated->schema->resultset('Artist')->find(1)
822     => 'replicant reactivated';
823
824 is $debug{storage_type}, 'REPLICANT', "got last query from a replicant: $debug{dsn}";
825
826 ## Test Discard changes
827
828 {
829     ok my $artist = $replicated->schema->resultset('Artist')->find(2)
830         => 'got an artist to test discard changes';
831
832     is $debug{storage_type}, 'REPLICANT', "got last query from a replicant: $debug{dsn}";
833
834     ok $artist->get_from_storage({force_pool=>'master'})
835        => 'properly discard changes';
836
837     is $debug{storage_type}, 'MASTER', "got last query from a master: $debug{dsn}";
838
839     ok $artist->discard_changes({force_pool=>'master'})
840        => 'properly called discard_changes against master (manual attrs)';
841
842     is $debug{storage_type}, 'MASTER', "got last query from a master: $debug{dsn}";
843
844     ok $artist->discard_changes()
845        => 'properly called discard_changes against master (default attrs)';
846
847     is $debug{storage_type}, 'MASTER', "got last query from a master: $debug{dsn}";
848
849     ok $artist->discard_changes({force_pool=>$replicant_names[0]})
850        => 'properly able to override the default attributes';
851
852     is $debug{storage_type}, 'REPLICANT', "got last query from a replicant: $debug{dsn}"
853 }
854
855 ## Test some edge cases, like trying to do a transaction inside a transaction, etc
856
857 {
858     ok my $result = $replicated->schema->txn_do(sub {
859         return $replicated->schema->txn_do(sub {
860             ok my $more = $replicated->schema->resultset('Artist')->find(1)
861             => 'found inside a transaction inside a transaction';
862             is $debug{storage_type}, 'MASTER', "got last query from a master: $debug{dsn}";
863             return $more;
864         });
865     }) => 'successfully processed transaction';
866
867     is $result->id, 1
868        => 'Got expected single result from transaction';
869 }
870
871 {
872     ok my $result = $replicated->schema->txn_do(sub {
873         return $replicated->schema->storage->execute_reliably(sub {
874             return $replicated->schema->txn_do(sub {
875                 return $replicated->schema->storage->execute_reliably(sub {
876                     ok my $more = $replicated->schema->resultset('Artist')->find(1)
877                       => 'found inside crazy deep transactions and execute_reliably';
878                     is $debug{storage_type}, 'MASTER', "got last query from a master: $debug{dsn}";
879                     return $more;
880                 });
881             });
882         });
883     }) => 'successfully processed transaction';
884
885     is $result->id, 1
886        => 'Got expected single result from transaction';
887 }
888
889 ## Test the force_pool resultset attribute.
890
891 {
892     ok my $artist_rs = $replicated->schema->resultset('Artist')
893         => 'got artist resultset';
894
895     ## Turn on Forced Pool Storage
896     ok my $reliable_artist_rs = $artist_rs->search(undef, {force_pool=>'master'})
897         => 'Created a resultset using force_pool storage';
898
899     ok my $artist = $reliable_artist_rs->find(2)
900         => 'got an artist result via force_pool storage';
901
902     is $debug{storage_type}, 'MASTER', "got last query from a master: $debug{dsn}";
903 }
904
905 ## Test the force_pool resultset attribute part two.
906
907 {
908     ok my $artist_rs = $replicated->schema->resultset('Artist')
909         => 'got artist resultset';
910
911     ## Turn on Forced Pool Storage
912     ok my $reliable_artist_rs = $artist_rs->search(undef, {force_pool=>$replicant_names[0]})
913         => 'Created a resultset using force_pool storage';
914
915     ok my $artist = $reliable_artist_rs->find(2)
916         => 'got an artist result via force_pool storage';
917
918     is $debug{storage_type}, 'REPLICANT', "got last query from a replicant: $debug{dsn}";
919 }
920 ## Delete the old database files
921 $replicated->cleanup;
922
923 done_testing;
924
925 # vim: sw=4 sts=4 :