File Coverage

blib/lib/Test/mysqld.pm
Criterion Covered Total %
statement 57 203 28.0
branch 10 110 9.0
condition 5 54 9.2
subroutine 14 30 46.6
pod 8 12 66.6
total 94 409 22.9


line stmt bran cond sub pod time code
1             package Test::mysqld;
2              
3 7     7   452045 use strict;
  7         73  
  7         213  
4 7     7   37 use warnings;
  7         12  
  7         187  
5              
6 7     7   174 use 5.008_001;
  7         22  
7 7     7   3392 use Class::Accessor::Lite;
  7         8661  
  7         49  
8 7     7   377 use Cwd;
  7         15  
  7         411  
9 7     7   40 use DBI;
  7         13  
  7         241  
10 7     7   3616 use File::Copy::Recursive qw(dircopy);
  7         60270  
  7         529  
11 7     7   5090 use File::Temp qw(tempdir);
  7         131239  
  7         550  
12 7     7   3374 use POSIX qw(SIGTERM WNOHANG);
  7         43482  
  7         43  
13 7     7   13813 use Time::HiRes qw(sleep);
  7         9092  
  7         31  
14              
15             our $VERSION = '1.0013';
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 1047 my $klass = shift;
36             my $self = bless {
37             %Defaults,
38 6 50       176 @_ == 1 ? %{$_[0]} : @_,
  0         0  
39             _owner_pid => $$,
40             }, $klass;
41             $self->my_cnf({
42 6         19 %{$self->my_cnf},
  6         30  
43             });
44 6 50       108 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       71 CLEANUP => $ENV{TEST_MYSQLD_PRESERVE} ? undef : 1,
51             ),
52             );
53             }
54 6   33     3998 $self->my_cnf->{socket} ||= $self->base_dir . "/tmp/mysql.sock";
55 6   33     103 $self->my_cnf->{datadir} ||= $self->base_dir . "/var";
56 6   33     79 $self->my_cnf->{'pid-file'} ||= $self->base_dir . "/tmp/mysqld.pid";
57 6   33     73 $self->my_cnf->{tmpdir} ||= $self->base_dir . "/tmp";
58 6 50       81 if (! defined $self->mysqld) {
59 6 50       49 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   50 my $self = shift;
78 6 50 33     183 $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             }
149              
150             sub stop {
151 0     0 1 0 my ($self, $sig) = @_;
152              
153             return
154 0 0       0 unless defined $self->pid;
155 0   0     0 $sig ||= SIGTERM;
156 0         0 $self->send_stop_signal($sig);
157 0         0 $self->wait_for_stop;
158             }
159              
160             sub send_stop_signal {
161 0     0 0 0 my ($self, $sig) = @_;
162             return
163 0 0       0 unless defined $self->pid;
164 0   0     0 $sig ||= SIGTERM;
165 0         0 kill $sig, $self->pid;
166             }
167              
168             sub wait_for_stop {
169 0     0 0 0 my $self = shift;
170 0         0 local $?; # waitpid may change this value :/
171 0         0 while (waitpid($self->pid, 0) <= 0) {
172             }
173 0         0 $self->pid(undef);
174             # might remain for example when sending SIGKILL
175 0         0 unlink $self->my_cnf->{'pid-file'};
176             }
177              
178             sub setup {
179 0     0 1 0 my $self = shift;
180             # (re)create directory structure
181 0         0 mkdir $self->base_dir;
182 0         0 for my $subdir (qw/etc var tmp/) {
183 0         0 mkdir $self->base_dir . "/$subdir";
184             }
185              
186             # copy the data before setup db for quick bootstrap.
187 0 0       0 if ($self->copy_data_from) {
188             dircopy($self->copy_data_from, $self->my_cnf->{datadir})
189 0 0       0 or die "could not dircopy @{[$self->copy_data_from]} to " .
  0         0  
190 0         0 "@{[$self->my_cnf->{datadir}]}:$!";
191 0 0 0     0 if (!$self->_is_maria && ($self->_mysql_major_version || 0) >= 8) {
      0        
192 0         0 my $mysql_db_dir = $self->my_cnf->{datadir} . '/mysql';
193 0 0       0 if (! -d $mysql_db_dir) {
194 0 0       0 mkdir $mysql_db_dir or die "failed to mkdir $mysql_db_dir: $!";
195             }
196             }
197             }
198             # my.cnf
199 0 0       0 open my $fh, '>', $self->base_dir . '/etc/my.cnf'
200             or die "failed to create file:" . $self->base_dir . "/etc/my.cnf:$!";
201 0         0 print $fh "[mysqld]\n";
202             print $fh map {
203 0         0 my $v = $self->my_cnf->{$_};
204 0 0 0     0 defined $v && length $v
205             ? "$_=$v" . "\n"
206             : "$_\n";
207 0         0 } sort keys %{$self->my_cnf};
  0         0  
208 0         0 close $fh;
209             # mysql_install_db
210 0 0       0 if (! -d $self->base_dir . '/var/mysql') {
211 0 0       0 my $cmd = $self->use_mysqld_initialize ? $self->mysqld : do {
212 0 0       0 if (! defined $self->mysql_install_db) {
213 0 0       0 my $prog = _find_program(qw/mysql_install_db bin scripts/)
214             or die 'failed to find mysql_install_db';
215 0         0 $self->mysql_install_db($prog);
216             }
217 0         0 $self->mysql_install_db;
218             };
219              
220             # We should specify --defaults-file option first.
221 0         0 $cmd .= " --defaults-file='" . $self->base_dir . "/etc/my.cnf'";
222              
223 0 0       0 if ($self->use_mysqld_initialize) {
224 0         0 $cmd .= ' --initialize-insecure';
225 0 0 0     0 if ($self->copy_data_from &&
      0        
226             !(!$self->_is_maria && ($self->_mysql_major_version || 0) >= 8)
227             ) {
228 0 0       0 opendir my $dh, $self->copy_data_from
229 0         0 or die "failed to open copy_data_from directory @{[$self->copy_data_from]}: $!";
230 0         0 while (my $entry = readdir $dh) {
231 0 0       0 next unless -d $self->copy_data_from . "/$entry";
232 0 0       0 next if $entry =~ /^\.\.?$/;
233 0         0 $cmd .= " --ignore-db-dir=$entry"
234             }
235             }
236             } else {
237             # `abs_path` resolves nested symlinks and returns canonical absolute path
238 0         0 my $mysql_base_dir = Cwd::abs_path($self->mysql_install_db);
239 0 0       0 if ($mysql_base_dir =~ s{/(?:bin|extra|scripts)/mysql_install_db$}{}) {
240 0         0 $cmd .= " --basedir='$mysql_base_dir'";
241             }
242             }
243 0         0 $cmd .= " 2>&1";
244             # The MySQL scripts are in Perl, so clear out all current Perl
245             # related environment variables before the call
246 0         0 local @ENV{ grep { /^PERL/ } keys %ENV };
  0         0  
247 0 0       0 open $fh, '-|', $cmd
248             or die "failed to spawn mysql_install_db:$!";
249 0         0 my $output;
250 0         0 while (my $l = <$fh>) {
251 0         0 $output .= $l;
252             }
253 0 0       0 close $fh
254             or die "*** mysql_install_db failed ***\n% $cmd\n$output\n";
255             }
256             }
257              
258             sub read_log {
259 0     0 1 0 my $self = shift;
260 0 0       0 open my $logfh, '<', $self->base_dir . '/tmp/mysqld.log'
261             or die "failed to open file:tmp/mysql.log:$!";
262 0         0 do { local $/; <$logfh> };
  0         0  
  0         0  
263             }
264              
265             sub _find_program {
266 6     6   25 my ($prog, @subdirs) = @_;
267 6         18 undef $errstr;
268 6         21 my $path = _get_path_of($prog);
269 6 50       103 return $path
270             if $path;
271 6         80 for my $mysql (_get_path_of('mysql'),
272 5         176 map { "$_/bin/mysql" } @SEARCH_PATHS) {
273 11 50       216 if (-x $mysql) {
274 0         0 for my $subdir (@subdirs) {
275 0         0 $path = $mysql;
276 0 0 0     0 if ($path =~ s|/bin/mysql$|/$subdir/$prog|
277             and -x $path) {
278 0         0 return $path;
279             }
280             }
281             }
282             }
283 6         108 $errstr = "could not find $prog, please set appropriate PATH";
284 6         320 return;
285             }
286              
287             sub _verbose_help {
288 0     0   0 my $self = shift;
289 0   0     0 $self->{_verbose_help} ||= `@{[$self->mysqld]} --verbose --help 2>/dev/null`;
290             }
291              
292             # Detecting if the mysqld supports `--initialize-insecure` option or not from the
293             # output of `mysqld --help --verbose`.
294             # `mysql_install_db` command is obsoleted MySQL 5.7.6 or later and
295             # `mysqld --initialize-insecure` should be used.
296             sub _use_mysqld_initialize {
297 0     0   0 shift->_verbose_help =~ /--initialize-insecure/ms;
298             }
299              
300             sub _is_maria {
301 0     0   0 my $self = shift;
302 0 0       0 unless (exists $self->{_is_maria}) {
303 0         0 $self->{_is_maria} = $self->_verbose_help =~ /\A.*MariaDB/;
304             }
305 0         0 $self->{_is_maria};
306             }
307              
308             sub _mysql_version {
309 0     0   0 my $self = shift;
310 0 0       0 unless (exists $self->{_mysql_version}) {
311             ($self->{_mysql_version})
312 0         0 = $self->_verbose_help =~ /\A.*Ver ([0-9]+\.[0-9]+\.[0-9]+)/;
313             }
314 0         0 $self->{_mysql_version};
315             }
316              
317             sub _mysql_major_version {
318 0     0   0 my $ver = shift->_mysql_version;
319 0 0       0 return unless $ver;
320 0         0 +(split /\./, $ver)[0];
321             }
322              
323             sub _get_path_of {
324 12     12   54 my $prog = shift;
325 12         49973 my $path = `which $prog 2> /dev/null`;
326 12 50       320 chomp $path
327             if $path;
328 12 50       392 $path = ''
329             unless -x $path;
330 12         561 $path;
331             }
332              
333             sub start_mysqlds {
334 0     0 1   my $class = shift;
335 0           my $number = shift;
336 0           my @args = @_;
337              
338 0           my @mysqlds = map { Test::mysqld->new(@args, auto_start => 0) } (1..$number);
  0            
339 0           for my $mysqld (@mysqlds) {
340 0           $mysqld->setup;
341 0           $mysqld->spawn;
342             }
343 0           for my $mysqld (@mysqlds) {
344 0           $mysqld->wait_for_setup;
345             }
346 0           return @mysqlds;
347             }
348              
349             sub stop_mysqlds {
350 0     0 1   my $class = shift;
351 0           my @mysqlds = @_;
352              
353 0           for my $mysqld (@mysqlds) {
354 0           $mysqld->send_stop_signal;
355             }
356 0           for my $mysqld (@mysqlds) {
357 0           $mysqld->wait_for_stop;
358             }
359 0           return @mysqlds;
360             }
361              
362             "lestrrat-san he";
363             __END__