File Coverage

blib/lib/Perlbal/Test.pm
Criterion Covered Total %
statement 112 135 82.9
branch 43 58 74.1
condition 16 24 66.6
subroutine 21 23 91.3
pod 0 14 0.0
total 192 254 75.5


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2              
3             package Perlbal::Test;
4              
5             =head1 NAME
6              
7             Perlbal::Test - Test harness for perlbal server
8              
9             =head1 SYNOPSIS
10              
11             # my $msock = Perlbal::Test::start_server();
12              
13             =head1 DESCRIPTION
14              
15             Perlbal::Test provides access to a perlbal server running on the
16             local host, for testing purposes.
17              
18             The server can be an already-existing server, a child process, or
19             the current process.
20              
21             Various functions are provided to interact with the server.
22              
23             =head1 FUNCTIONS
24              
25             =cut
26              
27 75     75   68904 use strict;
  75         134  
  75         2839  
28 75     75   78533 use POSIX qw( :sys_wait_h );
  75         3346838  
  75         545  
29 75     75   319388 use IO::Socket::INET;
  75         12057538  
  75         666  
30 75     75   57697 use Socket qw(MSG_NOSIGNAL IPPROTO_TCP TCP_NODELAY SOL_SOCKET);
  75         174  
  75         24870  
31 75     75   170681 use HTTP::Response;
  75         21483992  
  75         4011  
32              
33             require Exporter;
34 75     75   860 use vars qw(@ISA @EXPORT);
  75         149  
  75         147385  
