Annotation of embedaddon/lighttpd/tests/LightyTest.pm, revision 1.1.1.1
1.1 misho 1: #! /usr/bin/perl -w
2:
3: package LightyTest;
4: use strict;
5: use IO::Socket;
6: use Test::More;
7: use Socket;
8: use Cwd 'abs_path';
9: use POSIX qw(:sys_wait_h dup2);
10: use Errno qw(EADDRINUSE);
11:
12: sub mtime {
13: my $file = shift;
14: my @stat = stat $file;
15: return @stat ? $stat[9] : 0;
16: }
17: sub new {
18: my $class = shift;
19: my $self = {};
20: my $lpath;
21:
22: $self->{CONFIGFILE} = 'lighttpd.conf';
23:
24: $lpath = (defined $ENV{'top_builddir'} ? $ENV{'top_builddir'} : '..');
25: $self->{BASEDIR} = abs_path($lpath);
26:
27: $lpath = (defined $ENV{'top_builddir'} ? $ENV{'top_builddir'}."/tests/" : '.');
28: $self->{TESTDIR} = abs_path($lpath);
29:
30: $lpath = (defined $ENV{'srcdir'} ? $ENV{'srcdir'} : '.');
31: $self->{SRCDIR} = abs_path($lpath);
32:
33:
34: if (mtime($self->{BASEDIR}.'/src/lighttpd') > mtime($self->{BASEDIR}.'/build/lighttpd')) {
35: $self->{BINDIR} = $self->{BASEDIR}.'/src';
36: if (mtime($self->{BASEDIR}.'/src/.libs')) {
37: $self->{MODULES_PATH} = $self->{BASEDIR}.'/src/.libs';
38: } else {
39: $self->{MODULES_PATH} = $self->{BASEDIR}.'/src';
40: }
41: } else {
42: $self->{BINDIR} = $self->{BASEDIR}.'/build';
43: $self->{MODULES_PATH} = $self->{BASEDIR}.'/build';
44: }
45: $self->{LIGHTTPD_PATH} = $self->{BINDIR}.'/lighttpd';
46: $self->{PORT} = 2048;
47:
48: my ($name, $aliases, $addrtype, $net) = gethostbyaddr(inet_aton("127.0.0.1"), AF_INET);
49:
50: $self->{HOSTNAME} = $name;
51:
52: bless($self, $class);
53:
54: return $self;
55: }
56:
57: sub listening_on {
58: my $self = shift;
59: my $port = shift;
60:
61: my $remote =
62: IO::Socket::INET->new(Proto => "tcp",
63: PeerAddr => "127.0.0.1",
64: PeerPort => $port) or return 0;
65:
66: close $remote;
67:
68: return 1;
69: }
70:
71: sub stop_proc {
72: my $self = shift;
73:
74: my $pid = $self->{LIGHTTPD_PID};
75: if (defined $pid && $pid != -1) {
76: kill('TERM', $pid) or return -1;
77: return -1 if ($pid != waitpid($pid, 0));
78: } else {
79: diag("\nProcess not started, nothing to stop");
80: return -1;
81: }
82:
83: return 0;
84: }
85:
86: sub wait_for_port_with_proc {
87: my $self = shift;
88: my $port = shift;
89: my $child = shift;
90: my $timeout = 5*10; # 5 secs, select waits 0.1 s
91:
92: while (0 == $self->listening_on($port)) {
93: select(undef, undef, undef, 0.1);
94: $timeout--;
95:
96: # the process is gone, we failed
97: if (0 != waitpid($child, WNOHANG)) {
98: return -1;
99: }
100: if (0 >= $timeout) {
101: diag("\nTimeout while trying to connect; killing child");
102: kill('TERM', $child);
103: return -1;
104: }
105: }
106:
107: return 0;
108: }
109:
110: sub start_proc {
111: my $self = shift;
112: # kill old proc if necessary
113: #$self->stop_proc;
114:
115: if ($self->listening_on($self->{PORT})) {
116: diag("\nPort ".$self->{PORT}." already in use");
117: return -1;
118: }
119:
120: # pre-process configfile if necessary
121: #
122:
123: $ENV{'SRCDIR'} = $self->{BASEDIR}.'/tests';
124: $ENV{'PORT'} = $self->{PORT};
125:
126: my @cmdline = ($self->{LIGHTTPD_PATH}, "-D", "-f", $self->{SRCDIR}."/".$self->{CONFIGFILE}, "-m", $self->{MODULES_PATH});
127: if (defined $ENV{"TRACEME"} && $ENV{"TRACEME"} eq 'strace') {
128: @cmdline = (qw(strace -tt -s 512 -o strace), @cmdline);
129: } elsif (defined $ENV{"TRACEME"} && $ENV{"TRACEME"} eq 'truss') {
130: @cmdline = (qw(truss -a -l -w all -v all -o strace), @cmdline);
131: } elsif (defined $ENV{"TRACEME"} && $ENV{"TRACEME"} eq 'gdb') {
132: @cmdline = ('gdb', '--batch', '--ex', 'run', '--ex', 'bt full', '--args', @cmdline);
133: } elsif (defined $ENV{"TRACEME"} && $ENV{"TRACEME"} eq 'valgrind') {
134: @cmdline = (qw(valgrind --tool=memcheck --show-reachable=yes --leak-check=yes --log-file=valgrind), @cmdline);
135: }
136: # diag("\nstarting lighttpd at :".$self->{PORT}.", cmdline: ".@cmdline );
137: my $child = fork();
138: if (not defined $child) {
139: diag("\nFork failed");
140: return -1;
141: }
142: if ($child == 0) {
143: exec @cmdline or die($?);
144: }
145:
146: if (0 != $self->wait_for_port_with_proc($self->{PORT}, $child)) {
147: diag(sprintf('\nThe process %i is not up', $child));
148: return -1;
149: }
150:
151: $self->{LIGHTTPD_PID} = $child;
152:
153: 0;
154: }
155:
156: sub handle_http {
157: my $self = shift;
158: my $t = shift;
159: my $EOL = "\015\012";
160: my $BLANK = $EOL x 2;
161: my $host = "127.0.0.1";
162:
163: my @request = $t->{REQUEST};
164: my @response = $t->{RESPONSE};
165: my $slow = defined $t->{SLOWREQUEST};
166: my $is_debug = $ENV{"TRACE_HTTP"};
167:
168: my $remote =
169: IO::Socket::INET->new(Proto => "tcp",
170: PeerAddr => $host,
171: PeerPort => $self->{PORT});
172:
173: if (not defined $remote) {
174: diag("\nconnect failed: $!");
175: return -1;
176: }
177:
178: $remote->autoflush(1);
179:
180: if (!$slow) {
181: diag("\nsending request header to ".$host.":".$self->{PORT}) if $is_debug;
182: foreach(@request) {
183: # pipeline requests
184: s/\r//g;
185: s/\n/$EOL/g;
186:
187: print $remote $_.$BLANK;
188: diag("\n<< ".$_) if $is_debug;
189: }
190: shutdown($remote, 1); # I've stopped writing data
191: } else {
192: diag("\nsending request header to ".$host.":".$self->{PORT}) if $is_debug;
193: foreach(@request) {
194: # pipeline requests
195: chomp;
196: s/\r//g;
197: s/\n/$EOL/g;
198:
199: print $remote $_;
200: diag("<< ".$_."\n") if $is_debug;
201: select(undef, undef, undef, 0.1);
202: print $remote "\015";
203: select(undef, undef, undef, 0.1);
204: print $remote "\012";
205: select(undef, undef, undef, 0.1);
206: print $remote "\015";
207: select(undef, undef, undef, 0.1);
208: print $remote "\012";
209: select(undef, undef, undef, 0.1);
210: }
211:
212: }
213: diag("\n... done") if $is_debug;
214:
215: my $lines = "";
216:
217: diag("\nreceiving response") if $is_debug;
218: # read everything
219: while(<$remote>) {
220: $lines .= $_;
221: diag(">> ".$_) if $is_debug;
222: }
223: diag("\n... done") if $is_debug;
224:
225: close $remote;
226:
227: my $full_response = $lines;
228:
229: my $href;
230: foreach $href ( @{ $t->{RESPONSE} }) {
231: # first line is always response header
232: my %resp_hdr;
233: my $resp_body;
234: my $resp_line;
235: my $conditions = $_;
236:
237: for (my $ln = 0; defined $lines; $ln++) {
238: (my $line, $lines) = split($EOL, $lines, 2);
239:
240: # header finished
241: last if(!defined $line or length($line) == 0);
242:
243: if ($ln == 0) {
244: # response header
245: $resp_line = $line;
246: } else {
247: # response vars
248:
249: if ($line =~ /^([^:]+):\s*(.+)$/) {
250: (my $h = $1) =~ tr/[A-Z]/[a-z]/;
251:
252: if (defined $resp_hdr{$h}) {
253: # diag(sprintf("\nheader '%s' is duplicated: '%s' and '%s'\n",
254: # $h, $resp_hdr{$h}, $2));
255: $resp_hdr{$h} .= ', '.$2;
256: } else {
257: $resp_hdr{$h} = $2;
258: }
259: } else {
260: diag(sprintf("\nunexpected line '%s'", $line));
261: return -1;
262: }
263: }
264: }
265:
266: if (not defined($resp_line)) {
267: diag(sprintf("\nempty response"));
268: return -1;
269: }
270:
271: $t->{etag} = $resp_hdr{'etag'};
272: $t->{date} = $resp_hdr{'date'};
273:
274: # check length
275: if (defined $resp_hdr{"content-length"}) {
276: $resp_body = substr($lines, 0, $resp_hdr{"content-length"});
277: if (length($lines) < $resp_hdr{"content-length"}) {
278: $lines = "";
279: } else {
280: $lines = substr($lines, $resp_hdr{"content-length"});
281: }
282: undef $lines if (length($lines) == 0);
283: } else {
284: $resp_body = $lines;
285: undef $lines;
286: }
287:
288: # check conditions
289: if ($resp_line =~ /^(HTTP\/1\.[01]) ([0-9]{3}) .+$/) {
290: if ($href->{'HTTP-Protocol'} ne $1) {
291: diag(sprintf("\nproto failed: expected '%s', got '%s'", $href->{'HTTP-Protocol'}, $1));
292: return -1;
293: }
294: if ($href->{'HTTP-Status'} ne $2) {
295: diag(sprintf("\nstatus failed: expected '%s', got '%s'", $href->{'HTTP-Status'}, $2));
296: return -1;
297: }
298: } else {
299: diag(sprintf("\nunexpected resp_line '%s'", $resp_line));
300: return -1;
301: }
302:
303: if (defined $href->{'HTTP-Content'}) {
304: $resp_body = "" unless defined $resp_body;
305: if ($href->{'HTTP-Content'} ne $resp_body) {
306: diag(sprintf("\nbody failed: expected '%s', got '%s'", $href->{'HTTP-Content'}, $resp_body));
307: return -1;
308: }
309: }
310:
311: if (defined $href->{'-HTTP-Content'}) {
312: if (defined $resp_body && $resp_body ne '') {
313: diag(sprintf("\nbody failed: expected empty body, got '%s'", $resp_body));
314: return -1;
315: }
316: }
317:
318: foreach (keys %{ $href }) {
319: next if $_ eq 'HTTP-Protocol';
320: next if $_ eq 'HTTP-Status';
321: next if $_ eq 'HTTP-Content';
322: next if $_ eq '-HTTP-Content';
323:
324: (my $k = $_) =~ tr/[A-Z]/[a-z]/;
325:
326: my $verify_value = 1;
327: my $key_inverted = 0;
328:
329: if (substr($k, 0, 1) eq '+') {
330: $k = substr($k, 1);
331: $verify_value = 0;
332: } elsif (substr($k, 0, 1) eq '-') {
333: ## the key should NOT exist
334: $k = substr($k, 1);
335: $key_inverted = 1;
336: $verify_value = 0; ## skip the value check
337: }
338:
339: if ($key_inverted) {
340: if (defined $resp_hdr{$k}) {
341: diag(sprintf("\nheader '%s' MUST not be set", $k));
342: return -1;
343: }
344: } else {
345: if (not defined $resp_hdr{$k}) {
346: diag(sprintf("\nrequired header '%s' is missing", $k));
347: return -1;
348: }
349: }
350:
351: if ($verify_value) {
352: if ($href->{$_} =~ /^\/(.+)\/$/) {
353: if ($resp_hdr{$k} !~ /$1/) {
354: diag(sprintf("\nresponse-header failed: expected '%s', got '%s', regex: %s",
355: $href->{$_}, $resp_hdr{$k}, $1));
356: return -1;
357: }
358: } elsif ($href->{$_} ne $resp_hdr{$k}) {
359: diag(sprintf("\nresponse-header failed: expected '%s', got '%s'",
360: $href->{$_}, $resp_hdr{$k}));
361: return -1;
362: }
363: }
364: }
365: }
366:
367: # we should have sucked up everything
368: if (defined $lines) {
369: diag(sprintf("\nunexpected lines '%s'", $lines));
370: return -1;
371: }
372:
373: return 0;
374: }
375:
376: sub spawnfcgi {
377: my ($self, $binary, $port) = @_;
378: my $child = fork();
379: if (not defined $child) {
380: diag("\nCouldn't fork");
381: return -1;
382: }
383: if ($child == 0) {
384: my $iaddr = inet_aton('localhost') || die "no host: localhost";
385: my $proto = getprotobyname('tcp');
386: socket(SOCK, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
387: setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) || die "setsockopt: $!";
388: bind(SOCK, sockaddr_in($port, $iaddr)) || die "bind: $!";
389: listen(SOCK, 1024) || die "listen: $!";
390: dup2(fileno(SOCK), 0) || die "dup2: $!";
391: exec { $binary } ($binary) or die($?);
392: } else {
393: if (0 != $self->wait_for_port_with_proc($port, $child)) {
394: diag(sprintf("\nThe process %i is not up (port %i, %s)", $child, $port, $binary));
395: return -1;
396: }
397: return $child;
398: }
399: }
400:
401: sub endspawnfcgi {
402: my ($self, $pid) = @_;
403: return -1 if (-1 == $pid);
404: kill(2, $pid);
405: waitpid($pid, 0);
406: return 0;
407: }
408:
409: 1;
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>