File Coverage

blib/lib/Test/postgresql.pm
Criterion Covered Total %
statement 47 140 33.5
branch 12 106 11.3
condition 2 17 11.7
subroutine 12 18 66.6
pod 5 5 100.0
total 78 286 27.2


line stmt bran cond sub pod time code
1             package Test::postgresql;
2              
3 5     5   172997 use strict;
  5         11  
  5         197  
4 5     5   26 use warnings;
  5         8  
  5         142  
5              
6 5     5   140 use 5.008;
  5         17  
  5         183  
7 5     5   5078 use Class::Accessor::Lite;
  5         5841  
  5         32  
8 5     5   235 use Cwd;
  5         9  
  5         443  
9 5     5   2622 use DBI;
  5         19585  
  5         235  
10 5     5   6546 use File::Temp qw(tempdir);
  5         141189  
  5         395  
11 5     5   4430 use POSIX qw(SIGTERM WNOHANG setuid);
  5         42538  
  5         40  
12              
13             our $VERSION = '0.09';
14              
15             our @SEARCH_PATHS = (
16             # popular installtion dir?
17             qw(/usr/local/pgsql),
18             # ubuntu (maybe debian as well, find the newest version)
19             (sort { $b cmp $a } grep { -d $_ } glob "/usr/lib/postgresql/*"),
20             # macport
21             (sort { $b cmp $a } grep { -d $_ } glob "/opt/local/lib/postgresql-*"),
22             );
23              
24             our $errstr;
25             our $BASE_PORT = 15432;
26              
27             our %Defaults = (
28             auto_start => 2,
29             base_dir => undef,
30             initdb => undef,
31             initdb_args => '-U postgres -A trust',
32             pid => undef,
33             port => undef,
34             postmaster => undef,
35             postmaster_args => '-h 127.0.0.1',
36             uid => undef,
37             _owner_pid => undef,
38             );
39              
40             Class::Accessor::Lite->mk_accessors(keys %Defaults);
41              
42             sub new {
43 4     4 1 837 my $klass = shift;
44 0         0 my $self = bless {
45             %Defaults,
46 4 50       83 @_ == 1 ? %{$_[0]} : @_,
47             _owner_pid => $$,
48             }, $klass;
49 4 50 33     31 if (! defined $self->uid && $ENV{USER} eq 'root') {
50 0 0       0 my @a = getpwnam('nobody')
51             or die "user nobody does not exist, use uid() to specify user:$!";
52 0         0 $self->uid($a[2]);
53             }
54 4 50       816 if (defined $self->base_dir) {
55 0 0       0 $self->base_dir(cwd . '/' . $self->base_dir)
56             if $self->base_dir !~ m|^/|;
57             } else {
58 4 50       66 $self->base_dir(
59             tempdir(
60             CLEANUP => $ENV{TEST_POSTGRESQL_PRESERVE} ? undef : 1,
61             ),
62             );
63 4 50       3148 chown $self->uid, -1, $self->base_dir
64             if defined $self->uid;
65             }
66 4 50       58 if (! defined $self->initdb) {
67 4 50       43 my $prog = _find_program('initdb')
68             or return;
69 0         0 $self->initdb($prog);
70             }
71 0 0       0 if (! defined $self->postmaster) {
72 0 0       0 my $prog = _find_program('postmaster')
73             or return;
74 0         0 $self->postmaster($prog);
75             }
76 0 0       0 if ($self->auto_start) {
77 0 0       0 $self->setup
78             if $self->auto_start >= 2;
79 0         0 $self->start;
80             }
81 0         0 $self;
82             }
83              
84             sub DESTROY {
85 4     4   28 my $self = shift;
86 4 50 33     253 $self->stop
87             if defined $self->pid && $$ == $self->_owner_pid;
88             }
89              
90             sub dsn {
91 0     0 1 0 my ($self, %args) = @_;
92 0   0     0 $args{host} ||= '127.0.0.1';
93 0   0     0 $args{port} ||= $self->port;
94 0   0     0 $args{user} ||= 'postgres';
95 0   0     0 $args{dbname} ||= 'test';
96 0         0 return 'DBI:Pg:' . join(';', map { "$_=$args{$_}" } sort keys %args);
  0         0  
97             }
98              
99             sub start {
100 0     0 1 0 my $self = shift;
101             return
102 0 0       0 if defined $self->pid;
103             # start (or die)
104             sub {
105 0     0   0 my $err;
106 0 0       0 if ($self->port) {
107 0 0       0 $err = $self->_try_start($self->port)
108             or return;
109             } else {
110             # try by incrementing port no
111 0         0 for (my $port = $BASE_PORT; $port < $BASE_PORT + 100; $port++) {
112 0 0       0 $err = $self->_try_start($port)
113             or return;
114             }
115             }
116             # failed
117 0         0 die "failed to launch postgresql:$!\n$err";
118 0         0 }->();
119             { # create "test" database
120 0 0       0 my $dbh = DBI->connect($self->dsn(dbname => 'template1'), '', '', {})
  0         0  
121             or die $DBI::errstr;
122 0 0       0 if ($dbh->selectrow_arrayref(q{SELECT COUNT(*) FROM pg_database WHERE datname='test'})->[0] == 0) {
123 0 0       0 $dbh->do('CREATE DATABASE test')
124             or die $dbh->errstr;
125             }
126             }
127             }
128              
129             sub _try_start {
130 0     0   0 my ($self, $port) = @_;
131             # open log and fork
132 0 0       0 open my $logfh, '>', $self->base_dir . '/postgres.log'
133             or die 'failed to create log file:' . $self->base_dir
134             . "/postgres.log:$!";
135 0         0 my $pid = fork;
136 0 0       0 die "fork(2) failed:$!"
137             unless defined $pid;
138 0 0       0 if ($pid == 0) {
139 0 0       0 open STDOUT, '>&', $logfh
140             or die "dup(2) failed:$!";
141 0 0       0 open STDERR, '>&', $logfh
142             or die "dup(2) failed:$!";
143 0 0       0 chdir $self->base_dir
144             or die "failed to chdir to:" . $self->base_dir . ":$!";
145 0 0       0 if (defined $self->uid) {
146 0 0       0 setuid($self->uid)
147             or die "setuid failed:$!";
148             }
149 0         0 my $cmd = join(
150             ' ',
151             $self->postmaster,
152             $self->postmaster_args,
153             '-p', $port,
154             '-D', $self->base_dir . '/data',
155             '-k', $self->base_dir . '/tmp',
156             );
157 0         0 exec($cmd);
158 0         0 die "failed to launch postmaster:$?";
159             }
160 0         0 close $logfh;
161             # wait until server becomes ready (or dies)
162 0         0 for (my $i = 0; $i < 100; $i++) {
163 0 0       0 open $logfh, '<', $self->base_dir . '/postgres.log'
164             or die 'failed to open log file:' . $self->base_dir
165             . "/postgres.log:$!";
166 0         0 my $lines = do { join '', <$logfh> };
  0         0  
167 0         0 close $logfh;
168             last
169 0 0       0 if $lines =~ /is ready to accept connections/;
170 0 0       0 if (waitpid($pid, WNOHANG) > 0) {
171             # failed
172 0         0 return $lines;
173             }
174 0         0 sleep 1;
175             }
176             # postgresql is ready
177 0         0 $self->pid($pid);
178 0         0 $self->port($port);
179 0         0 return;
180             }
181              
182             sub stop {
183 0     0 1 0 my ($self, $sig) = @_;
184             return
185 0 0       0 unless defined $self->pid;
186 0   0     0 $sig ||= SIGTERM;
187 0         0 kill $sig, $self->pid;
188 0         0 while (waitpid($self->pid, 0) <= 0) {
189             }
190 0         0 $self->pid(undef);
191             }
192              
193             sub setup {
194 0     0 1 0 my $self = shift;
195             # (re)create directory structure
196 0         0 mkdir $self->base_dir;
197 0 0       0 chmod 0755, $self->base_dir
198             or die "failed to chmod 0755 dir:" . $self->base_dir . ":$!";
199 0 0       0 if ($ENV{USER} eq 'root') {
200 0 0       0 chown $self->uid, -1, $self->base_dir
201             or die "failed to chown dir:" . $self->base_dir . ":$!";
202             }
203 0 0       0 if (mkdir $self->base_dir . '/tmp') {
204 0 0       0 if ($self->uid) {
205 0 0       0 chown $self->uid, -1, $self->base_dir . '/tmp'
206             or die "failed to chown dir:" . $self->base_dir . "/tmp:$!";
207             }
208             }
209             # initdb
210 0 0       0 if (! -d $self->base_dir . '/data') {
211 0 0       0 pipe my $rfh, my $wfh
212             or die "failed to create pipe:$!";
213 0         0 my $pid = fork;
214 0 0       0 die "fork failed:$!"
215             unless defined $pid;
216 0 0       0 if ($pid == 0) {
217 0         0 close $rfh;
218 0 0       0 open STDOUT, '>&', $wfh
219             or die "dup(2) failed:$!";
220 0 0       0 open STDERR, '>&', $wfh
221             or die "dup(2) failed:$!";
222 0 0       0 chdir $self->base_dir
223             or die "failed to chdir to:" . $self->base_dir . ":$!";
224 0 0       0 if (defined $self->uid) {
225 0 0       0 setuid($self->uid)
226             or die "setuid failed:$!";
227             }
228 0         0 my $cmd = join(
229             ' ',
230             $self->initdb,
231             $self->initdb_args,
232             '-D', $self->base_dir . '/data',
233             );
234 0         0 exec($cmd);
235 0         0 die "failed to exec:$cmd:$!";
236             }
237 0         0 close $wfh;
238 0         0 my $output = '';
239 0         0 while (my $l = <$rfh>) {
240 0         0 $output .= $l;
241             }
242 0         0 close $rfh;
243 0         0 while (waitpid($pid, 0) <= 0) {
244             }
245 0 0       0 die "*** initdb failed ***\n$output\n"
246             if $? != 0;
247             }
248             }
249              
250             sub _find_program {
251 4     4   11 my $prog = shift;
252 4         13 undef $errstr;
253 4         16 my $path = _get_path_of($prog);
254 4 50       61 return $path
255             if $path;
256 4         91 for my $sp (@SEARCH_PATHS) {
257 3 50       504 return "$sp/bin/$prog"
258             if -x "$sp/bin/$prog";
259             }
260 4         110 $errstr = "could not find $prog, please set appropriate PATH";
261 4         502 return;
262             }
263              
264             sub _get_path_of {
265 4     4   9 my $prog = shift;
266 4         43465 my $path = `which $prog 2> /dev/null`;
267 4 50       213 chomp $path
268             if $path;
269 4 50       132 $path = ''
270             unless -x $path;
271 4         171 $path;
272             }
273              
274             1;
275             __END__