1 package # hide from PAUSE
4 # load early so that `perl -It/lib -MDBICTest` keeps working
11 # this noop trick initializes the STDOUT, so that the TAP::Harness
12 # issued IO::Select->can_read calls (which are blocking wtf wtf wtf)
13 # keep spinning and scheduling jobs
14 # This results in an overall much smoother job-queue drainage, since
15 # the Harness blocks less
16 # (ideally this needs to be addressed in T::H, but a quick patchjob
17 # broke everything so tabling it for now)
19 # FIXME - there probably is some way to determine a harness run (T::H or
20 # prove) but I do not know it offhand, especially on older environments
21 # Go with the safer option
22 if ($INC{'Test/Builder.pm'}) {
29 use DBICTest::Util qw(
30 local_umask slurp_bytes tmpdir await_flock
31 dbg DEBUG_TEST_CONCURRENCY_LOCKS PEEPEENESS
33 use DBICTest::Util::LeakTracer qw/populate_weakregistry assert_empty_weakregistry/;
35 use DBIx::Class::_Util qw( detected_reinvoked_destructor scope_guard );
37 use Fcntl qw/:DEFAULT :flock/;
42 DBICTest - Library to be used by DBIx::Class test scripts
46 BEGIN { do "./t/lib/ANFANG.pm" or die ( $@ || $! ) }
53 my $schema = DBICTest->init_schema();
57 This module provides the basic utilities to write tests against
62 The module does not export anything by default, nor provides individual
63 function exports in the conventional sense. Instead the following tags are
68 Same as C<use SQL::Abstract::Test
69 qw(L<is_same_sql_bind|SQL::Abstract::Test/is_same_sql_bind>
70 L<is_same_sql|SQL::Abstract::Test/is_same_sql>
71 L<is_same_bind|SQL::Abstract::Test/is_same_bind>)>
75 Some tests are very time sensitive and need to run on their own, without
76 being disturbed by anything else grabbing CPU or disk IO. Hence why everything
77 using C<DBICTest> grabs a shared lock, and the few tests that request a
78 C<:GlobalLock> will ask for an exclusive one and block until they can get it.
84 my $schema = DBICTest->init_schema(
87 storage_type=>'::DBI::Replicated',
89 balancer_type=>'DBIx::Class::Storage::DBI::Replicated::Balancer::Random'
93 This method removes the test SQLite database in t/var/DBIxClass.db
94 and then creates a new, empty database.
96 This method will call L<deploy_schema()|/deploy_schema> by default, unless the
97 C<no_deploy> flag is set.
99 Also, by default, this method will call L<populate_schema()|/populate_schema>
100 by default, unless the C<no_deploy> or C<no_populate> flags are set.
104 # see L</:GlobalLock>
105 our ($global_lock_fh, $global_exclusive_lock);
109 my $lockpath = tmpdir . '_dbictest_global.lock';
112 my $u = local_umask(0); # so that the file opens as 666, and any user can lock
113 sysopen ($global_lock_fh, $lockpath, O_RDWR|O_CREAT)
114 or die "Unable to open $lockpath: $!";
118 if ($exp eq ':GlobalLock') {
119 DEBUG_TEST_CONCURRENCY_LOCKS > 1
120 and dbg "Waiting for EXCLUSIVE global lock...";
122 await_flock ($global_lock_fh, LOCK_EX) or die "Unable to lock $lockpath: $!";
124 DEBUG_TEST_CONCURRENCY_LOCKS > 1
125 and dbg "Got EXCLUSIVE global lock";
127 $global_exclusive_lock = 1;
129 elsif ($exp eq ':DiffSQL') {
130 require SQL::Abstract::Test;
131 my $into = caller(0);
132 for (qw(is_same_sql_bind is_same_sql is_same_bind)) {
134 *{"${into}::$_"} = \&{"SQL::Abstract::Test::$_"};
138 croak "Unknown export $exp requested from $self";
142 unless ($global_exclusive_lock) {
143 DEBUG_TEST_CONCURRENCY_LOCKS > 1
144 and dbg "Waiting for SHARED global lock...";
146 await_flock ($global_lock_fh, LOCK_SH) or die "Unable to lock $lockpath: $!";
148 DEBUG_TEST_CONCURRENCY_LOCKS > 1
149 and dbg "Got SHARED global lock";
154 # referencing here delays destruction even more
155 if ($global_lock_fh) {
156 DEBUG_TEST_CONCURRENCY_LOCKS > 1
157 and dbg "Release @{[ $global_exclusive_lock ? 'EXCLUSIVE' : 'SHARED' ]} global lock (END)";
164 sub _sqlite_dbfilename {
165 my $holder = $ENV{DBICTEST_LOCK_HOLDER} || $$;
166 $holder = $$ if $holder == -1;
168 return "t/var/DBIxClass-$holder.db";
171 $SIG{INT} = sub { _cleanup_dbfile(); exit 1 };
173 my $need_global_cleanup;
174 sub _cleanup_dbfile {
175 # cleanup if this is us
177 ! $ENV{DBICTEST_LOCK_HOLDER}
179 $ENV{DBICTEST_LOCK_HOLDER} == -1
181 $ENV{DBICTEST_LOCK_HOLDER} == $$
183 if ($need_global_cleanup and my $dbh = DBICTest->schema->storage->_dbh) {
187 my $db_file = _sqlite_dbfilename();
188 unlink $_ for ($db_file, "${db_file}-journal");
193 return $ENV{"DBICTEST_DSN"} ? 1:0;
199 return $self->_sqlite_dbfilename if (
200 defined $args{sqlite_use_file} ? $args{sqlite_use_file} : $ENV{'DBICTEST_SQLITE_USE_FILE'}
209 if ($ENV{DBICTEST_DSN}) {
211 (map { $ENV{"DBICTEST_${_}"} || '' } qw/DSN DBUSER DBPASS/),
212 { AutoCommit => 1, %args },
215 my $db_file = $self->_sqlite_dbname(%args);
217 for ($db_file, "${db_file}-journal") {
219 unlink ($_) or carp (
220 "Unable to unlink existing test database file $_ ($!), creation of fresh database / further tests may fail!"
224 return ("dbi:SQLite:${db_file}", '', '', {
227 # this is executed on every connect, and thus installs a disconnect/DESTROY
228 # guard for every new $dbh
229 on_connect_do => sub {
232 my $dbh = $storage->_get_dbh;
235 $dbh->do ('PRAGMA synchronous = OFF');
238 $ENV{DBICTEST_SQLITE_REVERSE_DEFAULT_ORDER}
240 # the pragma does not work correctly before libsqlite 3.7.9
241 $storage->_server_info->{normalized_dbms_version} >= 3.007009
243 $dbh->do ('PRAGMA reverse_unordered_selects = ON');
246 # set a *DBI* disconnect callback, to make sure the physical SQLite
247 # file is still there (i.e. the test does not attempt to delete
248 # an open database, which fails on Win32)
249 if (! $storage->{master} and my $guard_cb = __mk_disconnect_guard($db_file)) {
250 $dbh->{Callbacks} = {
251 connect => sub { $guard_cb->('connect') },
252 disconnect => sub { $guard_cb->('disconnect') },
253 DESTROY => sub { &detected_reinvoked_destructor; $guard_cb->('DESTROY') },
261 sub __mk_disconnect_guard {
266 # this perl leaks handles, delaying DESTROY, can't work right
273 my $orig_inode = (stat($db_file))[1]
276 my $clan_connect_caller = '*UNKNOWN*';
278 while ( my ($pack, $file, $line) = caller(++$i) ) {
279 next if $file eq __FILE__;
280 next if $pack =~ /^DBIx::Class|^Try::Tiny/;
281 $clan_connect_caller = "$file line $line";
288 return if $failed_once;
291 if ($event eq 'connect') {
292 # this is necessary in case we are disconnected and connected again, all within the same $dbh object
296 elsif ($event eq 'disconnect') {
297 return unless $connected; # we already disconnected earlier
300 elsif ($event eq 'DESTROY' and ! $connected ) {
306 $fail_reason = 'is missing';
309 my $cur_inode = (stat($db_file))[1];
311 if ($orig_inode != $cur_inode) {
312 my @inodes = ($orig_inode, $cur_inode);
313 # unless this is a fixed perl (P5RT#84590) pack/unpack before display
314 # to match the unsigned longs returned by `stat`
315 @inodes = map { unpack ('L', pack ('l', $_) ) } @inodes
316 unless $Config{st_ino_size};
318 $fail_reason = sprintf
319 'was recreated (initially inode %s, now %s)',
328 require Test::Builder;
329 my $t = Test::Builder->new;
330 local $Test::Builder::Level = $Test::Builder::Level + 3;
332 "$db_file originally created at $clan_connect_caller $fail_reason before $event "
333 . 'of DBI handle - a strong indicator that the database file was tampered with while '
334 . 'still being open. This action would fail massively if running under Win32, hence '
335 . 'we make sure it fails on any OS :)'
339 return; # this empty return is a DBI requirement
343 my $weak_registry = {};
352 $ENV{DBICTEST_VIA_REPLICATED} &&=
353 ( !$args{storage_type} && !defined $args{sqlite_use_file} )
355 $args{storage_type} = ['::DBI::Replicated', { balancer_type => '::Random' }];
356 $args{sqlite_use_file} = 1;
359 my @dsn = $self->_database(%args);
361 if ($args{compose_connection}) {
362 $need_global_cleanup = 1;
363 $schema = DBICTest::Schema->compose_connection(
367 $schema = DBICTest::Schema->compose_namespace('DBICTest');
370 if( $args{storage_type}) {
371 $schema->storage_type($args{storage_type});
374 if ( !$args{no_connect} ) {
375 $schema->connection(@dsn);
377 $schema->storage->connect_replicants(\@dsn)
378 if $ENV{DBICTEST_VIA_REPLICATED};
381 if ( !$args{no_deploy} ) {
382 __PACKAGE__->deploy_schema( $schema, $args{deploy_args} );
383 __PACKAGE__->populate_schema( $schema )
384 if( !$args{no_populate} );
387 populate_weakregistry ( $weak_registry, $schema->storage )
388 if $INC{'Test/Builder.pm'} and $schema->storage;
394 # Make sure we run after any cleanup in other END blocks
395 push @{ B::end_av()->object_2svref }, sub {
396 assert_empty_weakregistry($weak_registry, 'quiet');
402 DBICTest->deploy_schema( $schema );
404 This method does one of two things to the schema. It can either call
405 the experimental $schema->deploy() if the DBICTEST_SQLT_DEPLOY environment
406 variable is set, otherwise the default is to read in the t/lib/sqlite.sql
407 file and execute the SQL within. Either way you end up with a fresh set
408 of tables for testing.
415 my $args = shift || {};
418 if ( ($ENV{TRAVIS}||'') eq 'true' and my $old_dbg = $schema->storage->debug ) {
419 $guard = scope_guard { $schema->storage->debug($old_dbg) };
420 $schema->storage->debug(0);
423 if ($ENV{"DBICTEST_SQLT_DEPLOY"}) {
424 $schema->deploy($args);
426 my $sql = slurp_bytes( 't/lib/sqlite.sql' );
427 for my $chunk ( split (/;\s*\n+/, $sql) ) {
428 if ( $chunk =~ / ^ (?! --\s* ) \S /xm ) { # there is some real sql in the chunk - a non-space at the start of the string which is not a comment
429 $schema->storage->dbh_do(sub { $_[1]->do($chunk) }) or print "Error on SQL: $chunk\n";
436 =head2 populate_schema
438 DBICTest->populate_schema( $schema );
440 After you deploy your schema you can use this method to populate
441 the tables with test data.
445 sub populate_schema {
450 if ( ($ENV{TRAVIS}||'') eq 'true' and my $old_dbg = $schema->storage->debug ) {
451 $guard = scope_guard { $schema->storage->debug($old_dbg) };
452 $schema->storage->debug(0);
455 $schema->populate('Genre', [
460 $schema->populate('Artist', [
461 [ qw/artistid name/ ],
462 [ 1, 'Caterwauler McCrae' ],
463 [ 2, 'Random Boy Band' ],
464 [ 3, 'We Are Goth' ],
467 $schema->populate('CD', [
468 [ qw/cdid artist title year genreid/ ],
469 [ 1, 1, "Spoonful of bees", 1999, 1 ],
470 [ 2, 1, "Forkful of bees", 2001 ],
471 [ 3, 1, "Caterwaulin' Blues", 1997 ],
472 [ 4, 2, "Generic Manufactured Singles", 2001 ],
473 [ 5, 3, "Come Be Depressed With Us", 1998 ],
476 $schema->populate('LinerNotes', [
477 [ qw/liner_id notes/ ],
478 [ 2, "Buy Whiskey!" ],
480 [ 5, "Kill Yourself!" ],
483 $schema->populate('Tag', [
484 [ qw/tagid cd tag/ ],
496 $schema->populate('TwoKeys', [
503 $schema->populate('FourKeys', [
504 [ qw/foo bar hello goodbye sensors/ ],
505 [ 1, 2, 3, 4, 'online' ],
506 [ 5, 4, 3, 6, 'offline' ],
509 $schema->populate('OneKey', [
510 [ qw/id artist cd/ ],
516 $schema->populate('SelfRef', [
522 $schema->populate('SelfRefAlias', [
523 [ qw/self_ref alias/ ],
527 $schema->populate('ArtistUndirectedMap', [
532 $schema->populate('Producer', [
533 [ qw/producerid name/ ],
534 [ 1, 'Matt S Trout' ],
535 [ 2, 'Bob The Builder' ],
536 [ 3, 'Fred The Phenotype' ],
539 $schema->populate('CD_to_Producer', [
546 $schema->populate('TreeLike', [
547 [ qw/id parent name/ ],
548 [ 1, undef, 'root' ],
557 $schema->populate('Track', [
558 [ qw/trackid cd position title/ ],
559 [ 4, 2, 1, "Stung with Success"],
560 [ 5, 2, 2, "Stripy"],
561 [ 6, 2, 3, "Sticky Honey"],
562 [ 7, 3, 1, "Yowlin"],
563 [ 8, 3, 2, "Howlin"],
564 [ 9, 3, 3, "Fowlin"],
565 [ 10, 4, 1, "Boring Name"],
566 [ 11, 4, 2, "Boring Song"],
567 [ 12, 4, 3, "No More Ideas"],
569 [ 14, 5, 2, "Under The Weather"],
570 [ 15, 5, 3, "Suicidal"],
571 [ 16, 1, 1, "The Bees Knees"],
572 [ 17, 1, 2, "Apiary"],
573 [ 18, 1, 3, "Beehind You"],
576 $schema->populate('Event', [
577 [ qw/id starts_at created_on varchar_date varchar_datetime skip_inflation/ ],
578 [ 1, '2006-04-25 22:24:33', '2006-06-22 21:00:05', '2006-07-23', '2006-05-22 19:05:07', '2006-04-21 18:04:06'],
581 $schema->populate('Link', [
582 [ qw/id url title/ ],
586 $schema->populate('Bookmark', [
591 $schema->populate('Collection', [
592 [ qw/collectionid name/ ],
597 $schema->populate('TypedObject', [
598 [ qw/objectid type value/ ],
599 [ 1, "pointy", "Awl" ],
600 [ 2, "round", "Bearing" ],
601 [ 3, "pointy", "Knife" ],
602 [ 4, "pointy", "Tooth" ],
603 [ 5, "round", "Head" ],
605 $schema->populate('CollectionObject', [
606 [ qw/collection object/ ],
614 $schema->populate('Owners', [
620 $schema->populate('BooksInLibrary', [
621 [ qw/id owner title source price/ ],
622 [ 1, 1, "Programming Perl", "Library", 23 ],
623 [ 2, 1, "Dynamical Systems", "Library", 37 ],
624 [ 3, 2, "Best Recipe Cookbook", "Library", 65 ],