line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Test::DB::Shared::mysqld; |
2
|
|
|
|
|
|
|
$Test::DB::Shared::mysqld::VERSION = '0.004'; |
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
=head1 NAME |
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
Test::DB::Shared::mysqld - Replaces (and decorate) Test::mysqld to share the MySQL instance between your tests |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
=head1 SYNOPSIS |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
If in your test you use L, this acts as a replacement for Test::mysqld: |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
my $mysqld = Test::DB::Shared::mysqld->new( |
13
|
|
|
|
|
|
|
test_namespace => 'myapp', |
14
|
|
|
|
|
|
|
# Then it's plain Test::mysqld config |
15
|
|
|
|
|
|
|
my_cnf => { |
16
|
|
|
|
|
|
|
'skip-networking' => '', # no TCP socket |
17
|
|
|
|
|
|
|
} |
18
|
|
|
|
|
|
|
); |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# and use like Test::mysqld: |
21
|
|
|
|
|
|
|
my $dbh = DBI->connect( |
22
|
|
|
|
|
|
|
$mysqld->dsn(), undef, '' |
23
|
|
|
|
|
|
|
); |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
And that's it. No special code to write, no restructuring of your tests, and using as |
26
|
|
|
|
|
|
|
a prove plugin is optional. |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
=head1 STATEMENT |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
What you need is a test database, not a test mysqld instance. |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
=head1 HOW TO USE IT |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
See synopsis for the change to your test code. For the rest, you need to use C |
35
|
|
|
|
|
|
|
to benefit from it. |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
If not all your test use the test db, best results will be obtained by using C |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
=head2 Using it as a prove Plugin |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
To speed things even further, you can use that as a prove plugin, with an optional config file: |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
prove -PTest::DB::Shared::mysqld |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
Or |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
prove -PTest::DB::Shared::mysqld=./testmysqld.json |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
The ./testmysqld.json file can contain the arguments to Test::DB::Shared::mysqld in a json format (see SYNOPSIS). They |
50
|
|
|
|
|
|
|
will be used to build one instance for the whole test suite. |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
If no such file is given, the default configuration is the one specified in the SYNOPSIS, but with a randomly generated test_namespace. |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
Note that using this plugin will result in all your Test::DB::Shared::mysqld instances in your t/ files using the same configuration, |
55
|
|
|
|
|
|
|
regardless of what configuration you give in this or this test. |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
=head1 LIMITATIONS |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
Do NOT use that if your test involves doing anything outside a test database. Tests that manage databases |
60
|
|
|
|
|
|
|
will probably break this. |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
Not all mysqld methods are available. Calls like 'start', 'stop', 'setup', 'read_log' .. are not implemented. |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=head1 WHAT THIS DOES |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
The first time this is used, it will create a Test::mysqld instance in the current process. Then concurrent processes |
67
|
|
|
|
|
|
|
that use the same module (with the same parameters) will be given a new Database in this already running instance, instead |
68
|
|
|
|
|
|
|
of a new MySQL instance. |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
When this goes out of scope, the test database is destroy, and the last process to destroy the last database will tear |
71
|
|
|
|
|
|
|
down the MySQL instance. |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=head1 BUGS, DIAGNOSTICS and TROUBLESHOOTING |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
There are probably some. To diagnose them, you can run your test in verbose mode ( prove -v ). If that doesn't help, |
76
|
|
|
|
|
|
|
you can 'use Log::Any::Adapter qw/Stderr/' at the top of your test to get some very verbose tracing. |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
If you SIGKILL your whole test suite, bad things will happen. Running in verbose mode |
79
|
|
|
|
|
|
|
will most probably tell you which files you should clean up on your filesystem to get back to a working state. |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
=head1 METHODS |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=cut |
84
|
|
|
|
|
|
|
|
85
|
5
|
|
|
5
|
|
167046
|
use Moo; |
|
5
|
|
|
|
|
46803
|
|
|
5
|
|
|
|
|
21
|
|
86
|
5
|
|
|
5
|
|
5510
|
use Carp qw/confess/; |
|
5
|
|
|
|
|
10
|
|
|
5
|
|
|
|
|
189
|
|
87
|
5
|
|
|
5
|
|
1870
|
use Log::Any qw/$log/; |
|
5
|
|
|
|
|
35201
|
|
|
5
|
|
|
|
|
21
|
|
88
|
|
|
|
|
|
|
|
89
|
5
|
|
|
5
|
|
8272
|
use DBI; |
|
5
|
|
|
|
|
11
|
|
|
5
|
|
|
|
|
138
|
|
90
|
|
|
|
|
|
|
|
91
|
5
|
|
|
5
|
|
2316
|
use JSON; |
|
5
|
|
|
|
|
35533
|
|
|
5
|
|
|
|
|
22
|
|
92
|
5
|
|
|
5
|
|
2289
|
use Test::mysqld; |
|
5
|
|
|
|
|
129589
|
|
|
5
|
|
|
|
|
139
|
|
93
|
|
|
|
|
|
|
|
94
|
5
|
|
|
5
|
|
1772
|
use File::Slurp; |
|
5
|
|
|
|
|
12329
|
|
|
5
|
|
|
|
|
316
|
|
95
|
5
|
|
|
5
|
|
35
|
use File::Spec; |
|
5
|
|
|
|
|
11
|
|
|
5
|
|
|
|
|
99
|
|
96
|
5
|
|
|
5
|
|
1925
|
use File::Flock::Tiny; |
|
5
|
|
|
|
|
4000
|
|
|
5
|
|
|
|
|
168
|
|
97
|
|
|
|
|
|
|
|
98
|
5
|
|
|
5
|
|
39
|
use POSIX qw(SIGTERM WNOHANG); |
|
5
|
|
|
|
|
10
|
|
|
5
|
|
|
|
|
26
|
|
99
|
|
|
|
|
|
|
|
100
|
5
|
|
|
5
|
|
248
|
use Test::More qw//; |
|
5
|
|
|
|
|
10
|
|
|
5
|
|
|
|
|
7910
|
|
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
# Settings |
103
|
|
|
|
|
|
|
has 'test_namespace' => ( is => 'ro', default => 'test_db_shared' ); |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
# Public facing stuff |
106
|
|
|
|
|
|
|
has 'dsn' => ( is => 'lazy' ); |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
# Internal cuisine |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
has '_lock_file' => ( is => 'lazy' ); |
112
|
|
|
|
|
|
|
has '_mysqld_file' => ( is => 'lazy' ); |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
sub _build__lock_file{ |
115
|
0
|
|
|
0
|
|
0
|
my ($self) = @_; |
116
|
0
|
|
|
|
|
0
|
return File::Spec->catfile( File::Spec->tmpdir() , $self->_namespace().'.lock' ).''; |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
sub _build__mysqld_file{ |
119
|
0
|
|
|
0
|
|
0
|
my ($self) = @_; |
120
|
0
|
|
|
|
|
0
|
return File::Spec->catfile( File::Spec->tmpdir() , $self->_namespace().'.mysqld' ).''; |
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
has '_testmysqld_args' => ( is => 'ro', required => 1); |
124
|
|
|
|
|
|
|
has '_temp_db_name' => ( is => 'lazy' ); |
125
|
|
|
|
|
|
|
has '_shared_mysqld' => ( is => 'lazy' ); |
126
|
|
|
|
|
|
|
has '_instance_pid' => ( is => 'ro', required => 1); |
127
|
|
|
|
|
|
|
has '_holds_mysqld' => ( is => 'rw' ); |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
my $PROCESS_INSTANCES = {}; |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
around BUILDARGS => sub { |
132
|
|
|
|
|
|
|
my ($orig, $class, @rest ) = @_; |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
my $hash_args = $class->$orig(@rest); |
135
|
|
|
|
|
|
|
my $test_namespace = delete $hash_args->{test_namespace}; |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
return { |
138
|
|
|
|
|
|
|
_testmysqld_args => $hash_args, |
139
|
|
|
|
|
|
|
_instance_pid => $$, |
140
|
|
|
|
|
|
|
( $test_namespace ? ( test_namespace => $test_namespace ) : () ), |
141
|
|
|
|
|
|
|
( $ENV{TEST_DB_SHARED_NAMESPACE} ? ( test_namespace => $ENV{TEST_DB_SHARED_NAMESPACE} ) : () ), |
142
|
|
|
|
|
|
|
} |
143
|
|
|
|
|
|
|
}; |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
sub BUILD{ |
146
|
0
|
|
|
0
|
0
|
0
|
my ($self) = @_; |
147
|
0
|
|
|
|
|
0
|
my $wself = \$self; |
148
|
0
|
|
|
|
|
0
|
Scalar::Util::weaken( $self ); |
149
|
0
|
|
|
|
|
0
|
$PROCESS_INSTANCES->{$self.''} = $wself; |
150
|
0
|
|
|
|
|
0
|
return $self; |
151
|
|
|
|
|
|
|
} |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
=head2 load |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
L plugin implementation. Do NOT use that yourself. |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
=cut |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
{ |
160
|
|
|
|
|
|
|
my $plugin_instance; |
161
|
|
|
|
|
|
|
sub load{ |
162
|
0
|
|
|
0
|
1
|
0
|
my ($class, $prove) = @_; |
163
|
0
|
0
|
|
|
|
0
|
my @args = @{$prove->{args} || []}; |
|
0
|
|
|
|
|
0
|
|
164
|
0
|
|
|
|
|
0
|
my $config = { |
165
|
|
|
|
|
|
|
test_namespace => 'plugin'.$$.int( rand(1000) ), |
166
|
|
|
|
|
|
|
my_cnf => { |
167
|
|
|
|
|
|
|
'skip-networking' => '', # no TCP socket |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
}; |
170
|
0
|
|
|
|
|
0
|
my $config_file = $args[0]; |
171
|
0
|
0
|
|
|
|
0
|
unless( $config_file ){ |
172
|
0
|
|
|
|
|
0
|
Test::More::diag( __PACKAGE__." PID $$ config file is not given. Using default config" ); |
173
|
|
|
|
|
|
|
}else{ |
174
|
0
|
0
|
|
|
|
0
|
if( ! -e $config_file ){ |
175
|
0
|
|
|
|
|
0
|
confess("Cannot find config file $config_file"); |
176
|
|
|
|
|
|
|
}else{ |
177
|
0
|
|
|
|
|
0
|
$config = JSON::decode_json( scalar( File::Slurp::read_file( $config_file, { binmode => ':raw' } ) ) ); |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
} |
180
|
0
|
|
|
|
|
0
|
$plugin_instance = $class->new( $config ); |
181
|
|
|
|
|
|
|
## Just in case. |
182
|
0
|
|
|
|
|
0
|
unlink( $plugin_instance->_mysqld_file() ); |
183
|
0
|
|
|
|
|
0
|
Test::More::diag( __PACKAGE__." PID $$ plugin instance mysqld lives at ".$plugin_instance->dsn() ); |
184
|
0
|
|
|
|
|
0
|
Test::More::diag( __PACKAGE__." PID $$ plugin instance mysqld descriptor is ".$plugin_instance->_mysqld_file() ); |
185
|
|
|
|
|
|
|
# This will inform all the other instances to reuse the namespace (see BUILDARGS). |
186
|
0
|
|
|
|
|
0
|
$ENV{TEST_DB_SHARED_NAMESPACE} = $plugin_instance->test_namespace(); |
187
|
0
|
|
|
|
|
0
|
return 1; |
188
|
|
|
|
|
|
|
} |
189
|
0
|
|
|
0
|
0
|
0
|
sub plugin_instance{ return $plugin_instance; } |
190
|
5
|
|
|
5
|
0
|
27
|
sub tear_down_plugin_instance{ $plugin_instance = undef; } |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
# For the plugin to 'just work' |
193
|
|
|
|
|
|
|
# on unloading this code. |
194
|
|
|
|
|
|
|
sub END{ |
195
|
5
|
|
|
5
|
|
1990
|
__PACKAGE__->tear_down_plugin_instance(); |
196
|
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
} |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
sub _namespace{ |
202
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
203
|
0
|
|
|
|
|
|
return 'tdbs49C7_'.$self->test_namespace(); |
204
|
|
|
|
|
|
|
} |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
# Build a temp DB name according to this pid. |
207
|
|
|
|
|
|
|
# Note it only works because the instance of the DB will run locally. |
208
|
|
|
|
|
|
|
sub _build__temp_db_name{ |
209
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
210
|
0
|
|
|
|
|
|
return $self->_namespace().( $self + $$ ); |
211
|
|
|
|
|
|
|
} |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
sub _build__shared_mysqld{ |
214
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
215
|
|
|
|
|
|
|
# Two cases here. |
216
|
|
|
|
|
|
|
# Either the test mysqld is there and we returned the already built dsn |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
# Or it's not there and we need to build it IN A MUTEX way. |
219
|
|
|
|
|
|
|
# For a start, let's assume it's not there |
220
|
|
|
|
|
|
|
return $self->_monitor(sub{ |
221
|
0
|
|
|
0
|
|
|
my $saved_mysqld; |
222
|
0
|
0
|
|
|
|
|
if( ! -e $self->_mysqld_file() ){ |
223
|
0
|
|
|
|
|
|
Test::More::note( "PID $$ Creating new Test::mysqld instance" ); |
224
|
0
|
|
|
|
|
|
$log->info("PID $$ Creating new Test::mysqld instance"); |
225
|
0
|
0
|
|
|
|
|
my $mysqld = Test::mysqld->new( %{$self->_testmysqld_args()} ) or confess |
|
0
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
$Test::mysqld::errstr; |
227
|
0
|
|
|
|
|
|
$log->trace("PID $$ Saving all $mysqld public properties"); |
228
|
|
|
|
|
|
|
|
229
|
0
|
|
|
|
|
|
$saved_mysqld = {}; |
230
|
0
|
|
|
|
|
|
foreach my $property ( 'dsn', 'pid' ){ |
231
|
0
|
|
|
|
|
|
$saved_mysqld->{$property} = $mysqld->$property().'' |
232
|
|
|
|
|
|
|
} |
233
|
0
|
|
|
|
|
|
$saved_mysqld->{pid_file} = $mysqld->my_cnf()->{'pid-file'}; |
234
|
|
|
|
|
|
|
# DO NOT LET mysql think it can manage its mysqld PID |
235
|
0
|
|
|
|
|
|
$mysqld->pid( undef ); |
236
|
|
|
|
|
|
|
|
237
|
0
|
|
|
|
|
|
$self->_holds_mysqld( $mysqld ); |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
# Create the pid_registry container. |
240
|
0
|
|
|
|
|
|
$log->trace("PID $$ creating pid_registry table in instance"); |
241
|
|
|
|
|
|
|
$self->_with_shared_dbh( $saved_mysqld->{dsn}, |
242
|
|
|
|
|
|
|
sub{ |
243
|
0
|
|
|
|
|
|
my ($dbh) = @_; |
244
|
0
|
|
|
|
|
|
$dbh->do('CREATE TABLE pid_registry(pid INTEGER NOT NULL, instance BIGINT NOT NULL, PRIMARY KEY(pid, instance))'); |
245
|
0
|
|
|
|
|
|
}); |
246
|
0
|
|
|
|
|
|
my $json_mysqld = JSON::encode_json( $saved_mysqld ); |
247
|
0
|
|
|
|
|
|
$log->trace("PID $$ Saving ".$json_mysqld ); |
248
|
0
|
|
|
|
|
|
File::Slurp::write_file( $self->_mysqld_file() , {binmode => ':raw'}, |
249
|
|
|
|
|
|
|
$json_mysqld ); |
250
|
|
|
|
|
|
|
} else { |
251
|
0
|
|
|
|
|
|
Test::More::note("PID $$ Reusing Test::mysqld from ".$self->_mysqld_file()); |
252
|
0
|
|
|
|
|
|
$log->info("PID $$ file ".$self->_mysqld_file()." is there. Reusing cluster"); |
253
|
0
|
|
|
|
|
|
$saved_mysqld = JSON::decode_json( |
254
|
|
|
|
|
|
|
scalar( File::Slurp::read_file( $self->_mysqld_file() , {binmode => ':raw'} ) ) |
255
|
|
|
|
|
|
|
); |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
$self->_with_shared_dbh( $saved_mysqld->{dsn}, |
259
|
|
|
|
|
|
|
sub{ |
260
|
0
|
|
|
|
|
|
my $dbh = shift; |
261
|
0
|
|
|
|
|
|
$dbh->do('INSERT INTO pid_registry( pid, instance ) VALUES (?,?)' , {}, |
262
|
|
|
|
|
|
|
$self->_instance_pid(), ( $self + $self->_instance_pid() ) |
263
|
|
|
|
|
|
|
); |
264
|
0
|
|
|
|
|
|
}); |
265
|
0
|
|
|
|
|
|
return $saved_mysqld; |
266
|
0
|
|
|
|
|
|
}); |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
sub _build_dsn{ |
270
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
271
|
0
|
0
|
|
|
|
|
if( $$ != $self->_instance_pid() ){ |
272
|
0
|
|
|
|
|
|
confess("Do not build the dsn in a subprocess of this instance creator"); |
273
|
|
|
|
|
|
|
} |
274
|
|
|
|
|
|
|
|
275
|
0
|
|
|
|
|
|
my $dsn = $self->_shared_mysqld()->{dsn}; |
276
|
|
|
|
|
|
|
return $self->_with_shared_dbh( $dsn, sub{ |
277
|
0
|
|
|
0
|
|
|
my $dbh = shift; |
278
|
0
|
|
|
|
|
|
my $temp_db_name = $self->_temp_db_name(); |
279
|
0
|
|
|
|
|
|
$log->info("PID $$ creating temporary database '$temp_db_name' on $dsn"); |
280
|
0
|
|
|
|
|
|
$dbh->do('CREATE DATABASE '.$temp_db_name); |
281
|
0
|
|
|
|
|
|
$dsn =~ s/dbname=([^;])+/dbname=$temp_db_name/; |
282
|
0
|
|
|
|
|
|
$log->info("PID $$ local dsn is '$dsn'"); |
283
|
0
|
|
|
|
|
|
return $dsn; |
284
|
0
|
|
|
|
|
|
}); |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
sub _teardown{ |
288
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
289
|
0
|
|
|
|
|
|
my $dsn = $self->_shared_mysqld()->{dsn}; |
290
|
|
|
|
|
|
|
$self->_with_shared_dbh( $dsn, |
291
|
|
|
|
|
|
|
sub{ |
292
|
0
|
|
|
0
|
|
|
my $dbh = shift; |
293
|
0
|
|
|
|
|
|
$dbh->do('DELETE FROM pid_registry WHERE pid = ? AND instance = ? ',{}, $self->_instance_pid() , ( $self + $self->_instance_pid() ) ); |
294
|
0
|
|
|
|
|
|
my ( $count_row ) = $dbh->selectrow_array('SELECT COUNT(*) FROM pid_registry'); |
295
|
0
|
0
|
|
|
|
|
if( $count_row ){ |
296
|
0
|
|
|
|
|
|
$log->info("PID $$ Some PIDs,Instances are still registered as using this DB. Not tearing down"); |
297
|
0
|
|
|
|
|
|
return; |
298
|
|
|
|
|
|
|
} |
299
|
0
|
|
|
|
|
|
$log->info("PID $$ no pids anymore in the DB. Tearing down"); |
300
|
0
|
|
|
|
|
|
$log->info("PID $$ unlinking ".$self->_mysqld_file()); |
301
|
0
|
|
|
|
|
|
unlink $self->_mysqld_file(); |
302
|
0
|
|
|
|
|
|
Test::More::note("PID $$ terminating mysqld instance (sending SIGTERM to ".$self->pid().")"); |
303
|
0
|
|
|
|
|
|
$log->info("PID $$ terminating mysqld instance (sending SIGTERM to ".$self->pid().")"); |
304
|
0
|
|
|
|
|
|
kill SIGTERM, $self->pid(); |
305
|
0
|
|
|
|
|
|
}); |
306
|
|
|
|
|
|
|
} |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
sub DEMOLISH{ |
309
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
310
|
0
|
0
|
|
|
|
|
if( $$ != $self->_instance_pid() ){ |
311
|
|
|
|
|
|
|
# Do NOT let subprocesses that forked |
312
|
|
|
|
|
|
|
# after the creation of this to have an impact. |
313
|
0
|
|
|
|
|
|
return; |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
|
316
|
0
|
|
|
|
|
|
delete $PROCESS_INSTANCES->{$self.''}; |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
$self->_monitor(sub{ |
320
|
|
|
|
|
|
|
# We always want to drop the local process database. |
321
|
0
|
|
|
0
|
|
|
my $dsn = $self->_shared_mysqld()->{dsn}; |
322
|
0
|
|
|
|
|
|
$log->info("PID $$ Will drop database on dsn = $dsn"); |
323
|
|
|
|
|
|
|
$self->_with_shared_dbh($dsn, sub{ |
324
|
0
|
|
|
|
|
|
my $dbh = shift; |
325
|
0
|
|
|
|
|
|
my $temp_db_name = $self->_temp_db_name(); |
326
|
0
|
|
|
|
|
|
$log->info("PID $$ dropping temporary database $temp_db_name"); |
327
|
0
|
|
|
|
|
|
$dbh->do("DROP DATABASE ".$temp_db_name); |
328
|
0
|
|
|
|
|
|
}); |
329
|
0
|
|
|
|
|
|
$self->_teardown(); |
330
|
0
|
|
|
|
|
|
}); |
331
|
|
|
|
|
|
|
|
332
|
0
|
0
|
|
|
|
|
if( my @other_instances = keys( %{$PROCESS_INSTANCES} ) ){ |
|
0
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
# Other instances are still alive (in the same PID). Pass on the test mysqld to them |
334
|
|
|
|
|
|
|
# if we have one. |
335
|
0
|
0
|
|
|
|
|
if( my $test_mysqld = $self->_holds_mysqld() ){ |
336
|
0
|
|
|
|
|
|
$log->info("PID $$ instance $self giving mysqld to other living instance ".$other_instances[0]); |
337
|
0
|
|
|
|
|
|
${$PROCESS_INSTANCES->{$other_instances[0]}}->_holds_mysqld( $self->_holds_mysqld() ); |
|
0
|
|
|
|
|
|
|
338
|
0
|
|
|
|
|
|
$self->_holds_mysqld( undef ); |
339
|
|
|
|
|
|
|
} |
340
|
|
|
|
|
|
|
} |
341
|
|
|
|
|
|
|
|
342
|
0
|
0
|
|
|
|
|
if( my $test_mysqld = $self->_holds_mysqld() ){ |
343
|
|
|
|
|
|
|
# This is the mysqld holder process. Need to wait for it |
344
|
|
|
|
|
|
|
# before exiting |
345
|
0
|
|
|
|
|
|
Test::More::note("PID $$ mysqld holder process waiting for mysqld termination"); |
346
|
0
|
|
|
|
|
|
$log->info("PID $$ mysqld holder process waiting for mysqld termination"); |
347
|
0
|
|
|
|
|
|
while( waitpid( $self->pid() , 0 ) <= 0 ){ |
348
|
0
|
|
|
|
|
|
$log->info("PID $$ db pid = ".$self->pid()." not down yet. Waiting 2 seconds"); |
349
|
0
|
|
|
|
|
|
sleep(2); |
350
|
|
|
|
|
|
|
} |
351
|
0
|
|
|
|
|
|
my $pid_file = $self->_shared_mysqld()->{pid_file}; |
352
|
0
|
|
|
|
|
|
$log->trace("PID $$ unlinking mysql pidfile $pid_file. Just in case"); |
353
|
0
|
|
|
|
|
|
unlink( $pid_file ); |
354
|
0
|
|
|
|
|
|
$log->info("PID $$ Ok, mysqld is gone"); |
355
|
|
|
|
|
|
|
} |
356
|
|
|
|
|
|
|
} |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
=head1 dsn |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
Returns the dsn to connect to the test database. Note that the user is root and the password |
361
|
|
|
|
|
|
|
is the empty string. |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
=cut |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
=head2 pid |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
See L |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
=cut |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
sub pid{ |
372
|
0
|
|
|
0
|
1
|
|
my ($self) = @_; |
373
|
0
|
|
|
|
|
|
return $self->_shared_mysqld()->{pid}; |
374
|
|
|
|
|
|
|
} |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
my $in_monitor = {}; |
377
|
|
|
|
|
|
|
sub _monitor{ |
378
|
0
|
|
|
0
|
|
|
my ($self, $sub) = @_; |
379
|
|
|
|
|
|
|
|
380
|
0
|
0
|
|
|
|
|
if( $in_monitor->{$self} ){ |
381
|
0
|
|
|
|
|
|
$log->warn("PID $$ Re-entrant monitor. Will execute sub without locking for deadlock protection"); |
382
|
0
|
|
|
|
|
|
return $sub->(); |
383
|
|
|
|
|
|
|
} |
384
|
0
|
|
|
|
|
|
$log->trace("PID $$ locking file ".$self->_lock_file()); |
385
|
0
|
|
|
|
|
|
$in_monitor->{$self} = 1; |
386
|
0
|
|
|
|
|
|
my $lock = File::Flock::Tiny->lock( $self->_lock_file() ); |
387
|
0
|
|
|
|
|
|
my $res = eval{ $sub->(); }; |
|
0
|
|
|
|
|
|
|
388
|
0
|
|
|
|
|
|
my $err = $@; |
389
|
0
|
|
|
|
|
|
delete $in_monitor->{$self}; |
390
|
0
|
|
|
|
|
|
$lock->release(); |
391
|
0
|
0
|
|
|
|
|
if( $err ){ |
392
|
0
|
|
|
|
|
|
confess($err); |
393
|
|
|
|
|
|
|
} |
394
|
0
|
|
|
|
|
|
return $res; |
395
|
|
|
|
|
|
|
} |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
sub _with_shared_dbh{ |
398
|
0
|
|
|
0
|
|
|
my ($self, $dsn, $code) = @_; |
399
|
0
|
|
|
|
|
|
my $dbh = DBI->connect_cached( $dsn, 'root', '' , { RaiseError => 1 }); |
400
|
0
|
|
|
|
|
|
return $code->($dbh); |
401
|
|
|
|
|
|
|
} |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
__PACKAGE__->meta->make_immutable(); |