root/trunk/lib/Perlbal/Test.pm @ 784

Revision 784, 8.4 kB (checked in by nickandrew, 17 months ago)

Move manage_multi() into Perlbal::Test

The manage_multi function is logically part of Perlbal::Test even though it
is currently used in only one test file.

Signed-off-by: Nick Andrew <nick@…>

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