summaryrefslogtreecommitdiffstats
path: root/debian/perl-framework/Apache-Test/lib/Apache/TestServer.pm
diff options
context:
space:
mode:
Diffstat (limited to 'debian/perl-framework/Apache-Test/lib/Apache/TestServer.pm')
-rw-r--r--debian/perl-framework/Apache-Test/lib/Apache/TestServer.pm724
1 files changed, 724 insertions, 0 deletions
diff --git a/debian/perl-framework/Apache-Test/lib/Apache/TestServer.pm b/debian/perl-framework/Apache-Test/lib/Apache/TestServer.pm
new file mode 100644
index 0000000..3a30a63
--- /dev/null
+++ b/debian/perl-framework/Apache-Test/lib/Apache/TestServer.pm
@@ -0,0 +1,724 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+package Apache::TestServer;
+
+use strict;
+use warnings FATAL => 'all';
+
+use Config;
+use Socket ();
+use File::Spec::Functions qw(catfile);
+
+use Apache::TestTrace;
+use Apache::TestRun;
+use Apache::TestConfig ();
+use Apache::TestRequest ();
+
+use constant COLOR => Apache::TestConfig::COLOR;
+use constant WIN32 => Apache::TestConfig::WIN32;
+
+my $CTRL_M = COLOR ? "\r" : "\n";
+
+# some debuggers use the same syntax as others, so we reuse the same
+# code by using the following mapping
+my %debuggers = (
+ gdb => 'gdb',
+ ddd => 'gdb',
+ valgrind => 'valgrind',
+ strace => 'strace',
+);
+
+sub new {
+ my $class = shift;
+ my $config = shift;
+
+ my $self = bless {
+ config => $config || Apache::TestConfig->thaw,
+ }, $class;
+
+ $self->{name} = join ':',
+ map { $self->{config}->{vars}->{$_} } qw(servername port);
+
+ $self->{port_counter} = $self->{config}->{vars}->{port};
+
+ $self;
+}
+
+# call this when you already know where httpd is
+sub post_config {
+ my($self) = @_;
+
+ $self->{version} = $self->{config}->httpd_version || '';
+ $self->{mpm} = $self->{config}->httpd_mpm || '';
+
+ # try to get the revision number from the standard Apache version
+ # string and various variations made by distributions which mangle
+ # that string
+
+ # Foo-Apache-Bar/x.y.z
+ ($self->{rev}) = $self->{version} =~ m|/(\d)\.|;
+
+ if ($self->{rev}) {
+ debug "Matched Apache revision $self->{version} $self->{rev}";
+ }
+ else {
+ # guessing is not good as it'll only mislead users
+ # and we can't die since a config object is required
+ # during Makefile.PL's write_perlscript when path to httpd may
+ # be unknown yet. so default to non-existing version 0 for now.
+ # and let TestRun.pm figure out the required pieces
+ debug "can't figure out Apache revision, from string: " .
+ "'$self->{version}', using a non-existing revision 0";
+ $self->{rev} = 0; # unknown
+ }
+
+ ($self->{revminor}) = $self->{version} =~ m|/\d\.(\d)|;
+
+ if ($self->{revminor}) {
+ debug "Matched Apache revminor $self->{version} $self->{revminor}";
+ }
+ else {
+ $self->{revminor} = 0;
+ }
+
+ $self;
+}
+
+sub version_of {
+ my($self, $thing) = @_;
+ die "Can't figure out what Apache server generation we are running"
+ unless $self->{rev};
+
+ $thing->{$self->{rev}};
+}
+
+my @apache_logs = qw(
+error_log access_log httpd.pid
+apache_runtime_status rewrite_log
+ssl_engine_log ssl_request_log
+cgisock
+);
+
+sub clean {
+ my $self = shift;
+
+ my $dir = $self->{config}->{vars}->{t_logs};
+
+ for (@apache_logs) {
+ my $file = catfile $dir, $_;
+ if (unlink $file) {
+ debug "unlink $file";
+ }
+ }
+}
+
+sub pid_file {
+ my $self = shift;
+
+ my $vars = $self->{config}->{vars};
+
+ return $vars->{t_pid_file} || catfile $vars->{t_logs}, 'httpd.pid';
+}
+
+sub dversion {
+ my $self = shift;
+
+ my $dv = "-D APACHE$self->{rev}";
+
+ if ($self->{rev} == 2 and $self->{revminor} == 4) {
+ $dv .= " -D APACHE2_4";
+ }
+
+ return $dv;
+}
+
+sub config_defines {
+ my $self = shift;
+
+ my @defines = ();
+
+ for my $item (qw(useithreads)) {
+ next unless $Config{$item} and $Config{$item} eq 'define';
+ push @defines, "-D PERL_\U$item";
+ }
+
+ if (my $defines = $self->{config}->{vars}->{defines}) {
+ push @defines, map { "-D $_" } split " ", $defines;
+ }
+
+ "@defines";
+}
+
+sub args {
+ my $self = shift;
+ my $vars = $self->{config}->{vars};
+ my $dversion = $self->dversion; #for .conf version conditionals
+ my $defines = $self->config_defines;
+
+ "-d $vars->{serverroot} -f $vars->{t_conf_file} $dversion $defines";
+}
+
+my %one_process = (1 => '-X', 2 => '-D ONE_PROCESS');
+
+sub start_cmd {
+ my $self = shift;
+
+ my $args = $self->args;
+ my $config = $self->{config};
+ my $vars = $config->{vars};
+ my $httpd = $vars->{httpd};
+
+ my $one_process = $self->{run}->{opts}->{'one-process'}
+ ? $self->version_of(\%one_process)
+ : '';
+
+ #XXX: threaded mpm does not respond to SIGTERM with -D ONE_PROCESS
+
+ return "$httpd $one_process $args";
+}
+
+sub default_gdbinit {
+ my $gdbinit = "";
+ my @sigs = qw(PIPE);
+
+ for my $sig (@sigs) {
+ for my $flag (qw(pass nostop)) {
+ $gdbinit .= "handle SIG$sig $flag\n";
+ }
+ }
+
+ $gdbinit;
+}
+
+sub strace_cmd {
+ my($self, $strace, $file) = @_;
+ #XXX truss, ktrace, etc.
+ "$strace -f -o $file -s1024";
+}
+
+sub valgrind_cmd {
+ my($self, $valgrind) = @_;
+ "$valgrind -v --leak-check=yes --show-reachable=yes --error-limit=no";
+}
+
+sub start_valgrind {
+ my $self = shift;
+ my $opts = shift;
+
+ my $config = $self->{config};
+ my $args = $self->args;
+ my $one_process = $self->version_of(\%one_process);
+ my $valgrind_cmd = $self->valgrind_cmd($opts->{debugger});
+ my $httpd = $config->{vars}->{httpd};
+
+ my $command = "$valgrind_cmd $httpd $one_process $args";
+
+ debug $command;
+ system $command;
+}
+
+sub start_strace {
+ my $self = shift;
+ my $opts = shift;
+
+ my $config = $self->{config};
+ my $args = $self->args;
+ my $one_process = $self->version_of(\%one_process);
+ my $file = catfile $config->{vars}->{t_logs}, 'strace.log';
+ my $strace_cmd = $self->strace_cmd($opts->{debugger}, $file);
+ my $httpd = $config->{vars}->{httpd};
+
+ $config->genfile($file); #just mark for cleanup
+
+ my $command = "$strace_cmd $httpd $one_process $args";
+
+ debug $command;
+ system $command;
+}
+
+sub start_gdb {
+ my $self = shift;
+ my $opts = shift;
+
+ my $debugger = $opts->{debugger};
+ my @breakpoints = @{ $opts->{breakpoint} || [] };
+ my $config = $self->{config};
+ my $args = $self->args;
+ my $one_process = $self->version_of(\%one_process);
+
+ my $file = catfile $config->{vars}->{serverroot}, '.gdb-test-start';
+ my $fh = $config->genfile($file);
+
+ print $fh default_gdbinit();
+
+ if (@breakpoints) {
+ print $fh "b ap_run_pre_config\n";
+ print $fh "run $one_process $args\n";
+ print $fh "finish\n";
+ for (@breakpoints) {
+ print $fh "b $_\n"
+ }
+ print $fh "continue\n";
+ }
+ else {
+ print $fh "run $one_process $args\n";
+ }
+ close $fh;
+
+ my $command;
+ my $httpd = $config->{vars}->{httpd};
+
+ if ($debugger eq 'ddd') {
+ $command = qq{ddd --gdb --debugger "gdb -command $file" $httpd};
+ }
+ else {
+ ## defaults to gdb if not set in %ENV or via -debug
+ $command = "$debugger $httpd -command $file";
+ }
+
+ $self->note_debugging;
+ debug $command;
+ system $command;
+
+ unlink $file;
+}
+
+sub debugger_file {
+ my $self = shift;
+ catfile $self->{config}->{vars}->{serverroot}, '.debugging';
+}
+
+#make a note that the server is running under the debugger
+#remove note when this process exits via END
+
+sub note_debugging {
+ my $self = shift;
+ my $file = $self->debugger_file;
+ my $fh = $self->{config}->genfile($file);
+ eval qq(END { unlink "$file" });
+}
+
+sub start_debugger {
+ my $self = shift;
+ my $opts = shift;
+
+ $opts->{debugger} ||= $ENV{MP_DEBUGGER} || 'gdb';
+
+ # XXX: FreeBSD 5.2+
+ # gdb 6.1 and before segfaults when trying to
+ # debug httpd startup code. 6.5 has been proven
+ # to work. FreeBSD typically installs this as
+ # gdb65.
+ # Is it worth it to check the debugger and os version
+ # and die ?
+
+ unless (grep { /^$opts->{debugger}/ } keys %debuggers) {
+ error "$opts->{debugger} is not a supported debugger",
+ "These are the supported debuggers: ".
+ join ", ", sort keys %debuggers;
+ die("\n");
+ }
+
+ my $debugger = $opts->{debugger};
+ $debugger =~ s/\d+$//;
+
+ my $method = "start_" . $debuggers{$debugger};
+
+ ## $opts->{debugger} is passed through unchanged
+ ## so when we try to run it next, its found.
+ $self->$method($opts);
+}
+
+sub pid {
+ my $self = shift;
+ my $file = $self->pid_file;
+ my $fh = Symbol::gensym();
+ open $fh, $file or do {
+ return 0;
+ };
+
+ # try to avoid the race condition when the pid file was created
+ # but not yet written to
+ for (1..8) {
+ last if -s $file > 0;
+ select undef, undef, undef, 0.25;
+ }
+
+ chomp(my $pid = <$fh> || '');
+ $pid;
+}
+
+sub select_next_port {
+ my $self = shift;
+
+ my $max_tries = 100; #XXX
+ while ($max_tries-- > 0) {
+ return $self->{port_counter}
+ if $self->port_available(++$self->{port_counter});
+ }
+
+ return 0;
+}
+
+sub port_available {
+ my $self = shift;
+ my $port = shift || $self->{config}->{vars}->{port};
+ local *S;
+
+ my $proto = getprotobyname('tcp');
+
+ socket(S, Socket::PF_INET(),
+ Socket::SOCK_STREAM(), $proto) || die "socket: $!";
+ setsockopt(S, Socket::SOL_SOCKET(),
+ Socket::SO_REUSEADDR(),
+ pack("l", 1)) || die "setsockopt: $!";
+
+ if (bind(S, Socket::sockaddr_in($port, Socket::INADDR_ANY()))) {
+ close S;
+ return 1;
+ }
+ else {
+ return 0;
+ }
+}
+
+=head2 stop()
+
+attempt to stop the server.
+
+returns:
+
+ on success: $pid of the server
+ on failure: -1
+
+=cut
+
+sub stop {
+ my $self = shift;
+ my $aborted = shift;
+
+ if (WIN32) {
+ require Win32::Process;
+ my $obj = $self->{config}->{win32obj};
+ my $pid = -1;
+ if ($pid = $obj ? $obj->GetProcessID : $self->pid) {
+ if (kill(0, $pid)) {
+ Win32::Process::KillProcess($pid, 0);
+ warning "server $self->{name} shutdown";
+ }
+ }
+ unlink $self->pid_file if -e $self->pid_file;
+ return $pid;
+ }
+
+ my $pid = 0;
+ my $tries = 3;
+ my $tried_kill = 0;
+
+ my $port = $self->{config}->{vars}->{port};
+
+ while ($self->ping) {
+ #my $state = $tried_kill ? "still" : "already";
+ #print "Port $port $state in use\n";
+
+ if ($pid = $self->pid and !$tried_kill++) {
+ if (kill TERM => $pid) {
+ warning "server $self->{name} shutdown";
+ sleep 1;
+
+ for (1..6) {
+ if (! $self->ping) {
+ if ($_ == 1) {
+ unlink $self->pid_file if -e $self->pid_file;
+ return $pid;
+ }
+ last;
+ }
+ if ($_ == 1) {
+ warning "port $port still in use...";
+ }
+ else {
+ print "...";
+ }
+ sleep $_;
+ }
+
+ if ($self->ping) {
+ error "\nserver was shutdown but port $port ".
+ "is still in use, please shutdown the service ".
+ "using this port or select another port ".
+ "for the tests";
+ }
+ else {
+ print "done\n";
+ }
+ }
+ else {
+ error "kill $pid failed: $!";
+ }
+ }
+ else {
+ error "port $port is in use, ".
+ "cannot determine server pid to shutdown";
+ return -1;
+ }
+
+ if (--$tries <= 0) {
+ error "cannot shutdown server on Port $port, ".
+ "please shutdown manually";
+ unlink $self->pid_file if -e $self->pid_file;
+ return -1;
+ }
+ }
+
+ unlink $self->pid_file if -e $self->pid_file;
+ return $pid;
+}
+
+sub ping {
+ my $self = shift;
+ my $pid = $self->pid;
+
+ if ($pid and kill 0, $pid) {
+ return $pid;
+ }
+ elsif (! $self->port_available) {
+ return -1;
+ }
+
+ return 0;
+}
+
+sub failed_msg {
+ my $self = shift;
+ my($log, $rlog) = $self->{config}->error_log;
+ my $log_file_info = -e $log ?
+ "please examine $rlog" :
+ "$rlog wasn't created, start the server in the debug mode";
+ error "@_ ($log_file_info)";
+}
+
+#this doesn't work well on solaris or hpux at the moment
+use constant USE_SIGCHLD => $^O eq 'linux';
+
+sub start {
+ my $self = shift;
+
+ my $old_pid = -1;
+ if (WIN32) {
+ # Stale PID files (e.g. left behind from a previous test run
+ # that crashed) cannot be trusted on Windows because PID's are
+ # re-used too frequently, so just remove it. If there is an old
+ # server still running then the attempt to start a new one below
+ # will simply fail because the port will be unavailable.
+ if (-f $self->pid_file) {
+ error "Removing old PID file -- " .
+ "Unclean shutdown of previous test run?\n";
+ unlink $self->pid_file;
+ }
+ $old_pid = 0;
+ }
+ else {
+ $old_pid = $self->stop;
+ }
+ my $cmd = $self->start_cmd;
+ my $config = $self->{config};
+ my $vars = $config->{vars};
+ my $httpd = $vars->{httpd} || 'unknown';
+
+ if ($old_pid == -1) {
+ return 0;
+ }
+
+ local $| = 1;
+
+ unless (-x $httpd) {
+ my $why = -e $httpd ? "is not executable" : "does not exist";
+ error "cannot start server: httpd ($httpd) $why";
+ return 0;
+ }
+
+ print "$cmd\n";
+ my $old_sig;
+
+ if (WIN32) {
+ #make sure only 1 process is started for win32
+ #else Kill will only shutdown the parent
+ my $one_process = $self->version_of(\%one_process);
+ require Win32::Process;
+ my $obj;
+ # We need the "1" below to inherit the calling processes
+ # handles when running Apache::TestSmoke so as to properly
+ # dup STDOUT/STDERR
+ Win32::Process::Create($obj,
+ $httpd,
+ "$cmd $one_process",
+ 1,
+ Win32::Process::NORMAL_PRIORITY_CLASS(),
+ '.');
+ unless ($obj) {
+ die "Could not start the server: " .
+ Win32::FormatMessage(Win32::GetLastError());
+ }
+ $config->{win32obj} = $obj;
+ }
+ else {
+ $old_sig = $SIG{CHLD};
+
+ if (USE_SIGCHLD) {
+ # XXX: try not to be POSIX dependent
+ require POSIX;
+
+ #XXX: this is not working well on solaris or hpux
+ $SIG{CHLD} = sub {
+ while ((my $child = waitpid(-1, POSIX::WNOHANG())) > 0) {
+ my $status = $? >> 8;
+ #error "got child exit $status";
+ if ($status) {
+ my $msg = "server has died with status $status";
+ $self->failed_msg("\n$msg");
+ Apache::TestRun->new(test_config => $config)->scan_core;
+ kill SIGTERM => $$;
+ }
+ }
+ };
+ }
+
+ defined(my $pid = fork) or die "Can't fork: $!";
+ unless ($pid) { # child
+ my $status = system "$cmd";
+ if ($status) {
+ $status = $? >> 8;
+ #error "httpd didn't start! $status";
+ }
+ CORE::exit $status;
+ }
+ }
+
+ while ($old_pid and $old_pid == $self->pid) {
+ warning "old pid file ($old_pid) still exists";
+ sleep 1;
+ }
+
+ my $version = $self->{version};
+ my $mpm = $config->{mpm} || "";
+ $mpm = "($mpm MPM)" if $mpm;
+ print "using $version $mpm\n";
+
+ my $timeout = $vars->{startup_timeout} ||
+ $ENV{APACHE_TEST_STARTUP_TIMEOUT} ||
+ 60;
+
+ my $start_time = time;
+ my $preamble = "${CTRL_M}waiting $timeout seconds for server to start: ";
+ print $preamble unless COLOR;
+ while (1) {
+ my $delta = time - $start_time;
+ print COLOR
+ ? ($preamble, sprintf "%02d:%02d", (gmtime $delta)[1,0])
+ : '.';
+ sleep 1;
+ if ($self->pid) {
+ print $preamble, "ok (waited $delta secs)\n";
+ last;
+ }
+ elsif ($delta > $timeout) {
+ my $suggestion = $timeout + 300;
+ print $preamble, "not ok\n";
+ error <<EOI;
+giving up after $delta secs. If you think that your system
+is slow or overloaded try again with a longer timeout value.
+by setting the environment variable APACHE_TEST_STARTUP_TIMEOUT
+to a high value (e.g. $suggestion) and repeat the last command.
+EOI
+ last;
+ }
+ }
+
+ # now that the server has started don't abort the test run if it
+ # dies
+ $SIG{CHLD} = $old_sig || 'DEFAULT';
+
+ if (my $pid = $self->pid) {
+ print "server $self->{name} started\n";
+
+ my $vh = $config->{vhosts};
+ my $by_port = sub { $vh->{$a}->{port} <=> $vh->{$b}->{port} };
+
+ for my $module (sort $by_port keys %$vh) {
+ print "server $vh->{$module}->{name} listening ($module)\n",
+ }
+
+ if ($config->configure_proxy) {
+ print "tests will be proxied through $vars->{proxy}\n";
+ }
+ }
+ else {
+ $self->failed_msg("server failed to start!");
+ return 0;
+ }
+
+ return 1 if $self->wait_till_is_up($timeout);
+
+ $self->failed_msg("failed to start server!");
+ return 0;
+}
+
+
+# wait till the server is up and return 1
+# if the waiting times out returns 0
+sub wait_till_is_up {
+ my($self, $timeout) = @_;
+ my $config = $self->{config};
+ my $sleep_interval = 1; # secs
+
+ my $server_up = sub {
+ local $SIG{__WARN__} = sub {}; #avoid "cannot connect ..." warnings
+ # avoid fatal errors when LWP is not available
+ return eval {
+ my $r=Apache::TestRequest::GET('/index.html');
+ $r->code!=500 or $r->header('client-warning')!~/internal/i;
+ } || 0;
+ };
+
+ if ($server_up->()) {
+ return 1;
+ }
+
+ my $start_time = time;
+ my $preamble = "${CTRL_M}still waiting for server to warm up: ";
+ print $preamble unless COLOR;
+ while (1) {
+ my $delta = time - $start_time;
+ print COLOR
+ ? ($preamble, sprintf "%02d:%02d", (gmtime $delta)[1,0])
+ : '.';
+ sleep $sleep_interval;
+ if ($server_up->()) {
+ print "${CTRL_M}the server is up (waited $delta secs) \n";
+ return 1;
+ }
+ elsif ($delta > $timeout) {
+ print "${CTRL_M}the server is down, giving up after $delta secs\n";
+ return 0;
+ }
+ else {
+ # continue
+ }
+ }
+}
+
+1;