home *** CD-ROM | disk | FTP | other *** search
- # Copyright 2001-2004 The Apache Software Foundation
- #
- # Licensed 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 %core_files = ();
- my %original_t_perms = ();
-
- my @std_run = qw(start-httpd run-tests stop-httpd);
- my @others = qw(verbose configure clean help ssl http11 bugreport
- save no-httpd);
- my @flag_opts = (@std_run, @others);
- my @string_opts = qw(order trace);
- my @ostring_opts = qw(proxy ping);
- my @debug_opts = qw(debug);
- my @num_opts = qw(times);
- 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',
- 'times=N' => 'repeat the tests N times',
- 'order=mode' => 'run the tests in one of the modes: ' .
- '(repeat|rotate|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, ...',
- 'save' => 'save test paramaters into Apache::TestConfigData',
- (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 "$_=i", @num_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;
- }
-
- # 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 server-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->{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;
-
- # custom config save if
- # 1) requested to save
- # 2) no saved config yet
- if ($self->{opts}->{save} or
- !Apache::TestConfig::custom_config_exists()) {
- $test_config->custom_config_save($self->{conf_opts});
- }
- }
-
- 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 httpd.pid file if found
- my $t_logs = $self->{test_config}->{vars}->{t_logs};
- my $pid_file = catfile $t_logs, "httpd.pid";
- 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->adjust_t_perms();
-
- 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},
- times => $self->{opts}->{times},
- 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;
-
- $self->restore_t_perms;
-
- 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";
- exec $orig_command;
- die "exec $orig_command has failed"; # shouldn't be reached
- }
-
- 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);
-
- # must be called after getopts so the tracing will be set right
- Apache::TestConfig::custom_config_load();
-
- $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();
-
- $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);
- }
-
- # if configure() fails for some reason before it has flushed the
- # config to a file, save it so -clean will be able to clean
- unless ($self->{opts}->{clean}) {
- 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->default_run_opts;
-
- $self->split_test_args;
-
- $self->die_on_invalid_args;
-
- $self->start unless $self->{opts}->{'no-httpd'};
-
- $self->run_tests;
-
- $self->stop unless $self->{opts}->{'no-httpd'};
- }
-
- sub rerun {
- $orig_cwd ||= Cwd::cwd();
- chdir $orig_cwd;
- warning "rerunning '$orig_command' with new config opts";
- exec $orig_command;
- die "exec $orig_command has failed"; # shouldn't be reached
- }
-
-
- # 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) {
- next unless -f;
- next unless /$core_pat/o;
- my $core = catfile $vars->{t_dir}, $_;
- next if exists $core_files{$core} &&
- $core_files{$core} == -M $core;
- $core_files{$core} = -M $core;
- push @cores, $core;
- }
- 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});
- }
-
- # this function handles the cases when the test suite is run under
- # 'root':
- #
- # 1. When user 'bar' is chosen to run Apache with, files and dirs
- # created by 'root' might be not writable/readable by 'bar'
- #
- # 2. when the source is extracted as user 'foo', and the chosen user
- # to run Apache under is 'bar', in which case normally 'bar' won't
- # have the right permissions to write into the fs created by 'foo'.
- #
- # We solve that by 'chown -R bar.bar t/' in a portable way.
- #
- # 3. If the parent directory is not rwx for the chosen user, that user
- # won't be able to read/write the DocumentRoot. In which case we
- # have nothing else to do, but to tell the user to fix the situation.
- #
- sub adjust_t_perms {
- my $self = shift;
-
- return if Apache::TestConfig::WINFU;
-
- %original_t_perms = (); # reset global
-
- my $user = getpwuid($>) || '';
- if ($user eq 'root') {
- my $vars = $self->{test_config}->{vars};
- my $user = $vars->{user};
- my($uid, $gid) = (getpwnam($user))[2..3]
- or die "Can't find out uid/gid of '$user'";
-
- warning "root mode: ".
- "changing the files ownership to '$user' ($uid:$gid)";
- finddepth(sub {
- $original_t_perms{$File::Find::name} = [(stat $_)[4..5]];
- chown $uid, $gid, $_;
- }, $vars->{t_dir});
-
- $self->check_perms($user, $uid, $gid);
-
- $self->become_nonroot($user, $uid, $gid);
- }
- }
-
- sub restore_t_perms {
- my $self = shift;
-
- return if Apache::TestConfig::WINFU;
-
- if (%original_t_perms) {
- warning "root mode: restoring the original files ownership";
- my $vars = $self->{test_config}->{vars};
- while (my($file, $ids) = each %original_t_perms) {
- next unless -e $file; # files could be deleted
- chown @$ids, $file;
- }
- }
- }
-
- # this sub is executed from an external process only, since it
- # "sudo"'s into a uid/gid of choice
- sub run_root_fs_test {
- my($uid, $gid, $dir) = @_;
-
- # first must change gid and egid ("$gid $gid" for an empty
- # setgroups() call as explained in perlvar.pod)
- my $groups = "$gid $gid";
- $( = $) = $groups;
- die "failed to change gid to $gid"
- unless $( eq $groups && $) eq $groups;
-
- # only now can change uid and euid
- $< = $> = $uid+0;
- die "failed to change uid to $uid" unless $< == $uid && $> == $uid;
-
- my $file = catfile $dir, ".apache-test-file-$$-".time.int(rand);
- eval "END { unlink q[$file] }";
-
- # unfortunately we can't run the what seems to be an obvious test:
- # -r $dir && -w _ && -x _
- # since not all perl implementations do it right (e.g. sometimes
- # acls are ignored, at other times setid/gid change is ignored)
- # therefore we test by trying to attempt to read/write/execute
-
- # -w
- open TEST, ">$file" or die "failed to open $file: $!";
-
- # -x
- -f $file or die "$file cannot be looked up";
- close TEST;
-
- # -r
- opendir DIR, $dir or die "failed to open dir $dir: $!";
- defined readdir DIR or die "failed to read dir $dir: $!";
- close DIR;
-
- # all tests passed
- print "OK";
- }
-
- sub check_perms {
- my ($self, $user, $uid, $gid) = @_;
-
- # test that the base dir is rwx by the selected non-root user
- my $vars = $self->{test_config}->{vars};
- my $dir = $vars->{t_dir};
- my $perl = Apache::TestConfig::shell_ready($vars->{perl});
-
- # find where Apache::TestRun was loaded from, so we load this
- # exact package from the external process
- my $inc = dirname dirname $INC{"Apache/TestRun.pm"};
- my $sub = "Apache::TestRun::run_root_fs_test";
- my $check = <<"EOI";
- $perl -Mlib=$inc -MApache::TestRun -e 'eval { $sub($uid, $gid, q[$dir]) }';
- EOI
- warning "testing whether '$user' is able to -rwx $dir\n$check\n";
-
- my $res = qx[$check] || '';
- warning "result: $res";
- unless ($res eq 'OK') {
- $self->user_error(1);
- #$self->restore_t_perms;
- error <<"EOI";
- You are running the test suite under user 'root'.
- Apache cannot spawn child processes as 'root', therefore
- we attempt to run the test suite with user '$user' ($uid:$gid).
- The problem is that the path (including all parent directories):
- $dir
- must be 'rwx' by user '$user', so Apache can read and write under that
- path.
-
- There are several ways to resolve this issue. One is to move and
- rebuild the distribution to '/tmp/' and repeat the 'make test'
- phase. The other is not to run 'make test' as root (i.e. building
- under your /home/user directory).
-
- You can test whether some directory is suitable for 'make test' under
- 'root', by running a simple test. For example to test a directory
- '$dir', run:
-
- % $check
- Only if the test prints 'OK', the directory is suitable to be used for
- testing.
- EOI
- skip_test_suite();
- exit_perl 0;
- }
- }
-
- # in case the client side creates any files after the initial chown
- # adjustments we want the server side to be able to read/write them, so
- # they better be with the same permissions. dropping root permissions
- # and becoming the same user as the server side solves this problem.
- sub become_nonroot {
- my ($self, $user, $uid, $gid) = @_;
-
- warning "the client side drops 'root' permissions and becomes '$user'";
-
- # first must change gid and egid ("$gid $gid" for an empty
- # setgroups() call as explained in perlvar.pod)
- my $groups = "$gid $gid";
- $( = $) = $groups;
- die "failed to change gid to $gid" unless $( eq $groups && $) eq $groups;
-
- # only now can change uid and euid
- $< = $> = $uid+0;
- die "failed to change uid to $uid" unless $< == $uid && $> == $uid;
- }
-
- 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";
- }
-
- 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; } }\n";
-
- $body .= Apache::TestConfig->modperl_2_inc_fixup;
-
- 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];
- }
-
- # successfully abort the test suite execution (to allow automatic
- # tools like CPAN.pm, to continue with installation).
- #
- # if a true value is passed, quit right away
- # otherwise ask the user, if they may want to change their mind which
- # will return them back to where they left
- sub skip_test_suite {
- my $no_doubt = shift;
-
- print qq[
-
- Running the test suite is important to make sure that the module that
- you are about to install works on your system. If you choose not to
- run the test suite and you have a problem using this module, make sure
- to return and run this test suite before reporting any problems to the
- developers of this module.
-
- ];
- unless ($no_doubt) {
- my $default = 'No';
- my $prompt = 'Skip the test suite?';
- my $ans = ExtUtils::MakeMaker::prompt($prompt, $default);
- return if lc($ans) =~ /no/;
- }
-
- error "Skipping the test suite execution, while returning success status";
- exit_perl 1;
- }
-
- 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
-
-
-
- =head1 Persistent Custom Configuration
-
- When C<Apache::Test> is first installed or used, it will save the
- values of C<httpd>, C<apxs>, C<port>, C<user>, and C<group>, if set,
- to a configuration file C<Apache::TestConfigData>. This information
- will then be used in setting these options for subsequent uses of
- C<Apache-Test> unless temprorarily overridden, either by setting the
- appropriate environment variable (C<APACHE_TEST_HTTPD>,
- C<APACHE_TEST_APXS>, C<APACHE_TEST_PORT>, C<APACHE_TEST_USER>, and
- C<APACHE_TEST_GROUP>) or by giving the relevant option (C<-httpd>,
- C<-apxs>, C<-port>, C<-user>, and C<-group>) when the C<TEST> script
- is run.
-
- To avoid either using previous persistent configurations or saving
- current configurations, set the C<APACHE_TEST_NO_STICKY_PREFERENCES>
- environment variable to a true value.
-
- Finally it's possible to permanently override the previously saved
- options by passing C<L<-save|/Saving_Custom_Configuration_Options>>.
-
- Here is the algorithm of how and when options are saved for the first
- time and when they are used. We will use a few variables to simplify
- the pseudo-code/pseudo-chart flow:
-
- C<$config_exists> - custom configuration has already been saved, to
- get this setting run C<custom_config_exists()>, which tests whether
- either C<apxs> or C<httpd> values are set. It doesn't check for other
- values, since all we need is C<apxs> or C<httpd> to get the test suite
- running. custom_config_exists() checks in the following order
- F<lib/Apache/TestConfigData.pm> (if during Apache-Test build) ,
- F<~/.apache-test/Apache/TestConfigData.pm> and
- F<Apache/TestConfigData.pm> in the perl's libraries.
-
- C<$config_overriden> - that means that we have either C<apxs> or
- C<httpd> values provided by user, via env vars or command line options.
-
- =over
-
- =item 1 Building Apache-Test or modperl-2.0 (or any other project that
- bundles Apache-Test).
-
- 1) perl Apache-Test/Makefile.PL
- (for bundles top-level Makefile.PL will run this as well)
-
- if $config_exists
- do nothing
- else
- create lib/Apache/TestConfigData.pm w/ empty config: {}
-
- 2) make
-
- 3) make test
-
- if $config_exists
- if $config_overriden
- override saved options (for those that were overriden)
- else
- use saved options
- else
- if $config_overriden
- save them in lib/Apache/TestConfigData.pm
- (which will be installed on 'make install')
- else
- - run interactive prompt for C<httpd> and optionally for C<apxs>
- - save the custom config in lib/Apache/TestConfigData.pm
- - restart the currently run program
-
- modperl-2.0 is a special case in (3). it always overrides 'httpd'
- and 'apxs' settings. Other settings like 'port', can be used from
- the saved config.
-
- 4) make install
-
- if $config_exists only in lib/Apache/TestConfigData.pm
- it will be installed system-wide
- else
- nothing changes (since lib/Apache/TestConfigData.pm won't exist)
-
- =item 2 Testing 3rd party modules (after Apache-Test was installed)
-
- Notice that the following situation is quite possible:
-
- cd Apache-Test
- perl Makefile.PL && make install
-
- so that Apache-Test was installed but no custom configuration saved
- (since its C<make test> wasn't run). In which case the interactive
- configuration should kick in (unless config options were passed) and
- in any case saved once configured.
-
- C<$custom_config_path> - perl's F<Apache/TestConfigData.pm> (at the
- same location as F<Apache/TestConfig.pm>) if that area is writable by
- that user (e.g. perl's lib is not owned by 'root'). If not, in
- F<~/.apache-test/Apache/TestConfigData.pm>.
-
- 1) perl Apache-Test/Makefile.PL
- 2) make
- 3) make test
-
- if $config_exists
- if $config_overriden
- override saved options (for those that were overriden)
- else
- use saved options
- else
- if $config_overriden
- save them in $custom_config_path
- else
- - run interactive prompt for C<httpd> and optionally for C<apxs>
- - save the custom config in $custom_config_path
- - restart the currently run program
-
- 4) make install
-
- =back
-
-
-
- =head2 Saving Custom Configuration Options
-
- If you want to override the existing custom configurations options to
- C<Apache::TestConfigData>, use the C<-save> flag when running C<TEST>.
-
- If you are running C<Apache::Test> as a user who does not have
- permission to alter the system C<Apache::TestConfigData>, you can
- place your own private configuration file F<TestConfigData.pm> under
- C<$ENV{HOME}/.apache-test/Apache/>, which C<Apache::Test> will use, if
- present. An example of such a configuration file is
-
- # file $ENV{HOME}/.apache-test/Apache/TestConfigData.pm
- package Apache::TestConfigData;
- use strict;
- use warnings;
- use vars qw($vars);
-
- $vars = {
- 'group' => 'me',
- 'user' => 'myself',
- 'port' => '8529',
- 'httpd' => '/usr/local/apache/bin/httpd',
-
- };
- 1;
-
-
-
-
- =cut
-