summaryrefslogtreecommitdiffstats
path: root/debian/perl-framework/Apache-Test/lib/Apache/TestRun.pm
diff options
context:
space:
mode:
Diffstat (limited to 'debian/perl-framework/Apache-Test/lib/Apache/TestRun.pm')
-rw-r--r--debian/perl-framework/Apache-Test/lib/Apache/TestRun.pm1220
1 files changed, 1220 insertions, 0 deletions
diff --git a/debian/perl-framework/Apache-Test/lib/Apache/TestRun.pm b/debian/perl-framework/Apache-Test/lib/Apache/TestRun.pm
new file mode 100644
index 0000000..f398eb5
--- /dev/null
+++ b/debian/perl-framework/Apache-Test/lib/Apache/TestRun.pm
@@ -0,0 +1,1220 @@
+# 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::TestRun;
+
+use strict;
+use warnings FATAL => 'all';
+
+use Apache::Test ();
+use Apache::TestMM ();
+use Apache::TestConfig ();
+use Apache::TestConfigC ();
+use Apache::TestRequest ();
+use Apache::TestHarness ();
+use Apache::TestTrace;
+
+use Cwd;
+use ExtUtils::MakeMaker;
+use File::Find qw(finddepth);
+use File::Path;
+use File::Spec::Functions qw(catfile catdir canonpath);
+use File::Basename qw(basename dirname);
+use Getopt::Long qw(GetOptions);
+use Config;
+
+use constant IS_APACHE_TEST_BUILD => Apache::TestConfig::IS_APACHE_TEST_BUILD;
+
+use constant STARTUP_TIMEOUT => 300; # secs (good for extreme debug cases)
+
+use subs qw(exit_shell exit_perl);
+
+my $orig_command;
+my $orig_cwd;
+my $orig_conf_opts;
+
+my %core_files = ();
+
+my @std_run = qw(start-httpd run-tests stop-httpd);
+my @others = qw(verbose configure clean help ssl http11 bugreport
+ save no-httpd one-process);
+my @flag_opts = (@std_run, @others);
+my @string_opts = qw(order trace);
+my @ostring_opts = qw(proxy ping);
+my @debug_opts = qw(debug);
+my @list_opts = qw(preamble postamble breakpoint);
+my @hash_opts = qw(header);
+my @help_opts = qw(clean help);
+my @request_opts = qw(get post head);
+
+my @exit_opts_no_need_httpd = (@help_opts);
+my @exit_opts_need_httpd = (@debug_opts, qw(ping));
+
+my %usage = (
+ 'start-httpd' => 'start the test server',
+ 'run-tests' => 'run the tests',
+ 'order=mode' => 'run the tests in one of the modes: ' .
+ '(repeat|random|SEED)',
+ 'stop-httpd' => 'stop the test server',
+ 'no-httpd' => 'run the tests without configuring or starting httpd',
+ 'verbose[=1]' => 'verbose output',
+ 'configure' => 'force regeneration of httpd.conf ' .
+ ' (tests will not be run)',
+ 'clean' => 'remove all generated test files',
+ 'help' => 'display this message',
+ 'bugreport' => 'print the hint how to report problems',
+ 'preamble' => 'config to add at the beginning of httpd.conf',
+ 'postamble' => 'config to add at the end of httpd.conf',
+ 'ping[=block]' => 'test if server is running or port in use',
+ 'debug[=name]' => 'start server under debugger name (gdb, ddd, etc.)',
+ 'breakpoint=bp' => 'set breakpoints (multiply bp can be set)',
+ 'header' => "add headers to (" .
+ join('|', @request_opts) . ") request",
+ 'http11' => 'run all tests with HTTP/1.1 (keep alive) requests',
+ 'ssl' => 'run tests through ssl',
+ 'proxy' => 'proxy requests (default proxy is localhost)',
+ 'trace=T' => 'change tracing default to: warning, notice, ' .
+ 'info, debug, ...',
+ 'one-process' => 'run the server in single process mode',
+ (map { $_, "\U$_\E url" } @request_opts),
+);
+
+sub fixup {
+ #make sure we use an absolute path to perl
+ #else Test::Harness uses the perl in our PATH
+ #which might not be the one we want
+ $^X = $Config{perlpath} unless -e $^X;
+}
+
+# if the test suite was aborted because of a user-error we don't want
+# to call the bugreport and invite users to submit a bug report -
+# after all it's a user error. but we still want the program to fail,
+# so raise this flag in such a case.
+my $user_error = 0;
+sub user_error {
+ my $self = shift;
+ $user_error = shift if @_;
+ $user_error;
+}
+
+sub new {
+ my $class = shift;
+
+ my $self = bless {
+ tests => [],
+ @_,
+ }, $class;
+
+ $self->fixup;
+
+ $self;
+}
+
+#split arguments into test files/dirs and options
+#take extra care if -e, the file matches /\.t$/
+# if -d, the dir contains .t files
+#so we dont slurp arguments that are not tests, example:
+# httpd $HOME/apache-2.0/bin/httpd
+
+sub split_test_args {
+ my($self) = @_;
+
+ my(@tests);
+ my $top_dir = $self->{test_config}->{vars}->{top_dir};
+ my $t_dir = $self->{test_config}->{vars}->{t_dir};
+
+ my $argv = $self->{argv};
+ my @leftovers = ();
+ for (@$argv) {
+ my $arg = $_;
+ # need the t/ (or t\) for stat-ing, but don't want to include
+ # it in test output
+ $arg =~ s@^(?:\.[\\/])?t[\\/]@@;
+ my $file = catfile $t_dir, $arg;
+ if (-d $file and $_ ne '/') {
+ my @files = <$file/*.t>;
+ my $remove = catfile $top_dir, "";
+ if (@files) {
+ push @tests, map { s,^\Q$remove,,; $_ } @files;
+ next;
+ }
+ }
+ else {
+ if ($file =~ /\.t$/ and -e $file) {
+ push @tests, "t/$arg";
+ next;
+ }
+ elsif (-e "$file.t") {
+ push @tests, "t/$arg.t";
+ next;
+ }
+ elsif (/^[\d.]+$/) {
+ my @t = $_;
+ #support range of subtests: t/TEST t/foo/bar 60..65
+ if (/^(\d+)\.\.(\d+)$/) {
+ @t = $1..$2;
+ }
+
+ push @{ $self->{subtests} }, @t;
+ next;
+ }
+ }
+ push @leftovers, $_;
+ }
+
+ $self->{tests} = [ map { canonpath($_) } @tests ];
+ $self->{argv} = \@leftovers;
+}
+
+sub die_on_invalid_args {
+ my($self) = @_;
+
+ # at this stage $self->{argv} should be empty
+ my @invalid_argv = @{ $self->{argv} };
+ if (@invalid_argv) {
+ error "unknown opts or test names: @invalid_argv\n" .
+ "-help will list options\n";
+ exit_perl 0;
+ }
+
+}
+
+sub passenv {
+ my $passenv = Apache::TestConfig->passenv;
+ for (keys %$passenv) {
+ return 1 if $ENV{$_};
+ }
+ 0;
+}
+
+sub getopts {
+ my($self, $argv) = @_;
+
+ local *ARGV = $argv;
+ my(%opts, %vopts, %conf_opts);
+
+ # a workaround to support -verbose and -verbose=0|1
+ # $Getopt::Long::VERSION > 2.26 can use the "verbose:1" rule
+ # but we have to support older versions as well
+ @ARGV = grep defined,
+ map {/-verbose=(\d)/ ? ($1 ? '-verbose' : undef) : $_ } @ARGV;
+
+ # permute : optional values can come before the options
+ # pass_through : all unknown things are to be left in @ARGV
+ Getopt::Long::Configure(qw(pass_through permute));
+
+ # grab from @ARGV only the options that we expect
+ GetOptions(\%opts, @flag_opts, @help_opts,
+ (map "$_:s", @debug_opts, @request_opts, @ostring_opts),
+ (map "$_=s", @string_opts),
+ (map { ("$_=s", $vopts{$_} ||= []) } @list_opts),
+ (map { ("$_=s", $vopts{$_} ||= {}) } @hash_opts));
+
+ $opts{$_} = $vopts{$_} for keys %vopts;
+
+ # separate configuration options and test files/dirs
+ my $req_wanted_args = Apache::TestRequest::wanted_args();
+ my @argv = ();
+ my %req_args = ();
+
+ while (@ARGV) {
+ my $val = shift @ARGV;
+ if ($val =~ /^--?(.+)/) { # must have a leading - or --
+ my $key = lc $1;
+ # a known config option?
+ if (exists $Apache::TestConfig::Usage{$key}) {
+ $conf_opts{$key} = shift @ARGV;
+ next;
+ } # a TestRequest config option?
+ elsif (exists $req_wanted_args->{$key}) {
+ $req_args{$key} = shift @ARGV;
+ next;
+ }
+ }
+ # to be processed later
+ push @argv, $val;
+ }
+
+ # save the orig args (make a deep copy)
+ $orig_conf_opts = { %conf_opts };
+
+ # fixup the filepath options on win32 (spaces, short names, etc.)
+ if (Apache::TestConfig::WIN32) {
+ for my $key (keys %conf_opts) {
+ next unless Apache::TestConfig::conf_opt_is_a_filepath($key);
+ next unless -e $conf_opts{$key};
+ $conf_opts{$key} = Win32::GetShortPathName($conf_opts{$key});
+ }
+ }
+
+ $opts{req_args} = \%req_args;
+
+ # only test files/dirs if any at all are left in argv
+ $self->{argv} = \@argv;
+
+ # force regeneration of httpd.conf if commandline args want to
+ # modify it. configure_opts() has more checks to decide whether to
+ # reconfigure or not.
+ # XXX: $self->passenv() is already tested in need_reconfiguration()
+ $self->{reconfigure} = $opts{configure} ||
+ (grep { $opts{$_}->[0] } qw(preamble postamble)) ||
+ (grep { $Apache::TestConfig::Usage{$_} } keys %conf_opts ) ||
+ $self->passenv() || (! -e 't/conf/httpd.conf');
+
+ if (exists $opts{debug}) {
+ $opts{debugger} = $opts{debug};
+ $opts{debug} = 1;
+ }
+
+ if ($opts{trace}) {
+ my %levels = map {$_ => 1} @Apache::TestTrace::Levels;
+ if (exists $levels{ $opts{trace} }) {
+ $Apache::TestTrace::Level = $opts{trace};
+ # propogate the override for the server-side.
+ # -trace overrides any previous APACHE_TEST_TRACE_LEVEL settings
+ $ENV{APACHE_TEST_TRACE_LEVEL} = $opts{trace};
+ }
+ else {
+ error "unknown trace level: $opts{trace}",
+ "valid levels are: @Apache::TestTrace::Levels";
+ exit_perl 0;
+ }
+ }
+
+ # breakpoint automatically turns the debug mode on
+ if (@{ $opts{breakpoint} }) {
+ $opts{debug} ||= 1;
+ }
+
+ if ($self->{reconfigure}) {
+ $conf_opts{save} = 1;
+ delete $self->{reconfigure};
+ }
+ else {
+ $conf_opts{thaw} = 1;
+ }
+
+ #propagate some values
+ for (qw(verbose)) {
+ $conf_opts{$_} = $opts{$_};
+ }
+
+ $self->{opts} = \%opts;
+ $self->{conf_opts} = \%conf_opts;
+}
+
+sub default_run_opts {
+ my $self = shift;
+ my($opts, $tests) = ($self->{opts}, $self->{tests});
+
+ unless (grep { exists $opts->{$_} } @std_run, @request_opts) {
+ if (@$tests && $self->{server}->ping) {
+ # if certain tests are specified and server is running,
+ # dont restart
+ $opts->{'run-tests'} = 1;
+ }
+ else {
+ #default is start-server run-tests stop-server
+ $opts->{$_} = 1 for @std_run;
+ }
+ }
+
+ $opts->{'run-tests'} ||= @$tests;
+}
+
+my $parent_pid = $$;
+sub is_parent { $$ == $parent_pid }
+
+my $caught_sig_int = 0;
+
+sub install_sighandlers {
+ my $self = shift;
+
+ my($server, $opts) = ($self->{server}, $self->{opts});
+
+ $SIG{__DIE__} = sub {
+ return unless $_[0] =~ /^Failed/i; #dont catch Test::ok failures
+
+ # _show_results() calls die() under a few conditions, such as
+ # when no tests are run or when tests fail. make sure the message
+ # is propagated back to the user.
+ print $_[0] if (caller(1))[3]||'' eq 'Test::Harness::_show_results';
+
+ $server->stop(1) if $opts->{'start-httpd'};
+ $server->failed_msg("error running tests");
+ exit_perl 0;
+ };
+
+ $SIG{INT} = sub {
+ if ($caught_sig_int++) {
+ warning "\ncaught SIGINT";
+ exit_perl 0;
+ }
+ warning "\nhalting tests";
+ $server->stop if $opts->{'start-httpd'};
+ exit_perl 0;
+ };
+
+ #try to make sure we scan for core no matter what happens
+ #must eval "" to "install" this END block, otherwise it will
+ #always run, a subclass might not want that
+ eval 'END {
+ return unless is_parent(); # because of fork
+ $self ||=
+ Apache::TestRun->new(test_config => Apache::TestConfig->thaw);
+ {
+ local $?; # preserve the exit status
+ eval {
+ $self->scan_core;
+ };
+ }
+ $self->try_bug_report();
+ }';
+ die "failed: $@" if $@;
+
+}
+
+sub try_bug_report {
+ my $self = shift;
+ if ($? && !$self->user_error &&
+ $self->{opts}->{bugreport} && $self->can('bug_report')) {
+ $self->bug_report;
+ }
+}
+
+#throw away cached config and start fresh
+sub refresh {
+ my $self = shift;
+ $self->opt_clean(1);
+ $self->{conf_opts}->{save} = delete $self->{conf_opts}->{thaw} || 1;
+ $self->{test_config} = $self->new_test_config()->httpd_config;
+ $self->{test_config}->{server}->{run} = $self;
+ $self->{server} = $self->{test_config}->server;
+}
+
+sub configure_opts {
+ my $self = shift;
+ my $save = shift;
+ my $refreshed = 0;
+
+ my($test_config, $opts) = ($self->{test_config}, $self->{opts});
+
+ $test_config->{vars}->{scheme} =
+ $opts->{ssl} ? 'https' :
+ $self->{conf_opts}->{scheme} || 'http';
+
+ if ($opts->{http11}) {
+ $ENV{APACHE_TEST_HTTP11} = 1;
+ }
+
+ # unless we are already reconfiguring, check for .conf.in files changes
+ if (!$$save &&
+ (my @reasons =
+ $self->{test_config}->need_reconfiguration($self->{conf_opts}))) {
+ warning "forcing re-configuration:";
+ warning "\t- $_." for @reasons;
+ unless ($refreshed) {
+ $self->refresh;
+ $refreshed = 1;
+ $test_config = $self->{test_config};
+ }
+ }
+
+ # unless we are already reconfiguring, check for -proxy
+ if (!$$save && exists $opts->{proxy}) {
+ my $max = $test_config->{vars}->{maxclients};
+ $opts->{proxy} ||= 'on';
+
+ #if config is cached and MaxClients == 1, must reconfigure
+ if (!$$save and $opts->{proxy} eq 'on' and $max == 1) {
+ $$save = 1;
+ warning "server is reconfigured for proxy";
+ unless ($refreshed) {
+ $self->refresh;
+ $refreshed = 1;
+ $test_config = $self->{test_config};
+ }
+ }
+
+ $test_config->{vars}->{proxy} = $opts->{proxy};
+ }
+ else {
+ $test_config->{vars}->{proxy} = 'off';
+ }
+
+ return unless $$save;
+
+ my $preamble = sub { shift->preamble($opts->{preamble}) };
+ my $postamble = sub { shift->postamble($opts->{postamble}) };
+
+ $test_config->preamble_register($preamble);
+ $test_config->postamble_register($postamble);
+}
+
+sub pre_configure { }
+
+sub configure {
+ my $self = shift;
+
+ if ($self->{opts}->{'no-httpd'}) {
+ warning "skipping httpd configuration";
+ return;
+ }
+
+ # create the conf dir as early as possible
+ $self->{test_config}->prepare_t_conf();
+
+ my $save = \$self->{conf_opts}->{save};
+ $self->configure_opts($save);
+
+ my $config = $self->{test_config};
+ unless ($$save) {
+ my $addr = \$config->{vars}->{remote_addr};
+ my $remote_addr = $config->our_remote_addr;
+ unless ($$addr eq $remote_addr) {
+ warning "local ip address has changed, updating config cache";
+ $$addr = $remote_addr;
+ }
+ #update minor changes to cached config
+ #without complete regeneration
+ #for example this allows switching between
+ #'t/TEST' and 't/TEST -ssl'
+ $config->sync_vars(qw(scheme proxy remote_addr));
+ return;
+ }
+
+ my $test_config = $self->{test_config};
+ $test_config->sslca_generate;
+ $test_config->generate_ssl_conf if $self->{opts}->{ssl};
+ $test_config->cmodules_configure;
+ $test_config->generate_httpd_conf;
+ $test_config->save;
+
+}
+
+sub try_exit_opts {
+ my $self = shift;
+ my @opts = @_;
+
+ for (@opts) {
+ next unless exists $self->{opts}->{$_};
+ my $method = "opt_$_";
+ my $rc = $self->$method();
+ exit_perl $rc if $rc;
+ }
+
+ if ($self->{opts}->{'stop-httpd'}) {
+ my $ok = 1;
+ if ($self->{server}->ping) {
+ $ok = $self->{server}->stop;
+ $ok = $ok < 0 ? 0 : 1; # adjust to 0/1 logic
+ }
+ else {
+ warning "server $self->{server}->{name} is not running";
+ # cleanup a stale pid file if found
+ my $pid_file = $self->{test_config}->{vars}->{t_pid_file};
+ unlink $pid_file if -e $pid_file;
+ }
+ exit_perl $ok;
+ }
+}
+
+sub start {
+ my $self = shift;
+
+ my $opts = $self->{opts};
+ my $server = $self->{server};
+
+ #if t/TEST -d is running make sure we don't try to stop/start the server
+ my $file = $server->debugger_file;
+ if (-e $file and $opts->{'start-httpd'}) {
+ if ($server->ping) {
+ warning "server is running under the debugger, " .
+ "defaulting to -run";
+ $opts->{'start-httpd'} = $opts->{'stop-httpd'} = 0;
+ }
+ else {
+ warning "removing stale debugger note: $file";
+ unlink $file;
+ }
+ }
+
+ $self->check_runtime_user();
+
+ if ($opts->{'start-httpd'}) {
+ exit_perl 0 unless $server->start;
+ }
+ elsif ($opts->{'run-tests'}) {
+ my $is_up = $server->ping
+ || (exists $self->{opts}->{ping}
+ && $self->{opts}->{ping} eq 'block'
+ && $server->wait_till_is_up(STARTUP_TIMEOUT));
+ unless ($is_up) {
+ error "server is not ready yet, try again.";
+ exit_perl 0;
+ }
+ }
+}
+
+sub run_tests {
+ my $self = shift;
+
+ my $test_opts = {
+ verbose => $self->{opts}->{verbose},
+ tests => $self->{tests},
+ order => $self->{opts}->{order},
+ subtests => $self->{subtests} || [],
+ };
+
+ if (grep { exists $self->{opts}->{$_} } @request_opts) {
+ run_request($self->{test_config}, $self->{opts});
+ }
+ else {
+ Apache::TestHarness->run($test_opts)
+ if $self->{opts}->{'run-tests'};
+ }
+}
+
+sub stop {
+ my $self = shift;
+
+ return $self->{server}->stop if $self->{opts}->{'stop-httpd'};
+}
+
+sub new_test_config {
+ my $self = shift;
+
+ Apache::TestConfig->new($self->{conf_opts});
+}
+
+sub set_ulimit_via_sh {
+ return if Apache::TestConfig::WINFU;
+ return if $ENV{APACHE_TEST_ULIMIT_SET};
+
+ # only root can allow unlimited core dumps on Solaris (8 && 9?)
+ if (Apache::TestConfig::SOLARIS) {
+ my $user = getpwuid($>) || '';
+ if ($user ne 'root') {
+ warning "Skipping 'set unlimited ulimit for coredumps', " .
+ "since we are running as a non-root user on Solaris";
+ return;
+ }
+ }
+
+ my $binsh = '/bin/sh';
+ return unless -e $binsh;
+ $ENV{APACHE_TEST_ULIMIT_SET} = 1;
+
+ my $sh = Symbol::gensym();
+ open $sh, "echo ulimit -a | $binsh|" or die;
+ local $_;
+ while (<$sh>) {
+ if (/^core.*unlimited$/) {
+ #already set to unlimited
+ $ENV{APACHE_TEST_ULIMIT_SET} = 1;
+ return;
+ }
+ }
+ close $sh;
+
+ $orig_command = "ulimit -c unlimited; $orig_command";
+ warning "setting ulimit to allow core files\n$orig_command";
+ # use 'or die' to avoid warnings due to possible overrides of die
+ exec $orig_command or die "exec $orig_command has failed";
+}
+
+sub set_ulimit {
+ my $self = shift;
+ #return if $self->set_ulimit_via_bsd_resource;
+ eval { $self->set_ulimit_via_sh };
+}
+
+sub set_env {
+ #export some environment variables for t/modules/env.t
+ #(the values are unimportant)
+ $ENV{APACHE_TEST_HOSTNAME} = 'test.host.name';
+ $ENV{APACHE_TEST_HOSTTYPE} = 'z80';
+}
+
+sub run {
+ my $self = shift;
+
+ # assuming that test files are always in the same directory as the
+ # driving script, make it possible to run the test suite from any place
+ # use a full path, which will work after chdir (e.g. ./TEST)
+ $0 = File::Spec->rel2abs($0);
+ if (-e $0) {
+ my $top = dirname dirname $0;
+ chdir $top if $top and -d $top;
+ }
+
+ # reconstruct argv, preserve multiwords args, eg 'PerlTrace all'
+ my $argv = join " ", map { /^-/ ? $_ : qq['$_'] } @ARGV;
+ $orig_command = "$^X $0 $argv";
+ $orig_cwd = Cwd::cwd();
+ $self->set_ulimit;
+ $self->set_env; #make sure these are always set
+
+ $self->detect_relocation($orig_cwd);
+
+ my(@argv) = @_;
+
+ $self->getopts(\@argv);
+
+ $self->pre_configure();
+
+ # can't setup the httpd-specific parts of the config object yet
+ $self->{test_config} = $self->new_test_config();
+
+ $self->warn_core();
+
+ # give TestServer access to our runtime configuration directives
+ # so we can tell the server stuff if we need to
+ $self->{test_config}->{server}->{run} = $self;
+
+ $self->{server} = $self->{test_config}->server;
+
+ local($SIG{__DIE__}, $SIG{INT});
+ $self->install_sighandlers;
+
+ $self->try_exit_opts(@exit_opts_no_need_httpd);
+
+ # httpd is found here (unless it was already configured before)
+ $self->{test_config}->httpd_config();
+
+ $self->try_exit_opts(@exit_opts_need_httpd);
+
+ if ($self->{opts}->{configure}) {
+ warning "cleaning out current configuration";
+ $self->opt_clean(1);
+ }
+
+ $self->split_test_args;
+
+ $self->die_on_invalid_args;
+
+ $self->default_run_opts;
+
+ # if configure() fails for some reason before it has flushed the
+ # config to a file, save it so -clean will be able to clean
+ if ($self->{opts}->{'start-httpd'} || $self->{opts}->{'configure'}) {
+ eval { $self->configure };
+ if ($@) {
+ error "configure() has failed:\n$@";
+ warning "forcing Apache::TestConfig object save";
+ $self->{test_config}->save;
+ warning "run 't/TEST -clean' to clean up before continuing";
+ exit_perl 0;
+ }
+ }
+
+ if ($self->{opts}->{configure}) {
+ warning "reconfiguration done";
+ exit_perl 1;
+ }
+
+ $self->start unless $self->{opts}->{'no-httpd'};
+
+ $self->run_tests;
+
+ $self->stop unless $self->{opts}->{'no-httpd'};
+}
+
+sub rerun {
+ my $vars = shift;
+
+ # in %$vars
+ # - httpd will be always set
+ # - apxs is optional
+
+ $orig_cwd ||= Cwd::cwd();
+ chdir $orig_cwd;
+ my $new_opts = " -httpd $vars->{httpd}";
+ $new_opts .= " -apxs $vars->{apxs}" if $vars->{apxs};
+
+ my $new_command = $orig_command;
+
+ # strip any old bogus -httpd/-apxs
+ $new_command =~ s/--?httpd\s+$orig_conf_opts->{httpd}//
+ if $orig_conf_opts->{httpd};
+ $new_command =~ s/--?httpd\s+$orig_conf_opts->{httpd}//
+ if $orig_conf_opts->{httpd} and $vars->{apxs};
+
+ # add new opts
+ $new_command .= $new_opts;
+
+ warning "running with new config opts: $new_command";
+
+ # use 'or die' to avoid warnings due to possible overrides of die
+ exec $new_command or die "exec $new_command has failed";
+}
+
+
+# make it easy to move the whole distro w/o running
+# 't/TEST -clean' before moving. when moving the whole package,
+# the old cached config will stay, so we want to nuke it only if
+# we realize that it's no longer valid. we can't just check the
+# existance of the saved top_dir value, since the project may have
+# been copied and the old dir could be still there, but that's not
+# the one that we work in
+sub detect_relocation {
+ my($self, $cur_top_dir) = @_;
+
+ my $config_file = catfile qw(t conf apache_test_config.pm);
+ return unless -e $config_file;
+
+ my %inc = %INC;
+ eval { require "$config_file" };
+ %INC = %inc; # be stealth
+ warn($@), return if $@;
+
+ my $cfg = 'apache_test_config'->new;
+
+ # if the top_dir from saved config doesn't match the current
+ # top_dir, that means that the whole project was relocated to a
+ # different directory, w/o running t/TEST -clean first (in each
+ # directory with a test suite)
+ my $cfg_top_dir = $cfg->{vars}->{top_dir};
+ return unless $cfg_top_dir;
+ return if $cfg_top_dir eq $cur_top_dir;
+
+ # if that's the case silently fixup the saved config to use the
+ # new paths, and force a complete cleanup. if we don't fixup the
+ # config files, the cleanup process won't be able to locate files
+ # to delete and re-configuration will fail
+ {
+ # in place editing
+ local @ARGV = $config_file;
+ local $^I = ".bak"; # Win32 needs a backup
+ while (<>) {
+ s{$cfg_top_dir}{$cur_top_dir}g;
+ print;
+ }
+ unlink $config_file . $^I;
+ }
+
+ my $cleanup_cmd = "$^X $0 -clean";
+ warning "cleaning up the old config";
+ # XXX: do we care to check success?
+ system $cleanup_cmd;
+
+ # XXX: I tried hard to accomplish that w/o starting a new process,
+ # but too many things get on the way, so for now just keep it as an
+ # external process, as it's absolutely transparent to the normal
+ # app-run
+}
+
+my @oh = qw(jeez golly gosh darn shucks dangit rats nuts dangnabit crap);
+sub oh {
+ $oh[ rand scalar @oh ];
+}
+
+#e.g. t/core or t/core.12499
+my $core_pat = '^core(\.\d+)?' . "\$";
+
+# $self->scan_core_incremental([$only_top_dir])
+# normally would be called after each test
+# and since it updates the list of seen core files
+# scan_core() won't report these again
+# currently used in Apache::TestSmoke
+#
+# if $only_t_dir arg is true only the t_dir dir (t/) will be scanned
+sub scan_core_incremental {
+ my($self, $only_t_dir) = @_;
+ my $vars = $self->{test_config}->{vars};
+
+ # no core files dropped on win32
+ return () if Apache::TestConfig::WIN32;
+
+ if ($only_t_dir) {
+ require IO::Dir;
+ my @cores = ();
+ for (IO::Dir->new($vars->{t_dir})->read) {
+ my $file = catfile $vars->{t_dir}, $_;
+ next unless -f $file;
+ next unless /$core_pat/o;
+ next if exists $core_files{$file} &&
+ $core_files{$file} == -M $file;
+ $core_files{$file} = -M $file;
+ push @cores, $file;
+ }
+ return @cores
+ ? join "\n", "server dumped core, for stacktrace, run:",
+ map { "gdb $vars->{httpd} -core $_" } @cores
+ : ();
+ }
+
+ my @msg = ();
+ finddepth({ no_chdir => 1,
+ wanted => sub {
+ return unless -f $_;
+ my $file = basename $File::Find::name;
+ return unless $file =~ /$core_pat/o;
+ my $core = $File::Find::name;
+ unless (exists $core_files{$core} && $core_files{$core} == -M $core) {
+ # new core file!
+
+ # XXX: could rename the file if it doesn't include the pid
+ # in its name (i.e., just called 'core', instead of 'core.365')
+
+ # XXX: could pass the test name and rename the core file
+ # to use that name as a suffix, plus pid, time or some
+ # other unique identifier, in case the same test is run
+ # more than once and each time it caused a segfault
+ $core_files{$core} = -M $core;
+ push @msg, "server dumped core, for stacktrace, run:\n" .
+ "gdb $vars->{httpd} -core $core";
+ }
+ }}, $vars->{top_dir});
+
+ return @msg;
+
+}
+
+sub scan_core {
+ my $self = shift;
+ my $vars = $self->{test_config}->{vars};
+ my $times = 0;
+
+ # no core files dropped on win32
+ return if Apache::TestConfig::WIN32;
+
+ finddepth({ no_chdir => 1,
+ wanted => sub {
+ return unless -f $_;
+ my $file = basename $File::Find::name;
+ return unless $file =~ /$core_pat/o;
+ my $core = $File::Find::name;
+ if (exists $core_files{$core} && $core_files{$core} == -M $core) {
+ # we have seen this core file before the start of the test
+ info "an old core file has been found: $core";
+ }
+ else {
+ my $oh = oh();
+ my $again = $times++ ? "again" : "";
+ error "oh $oh, server dumped core $again";
+ error "for stacktrace, run: gdb $vars->{httpd} -core $core";
+ }
+ }}, $vars->{top_dir});
+}
+
+# warn the user that there is a core file before the tests
+# start. suggest to delete it before proceeding or a false alarm can
+# be generated at the end of the test routine run.
+sub warn_core {
+ my $self = shift;
+ my $vars = $self->{test_config}->{vars};
+ %core_files = (); # reset global
+
+ # no core files dropped on win32
+ return if Apache::TestConfig::WIN32;
+
+ finddepth(sub {
+ return unless -f $_;
+ return unless /$core_pat/o;
+ my $core = "$File::Find::dir/$_";
+ info "consider removing an old $core file before running tests";
+ # remember the timestamp of $core so we can check if it's the
+ # old core file at the end of the run and not complain then
+ $core_files{$core} = -M $core;
+ }, $vars->{top_dir});
+}
+
+# catch any attempts to ./t/TEST the tests as root user
+
+sub check_runtime_user {
+ my $self = shift;
+
+ return if Apache::TestConfig::WINFU;
+
+ my $user = getpwuid($>) || '';
+
+ if ($user eq 'root') {
+ error "Apache cannot spawn child processes as root, therefore the test suite must be run as a non-privileged user.";
+ exit_perl(1);
+ }
+
+ return 1;
+}
+
+sub run_request {
+ my($test_config, $opts) = @_;
+
+ my @args = (%{ $opts->{header} }, %{ $opts->{req_args} });
+
+ my($request, $url) = ("", "");
+
+ for (@request_opts) {
+ next unless exists $opts->{$_};
+ $url = $opts->{$_} if $opts->{$_};
+ $request = join $request ? '_' : '', $request, $_;
+ }
+
+ if ($request) {
+ my $method = \&{"Apache::TestRequest::\U$request"};
+ my $res = $method->($url, @args);
+ print Apache::TestRequest::to_string($res);
+ }
+}
+
+sub opt_clean {
+ my($self, $level) = @_;
+ my $test_config = $self->{test_config};
+ $test_config->server->stop;
+ $test_config->clean($level);
+ 1;
+}
+
+sub opt_ping {
+ my($self) = @_;
+
+ my $test_config = $self->{test_config};
+ my $server = $test_config->server;
+ my $pid = $server->ping;
+ my $name = $server->{name};
+ # support t/TEST -ping=block -run ...
+ my $exit = not $self->{opts}->{'run-tests'};
+
+ if ($pid) {
+ if ($pid == -1) {
+ error "port $test_config->{vars}->{port} is in use, ".
+ "but cannot determine server pid";
+ }
+ else {
+ my $version = $server->{version};
+ warning "server $name running (pid=$pid, version=$version)";
+ }
+ return $exit;
+ }
+
+ if (exists $self->{opts}->{ping} && $self->{opts}->{ping} eq 'block') {
+ $server->wait_till_is_up(STARTUP_TIMEOUT);
+ }
+ else {
+ warning "no server is running on $name";
+ exit_perl(0);
+ }
+
+ return $exit; #means call exit() if true
+}
+
+sub test_inc {
+ map { "$_/Apache-Test/lib" } qw(. ..);
+}
+
+sub set_perl5lib {
+ $ENV{PERL5LIB} = join $Config{path_sep}, shift->test_inc();
+}
+
+sub set_perldb_opts {
+ my $config = shift->{test_config};
+ my $file = catfile $config->{vars}->{t_logs}, 'perldb.out';
+ $config->genfile($file); #mark for -clean
+ $ENV{PERLDB_OPTS} = "NonStop frame=4 AutoTrace LineInfo=$file";
+ warning "perldb log is t/logs/perldb.out";
+}
+
+sub opt_debug {
+ my $self = shift;
+ my $server = $self->{server};
+
+ my $opts = $self->{opts};
+ my $debug_opts = {};
+
+ for (qw(debugger breakpoint)) {
+ $debug_opts->{$_} = $opts->{$_};
+ }
+
+ if (my $db = $opts->{debugger}) {
+ if ($db =~ s/^perl=?//) {
+ $opts->{'run-tests'} = 1;
+ $self->start; #if not already running
+ $self->set_perl5lib;
+ $self->set_perldb_opts if $db eq 'nostop';
+ system $^X, '-MApache::TestPerlDB', '-d', @{ $self->{tests} };
+ $self->stop;
+ return 1;
+ }
+ elsif ($db =~ s/^lwp[=:]?//) {
+ $ENV{APACHE_TEST_DEBUG_LWP} = $db || 1;
+ $opts->{verbose} = 1;
+ return 0;
+ }
+ }
+
+ $server->stop;
+ $server->start_debugger($debug_opts);
+ 1;
+}
+
+sub opt_help {
+ my $self = shift;
+
+ print <<EOM;
+usage: TEST [options ...]
+ where options include:
+EOM
+
+ for (sort keys %usage){
+ printf " -%-13s %s\n", $_, $usage{$_};
+ }
+
+ print "\n configuration options:\n";
+
+ Apache::TestConfig->usage;
+ 1;
+}
+
+# generate t/TEST script (or a different filename) which will drive
+# Apache::TestRun
+sub generate_script {
+ my ($class, @opts) = @_;
+
+ my %opts = ();
+
+ # back-compat
+ if (@opts == 1) {
+ $opts{file} = $opts[0];
+ }
+ else {
+ %opts = @opts;
+ $opts{file} ||= catfile 't', 'TEST';
+ }
+
+ my $body = "BEGIN { eval { require blib && blib->import; } }\n";
+
+ my %args = @Apache::TestMM::Argv;
+ while (my($k, $v) = each %args) {
+ $v =~ s/\|/\\|/g;
+ $body .= "\n\$Apache::TestConfig::Argv{'$k'} = q|$v|;\n";
+ }
+
+ my $header = Apache::TestConfig->perlscript_header;
+
+ $body .= join "\n",
+ $header, "use $class ();";
+
+ if (my $report = $opts{bugreport}) {
+ $body .= "\n\npackage $class;\n" .
+ "sub bug_report { print '$report' }\n\n";
+ }
+
+ $body .= "$class->new->run(\@ARGV);";
+
+ Apache::Test::basic_config()->write_perlscript($opts{file},
+ $body);
+}
+
+# in idiomatic perl functions return 1 on success and 0 on
+# failure. Shell expects the opposite behavior. So this function
+# reverses the status.
+sub exit_perl {
+ exit_shell $_[0] ? 0 : 1;
+}
+
+# expects shell's exit status values (0==success)
+sub exit_shell {
+# require Carp;
+# Carp::cluck('exiting');
+ CORE::exit $_[0];
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Apache::TestRun - Run the test suite
+
+=head1 SYNOPSIS
+
+
+=head1 DESCRIPTION
+
+The C<Apache::TestRun> package controls the configuration and running
+of the test suite.
+
+=head1 METHODS
+
+Several methods are sub-classable, if the default behavior should be
+changed.
+
+=head2 C<bug_report>
+
+The C<bug_report()> method is executed when C<t/TEST> was executed
+with the C<-bugreport> option, and C<make test> (or C<t/TEST>)
+fail. Normally this is callback which you can use to tell the user how
+to deal with the problem, e.g. suggesting to read some document or
+email some details to someone who can take care of it. By default
+nothing is executed.
+
+The C<-bugreport> option is needed so this feature won't become
+annoying to developers themselves. It's automatically added to the
+C<run_tests> target in F<Makefile>. So if you repeateadly have to test
+your code, just don't use C<make test> but run C<t/TEST>
+directly. Here is an example of a custom C<t/TEST>
+
+ My::TestRun->new->run(@ARGV);
+
+ package My::TestRun;
+ use base 'Apache::TestRun';
+
+ sub bug_report {
+ my $self = shift;
+
+ print <<EOI;
+ +--------------------------------------------------------+
+ | Please file a bug report: http://perl.apache.org/bugs/ |
+ +--------------------------------------------------------+
+ EOI
+ }
+
+=head2 C<pre_configure>
+
+The C<pre_configure()> method is executed before the configuration for
+C<Apache::Test> is generated. So if you need to adjust the setup
+before I<httpd.conf> and other files are autogenerated, this is the
+right place to do so.
+
+For example if you don't want to inherit a LoadModule directive for
+I<mod_apreq.so> but to make sure that the local version is used, you
+can sub-class C<Apache::TestRun> and override this method in
+I<t/TEST.PL>:
+
+ package My::TestRun;
+ use base 'Apache::TestRun';
+ use Apache::TestConfig;
+ __PACKAGE__->new->run(@ARGV);
+
+ sub pre_configure {
+ my $self = shift;
+ # Don't load an installed mod_apreq
+ Apache::TestConfig::autoconfig_skip_module_add('mod_apreq.c');
+
+ $self->SUPER::pre_configure();
+ }
+
+Notice that the extension is I<.c>, and not I<.so>.
+
+Don't forget to run the super class' c<pre_configure()> method.
+
+
+
+=head2 C<new_test_config>
+
+META: to be completed
+
+=cut