Commit | Line | Data |
bab77431 |
1 | package # hide from PAUSE |
c6d74d3e |
2 | DBICTest::Schema; |
a02675cd |
3 | |
65d35121 |
4 | use strict; |
5 | use warnings; |
6 | no warnings 'qw'; |
a02675cd |
7 | |
65d35121 |
8 | use base 'DBIx::Class::Schema'; |
9 | |
8d6b1478 |
10 | use Fcntl qw/:DEFAULT :seek :flock/; |
11 | use Time::HiRes 'sleep'; |
9b871b00 |
12 | use DBICTest::RunMode; |
8d6b1478 |
13 | use DBICTest::Util qw/populate_weakregistry assert_empty_weakregistry local_umask/; |
65d35121 |
14 | use namespace::clean; |
5ce32fc1 |
15 | |
71829446 |
16 | __PACKAGE__->mk_group_accessors(simple => 'custom_attr'); |
17 | |
a02675cd |
18 | __PACKAGE__->load_classes(qw/ |
5ce32fc1 |
19 | Artist |
b829910c |
20 | SequenceTest |
6ec7d1bb |
21 | BindType |
4e298a80 |
22 | Employee |
5ce32fc1 |
23 | CD |
370f2ba2 |
24 | Genre |
9c2c91ea |
25 | Bookmark |
97c96475 |
26 | Link |
5ce32fc1 |
27 | #dummy |
28 | Track |
29 | Tag |
a648ec78 |
30 | Year2000CDs |
1ee9aa72 |
31 | Year1999CDs |
b8b55c8e |
32 | CustomSql |
818ec409 |
33 | Money |
4d4dc518 |
34 | TimestampPrimaryKey |
5ce32fc1 |
35 | /, |
36 | { 'DBICTest::Schema' => [qw/ |
37 | LinerNotes |
4f6386b0 |
38 | Artwork |
d5633096 |
39 | Artwork_to_Artist |
4f6386b0 |
40 | Image |
41 | Lyrics |
42 | LyricVersion |
5ce32fc1 |
43 | OneKey |
44 | #dummy |
45 | TwoKeys |
9fcda149 |
46 | Serialized |
5ce32fc1 |
47 | /]}, |
48 | ( |
49 | 'FourKeys', |
3bd6e3e0 |
50 | 'FourKeys_to_TwoKeys', |
5ce32fc1 |
51 | '#dummy', |
52 | 'SelfRef', |
5efe4c79 |
53 | 'ArtistUndirectedMap', |
bab77431 |
54 | 'ArtistSourceName', |
b1fb2c94 |
55 | 'ArtistSubclass', |
7411204b |
56 | 'Producer', |
57 | 'CD_to_Producer', |
181c0934 |
58 | 'Dummy', # this is a real result class we remove in the hook below |
5ce32fc1 |
59 | ), |
dda9af55 |
60 | qw/SelfRefAlias TreeLike TwoKeyTreeLike Event EventTZ NoPrimaryKey/, |
a7e65bb5 |
61 | qw/Collection CollectionObject TypedObject Owners BooksInLibrary/, |
6e6b37a7 |
62 | qw/ForceForeign Encoded/, |
5ce32fc1 |
63 | ); |
a02675cd |
64 | |
d6c79cb3 |
65 | sub sqlt_deploy_hook { |
66 | my ($self, $sqlt_schema) = @_; |
67 | |
458e0292 |
68 | $sqlt_schema->drop_table('dummy'); |
d6c79cb3 |
69 | } |
70 | |
65d35121 |
71 | |
8d6b1478 |
72 | our $locker; |
73 | END { |
74 | # we need the $locker to be referenced here for delayed destruction |
75 | if ($locker->{lock_name} and ($ENV{DBICTEST_LOCK_HOLDER}||0) == $$) { |
76 | #warn "$$ $0 $locktype LOCK RELEASED"; |
77 | } |
65d35121 |
78 | } |
79 | |
8d6b1478 |
80 | my $weak_registry = {}; |
81 | |
6892eb09 |
82 | sub connection { |
83 | my $self = shift->next::method(@_); |
6918c70e |
84 | |
8d6b1478 |
85 | # MASSIVE FIXME |
86 | # we can't really lock based on DSN, as we do not yet have a way to tell that e.g. |
87 | # DBICTEST_MSSQL_DSN=dbi:Sybase:server=192.168.0.11:1433;database=dbtst |
88 | # and |
89 | # DBICTEST_MSSQL_ODBC_DSN=dbi:ODBC:server=192.168.0.11;port=1433;database=dbtst;driver=FreeTDS;tds_version=8.0 |
90 | # are the same server |
91 | # hence we lock everything based on sqlt_type or just globally if not available |
92 | # just pretend we are python you know? :) |
93 | |
94 | |
95 | # when we get a proper DSN resolution sanitize to produce a portable lockfile name |
96 | # this may look weird and unnecessary, but consider running tests from |
97 | # windows over a samba share >.> |
98 | #utf8::encode($dsn); |
99 | #$dsn =~ s/([^A-Za-z0-9_\-\.\=])/ sprintf '~%02X', ord($1) /ge; |
100 | #$dsn =~ s/^dbi/dbi/i; |
101 | |
102 | # provide locking for physical (non-memory) DSNs, so that tests can |
103 | # safely run in parallel. While the harness (make -jN test) does set |
104 | # an envvar, we can not detect when a user invokes prove -jN. Hence |
105 | # perform the locking at all times, it shouldn't hurt. |
106 | # the lock fh *should* inherit across forks/subprocesses |
107 | # |
108 | # File locking is hard. Really hard. By far the best lock implementation |
109 | # I've seen is part of the guts of File::Temp. However it is sadly not |
110 | # reusable. Since I am not aware of folks doing NFS parallel testing, |
111 | # nor are we known to work on VMS, I am just going to punt this and |
112 | # use the portable-ish flock() provided by perl itself. If this does |
113 | # not work for you - patches more than welcome. |
114 | if ( |
115 | ! $DBICTest::global_exclusive_lock |
116 | and |
117 | ( ! $ENV{DBICTEST_LOCK_HOLDER} or $ENV{DBICTEST_LOCK_HOLDER} == $$ ) |
118 | and |
119 | ref($_[0]) ne 'CODE' |
120 | and |
121 | ($_[0]||'') !~ /^ (?i:dbi) \: SQLite \: (?: dbname\= )? (?: \:memory\: | t [\/\\] var [\/\\] DBIxClass\-) /x |
122 | ) { |
123 | |
124 | my $locktype = do { |
125 | # guard against infinite recursion |
126 | local $ENV{DBICTEST_LOCK_HOLDER} = -1; |
127 | |
128 | # we need to connect a forced fresh clone so that we do not upset any state |
129 | # of the main $schema (some tests examine it quite closely) |
130 | local $@; |
131 | my $storage = eval { |
132 | my $st = ref($self)->connect(@{$self->storage->connect_info})->storage; |
133 | $st->ensure_connected; # do connect here, to catch a possible throw |
134 | $st; |
135 | }; |
136 | $storage |
137 | ? do { |
138 | my $t = $storage->sqlt_type || 'generic'; |
139 | eval { $storage->disconnect }; |
140 | $t; |
141 | } |
142 | : undef |
143 | ; |
144 | }; |
145 | |
146 | |
147 | # Never hold more than one lock. This solves the "lock in order" issues |
148 | # unrelated tests may have |
149 | # Also if there is no connection - there is no lock to be had |
150 | if ($locktype and (!$locker or $locker->{type} ne $locktype)) { |
151 | |
9b871b00 |
152 | warn "$$ $0 $locktype" if ( |
153 | ($locktype eq 'generic' or $locktype eq 'SQLite') |
154 | and |
155 | DBICTest::RunMode->is_author |
156 | ); |
8d6b1478 |
157 | |
9b871b00 |
158 | my $lockpath = DBICTest::RunMode->tmpdir->file(".dbictest_$locktype.lock"); |
8d6b1478 |
159 | |
160 | my $lock_fh; |
161 | { |
162 | my $u = local_umask(0); # so that the file opens as 666, and any user can lock |
163 | sysopen ($lock_fh, $lockpath, O_RDWR|O_CREAT) or die "Unable to open $lockpath: $!"; |
164 | } |
165 | flock ($lock_fh, LOCK_EX) or die "Unable to lock $lockpath: $!"; |
166 | #warn "$$ $0 $locktype LOCK GRABBED"; |
167 | |
168 | # see if anyone was holding a lock before us, and wait up to 5 seconds for them to terminate |
169 | # if we do not do this we may end up trampling over some long-running END or somesuch |
170 | seek ($lock_fh, 0, SEEK_SET) or die "seek failed $!"; |
f3ec358e |
171 | my $old_pid; |
172 | if ( |
173 | read ($lock_fh, $old_pid, 100) |
174 | and |
175 | ($old_pid) = $old_pid =~ /^(\d+)$/ |
176 | ) { |
8d6b1478 |
177 | for (1..50) { |
178 | kill (0, $old_pid) or last; |
179 | sleep 0.1; |
180 | } |
181 | } |
182 | #warn "$$ $0 $locktype POST GRAB WAIT"; |
183 | |
184 | truncate $lock_fh, 0; |
185 | seek ($lock_fh, 0, SEEK_SET) or die "seek failed $!"; |
186 | $lock_fh->autoflush(1); |
187 | print $lock_fh $$; |
188 | |
189 | $ENV{DBICTEST_LOCK_HOLDER} ||= $$; |
190 | |
191 | $locker = { |
192 | type => $locktype, |
193 | fh => $lock_fh, |
194 | lock_name => "$lockpath", |
195 | }; |
196 | } |
197 | } |
198 | |
6918c70e |
199 | if ($INC{'Test/Builder.pm'}) { |
200 | populate_weakregistry ( $weak_registry, $self->storage ); |
201 | |
202 | my $cur_connect_call = $self->storage->on_connect_call; |
203 | |
204 | $self->storage->on_connect_call([ |
205 | (ref $cur_connect_call eq 'ARRAY' |
206 | ? @$cur_connect_call |
207 | : ($cur_connect_call || ()) |
208 | ), |
209 | [sub { |
210 | populate_weakregistry( $weak_registry, shift->_dbh ) |
211 | }], |
212 | ]); |
213 | } |
214 | |
8d6b1478 |
215 | return $self; |
216 | } |
217 | |
218 | sub clone { |
219 | my $self = shift->next::method(@_); |
220 | populate_weakregistry ( $weak_registry, $self ) |
221 | if $INC{'Test/Builder.pm'}; |
6892eb09 |
222 | $self; |
223 | } |
224 | |
65d35121 |
225 | END { |
226 | assert_empty_weakregistry($weak_registry, 'quiet'); |
227 | } |
228 | |
a02675cd |
229 | 1; |