35             @ISA = qw(Exporter);
36             @EXPORT = qw(ua start_server foreach_aio manage filecontent tempdir new_port
37             manage_multi
38             mgmt_port wait_on_child dump_res resp_from_sock msock);
39              
40             our $i_am_parent = 0;
41             our $msock; # management sock of child
42             our $to_kill = 0;
43             our $mgmt_port;
44              
45             our $free_port = 60000;
46              
47             =head1 I
48              
49             Return the current management port number.
50              
51             =cut
52              
53             sub mgmt_port {
54 0     0 0 0 return $mgmt_port;
55             }
56              
57             END {
58 75 100   75   195920 manage("shutdown") if $i_am_parent;
59             }
60              
61             =head1 I
62              
63             Return a readable string formatted from an HTTP::Response object.
64             Only the first 80 characters of returned content are returned.
65              
66             =cut
67              
68             sub dump_res {
69 0     0 0 0 my $res = shift;
70 0         0 my ($pkg, $filename, $line) = caller;
71 0         0 my $ret = "$filename:$line ==> ";
72 0 0       0 unless ($res) {
73 0         0 $ret .= "[response undefined]\n";
74 0         0 return $ret;
75             }
76 0         0 my $ct = $res->content;
77 0         0 my $len = length $ct;
78 0 0       0 if ($len > 80) {
79 0         0 $ct = substr($ct, 0, 80) . "...";
80             }
81 0         0 my $status = $res->status_line;
82 0         0 $status =~ s/[\r\n]//g;
83 0         0 return $ret . "status=[$status] content=$len" . "[$ct]\n";
84             }
85              
86             =head1 I
87              
88             Return a newly created temporary directory. The directory will be
89             removed automatically upon program exit.
90              
91             =cut
92              
93             sub tempdir {
94 29     29 0 174366 require File::Temp;
95 29         646851 return File::Temp::tempdir( CLEANUP => 1 );
96             }
97              
98             =head1 I
99              
100             Return the next free port number in the series. Port numbers are assigned
101             starting at 60000.
102              
103             =cut
104              
105             sub new_port {
106 603 100   603 0 157588 test_port() ? return $free_port++ : return new_port($free_port++);
107             }
108              
109             =head1 I
110              
111             Return 1 if the port is free to use for listening on $free_port else return 0.
112              
113             =cut
114              
115             sub test_port {
116 603 100   603 0 5094 my $sock = IO::Socket::INET->new(LocalPort => $free_port) or return 0;
117 168         43780 $sock->close();
118 168         8746 return 1;
119             }
120              
121             =head1 I>
122              
123             Return a string containing the contents of the file $file. If $file
124             cannot be opened, then return undef.
125              
126             =cut
127              
128             sub filecontent {
129 11     11 0 377416 my $file = shift;
130 11         33 my $ct;
131 11 50       835 open (F, $file) or return undef;
132 11         26 $ct = do { local $/; ; };
  11         425  
  11         782  
133 11         144 close F;
134 11         9269 return $ct;
135             }
136              
137             =head1 I
138              
139             Set the server into each AIO mode (none, ioaio) and call the specified
140             callback function with the mode name as argument.
141              
142             =cut
143              
144             sub foreach_aio (&) {
145 4     4 0 5518 my $cb = shift;
146              
147 4         13 foreach my $mode (qw(none ioaio)) {
148 8         18154 my $line = manage("SERVER aio_mode = $mode");
149 8 100       48 next unless $line;
150 4         26 $cb->($mode);
151             }
152             }
153              
154             =head1 I
155              
156             Send a command $cmd to the server, and return the response line from
157             the server.
158              
159             Optional arguments are:
160              
161             quiet_failure => 1
162              
163             Output a warning if the response indicated an error,
164             unless $opts{quiet_failure} is true, or the command
165             was 'shutdown' (which doesn't return a response).
166              
167             =cut
168              
169             sub manage {
170 94     94 0 1495339 my $cmd = shift;
171 94         325 my %opts = @_;
172              
173 94         50847 print $msock "$cmd\r\n";
174 93         126423735 my $res = <$msock>;
175              
176 93 100 100     1829 if (!$res || $res =~ /^ERR/) {
177             # Make the result visible in failure cases, unless
178             # the command was 'shutdown'... cause that never
179             # returns anything.
180 26 100 100     870 warn "Manage command failed: '$cmd' '$res'\n"
181             unless($opts{quiet_failure} || $cmd eq 'shutdown');
182              
183 26         183 return 0;
184             }
185 67         614 return $res;
186             }
187              
188             =head1 I
189              
190             Send a command $cmd to the server, and return a multi-line
191             response. Return the number zero if there was an error or
192             no response.
193              
194             =cut
195              
196             sub manage_multi {
197 1     1 0 2 my $cmd = shift;
198              
199 1         25 print $msock "$cmd\r\n";
200 1         3 my $res;
201 1         738 while (<$msock>) {
202 5 100       49 last if /^\./;
203 4 50       13 last if /^ERROR/;
204 4         40062 $res .= $_;
205             }
206 1 50 33     28 return 0 if !$res || $res =~ /^ERR/;
207 1         10 return $res;
208             }
209              
210             =head1 I
211              
212             Optionally start a perlbal server and return a socket connected to its
213             management port.
214              
215             The argument $conf is a string specifying initial configuration
216             commands.
217              
218             If the environment variable TEST_PERLBAL_FOREGROUND is set to a true
219             value then a server will be started in the foreground, in which case
220             this function does not return. When the server function finishes,
221             exit() will be called to terminate the process.
222              
223             If the environment variable TEST_PERLBAL_USE_EXISTING is set to a true
224             value then a socket will be returned which is connected to an existing
225             server's management port.
226              
227             Otherwise, a child process is forked and a socket is returned which is
228             connected to the child's management port.
229              
230             The management port is assigned automatically, a new port number each
231             time this function is called. The starting port number is 60000.
232              
233             =cut
234              
235             sub start_server {
236 37     37 0 1395668 my $conf = shift;
237 37         267 $mgmt_port = new_port();
238              
239 37 50       315 if ($ENV{'TEST_PERLBAL_FOREGROUND'}) {
240 0         0 _start_perbal_server($conf, $mgmt_port);
241             }
242              
243 37 50       466 if ($ENV{'TEST_PERLBAL_USE_EXISTING'}) {
244 0         0 my $msock = wait_on_child(0, $mgmt_port);
245 0         0 return $msock;
246             }
247              
248 37         81449 my $child = fork;
249 37 100       3659 if ($child) {
250 19         366 $i_am_parent = 1;
251 19         568 $to_kill = $child;
252 19         1216 my $msock = wait_on_child($child, $mgmt_port);
253 18         200 my $rv = waitpid($child, WNOHANG);
254 18 50       168 if ($rv) {
255 0         0 die "Child process (webserver) died.\n";
256             }
257 18         2119 print $msock "proc\r\n";
258 18         102 my $spid = undef;
259 18         76958 while (<$msock>) {
260 108 100       1312 last if m!^\.\r?\n!;
261 90 100       3717 next unless /^pid:\s+(\d+)/;
262 18         74157 $spid = $1;
263             }
264 18 50       147 die "Our child was $child, but we connected and it says it's $spid."
265             unless $child == $spid;
266              
267 18         962 return $msock;
268             }
269              
270             # child process...
271 18         1805 _start_perbal_server($conf, $mgmt_port);
272             }
273              
274             # Start a perlbal server running and tell it to listen on the specified
275             # management port number. This function does not return.
276              
277             sub _start_perbal_server {
278 18     18   575 my ($conf, $mgmt_port) = @_;
279              
280 18         103003 require Perlbal;
281              
282 18         137 $conf .= qq{
283             CREATE SERVICE mgmt
284             SET mgmt.listen = 127.0.0.1:$mgmt_port
285             SET mgmt.role = management
286             ENABLE mgmt
287             };
288              
289 18     1   285 my $out = sub { print STDOUT "$_[0]\n"; };
  1         441  
290 18 100       118 die "Configuration error" unless Perlbal::run_manage_commands($conf, $out);
291              
292 17 50       245 unless (Perlbal::Socket->WatchedSockets() > 0) {
293 0         0 die "Invalid configuration. (shouldn't happen?) Stopping (self=$$).\n";
294             }
295              
296 17         184 Perlbal::run();
297 0         0 exit 0;
298             }
299              
300              
301             =head1 I
302              
303             Return a reference to the socket connected to the server's management
304             port.
305              
306             =cut
307              
308             sub msock {
309 6     6 0 1071876 return $msock;
310             }
311              
312              
313             =head1 I
314              
315             Return a new instance of LWP::UserAgent.
316              
317             =cut
318              
319             sub ua {
320 7     7 0 20035 require LWP::UserAgent;
321 7         146352 return LWP::UserAgent->new;
322             }
323              
324             =head1 I
325              
326             Return a socket which is connected to a child process.
327              
328             $pid specifies the child process id, and $port is the port number on
329             which the child is listening.
330              
331             Several attempts are made; if the child dies or a connection cannot
332             be made within 5 seconds then this function dies with an error message.
333              
334             =cut
335              
336             sub wait_on_child {
337 62     62 0 600 my $pid = shift;
338 62         869 my $port = shift;
339              
340 62         1136120 my $start = time;
341 62         492 while (1) {
342 151         9409 $msock = IO::Socket::INET->new(PeerAddr => "127.0.0.1:$port");
343 151 100       2387048 return $msock if $msock;
344 90         22576128 select undef, undef, undef, 0.25;
345 90 50 33     4681620 if ($pid && waitpid($pid, WNOHANG) > 0) {
346 0         0 die "Child process (webserver) died.\n";
347             }
348 90 100       2073828 die "Timeout waiting for port $port to startup" if time > $start + 5;
349             }
350             }
351              
352             =head1 I
353              
354             Read an HTTP response from a socket and return it
355             as an HTTP::Response object
356              
357             In scalar mode, return only the $http_response object.
358              
359             In array mode, return an array of ($http_response, $firstline) where
360             $firstline is the first line read from the socket, for example:
361              
362             "HTTP/1.1 200 OK"
363              
364             =cut
365              
366             sub resp_from_sock {
367 145     145 0 8358413 my $sock = shift;
368              
369 145         458 my $res = "";
370 145         424 my $firstline = undef;
371              
372 145         7197579 while (<$sock>) {
373 886         18319 $res .= $_;
374 886   66     3508 $firstline ||= $_;
375 886 100 66     17341 last if ! $_ || /^\r?\n/;
376             }
377              
378 145 50       1333 unless ($firstline) {
379 0         0 print STDERR "Didn't get a firstline in HTTP response.\n";
380 0         0 return undef;
381             }
382              
383 145         4319 my $resp = HTTP::Response->parse($res);
384 145 50       105474 return undef unless $resp;
385              
386 145         1472 my $cl = $resp->header('Content-Length');
387 145 100 66     12150 if (defined $cl && $cl > 0) {
388 140         380 my $content = '';
389 140         347 my $rv;
390 140   66     16853 while (($rv = read($sock, $content, $cl)) &&
391             ($cl -= $rv) > 0) {
392             # don't do anything, the loop is it
393             }
394 140         915 $resp->content($content);
395             }
396              
397 145 100       7920 return wantarray ? ($resp, $firstline) : $resp;
398             }
399              
400             1;