cleanup of some docs, got the default shuffling balancer to work properly. Don't...
[dbsrgits/DBIx-Class.git] / t / 93storage_replication.t
1 use strict;
2 use warnings;
3 use lib qw(t/lib);
4 use Test::More;
5 use Data::Dump qw/dump/;
6
7 BEGIN {
8     eval "use Moose";
9     plan $@
10         ? ( skip_all => 'needs Moose for testing' )
11         : ( tests => 33 );
12 }
13
14 use_ok 'DBIx::Class::Storage::DBI::Replicated::Pool';
15 use_ok 'DBIx::Class::Storage::DBI::Replicated::Balancer';
16 use_ok 'DBIx::Class::Storage::DBI::Replicated::Replicant';
17 use_ok 'DBIx::Class::Storage::DBI::Replicated';
18
19 ## ----------------------------------------------------------------------------
20 ## Build a class to hold all our required testing data and methods.
21 ## ----------------------------------------------------------------------------
22
23 TESTSCHEMACLASS: {
24
25     package DBIx::Class::DBI::Replicated::TestReplication;
26    
27     use DBICTest;
28     use File::Copy;
29     use Data::Dump qw/dump/;
30     
31     use base qw/Class::Accessor::Fast/;
32     
33     __PACKAGE__->mk_accessors( qw/schema master_path slave_paths/ );
34
35     ## Initialize the object
36     
37         sub new {
38             my $class = shift @_;
39             my $self = $class->SUPER::new(@_);
40         
41             $self->schema( $self->init_schema );
42             $self->master_path("t/var/DBIxClass.db");
43         
44             return $self;
45         }
46     
47     ## Get the Schema and set the replication storage type
48     
49     sub init_schema {
50         my $class = shift @_;
51         my $schema = DBICTest->init_schema(storage_type=>'::DBI::Replicated');
52         return $schema;
53     }
54     
55     ## Return an Array of ArrayRefs where each ArrayRef is suitable to use for
56     ## $storage->connect_info to be used for connecting replicants.
57     
58     sub generate_replicant_connect_info {
59         my $self = shift @_;
60         my @dsn = map {
61             "dbi:SQLite:${_}";
62         } @{$self->slave_paths};
63         
64         return map { [$_,'','',{}] } @dsn;
65     }
66     
67     ## Do a 'good enough' replication by copying the master dbfile over each of
68     ## the slave dbfiles.  If the master is SQLite we do this, otherwise we
69     ## just do a one second pause to let the slaves catch up.
70     
71     sub replicate {
72         my $self = shift @_;
73         foreach my $slave (@{$self->slave_paths}) {
74             copy($self->master_path, $slave);
75         }
76     }
77     
78     ## Cleanup after ourselves.  Unlink all gthe slave paths.
79     
80     sub cleanup {
81         my $self = shift @_;
82         foreach my $slave (@{$self->slave_paths}) {
83             unlink $slave;
84         }     
85     }
86 }
87
88 ## ----------------------------------------------------------------------------
89 ## Create an object and run some tests
90 ## ----------------------------------------------------------------------------
91
92 ## Thi first bunch of tests are basic, just make sure all the bits are behaving
93
94 ok my $replicated = DBIx::Class::DBI::Replicated::TestReplication
95     ->new({
96         slave_paths=>[
97                 "t/var/DBIxClass_slave1.db",
98                 "t/var/DBIxClass_slave2.db",    
99         ],
100     }) => 'Created a replication object';
101     
102 isa_ok $replicated->schema
103     => 'DBIx::Class::Schema';
104     
105 isa_ok $replicated->schema->storage
106     => 'DBIx::Class::Storage::DBI::Replicated';
107
108 ok $replicated->schema->storage->meta
109     => 'has a meta object';
110     
111 isa_ok $replicated->schema->storage->master
112     => 'DBIx::Class::Storage::DBI';
113     
114 isa_ok $replicated->schema->storage->pool
115     => 'DBIx::Class::Storage::DBI::Replicated::Pool';
116     
117 isa_ok $replicated->schema->storage->balancer
118     => 'DBIx::Class::Storage::DBI::Replicated::Balancer'; 
119
120 ok my @replicant_connects = $replicated->generate_replicant_connect_info
121     => 'got replication connect information';
122
123 ok my @replicated_storages = $replicated->schema->storage->connect_replicants(@replicant_connects)
124     => 'Created some storages suitable for replicants';
125     
126 isa_ok $replicated->schema->storage->current_replicant
127     => 'DBIx::Class::Storage::DBI';
128     
129 ok $replicated->schema->storage->pool->has_replicants
130     => 'does have replicants';     
131
132 is $replicated->schema->storage->num_replicants => 2
133     => 'has two replicants';
134        
135 isa_ok $replicated_storages[0]
136     => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
137
138 isa_ok $replicated_storages[1]
139     => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
140     
141 isa_ok $replicated->schema->storage->replicants->{"t/var/DBIxClass_slave1.db"}
142     => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
143
144 isa_ok $replicated->schema->storage->replicants->{"t/var/DBIxClass_slave2.db"}
145     => 'DBIx::Class::Storage::DBI::Replicated::Replicant';  
146
147 ## Add some info to the database
148
149 $replicated
150     ->schema
151     ->populate('Artist', [
152         [ qw/artistid name/ ],
153         [ 4, "Ozric Tentacles"],
154     ]);
155                 
156 ## Make sure all the slaves have the table definitions
157
158 $replicated->replicate;
159
160 ## Make sure we can read the data.
161
162 ok my $artist1 = $replicated->schema->resultset('Artist')->find(4)
163     => 'Created Result';
164
165 isa_ok $artist1
166     => 'DBICTest::Artist';
167     
168 is $artist1->name, 'Ozric Tentacles'
169     => 'Found expected name for first result';
170
171 ## Add some new rows that only the master will have  This is because
172 ## we overload any type of write operation so that is must hit the master
173 ## database.
174
175 $replicated
176     ->schema
177     ->populate('Artist', [
178         [ qw/artistid name/ ],
179         [ 5, "Doom's Children"],
180         [ 6, "Dead On Arrival"],
181         [ 7, "Watergate"],
182     ]);
183
184 ## Alright, the database 'cluster' is not in a consistent state.  When we do
185 ## a read now we expect bad news
186
187 is $replicated->schema->resultset('Artist')->find(5), undef
188     => 'read after disconnect fails because it uses a replicant which we have neglected to "replicate" yet';
189
190 ## Make sure all the slaves have the table definitions
191 $replicated->replicate;
192
193 ## Should find some data now
194
195 ok my $artist2 = $replicated->schema->resultset('Artist')->find(5)
196     => 'Sync succeed';
197     
198 isa_ok $artist2
199     => 'DBICTest::Artist';
200     
201 is $artist2->name, "Doom's Children"
202     => 'Found expected name for first result';
203
204 ## What happens when we disconnect all the replicants?
205
206 is $replicated->schema->storage->pool->connected_replicants => 2
207     => "both replicants are connected";
208     
209 $replicated->schema->storage->replicants->{"t/var/DBIxClass_slave1.db"}->disconnect;
210 $replicated->schema->storage->replicants->{"t/var/DBIxClass_slave2.db"}->disconnect;
211
212 is $replicated->schema->storage->pool->connected_replicants => 0
213     => "both replicants are now disconnected";
214
215 ## All these should pass, since the database should automatically reconnect
216
217 ok my $artist3 = $replicated->schema->resultset('Artist')->find(6)
218     => 'Still finding stuff.';
219     
220 isa_ok $artist3
221     => 'DBICTest::Artist';
222     
223 is $artist3->name, "Dead On Arrival"
224     => 'Found expected name for first result';
225
226 is $replicated->schema->storage->pool->connected_replicants => 1
227     => "One replicant reconnected to handle the job";
228
229 ## Delete the old database files
230 $replicated->cleanup;
231
232
233
234
235
236