File Coverage

blib/lib/Test/mysqld.pm
Criterion Covered Total %
statement 57 186 30.6
branch 10 98 10.2
condition 5 43 11.6
subroutine 14 27 51.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   487250 use strict;
  7         77  
  7         192  
4 7     7   36 use warnings;
  7         11  
  7         213  
5              
6 7     7   151 use 5.008_001;
  7         21  
7 7     7   2971 use Class::Accessor::Lite;
  7         8358  
  7         43  
8 7     7   354 use Cwd;
  7         15  
  7         409  
9 7     7   41 use DBI;
  7         14  
  7         235  
10 7     7   3445 use File::Copy::Recursive qw(dircopy);
  7         58889  
  7         562  
11 7     7   4571 use File::Temp qw(tempdir);
  7         133297  
  7         502  
12 7     7   3393 use POSIX qw(SIGTERM WNOHANG);
  7         46002  
  7         49  
13 7     7   14664 use Time::HiRes qw(sleep);
  7         8948  
  7         33  
14              
15             our $VERSION = '1.0011';
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 893 my $klass = shift;
36             my $self = bless {
37             %Defaults,
38 6 50       146 @_ == 1 ? %{$_[0]} : @_,
  0         0  
39             _owner_pid => $$,
40             }, $klass;
41             $self->my_cnf({
42 6         16 %{$self->my_cnf},
  6         27  
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       63 CLEANUP => $ENV{TEST_MYSQLD_PRESERVE} ? undef : 1,
51             ),
52             );
53             }
54 6   33     3302 $self->my_cnf->{socket} ||= $self->base_dir . "/tmp/mysql.sock";
55 6   33     93 $self->my_cnf->{datadir} ||= $self->base_dir . "/var";
56 6   33     67 $self->my_cnf->{'pid-file'} ||= $self->base_dir . "/tmp/mysqld.pid";
57 6   33     62 $self->my_cnf->{tmpdir} ||= $self->base_dir . "/tmp";
58 6 50       71 if (! defined $self->mysqld) {
59 6 50       41 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   37 my $self = shift;
78 6 50 33     166 $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              
142 0 0       0 unless ($self->copy_data_from) { # create 'test' database
143 0 0       0 my $dbh = DBI->connect($self->dsn(dbname => 'mysql'))
144             or die $DBI::errstr;
145 0 0       0 $dbh->do('CREATE DATABASE IF NOT EXISTS test')
146             or die $dbh->errstr;
147             }
148             # XXX copy_data_from doesn't work on MySQL8
149             }
150              
151             sub stop {
152 0     0 1 0 my ($self, $sig) = @_;
153              
154             return
155 0 0       0 unless defined $self->pid;
156 0   0     0 $sig ||= SIGTERM;
157 0         0 $self->send_stop_signal($sig);
158 0         0 $self->wait_for_stop;
159             }
160              
161             sub send_stop_signal {
162 0     0 0 0 my ($self, $sig) = @_;
163             return
164 0 0       0 unless defined $self->pid;
165 0   0     0 $sig ||= SIGTERM;
166 0         0 kill $sig, $self->pid;
167             }
168              
169             sub wait_for_stop {
170 0     0 0 0 my $self = shift;
171 0         0 local $?; # waitpid may change this value :/
172 0         0 while (waitpid($self->pid, 0) <= 0) {
173             }
174 0         0 $self->pid(undef);
175             # might remain for example when sending SIGKILL
176 0         0 unlink $self->my_cnf->{'pid-file'};
177             }
178              
179             sub setup {
180 0     0 1 0 my $self = shift;
181             # (re)create directory structure
182 0         0 mkdir $self->base_dir;
183 0         0 for my $subdir (qw/etc var tmp/) {
184 0         0 mkdir $self->base_dir . "/$subdir";
185             }
186              
187             # copy the data before setup db for quick bootstrap.
188 0 0       0 if ($self->copy_data_from) {
189             dircopy($self->copy_data_from, $self->my_cnf->{datadir})
190 0 0       0 or die "could not dircopy @{[$self->copy_data_from]} to " .
  0         0  
191 0         0 "@{[$self->my_cnf->{datadir}]}:$!";
192             }
193             # my.cnf
194 0 0       0 open my $fh, '>', $self->base_dir . '/etc/my.cnf'
195             or die "failed to create file:" . $self->base_dir . "/etc/my.cnf:$!";
196 0         0 print $fh "[mysqld]\n";
197             print $fh map {
198 0         0 my $v = $self->my_cnf->{$_};
199 0 0 0     0 defined $v && length $v
200             ? "$_=$v" . "\n"
201             : "$_\n";
202 0         0 } sort keys %{$self->my_cnf};
  0         0  
203 0         0 close $fh;
204             # mysql_install_db
205 0 0       0 if (! -d $self->base_dir . '/var/mysql') {
206 0 0       0 my $cmd = $self->use_mysqld_initialize ? $self->mysqld : do {
207 0 0       0 if (! defined $self->mysql_install_db) {
208 0 0       0 my $prog = _find_program(qw/mysql_install_db bin scripts/)
209             or die 'failed to find mysql_install_db';
210 0         0 $self->mysql_install_db($prog);
211             }
212 0         0 $self->mysql_install_db;
213             };
214              
215             # We should specify --defaults-file option first.
216 0         0 $cmd .= " --defaults-file='" . $self->base_dir . "/etc/my.cnf'";
217              
218 0 0       0 if ($self->use_mysqld_initialize) {
219 0         0 $cmd .= ' --initialize-insecure';
220 0 0       0 if ($self->copy_data_from) {
221 0 0       0 opendir my $dh, $self->copy_data_from
222 0         0 or die "failed to open copy_data_from directory @{[$self->copy_data_from]}: $!";
223 0         0 while (my $entry = readdir $dh) {
224 0 0       0 next unless -d $self->copy_data_from . "/$entry";
225 0 0       0 next if $entry =~ /^\.\.?$/;
226 0         0 $cmd .= " --ignore-db-dir=$entry"
227             }
228             }
229             } else {
230             # `abs_path` resolves nested symlinks and returns canonical absolute path
231 0         0 my $mysql_base_dir = Cwd::abs_path($self->mysql_install_db);
232 0 0       0 if ($mysql_base_dir =~ s{/(?:bin|extra|scripts)/mysql_install_db$}{}) {
233 0         0 $cmd .= " --basedir='$mysql_base_dir'";
234             }
235             }
236 0         0 $cmd .= " 2>&1";
237 0 0       0 open $fh, '-|', $cmd
238             or die "failed to spawn mysql_install_db:$!";
239 0         0 my $output;
240 0         0 while (my $l = <$fh>) {
241 0         0 $output .= $l;
242             }
243 0 0       0 close $fh
244             or die "*** mysql_install_db failed ***\n% $cmd\n$output\n";
245             }
246             }
247              
248             sub read_log {
249 0     0 1 0 my $self = shift;
250 0 0       0 open my $logfh, '<', $self->base_dir . '/tmp/mysqld.log'
251             or die "failed to open file:tmp/mysql.log:$!";
252 0         0 do { local $/; <$logfh> };
  0         0  
  0         0  
253             }
254              
255             sub _find_program {
256 6     6   21 my ($prog, @subdirs) = @_;
257 6         14 undef $errstr;
258 6         18 my $path = _get_path_of($prog);
259 6 50       65 return $path
260             if $path;
261 6         64 for my $mysql (_get_path_of('mysql'),
262 5         118 map { "$_/bin/mysql" } @SEARCH_PATHS) {
263 11 50       165 if (-x $mysql) {
264 0         0 for my $subdir (@subdirs) {
265 0         0 $path = $mysql;
266 0 0 0     0 if ($path =~ s|/bin/mysql$|/$subdir/$prog|
267             and -x $path) {
268 0         0 return $path;
269             }
270             }
271             }
272             }
273 6         80 $errstr = "could not find $prog, please set appropriate PATH";
274 6         240 return;
275             }
276              
277             sub _verbose_help {
278 0     0   0 my $self = shift;
279 0   0     0 $self->{_verbose_help} ||= `@{[$self->mysqld]} --verbose --help 2>/dev/null`;
280             }
281              
282             # Detecting if the mysqld supports `--initialize-insecure` option or not from the
283             # output of `mysqld --help --verbose`.
284             # `mysql_install_db` command is obsoleted MySQL 5.7.6 or later and
285             # `mysqld --initialize-insecure` should be used.
286             sub _use_mysqld_initialize {
287 0     0   0 shift->_verbose_help =~ /--initialize-insecure/ms;
288             }
289              
290             sub _get_path_of {
291 12     12   44 my $prog = shift;
292 12         41343 my $path = `which $prog 2> /dev/null`;
293 12 50       207 chomp $path
294             if $path;
295 12 50       332 $path = ''
296             unless -x $path;
297 12         349 $path;
298             }
299              
300             sub start_mysqlds {
301 0     0 1   my $class = shift;
302 0           my $number = shift;
303 0           my @args = @_;
304              
305 0           my @mysqlds = map { Test::mysqld->new(@args, auto_start => 0) } (1..$number);
  0            
306 0           for my $mysqld (@mysqlds) {
307 0           $mysqld->setup;
308 0           $mysqld->spawn;
309             }
310 0           for my $mysqld (@mysqlds) {
311 0           $mysqld->wait_for_setup;
312             }
313 0           return @mysqlds;
314             }
315              
316             sub stop_mysqlds {
317 0     0 1   my $class = shift;
318 0           my @mysqlds = @_;
319              
320 0           for my $mysqld (@mysqlds) {
321 0           $mysqld->send_stop_signal;
322             }
323 0           for my $mysqld (@mysqlds) {
324 0           $mysqld->wait_for_stop;
325             }
326 0           return @mysqlds;
327             }
328              
329             "lestrrat-san he";
330             __END__