8 use POSIX
qw(:sys_wait_h dup2);
9 use Errno
qw(EADDRINUSE);
12 my @DEFAULT_PATHS = ('/usr/bin/', '/usr/local/bin/');
13 my ($envname, $program) = @_;
16 if (defined $ENV{$envname}) {
17 $location = $ENV{$envname};
19 $location = `which "$program" 2>/dev/null`;
22 for my $path (@DEFAULT_PATHS) {
23 $location = $path . $program;
30 $ENV{$envname} = $location;
33 delete $ENV{$envname};
39 our $HAVE_PHP = find_program
('PHP', 'php-cgi');
40 our $HAVE_PERL = find_program
('PERL', 'perl');
42 die "Couldn't find path to perl, but it obviously seems to be running";
48 my @stat = stat $file;
49 return @stat ?
$stat[9] : 0;
57 $self->{CONFIGFILE
} = 'lighttpd.conf';
59 $lpath = (defined $ENV{'top_builddir'} ?
$ENV{'top_builddir'} : '..');
60 $self->{BASEDIR
} = abs_path
($lpath);
62 $lpath = (defined $ENV{'top_builddir'} ?
$ENV{'top_builddir'}."/tests/" : '.');
63 $self->{TESTDIR
} = abs_path
($lpath);
65 $lpath = (defined $ENV{'srcdir'} ?
$ENV{'srcdir'} : '.');
66 $self->{SRCDIR
} = abs_path
($lpath);
69 if (mtime
($self->{BASEDIR
}.'/src/lighttpd') > mtime
($self->{BASEDIR
}.'/build/lighttpd')) {
70 $self->{BINDIR
} = $self->{BASEDIR
}.'/src';
71 if (mtime
($self->{BASEDIR
}.'/src/.libs')) {
72 $self->{MODULES_PATH
} = $self->{BASEDIR
}.'/src/.libs';
74 $self->{MODULES_PATH
} = $self->{BASEDIR
}.'/src';
77 $self->{BINDIR
} = $self->{BASEDIR
}.'/build';
78 $self->{MODULES_PATH
} = $self->{BASEDIR
}.'/build';
80 $self->{LIGHTTPD_PATH
} = $self->{BINDIR
}.'/lighttpd';
83 my ($name, $aliases, $addrtype, $net) = gethostbyaddr(inet_aton
("127.0.0.1"), AF_INET
);
85 $self->{HOSTNAME
} = $name;
96 my $remote = IO
::Socket
::INET
->new(
98 PeerAddr
=> "127.0.0.1",
99 PeerPort
=> $port) or return 0;
109 my $pid = $self->{LIGHTTPD_PID
};
110 if (defined $pid && $pid != -1) {
111 kill('TERM', $pid) or return -1;
112 return -1 if ($pid != waitpid($pid, 0));
114 diag
("\nProcess not started, nothing to stop");
121 sub wait_for_port_with_proc
{
125 my $timeout = 10*50; # 10 secs (valgrind might take a while), select waits 0.02 s
127 while (0 == $self->listening_on($port)) {
128 select(undef, undef, undef, 0.02);
131 # the process is gone, we failed
132 if (0 != waitpid($child, WNOHANG
)) {
136 diag
("\nTimeout while trying to connect; killing child");
137 kill('TERM', $child);
147 # kill old proc if necessary
150 if ($self->listening_on($self->{PORT
})) {
151 diag
("\nPort ".$self->{PORT
}." already in use");
155 # pre-process configfile if necessary
158 $ENV{'SRCDIR'} = $self->{BASEDIR
}.'/tests';
159 $ENV{'PORT'} = $self->{PORT
};
161 my @cmdline = ($self->{LIGHTTPD_PATH
}, "-D", "-f", $self->{SRCDIR
}."/".$self->{CONFIGFILE
}, "-m", $self->{MODULES_PATH
});
162 if (defined $ENV{"TRACEME"} && $ENV{"TRACEME"} eq 'strace') {
163 @cmdline = (qw(strace -tt -s 4096 -o strace -f -v), @cmdline);
164 } elsif (defined $ENV{"TRACEME"} && $ENV{"TRACEME"} eq 'truss') {
165 @cmdline = (qw(truss -a -l -w all -v all -o strace), @cmdline);
166 } elsif (defined $ENV{"TRACEME"} && $ENV{"TRACEME"} eq 'gdb') {
167 @cmdline = ('gdb', '--batch', '--ex', 'run', '--ex', 'bt full', '--args', @cmdline);
168 } elsif (defined $ENV{"TRACEME"} && $ENV{"TRACEME"} eq 'valgrind') {
169 @cmdline = (qw(valgrind --tool=memcheck --track-origins=yes --show-reachable=yes --leak-check=yes --log-file=valgrind.%p), @cmdline);
171 # diag("\nstarting lighttpd at :".$self->{PORT}.", cmdline: ".@cmdline );
173 if (not defined $child) {
174 diag("\nFork failed");
178 exec @cmdline or die($?);
181 if (0 != $self->wait_for_port_with_proc($self->{PORT}, $child)) {
182 diag(sprintf('\nThe process %i is not up', $child));
186 $self->{LIGHTTPD_PID} = $child;
194 my $EOL = "\015\012";
195 my $BLANK = $EOL x 2;
196 my $host = "127.0.0.1";
198 my @request = $t->{REQUEST};
199 my @response = $t->{RESPONSE};
200 my $slow = defined $t->{SLOWREQUEST};
201 my $is_debug = $ENV{"TRACE_HTTP"};
204 IO::Socket::INET->new(
207 PeerPort => $self->{PORT});
209 if (not defined $remote) {
210 diag("\nconnect failed: $!");
214 $remote->autoflush(1);
217 diag("\nsending request header to ".$host.":".$self->{PORT}) if $is_debug;
223 print $remote $_.$BLANK;
224 diag("\n<< ".$_) if $is_debug;
226 shutdown($remote, 1) if ($^O ne "openbsd" && $^O ne "dragonfly"); # I've stopped writing data
228 diag("\nsending request header to ".$host.":".$self->{PORT}) if $is_debug;
236 diag("<< ".$_."\n") if $is_debug;
237 select(undef, undef, undef, 0.1);
238 print $remote "\015";
239 select(undef, undef, undef, 0.1);
240 print $remote "\012";
241 select(undef, undef, undef, 0.1);
242 print $remote "\015";
243 select(undef, undef, undef, 0.1);
244 print $remote "\012";
245 select(undef, undef, undef, 0.1);
249 diag("\n... done") if $is_debug;
253 diag("\nreceiving response") if $is_debug;
257 diag(">> ".$_) if $is_debug;
259 diag("\n... done") if $is_debug;
263 my $full_response = $lines;
266 foreach $href ( @{ $t->{RESPONSE} }) {
267 # first line is always response header
273 for (my $ln = 0; defined $lines; $ln++) {
274 (my $line, $lines) = split($EOL, $lines, 2);
277 last if(!defined $line or length($line) == 0);
285 if ($line =~ /^([^:]+):\s*(.+)$/) {
286 (my $h = $1) =~ tr/[A-Z]/[a-z]/;
288 if (defined $resp_hdr{$h}) {
289 # diag(sprintf("\nheader '%s' is duplicated: '%s' and '%s'\n",
290 # $h, $resp_hdr{$h}, $2));
291 $resp_hdr{$h} .= ', '.$2;
296 diag(sprintf("\nunexpected line '%s'", $line));
302 if (not defined($resp_line)) {
303 diag(sprintf("\nempty response"));
307 $t->{etag} = $resp_hdr{'etag'};
308 $t->{date} = $resp_hdr{'date'};
311 if (defined $resp_hdr{"content-length"}) {
312 $resp_body = substr($lines, 0, $resp_hdr{"content-length"});
313 if (length($lines) < $resp_hdr{"content-length"}) {
316 $lines = substr($lines, $resp_hdr{"content-length"});
318 undef $lines if (length($lines) == 0);
325 if ($resp_line =~ /^(HTTP\/1\.[01]) ([0-9]{3}) .+$/) {
326 if ($href->{'HTTP-Protocol'} ne $1) {
327 diag(sprintf("\nproto failed: expected '%s', got '%s'", $href->{'HTTP-Protocol'}, $1));
330 if ($href->{'HTTP-Status'} ne $2) {
331 diag(sprintf("\nstatus failed: expected '%s', got '%s'", $href->{'HTTP-Status'}, $2));
335 diag(sprintf("\nunexpected resp_line '%s'", $resp_line));
339 if (defined $href->{'HTTP-Content'}) {
340 $resp_body = "" unless defined $resp_body;
341 if ($href->{'HTTP-Content'} ne $resp_body) {
342 diag(sprintf("\nbody failed: expected '%s', got '%s'", $href->{'HTTP-Content'}, $resp_body));
347 if (defined $href->{'-HTTP-Content'}) {
348 if (defined $resp_body && $resp_body ne '') {
349 diag(sprintf("\nbody failed: expected empty body, got '%s'", $resp_body));
354 foreach (keys %{ $href }) {
355 next if $_ eq 'HTTP-Protocol';
356 next if $_ eq 'HTTP-Status';
357 next if $_ eq 'HTTP-Content';
358 next if $_ eq '-HTTP-Content';
360 (my $k = $_) =~ tr/[A-Z]/[a-z]/;
362 my $verify_value = 1;
363 my $key_inverted = 0;
365 if (substr($k, 0, 1) eq '+') {
368 } elsif (substr($k, 0, 1) eq '-') {
369 ## the key should NOT exist
372 $verify_value = 0; ## skip the value check
376 if (defined $resp_hdr{$k}) {
377 diag(sprintf("\nheader '%s' MUST not be set", $k));
381 if (not defined $resp_hdr{$k}) {
382 diag(sprintf("\nrequired header '%s' is missing", $k));
388 if ($href->{$_} =~ /^\/(.+)\/$/) {
389 if ($resp_hdr{$k} !~ /$1/) {
391 "\nresponse-header failed: expected '%s', got '%s', regex: %s",
392 $href->{$_}, $resp_hdr{$k}, $1));
395 } elsif ($href->{$_} ne $resp_hdr{$k}) {
397 "\nresponse-header failed: expected '%s', got '%s'",
398 $href->{$_}, $resp_hdr{$k}));
405 # we should have sucked up everything
406 if (defined $lines) {
407 diag(sprintf("\nunexpected lines '%s'", $lines));
415 my ($self, $binary, $port) = @_;
417 if (not defined $child) {
418 diag("\nCouldn't fork");
422 my $iaddr = inet_aton('localhost') || die "no host: localhost";
423 my $proto = getprotobyname('tcp');
424 socket(SOCK, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
425 setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) || die "setsockopt: $!";
426 bind(SOCK, sockaddr_in($port, $iaddr)) || die "bind: $!";
427 listen(SOCK, 1024) || die "listen: $!";
428 dup2(fileno(SOCK), 0) || die "dup2: $!";
429 exec { $binary } ($binary) or die($?);
431 if (0 != $self->wait_for_port_with_proc($port, $child)) {
432 diag(sprintf("\nThe process %i is not up (port %i, %s)", $child, $port, $binary));
440 my ($self, $pid) = @_;
441 return -1 if (-1 == $pid);
448 # quick-n-dirty crude parse of "lighttpd -V"
449 # (XXX: should be run on demand and only once per instance, then cached)
450 my ($self, $feature) = @_;
452 open($FH, "-|",$self->{LIGHTTPD_PATH}, "-V") || return 0;
454 return ($1 eq '+') if (/([-+]) \Q$feature\E/);