File Coverage

blib/lib/Test/mysqld.pm
Criterion Covered Total %
statement 57 201 28.3
branch 10 110 9.0
condition 5 54 9.2
subroutine 14 30 46.6
pod 8 12 66.6
total 94 407 23.1


line stmt bran cond sub pod time code
1             package Test::mysqld;
2              
3 7     7   407513 use strict;
  7         68  
  7         182  
4 7     7   32 use warnings;
  7         13  
  7         171  
5              
6 7     7   139 use 5.008_001;
  7         19  
7 7     7   2946 use Class::Accessor::Lite;
  7         7496  
  7         39  
8 7     7   341 use Cwd;
  7         25  
  7         378  
9 7     7   40 use DBI;
  7         12  
  7         281  
10 7     7   3335 use File::Copy::Recursive qw(dircopy);
  7         53649  
  7         419  
11 7     7   4456 use File::Temp qw(tempdir);
  7         108908  
  7         408  
12 7     7   3197 use POSIX qw(SIGTERM WNOHANG);
  7         35654  
  7         34  
13 7     7   11239 use Time::HiRes qw(sleep);
  7         7634  
  7         25  
14              
15             our $VERSION = '1.0012';
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 848 my $klass = shift;
36             my $self = bless {
37             %Defaults,
38 6 50       147 @_ == 1 ? %{$_[0]} : @_,
  0         0  
39             _owner_pid => $$,
40             }, $klass;
41             $self->my_cnf({
42 6         17 %{$self->my_cnf},
  6         25  
43             });
44 6 50       87 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     3431 $self->my_cnf->{socket} ||= $self->base_dir . "/tmp/mysql.sock";
55 6   33     89 $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     63 $self->my_cnf->{tmpdir} ||= $self->base_dir . "/tmp";
58 6 50       66 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   39 my $self = shift;
78 6 50 33     146 $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 0 0       0 open $fh, '-|', $cmd
245             or die "failed to spawn mysql_install_db:$!";
246 0         0 my $output;
247 0         0 while (my $l = <$fh>) {
248 0         0 $output .= $l;
249             }
250 0 0       0 close $fh
251             or die "*** mysql_install_db failed ***\n% $cmd\n$output\n";
252             }
253             }
254              
255             sub read_log {
256 0     0 1 0 my $self = shift;
257 0 0       0 open my $logfh, '<', $self->base_dir . '/tmp/mysqld.log'
258             or die "failed to open file:tmp/mysql.log:$!";
259 0         0 do { local $/; <$logfh> };
  0         0  
  0         0  
260             }
261              
262             sub _find_program {
263 6     6   23 my ($prog, @subdirs) = @_;
264 6         13 undef $errstr;
265 6         21 my $path = _get_path_of($prog);
266 6 50       57 return $path
267             if $path;
268 6         63 for my $mysql (_get_path_of('mysql'),
269 5         131 map { "$_/bin/mysql" } @SEARCH_PATHS) {
270 11 50       163 if (-x $mysql) {
271 0         0 for my $subdir (@subdirs) {
272 0         0 $path = $mysql;
273 0 0 0     0 if ($path =~ s|/bin/mysql$|/$subdir/$prog|
274             and -x $path) {
275 0         0 return $path;
276             }
277             }
278             }
279             }
280 6         67 $errstr = "could not find $prog, please set appropriate PATH";
281 6         238 return;
282             }
283              
284             sub _verbose_help {
285 0     0   0 my $self = shift;
286 0   0     0 $self->{_verbose_help} ||= `@{[$self->mysqld]} --verbose --help 2>/dev/null`;
287             }
288              
289             # Detecting if the mysqld supports `--initialize-insecure` option or not from the
290             # output of `mysqld --help --verbose`.
291             # `mysql_install_db` command is obsoleted MySQL 5.7.6 or later and
292             # `mysqld --initialize-insecure` should be used.
293             sub _use_mysqld_initialize {
294 0     0   0 shift->_verbose_help =~ /--initialize-insecure/ms;
295             }
296              
297             sub _is_maria {
298 0     0   0 my $self = shift;
299 0 0       0 unless (exists $self->{_is_maria}) {
300 0         0 $self->{_is_maria} = $self->_verbose_help =~ /\A.*MariaDB/;
301             }
302 0         0 $self->{_is_maria};
303             }
304              
305             sub _mysql_version {
306 0     0   0 my $self = shift;
307 0 0       0 unless (exists $self->{_mysql_version}) {
308             ($self->{_mysql_version})
309 0         0 = $self->_verbose_help =~ /\A.*Ver ([0-9]+\.[0-9]+\.[0-9]+)/;
310             }
311 0         0 $self->{_mysql_version};
312             }
313              
314             sub _mysql_major_version {
315 0     0   0 my $ver = shift->_mysql_version;
316 0 0       0 return unless $ver;
317 0         0 +(split /\./, $ver)[0];
318             }
319              
320             sub _get_path_of {
321 12     12   42 my $prog = shift;
322 12         40329 my $path = `which $prog 2> /dev/null`;
323 12 50       223 chomp $path
324             if $path;
325 12 50       298 $path = ''
326             unless -x $path;
327 12         340 $path;
328             }
329              
330             sub start_mysqlds {
331 0     0 1   my $class = shift;
332 0           my $number = shift;
333 0           my @args = @_;
334              
335 0           my @mysqlds = map { Test::mysqld->new(@args, auto_start => 0) } (1..$number);
  0            
336 0           for my $mysqld (@mysqlds) {
337 0           $mysqld->setup;
338 0           $mysqld->spawn;
339             }
340 0           for my $mysqld (@mysqlds) {
341 0           $mysqld->wait_for_setup;
342             }
343 0           return @mysqlds;
344             }
345              
346             sub stop_mysqlds {
347 0     0 1   my $class = shift;
348 0           my @mysqlds = @_;
349              
350 0           for my $mysqld (@mysqlds) {
351 0           $mysqld->send_stop_signal;
352             }
353 0           for my $mysqld (@mysqlds) {
354 0           $mysqld->wait_for_stop;
355             }
356 0           return @mysqlds;
357             }
358              
359             "lestrrat-san he";
360             __END__