File Coverage

blib/lib/Test/mysqld.pm
Criterion Covered Total %
statement 57 188 30.3
branch 10 94 10.6
condition 5 46 10.8
subroutine 14 26 53.8
pod 8 12 66.6
total 94 366 25.6


line stmt bran cond sub pod time code
1             package Test::mysqld;
2              
3 7     7   146754 use strict;
  7         9  
  7         157  
4 7     7   22 use warnings;
  7         7  
  7         134  
5              
6 7     7   144 use 5.008;
  7         16  
7 7     7   2689 use Class::Accessor::Lite;
  7         5464  
  7         33  
8 7     7   265 use Cwd;
  7         7  
  7         339  
9 7     7   27 use DBI;
  7         6  
  7         178  
10 7     7   2947 use File::Copy::Recursive qw(dircopy);
  7         39419  
  7         375  
11 7     7   4142 use File::Temp qw(tempdir);
  7         91772  
  7         381  
12 7     7   2764 use POSIX qw(SIGTERM WNOHANG);
  7         28739  
  7         37  
13 7     7   8888 use Time::HiRes qw(sleep);
  7         6790  
  7         23  
14              
15             our $VERSION = '0.18';
16              
17             our $errstr;
18             our @SEARCH_PATHS = qw(/usr/local/mysql);
19              
20             my %Defaults = (
21             auto_start => 2,
22             base_dir => undef,
23             my_cnf => {},
24             mysqld => undef,
25             use_mysqld_initialize => undef,
26             mysql_install_db => undef,
27             pid => undef,
28             copy_data_from => undef,
29             _owner_pid => undef,
30             );
31              
32             Class::Accessor::Lite->mk_accessors(keys %Defaults);
33              
34             sub new {
35 6     6 1 340 my $klass = shift;
36             my $self = bless {
37             %Defaults,
38 6 50       110 @_ == 1 ? %{$_[0]} : @_,
  0         0  
39             _owner_pid => $$,
40             }, $klass;
41             $self->my_cnf({
42 6         13 %{$self->my_cnf},
  6         26  
43             });
44 6 50       85 if (defined $self->base_dir) {
45 0 0       0 $self->base_dir(cwd . '/' . $self->base_dir)
46             if $self->base_dir !~ m|^/|;
47             } else {
48             $self->base_dir(
49             tempdir(
50 6 50       60 CLEANUP => $ENV{TEST_MYSQLD_PRESERVE} ? undef : 1,
51             ),
52             );
53             }
54 6   33     5002 $self->my_cnf->{socket} ||= $self->base_dir . "/tmp/mysql.sock";
55 6   33     90 $self->my_cnf->{datadir} ||= $self->base_dir . "/var";
56 6   33     69 $self->my_cnf->{'pid-file'} ||= $self->base_dir . "/tmp/mysqld.pid";
57 6   33     94 $self->my_cnf->{tmpdir} ||= $self->base_dir . "/tmp";
58 6 50       71 if (! defined $self->mysqld) {
59 6 50       39 my $prog = _find_program(qw/mysqld bin libexec sbin/)
60             or return;
61 0         0 $self->mysqld($prog);
62             }
63 0 0       0 if (! defined $self->use_mysqld_initialize) {
64 0         0 $self->use_mysqld_initialize($self->_use_mysqld_initialize);
65             }
66 0 0       0 if ($self->auto_start) {
67             die 'mysqld is already running (' . $self->my_cnf->{'pid-file'} . ')'
68 0 0       0 if -e $self->my_cnf->{'pid-file'};
69 0 0       0 $self->setup
70             if $self->auto_start >= 2;
71 0         0 $self->start;
72             }
73 0         0 $self;
74             }
75              
76             sub DESTROY {
77 6     6   14 my $self = shift;
78 6 50 33     49 $self->stop
79             if defined $self->pid && $$ == $self->_owner_pid;
80             }
81              
82             sub dsn {
83 0     0 1 0 my ($self, %args) = @_;
84             $args{port} ||= $self->my_cnf->{port}
85 0 0 0     0 if $self->my_cnf->{port};
86 0 0       0 if (defined $args{port}) {
87 0   0     0 $args{host} ||= $self->my_cnf->{'bind-address'} || '127.0.0.1';
      0        
88             } else {
89 0   0     0 $args{mysql_socket} ||= $self->my_cnf->{socket};
90             }
91 0   0     0 $args{user} ||= 'root';
92 0   0     0 $args{dbname} ||= 'test';
93 0         0 return 'DBI:mysql:' . join(';', map { "$_=$args{$_}" } sort keys %args);
  0         0  
94             }
95              
96             sub start {
97 0     0 1 0 my $self = shift;
98             return
99 0 0       0 if defined $self->pid;
100 0         0 $self->spawn;
101 0         0 $self->wait_for_setup;
102             }
103              
104             sub spawn {
105 0     0 0 0 my $self = shift;
106             return
107 0 0       0 if defined $self->pid;
108 0 0       0 open my $logfh, '>>', $self->base_dir . '/tmp/mysqld.log'
109             or die 'failed to create log file:' . $self->base_dir
110             . "/tmp/mysqld.log:$!";
111 0         0 my $pid = fork;
112 0 0       0 die "fork(2) failed:$!"
113             unless defined $pid;
114 0 0       0 if ($pid == 0) {
115 0 0       0 open STDOUT, '>&', $logfh
116             or die "dup(2) failed:$!";
117 0 0       0 open STDERR, '>&', $logfh
118             or die "dup(2) failed:$!";
119 0         0 exec(
120             $self->mysqld,
121             '--defaults-file=' . $self->base_dir . '/etc/my.cnf',
122             '--user=root',
123             );
124 0         0 die "failed to launch mysqld:$?";
125             }
126 0         0 close $logfh;
127 0         0 $self->pid($pid);
128             }
129              
130             sub wait_for_setup {
131 0     0 0 0 my $self = shift;
132             return
133 0 0       0 unless defined $self->pid;
134 0         0 my $pid = $self->pid;
135 0         0 while (! -e $self->my_cnf->{'pid-file'}) {
136 0 0       0 if (waitpid($pid, WNOHANG) > 0) {
137 0         0 die "*** failed to launch mysqld ***\n" . $self->read_log;
138             }
139 0         0 sleep 0.1;
140             }
141             { # create 'test' database
142 0 0       0 my $dbh = DBI->connect($self->dsn(dbname => 'mysql'))
  0         0  
143             or die $DBI::errstr;
144 0 0       0 $dbh->do('CREATE DATABASE IF NOT EXISTS test')
145             or die $dbh->errstr;
146             }
147             }
148              
149             sub stop {
150 0     0 1 0 my ($self, $sig) = @_;
151              
152             return
153 0 0       0 unless defined $self->pid;
154 0   0     0 $sig ||= SIGTERM;
155 0         0 $self->send_stop_signal($sig);
156 0         0 $self->wait_for_stop;
157             }
158              
159             sub send_stop_signal {
160 0     0 0 0 my ($self, $sig) = @_;
161             return
162 0 0       0 unless defined $self->pid;
163 0   0     0 $sig ||= SIGTERM;
164 0         0 kill $sig, $self->pid;
165             }
166              
167             sub wait_for_stop {
168 0     0 0 0 my $self = shift;
169 0         0 local $?; # waitpid may change this value :/
170 0         0 while (waitpid($self->pid, 0) <= 0) {
171             }
172 0         0 $self->pid(undef);
173             # might remain for example when sending SIGKILL
174 0         0 unlink $self->my_cnf->{'pid-file'};
175             }
176              
177             sub setup {
178 0     0 1 0 my $self = shift;
179             # (re)create directory structure
180 0         0 mkdir $self->base_dir;
181 0         0 for my $subdir (qw/etc var tmp/) {
182 0         0 mkdir $self->base_dir . "/$subdir";
183             }
184              
185             # When using `mysql_install_db`, copy the data before setup db for quick bootstrap.
186             # But `mysqld --initialize-insecure` doesn't work while the data dir exists,
187             # so don't copy here and do after setup db.
188 0 0 0     0 if (!$self->use_mysqld_initialize && $self->copy_data_from) {
189             dircopy($self->copy_data_from, $self->my_cnf->{datadir})
190 0 0       0 or die(
191 0         0 "could not dircopy @{[$self->copy_data_from]} to "
192 0         0 . "@{[$self->my_cnf->{datadir}]}:$!"
193             );
194             }
195             # my.cnf
196 0 0       0 open my $fh, '>', $self->base_dir . '/etc/my.cnf'
197             or die "failed to create file:" . $self->base_dir . "/etc/my.cnf:$!";
198 0         0 print $fh "[mysqld]\n";
199             print $fh map {
200 0         0 my $v = $self->my_cnf->{$_};
201 0 0 0     0 defined $v && length $v
202             ? "$_=$v" . "\n"
203             : "$_\n";
204 0         0 } sort keys %{$self->my_cnf};
  0         0  
205 0         0 close $fh;
206             # mysql_install_db
207 0 0       0 if (! -d $self->base_dir . '/var/mysql') {
208 0 0       0 my $cmd = $self->use_mysqld_initialize ? $self->mysqld : do {
209 0 0       0 if (! defined $self->mysql_install_db) {
210 0 0       0 my $prog = _find_program(qw/mysql_install_db bin scripts/)
211             or die 'failed to find mysql_install_db';
212 0         0 $self->mysql_install_db($prog);
213             }
214 0         0 $self->mysql_install_db;
215             };
216              
217             # We should specify --defaults-file option first.
218 0         0 $cmd .= " --defaults-file='" . $self->base_dir . "/etc/my.cnf'";
219              
220 0 0       0 if ($self->use_mysqld_initialize) {
221 0         0 $cmd .= ' --initialize-insecure';
222             } else {
223 0         0 my $mysql_base_dir = $self->mysql_install_db;
224 0 0       0 if (-l $mysql_base_dir) {
225 0         0 require File::Spec;
226 0         0 require File::Basename;
227 0         0 my $base = File::Basename::dirname($mysql_base_dir);
228 0         0 $mysql_base_dir = File::Spec->rel2abs(readlink($mysql_base_dir), $base);
229             }
230 0 0       0 if ($mysql_base_dir =~ s|/[^/]+/mysql_install_db$||) {
231 0         0 $cmd .= " --basedir='$mysql_base_dir'";
232             }
233             }
234 0         0 $cmd .= " 2>&1";
235 0 0       0 open $fh, '-|', $cmd
236             or die "failed to spawn mysql_install_db:$!";
237 0         0 my $output;
238 0         0 while (my $l = <$fh>) {
239 0         0 $output .= $l;
240             }
241 0 0       0 close $fh
242             or die "*** mysql_install_db failed ***\n$output\n";
243             }
244             # copy data files
245 0 0 0     0 if ($self->use_mysqld_initialize && $self->copy_data_from) {
246             dircopy($self->copy_data_from, $self->my_cnf->{datadir})
247 0 0       0 or die(
248 0         0 "could not dircopy @{[$self->copy_data_from]} to "
249 0         0 . "@{[$self->my_cnf->{datadir}]}:$!"
250             );
251             }
252             }
253              
254             sub read_log {
255 0     0 1 0 my $self = shift;
256 0 0       0 open my $logfh, '<', $self->base_dir . '/tmp/mysqld.log'
257             or die "failed to open file:tmp/mysql.log:$!";
258 0         0 do { local $/; <$logfh> };
  0         0  
  0         0  
259             }
260              
261             sub _find_program {
262 6     6   13 my ($prog, @subdirs) = @_;
263 6         13 undef $errstr;
264 6         23 my $path = _get_path_of($prog);
265 6 50       31 return $path
266             if $path;
267 6         27 for my $mysql (_get_path_of('mysql'),
268 5         213 map { "$_/bin/mysql" } @SEARCH_PATHS) {
269 11 50       137 if (-x $mysql) {
270 0         0 for my $subdir (@subdirs) {
271 0         0 $path = $mysql;
272 0 0 0     0 if ($path =~ s|/bin/mysql$|/$subdir/$prog|
273             and -x $path) {
274 0         0 return $path;
275             }
276             }
277             }
278             }
279 6         39 $errstr = "could not find $prog, please set appropriate PATH";
280 6         97 return;
281             }
282              
283             # Detecting if the mysqld supports `--initialize-insecure` option or not from the
284             # output of `mysqld --help --verbose`.
285             # `mysql_install_db` command is obsoleted MySQL 5.7.6 or later and
286             # `mysqld --initialize-insecure` should be used.
287             sub _use_mysqld_initialize {
288 0     0   0 my $self = shift;
289              
290 0         0 my $mysqld = $self->mysqld;
291 0         0 `$mysqld --verbose --help` =~ /--initialize-insecure/ms;
292             }
293              
294             sub _get_path_of {
295 12     12   21 my $prog = shift;
296 12         33109 my $path = `which $prog 2> /dev/null`;
297 12 50       98 chomp $path
298             if $path;
299 12 50       100 $path = ''
300             unless -x $path;
301 12         122 $path;
302             }
303              
304             sub start_mysqlds {
305 0     0 1   my $class = shift;
306 0           my $number = shift;
307 0           my @args = @_;
308              
309 0           my @mysqlds = map { Test::mysqld->new(@args, auto_start => 0) } (1..$number);
  0            
310 0           for my $mysqld (@mysqlds) {
311 0           $mysqld->setup;
312 0           $mysqld->spawn;
313             }
314 0           for my $mysqld (@mysqlds) {
315 0           $mysqld->wait_for_setup;
316             }
317 0           return @mysqlds;
318             }
319              
320             sub stop_mysqlds {
321 0     0 1   my $class = shift;
322 0           my @mysqlds = @_;
323              
324 0           for my $mysqld (@mysqlds) {
325 0           $mysqld->send_stop_signal;
326             }
327 0           for my $mysqld (@mysqlds) {
328 0           $mysqld->wait_for_stop;
329             }
330 0           return @mysqlds;
331             }
332              
333             "lestrrat-san he";
334             __END__