10 ? ( skip_all => 'needs Moose for testing' )
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';
21 This is a test of the replicated storage system. This will work in one of
22 two ways, either it was try to fake replication with a couple of SQLite DBs
23 and creative use of copy, or if you define a couple of %ENV vars correctly
24 will try to test those. If you do that, it will assume the setup is properly
25 replicating. Your results may vary, but I have demonstrated this to work with
26 mysql native replication.
31 ## ----------------------------------------------------------------------------
32 ## Build a class to hold all our required testing data and methods.
33 ## ----------------------------------------------------------------------------
37 ## --------------------------------------------------------------------- ##
38 ## Create an object to contain your replicated stuff.
39 ## --------------------------------------------------------------------- ##
41 package DBIx::Class::DBI::Replicated::TestReplication;
44 use base qw/Class::Accessor::Fast/;
46 __PACKAGE__->mk_accessors( qw/schema/ );
48 ## Initialize the object
52 my $self = $class->SUPER::new(@_);
54 $self->schema( $self->init_schema );
58 ## Get the Schema and set the replication storage type
63 my $schema = DBICTest->init_schema(
65 '::DBI::Replicated' => {
66 balancer_type=>'::Random',
77 sub generate_replicant_connect_info {}
82 ## --------------------------------------------------------------------- ##
83 ## Subclass for when you are using SQLite for testing, this provides a fake
84 ## replication support.
85 ## --------------------------------------------------------------------- ##
87 package DBIx::Class::DBI::Replicated::TestReplication::SQLite;
91 use base 'DBIx::Class::DBI::Replicated::TestReplication';
93 __PACKAGE__->mk_accessors( qw/master_path slave_paths/ );
95 ## Set the mastep path from DBICTest
99 my $self = $class->SUPER::new(@_);
101 $self->master_path( DBICTest->_sqlite_dbfilename );
103 "t/var/DBIxClass_slave1.db",
104 "t/var/DBIxClass_slave2.db",
110 ## Return an Array of ArrayRefs where each ArrayRef is suitable to use for
111 ## $storage->connect_info to be used for connecting replicants.
113 sub generate_replicant_connect_info {
117 } @{$self->slave_paths};
119 return map { [$_,'','',{AutoCommit=>1}] } @dsn;
122 ## Do a 'good enough' replication by copying the master dbfile over each of
123 ## the slave dbfiles. If the master is SQLite we do this, otherwise we
124 ## just do a one second pause to let the slaves catch up.
128 foreach my $slave (@{$self->slave_paths}) {
129 copy($self->master_path, $slave);
133 ## Cleanup after ourselves. Unlink all gthe slave paths.
137 foreach my $slave (@{$self->slave_paths}) {
142 ## --------------------------------------------------------------------- ##
143 ## Subclass for when you are setting the databases via custom export vars
144 ## This is for when you have a replicating database setup that you are
145 ## going to test against. You'll need to define the correct $ENV and have
146 ## two slave databases to test against, as well as a replication system
147 ## that will replicate in less than 1 second.
148 ## --------------------------------------------------------------------- ##
150 package DBIx::Class::DBI::Replicated::TestReplication::Custom;
151 use base 'DBIx::Class::DBI::Replicated::TestReplication';
153 ## Return an Array of ArrayRefs where each ArrayRef is suitable to use for
154 ## $storage->connect_info to be used for connecting replicants.
156 sub generate_replicant_connect_info {
158 [$ENV{"DBICTEST_SLAVE0_DSN"}, $ENV{"DBICTEST_SLAVE0_DBUSER"}, $ENV{"DBICTEST_SLAVE0_DBPASS"}, {AutoCommit => 1}],
159 [$ENV{"DBICTEST_SLAVE1_DSN"}, $ENV{"DBICTEST_SLAVE1_DBUSER"}, $ENV{"DBICTEST_SLAVE1_DBPASS"}, {AutoCommit => 1}],
163 ## pause a bit to let the replication catch up
170 ## ----------------------------------------------------------------------------
171 ## Create an object and run some tests
172 ## ----------------------------------------------------------------------------
174 ## Thi first bunch of tests are basic, just make sure all the bits are behaving
176 my $replicated_class = DBICTest->has_custom_dsn ?
177 'DBIx::Class::DBI::Replicated::TestReplication::Custom' :
178 'DBIx::Class::DBI::Replicated::TestReplication::SQLite';
180 ok my $replicated = $replicated_class->new
181 => 'Created a replication object';
183 isa_ok $replicated->schema
184 => 'DBIx::Class::Schema';
186 isa_ok $replicated->schema->storage
187 => 'DBIx::Class::Storage::DBI::Replicated';
189 ok $replicated->schema->storage->meta
190 => 'has a meta object';
192 isa_ok $replicated->schema->storage->master
193 => 'DBIx::Class::Storage::DBI';
195 isa_ok $replicated->schema->storage->pool
196 => 'DBIx::Class::Storage::DBI::Replicated::Pool';
198 isa_ok $replicated->schema->storage->balancer
199 => 'DBIx::Class::Storage::DBI::Replicated::Balancer';
201 ok my @replicant_connects = $replicated->generate_replicant_connect_info
202 => 'got replication connect information';
204 ok my @replicated_storages = $replicated->schema->storage->connect_replicants(@replicant_connects)
205 => 'Created some storages suitable for replicants';
207 isa_ok $replicated->schema->storage->balancer->current_replicant
208 => 'DBIx::Class::Storage::DBI';
210 ok $replicated->schema->storage->pool->has_replicants
211 => 'does have replicants';
213 is $replicated->schema->storage->num_replicants => 2
214 => 'has two replicants';
216 isa_ok $replicated_storages[0]
217 => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
219 isa_ok $replicated_storages[1]
220 => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
222 my @replicant_names = keys %{$replicated->schema->storage->replicants};
224 isa_ok $replicated->schema->storage->replicants->{$replicant_names[0]}
225 => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
227 isa_ok $replicated->schema->storage->replicants->{$replicant_names[1]}
228 => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
230 ## Add some info to the database
234 ->populate('Artist', [
235 [ qw/artistid name/ ],
236 [ 4, "Ozric Tentacles"],
239 ## Make sure all the slaves have the table definitions
241 $replicated->replicate;
243 ## Make sure we can read the data.
245 ok my $artist1 = $replicated->schema->resultset('Artist')->find(4)
249 => 'DBICTest::Artist';
251 is $artist1->name, 'Ozric Tentacles'
252 => 'Found expected name for first result';
254 ## Add some new rows that only the master will have This is because
255 ## we overload any type of write operation so that is must hit the master
260 ->populate('Artist', [
261 [ qw/artistid name/ ],
262 [ 5, "Doom's Children"],
263 [ 6, "Dead On Arrival"],
268 ## We can't do this test if we have a custom replicants, since we assume
269 ## if there are custom one that you are trying to test a real replicating
270 ## system. See docs above for more.
272 skip 'Cannot test inconsistent replication since you have a real replication system', 1
273 if DBICTest->has_custom_dsn && $ENV{"DBICTEST_SLAVE0_DSN"};
275 ## Alright, the database 'cluster' is not in a consistent state. When we do
276 ## a read now we expect bad news
277 is $replicated->schema->resultset('Artist')->find(5), undef
278 => 'read after disconnect fails because it uses a replicant which we have neglected to "replicate" yet';
281 ## Make sure all the slaves have the table definitions
282 $replicated->replicate;
284 ## Should find some data now
286 ok my $artist2 = $replicated->schema->resultset('Artist')->find(5)
290 => 'DBICTest::Artist';
292 is $artist2->name, "Doom's Children"
293 => 'Found expected name for first result';
295 ## What happens when we disconnect all the replicants?
297 is $replicated->schema->storage->pool->connected_replicants => 2
298 => "both replicants are connected";
300 $replicated->schema->storage->replicants->{$replicant_names[0]}->disconnect;
301 $replicated->schema->storage->replicants->{$replicant_names[1]}->disconnect;
303 is $replicated->schema->storage->pool->connected_replicants => 0
304 => "both replicants are now disconnected";
306 ## All these should pass, since the database should automatically reconnect
308 ok my $artist3 = $replicated->schema->resultset('Artist')->find(6)
309 => 'Still finding stuff.';
312 => 'DBICTest::Artist';
314 is $artist3->name, "Dead On Arrival"
315 => 'Found expected name for first result';
317 is $replicated->schema->storage->pool->connected_replicants => 1
318 => "One replicant reconnected to handle the job";
320 ## What happens when we try to select something that doesn't exist?
322 ok ! $replicated->schema->resultset('Artist')->find(666)
323 => 'Correctly failed to find something.';
325 ## test the reliable option
329 $replicated->schema->storage->set_reliable_storage;
331 ok $replicated->schema->resultset('Artist')->find(2)
332 => 'Read from master 1';
334 ok $replicated->schema->resultset('Artist')->find(5)
335 => 'Read from master 2';
337 $replicated->schema->storage->set_balanced_storage;
339 ok $replicated->schema->resultset('Artist')->find(3)
340 => 'Read from replicant';
343 ## Make sure when reliable goes out of scope, we are using replicants again
345 ok $replicated->schema->resultset('Artist')->find(1)
346 => 'back to replicant 1.';
348 ok $replicated->schema->resultset('Artist')->find(2)
349 => 'back to replicant 2.';
351 ## set all the replicants to inactive, and make sure the balancer falls back to
354 $replicated->schema->storage->replicants->{$replicant_names[0]}->active(0);
355 $replicated->schema->storage->replicants->{$replicant_names[1]}->active(0);
357 ok $replicated->schema->resultset('Artist')->find(2)
358 => 'Fallback to master';
360 ## Delete the old database files
361 $replicated->cleanup;