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::TestSmoke;
-
- use strict;
- use warnings FATAL => 'all';
-
- use Apache::Test ();
- use Apache::TestConfig ();
- use Apache::TestTrace;
-
- use Apache::TestHarness ();
- use Apache::TestRun (); # for core scan functions
-
- use Getopt::Long qw(GetOptions);
- use File::Spec::Functions qw(catfile);
- use FindBin;
- use POSIX ();
- use Symbol ();
-
- #use constant DEBUG => 1;
-
- # how many times to run all tests at the first iteration
- use constant DEFAULT_TIMES => 10;
-
- # how many various seeds to try in NONSTOP mode
- use constant DEFAULT_ITERATIONS => 10;
-
- # if after this number of tries to reduce the number of tests fails we
- # give up on more tries
- use constant MAX_REDUCTION_TRIES => 50;
-
- my @num_opts = qw(times iterations);
- my @string_opts = qw(order report);
- my @flag_opts = qw(help verbose bug_mode);
-
- my %order = map {$_ => 1} qw(random repeat rotate);
-
- my %usage = (
- 'iterations=N' => 'number of random iterations to run' .
- ' (default: ' . DEFAULT_ITERATIONS . ')',
- 'times=N' => 'try to repeat all tests at most N times' .
- ' (default: ' . DEFAULT_TIMES . ')',
- 'order=MODE' => 'modes: random, repeat, rotate' .
- ' (default: random)',
- 'report=FILENAME' => 'save report in a filename' .
- ' (default: smoke-report-<date>.txt)',
- 'verbose[=1]' => 'verbose output' .
- ' (default: 0)',
- 'bug_mode' => 'bug report mode' .
- ' (default: 0)',
- );
-
- sub new {
- my($class, @argv) = @_;
-
- my $self = bless {
- seen => {}, # seen sequences and tried them md5 hash
- results => {}, # final reduced sequences md5 hash
- smoking_completed => 0,
- tests => [],
- total_iterations => 0,
- total_reduction_attempts => 0,
- total_reduction_successes => 0,
- total_tests_run => 0,
- }, ref($class)||$class;
-
- $self->{test_config} = Apache::TestConfig->thaw;
-
- $self->getopts(\@argv);
- my $opts = $self->{opts};
-
- chdir "$FindBin::Bin/..";
-
- $self->{times} = $opts->{times} || DEFAULT_TIMES;
- $self->{order} = $opts->{order} || 'random';
- $self->{verbose} = $opts->{verbose} || 0;
-
- # it doesn't make sense to run a known sequence more than once
- if ($self->{order} eq 'random') {
- $self->{run_iter} = $opts->{iterations} || DEFAULT_ITERATIONS;
- }
- else {
- error "forcing only one iteration for non-random order";
- $self->{run_iter} = 1;
- }
-
- # this is like 'make test' but produces an output to be used in
- # the bug report
- if ($opts->{bug_mode}) {
- $self->{bug_mode} = 1;
- $self->{run_iter} = 1;
- $self->{times} = 1;
- $self->{verbose} = 1;
- $self->{order} = 'rotate';
- $self->{trace} = 'debug';
- }
-
- # specific tests end up in $self->{tests} and $self->{subtests};
- # and get removed from $self->{argv}
- $self->Apache::TestRun::split_test_args();
-
- my $test_opts = {
- #verbose => $self->{verbose},
- tests => $self->{tests},
- times => $self->{times},
- order => $self->{order},
- subtests => $self->{subtests} || [],
- };
-
- @{ $self->{tests} } = $self->get_tests($test_opts);
-
- $self->{base_command} = "$^X $FindBin::Bin/TEST";
-
- # options common to all
- $self->{base_command} .= " -verbose" if $self->{verbose};
-
- # options specific to the startup
- $self->{start_command} = "$self->{base_command} -start";
- $self->{start_command} .= " -trace=" . $self->{trace} if $self->{trace};
-
- # options specific to the run
- $self->{run_command} = "$self->{base_command} -run";
-
- # options specific to the stop
- $self->{stop_command} = "$self->{base_command} -stop";
-
- $self;
- }
-
- sub getopts {
- my($self, $argv) = @_;
- my %opts;
- local *ARGV = $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,
- (map "$_=s", @string_opts),
- (map "$_=i", @num_opts));
-
- if (exists $opts{order} && !exists $order{$opts{order}}) {
- error "unknown -order mode: $opts{order}";
- $self->opt_help();
- exit;
- }
-
- if ($opts{help}) {
- $self->opt_help;
- exit;
- }
-
- # min
- $self->{opts} = \%opts;
-
- $self->{argv} = [@ARGV];
- }
-
- # XXX: need proper sub-classing
- # from Apache::TestHarness
- sub skip { Apache::TestHarness::skip(@_); }
- sub prune { Apache::TestHarness::prune(@_); }
- sub get_tests { Apache::TestHarness::get_tests(@_);}
-
- sub install_sighandlers {
- my $self = shift;
-
- $SIG{INT} = sub {
- # make sure that there the server is down
- $self->kill_proc();
-
- $self->report_finish;
- exit;
- };
- }
-
- END {
- local $?; # preserve the exit status
- eval {
- Apache::TestRun->new(test_config =>
- Apache::TestConfig->thaw)->scan_core;
- };
- }
-
- sub run {
- my($self) = shift;
-
- $self->Apache::TestRun::warn_core();
- local $SIG{INT};
- $self->install_sighandlers;
-
- $self->report_start();
-
- if ($self->{bug_mode}) {
- # 'make test', but useful for bug reports
- $self->run_bug_mode();
- }
- else {
- # normal smoke
- my $iter = 0;
- while ($iter++ < $self->{run_iter}) {
- my $last = $self->run_iter($iter);
- last if $last;
- }
- }
- $self->{smoking_completed} = 1;
- $self->report_finish();
- exit;
- }
-
- sub sep {
- my($char, $title) = @_;
- my $width = 60;
- if ($title) {
- my $side = int( ($width - length($title) - 2) / 2);
- my $pad = ($side+1) * 2 + length($title) < $width ? 1 : 0;
- return $char x $side . " $title " . $char x ($side+$pad);
- }
- else {
- return $char x $width;
- }
- }
-
- my %log_files = ();
- use constant FH => 0;
- use constant POS => 1;
- sub logs_init {
- my($self, @log_files) = @_;
-
- for my $path (@log_files) {
- my $fh = Symbol::gensym();
- open $fh, "<$path" or die "Can't open $path: $!";
- seek $fh, 0, POSIX::SEEK_END();
- $log_files{$path}[FH] = $fh;
- $log_files{$path}[POS] = tell $fh;
- }
- }
-
- sub logs_end {
- for my $path (keys %log_files) {
- close $log_files{$path}[FH];
- }
- }
-
- sub log_diff {
- my($self, $path) = @_;
-
- my $log = $log_files{$path};
- die "no such log file: $path" unless $log;
-
- my $fh = $log->[FH];
- # no checkpoints were made yet?
- unless (defined $log->[POS]) {
- seek $fh, 0, POSIX::SEEK_END();
- $log->[POS] = tell $fh;
- return '';
- }
-
- seek $fh, $log->[POS], POSIX::SEEK_SET(); # not really needed
- local $/; # slurp mode
- my $diff = <$fh>;
- seek $fh, 0, POSIX::SEEK_END(); # not really needed
- $log->[POS] = tell $fh;
-
- return $diff || '';
- }
-
- # this is a special mode, which really just runs 't/TEST -start;
- # t/TEST -run; t/TEST -stop;' but it runs '-run' separately for each
- # test, and checks whether anything bad has happened after the run
- # of each test (i.e. either a test has failed, or a test may be successful,
- # but server may have dumped a core file, we detect that).
- sub run_bug_mode {
- my($self) = @_;
-
- my $iter = 0;
-
- warning "running t/TEST in the bug report mode";
-
- my $reduce_iter = 0;
- my @good = ();
-
- # first time run all tests, or all specified tests
- my @tests = @{ $self->{tests} }; # copy
- my $bad = $self->run_test($iter, $reduce_iter, \@tests, \@good);
- $self->{total_iterations}++;
-
- }
-
-
- # returns true if for some reason no more iterations should be made
- sub run_iter {
- my($self, $iter) = @_;
-
- my $stop_now = 0;
- my $reduce_iter = 0;
- my @good = ();
- warning "\n" . sep("-");
- warning sprintf "[%03d-%02d-%02d] trying all tests $self->{times} times",
- $iter, $reduce_iter, 0;
-
- # first time run all tests, or all specified tests
- my @tests = @{ $self->{tests} }; # copy
- my $bad = $self->run_test($iter, $reduce_iter, \@tests, \@good);
- unless ($bad) {
- $self->{total_iterations}++;
- return $stop_now;
- }
- error "recorded a positive failure ('$bad'), " .
- "will try to minimize the input now";
-
- my $command = $self->{base_command};
-
- # does the test fail on its own
- {
- $reduce_iter++;
- warning sprintf "[%03d-%02d-%02d] trying '$bad' on its own",
- $iter, $reduce_iter, 1;
- my @good = ();
- my @tests = ($bad);
- my $bad = $self->run_test($iter, $reduce_iter, \@tests, \@good);
- # if a test is failing on its own there is no point to
- # continue looking for other sequences
- if ($bad) {
- $stop_now = 1;
- $self->{total_iterations}++;
- unless ($self->sequence_seen($self->{results}, [@good, $bad])) {
- $self->report_success($iter, $reduce_iter, "$command $bad", 1);
- }
- return $stop_now;
- }
- }
-
- # positive failure
- my $ok_tests = @good;
- my $reduction_success = 0;
- my $done = 0;
- while (@good > 1) {
- my $tries = 0;
- my $reduce_sub = $self->reduce_stream(\@good);
- $reduce_iter++;
- while ($tries++ < MAX_REDUCTION_TRIES) {
- $self->{total_reduction_attempts}++;
- my @try = @{ $reduce_sub->() };
-
- # reduction stream is empty (tried all?)
- unless (@try) {
- $done = 1;
- last;
- }
-
- warning sprintf "\n[%03d-%02d-%02d] trying %d tests",
- $iter, $reduce_iter, $tries, scalar(@try);
- my @ok = ();
- my @tests = (@try, $bad);
- my $new_bad = $self->run_test($iter, $reduce_iter, \@tests, \@ok);
- if ($new_bad) {
- # successful reduction
- $reduction_success++;
- @good = @ok;
- $tries = 0;
- my $num = @ok;
- error "*** reduction $reduce_iter succeeded ($num tests) ***";
- $self->{total_reduction_successes}++;
- last;
- }
- }
-
- # last round of reducing has failed, so we give up
- if ($done || $tries >= MAX_REDUCTION_TRIES){
- error "no further reductions were made";
- $done = 1;
- last;
- }
-
- }
-
- # we have a minimal failure sequence at this point (to the extend
- # of success of our attempts to reduce)
-
- # report the sequence if we didn't see such one yet in the
- # previous iterations
- unless ($self->sequence_seen($self->{results}, [@good, $bad])) {
- # if no reduction succeeded, it's 0
- $reduce_iter = 0 unless $reduction_success;
- $self->report_success($iter, $reduce_iter,
- "$command @good $bad", @good + 1);
- }
-
- $self->{total_iterations}++;
-
- return $stop_now;
- }
-
- # my $sub = $self->reduce_stream(\@items);
- sub reduce_stream {
- my($self) = shift;
- my @items = @{+shift};
-
- my $items = @items;
- my $odd = $items % 2 ? 1 : 0;
- my $middle = int($items/2) - 1;
- my $c = 0;
-
- return sub {
- $c++; # remember stream's state
-
- # a single item is not reduce-able
- return \@items if $items == 1;
-
- my @try = ();
- my $max_repeat_tries = 50; # avoid seen sequences
- my $repeat = 0;
- while ($repeat++ <= $max_repeat_tries) {
-
- # try to use a binary search
- if ($c == 1) {
- # right half
- @try = @items[($middle+1)..($items-1)];
- }
- elsif ($c == 2) {
- # left half
- @try = @items[0..$middle];
- }
-
- # try to use a random window size alg
- else {
- my $left = int rand($items);
- $left = $items - 1 if $left == $items - 1;
- my $right = $left + int rand($items - $left);
- $right = $items - 1 if $right >= $items;
- @try = @items[$left..$right];
- }
-
- if ($self->sequence_seen($self->{seen}, \@try)) {
- @try = ();
- }
- else {
- last; # found an unseen sequence
- }
- }
- return \@try;
- }
- }
-
- sub sequence_seen {
- my ($self, $rh_store, $ra_tests) = @_;
-
- require Digest::MD5;
- my $digest = Digest::MD5::md5_hex(join '', @$ra_tests);
- #error $self->{seen};
- return $rh_store->{$digest}++ ? 1 : 0
-
- }
-
- sub run_test {
- require IPC::Run3;
- my($self, $iter, $count, $tests, $ra_ok) = @_;
- my $bad = '';
- my $ra_nok = [];
-
- #warning "$self->{base_command} @$tests";
-
- #$SIG{PIPE} = 'IGNORE';
- $SIG{PIPE} = sub { die "pipe broke" };
-
- # start server
- {
- my $command = $self->{start_command};
- my $log = '';
- IPC::Run3::run3($command, undef, \$log, \$log);
- my $started_ok = ($log =~ /started/) ? 1 : 0;
- unless ($started_ok) {
- error "failed to start server\n $log";
- exit 1;
- }
- }
-
- my $t_logs = $self->{test_config}->{vars}->{t_logs};
- my @log_files = map { catfile $t_logs, $_ } qw(error_log access_log);
- $self->logs_init(@log_files);
-
- # run tests
- {
- my $command = $self->{run_command};
-
- my $max_len = 1;
- for my $test (@$tests) {
- $max_len = length $test if length $test > $max_len;
- }
-
- for my $test (@$tests) {
- (my $test_name = $test) =~ s/\.t$//;
- my $fill = "." x ($max_len - length $test_name);
- $self->{total_tests_run}++;
-
- my $test_command = "$command $test";
- my $log = '';
- IPC::Run3::run3($test_command, undef, \$log, \$log);
- my $ok = ($log =~ /All tests successful/) ? 1 : 0;
-
- my @core_files_msg = $self->Apache::TestRun::scan_core_incremental(1);
-
- # if the test has caused core file(s) it's not ok
- $ok = 0 if @core_files_msg;
-
- if ($ok) {
- push @$ra_ok, $test;
- if ($self->{verbose}) {
- print STDERR "$test_name${fill}ok\n";
- }
- # need to run log_diff to reset the position of the fh
- my %log_diffs = map { $_ => $self->log_diff($_) } @log_files;
-
- }
- else {
- push @$ra_nok, $test;
- $bad = $test;
-
- if ($self->{verbose}) {
- print STDERR "$test_name${fill}FAILED\n";
- error sep("-");
-
- # give server some time to finish the
- # logging. it's ok to wait long time since we have
- # to deal with an error
- sleep 5;
- my %log_diffs = map { $_ => $self->log_diff($_) } @log_files;
-
- # client log
- error "\t\t*** run log ***";
- $log =~ s/^/ /mg;
- print STDERR "$log\n";
-
- # server logs
- for my $path (@log_files) {
- next unless length $log_diffs{$path};
- error "\t\t*** $path ***";
- $log_diffs{$path} =~ s/^/ /mg;
- print STDERR "$log_diffs{$path}\n";
- }
- }
- if (@core_files_msg) {
- unless ($self->{verbose}) {
- # currently the output of 'run log' already
- # includes the information about core files once
- # Test::Harness::Straps allows us to run callbacks
- # after each test, and we move back to run all
- # tests at once, we will log the message here
- error "$test_name caused core";
- print STDERR join "\n", @core_files_msg, "\n";
- }
- }
-
- if ($self->{verbose}) {
- error sep("-");
- }
-
- unless ($self->{bug_mode}) {
- # normal smoke stop the run, but in the bug_mode
- # we want to complete all the tests
- last;
- }
- }
-
-
- }
- }
-
- $self->logs_end();
-
- # stop server
- $self->kill_proc();
-
- if ($self->{bug_mode}) {
- warning sep("-");
- if (@$ra_nok == 0) {
- printf STDERR "All tests successful (%d)\n", scalar @$ra_ok;
- }
- else {
- error sprintf "error running %d tests out of %d\n",
- scalar(@$ra_nok), scalar @$ra_ok + @$ra_nok;
- }
- }
- else {
- return $bad;
- }
-
-
- }
-
- sub report_start {
- my($self) = shift;
-
- my $time = scalar localtime;
- $self->{start_time} = $time;
- $time =~ s/\s/_/g;
- $time =~ s/:/-/g; # winFU
- my $file = $self->{opts}->{report} ||
- catfile Apache::Test::vars('top_dir'), "smoke-report-$time.txt";
- info "Report file: $file";
-
- open my $fh, ">$file" or die "cannot open $file for writing: $!";
- $self->{fh} = $fh;
- my $sep = sep("-");
- my $title = sep('=', "Special Tests Sequence Failure Finder Report");
-
- print $fh <<EOM;
- $title
- $sep
- First iteration used:
- $self->{base_command} @{$self->{tests}}
- $sep
- EOM
-
- }
-
- sub report_success {
- my($self, $iter, $reduce_iter, $sequence, $tests) = @_;
-
- my @report = ("iteration $iter ($tests tests):\n",
- "\t$sequence\n",
- "(made $reduce_iter successful reductions)\n\n");
-
- print @report;
- if (my $fh = $self->{fh}) {
- print $fh @report;
- }
- }
-
- sub report_finish {
- my($self) = @_;
-
- my $start_time = $self->{start_time};
- my $end_time = scalar localtime;
- if (my $fh = delete $self->{fh}) {
- my $failures = scalar keys %{ $self->{results} };
-
- my $sep = sep("-");
- my $cfg_as_string = $self->build_config_as_string;
- my $unique_seqs = scalar keys %{ $self->{results} };
- my $attempts = $self->{total_reduction_attempts};
- my $successes = $self->{total_reduction_successes};
- my $completion = $self->{smoking_completed}
- ? "Completed"
- : "Not Completed (aborted by user)";
-
- my $status = "Unknown";
- if ($self->{total_iterations} > 0) {
- if ($failures) {
- $status = "*** NOT OK ***";
- }
- else {
- $status = "+++ OK +++";
- }
- }
-
- my $title = sep('=', "Summary");
-
- my $iter_made = sprintf "Iterations (%s) made : %d",
- $self->{order}, $self->{total_iterations};
-
- print $fh <<EOM;
-
- $title
- Completion : $completion
- Status : $status
- Tests run : $self->{total_tests_run}
- $iter_made
- EOM
-
- if ($attempts > 0 && $failures) {
- my $reduction_stats = sprintf "%d/%d (%d%% success)",
- $attempts, $successes, $successes / $attempts * 100;
-
- print $fh <<EOM;
- Unique sequences found : $unique_seqs
- Reduction tries/success : $reduction_stats
- EOM
- }
-
- print $fh <<EOM;
- $sep
- --- Started at: $start_time ---
- --- Ended at: $end_time ---
- $sep
- The smoke testing was run on the system with the following
- parameters:
-
- $cfg_as_string
-
- -- this report was generated by $0
- EOM
- close $fh;
- }
- }
-
- sub build_config_as_string {
- Apache::TestConfig::as_string();
- }
-
- sub kill_proc {
- my($self) = @_;
-
- my $command = $self->{stop_command};
- my $log = '';
- require IPC::Run3;
- IPC::Run3::run3($command, undef, \$log, \$log);
-
- my $stopped_ok = ($log =~ /shutdown/) ? 1 : 0;
- unless ($stopped_ok) {
- error "failed to stop server\n $log";
- }
- }
-
- sub opt_help {
- my $self = shift;
-
- print <<EOM;
- usage: t/SMOKE [options ...] [tests]
- where the options are:
- EOM
-
- for (sort keys %usage){
- printf " -%-16s %s\n", $_, $usage{$_};
- }
- print <<EOM;
-
- if 'tests' argument is not provided all available tests will be run
- EOM
- }
-
- # generate t/SMOKE script (or a different filename) which will drive
- # Apache::TestSmoke
- sub generate_script {
- my ($class, $file) = @_;
-
- $file ||= catfile 't', 'SMOKE';
-
- my $content = join "\n",
- "BEGIN { eval { require blib; } }",
- Apache::TestConfig->modperl_2_inc_fixup,
- Apache::TestConfig->perlscript_header,
- "use $class;",
- "$class->new(\@ARGV)->run;";
-
- Apache::Test::basic_config()->write_perlscript($file, $content);
- }
-
- 1;
- __END__
-
- =head1 NAME
-
- Apache::TestSmoke - Special Tests Sequence Failure Finder
-
- =head1 SYNOPSIS
-
- # get the usage and the default values
- % t/SMOKE -help
-
- # repeat all tests 5 times and try 20 random iterations
- # and save the report into the file 'myreport'
- % t/SMOKE -times=5 -iterations=20 -report=myreport
-
- # run all tests default number of iterations, and repeat tests
- # default number of times
- % t/SMOKE
-
- # same as above but work only the specified tests
- % t/SMOKE foo/bar foo/tar
-
- # run once a sequence of tests in a non-random mode
- # e.g. when trying to reduce a known long sequence that fails
- % t/SMOKE -order=rotate -times=1 foo/bar foo/tar
-
- # show me each currently running test
- # it's not the same as running the tests in the verbose mode
- % t/SMOKE -verbose
-
- # run t/TEST, but show any problems after *each* tests is run
- # useful for bug reports (it actually runs t/TEST -start, then
- # t/TEST -run for each test separately and finally t/TEST -stop
- % t/SMOKE -bug_mode
-
- # now read the created report file
-
- =head1 DESCRIPTION
-
- =head2 The Problem
-
- When we try to test a stateless machine (i.e. all tests are
- independent), running all tests once ensures that all tested things
- properly work. However when a state machine is tested (i.e. where a
- run of one test may influence another test) it's not enough to run all
- the tests once to know that the tested features actually work. It's
- quite possible that if the same tests are run in a different order
- and/or repeated a few times, some tests may fail. This usually
- happens when some tests don't restore the system under test to its
- pristine state at the end of the run, which may influence other tests
- which rely on the fact that they start on pristine state, when in fact
- it's not true anymore. In fact it's possible that a single test may
- fail when run twice or three times in a sequence.
-
- =head2 The Solution
-
- To reduce the possibility of such dependency errors, it's helpful to
- run random testing repeated many times with many different srand
- seeds. Of course if no failures get spotted that doesn't mean that
- there are no tests inter-dependencies, which may cause a failure in
- production. But random testing definitely helps to spot many problems
- and can give better test coverage.
-
- =head2 Resolving Sequence Problems
-
- When this kind of testing is used and a failure is detected there are
- two problems:
-
- =over
-
- =item 1
-
- First is to be able to reproduce the problem so if we think we fixed
- it, we could verify the fix. This one is easy, just remember the
- sequence of tests run till the failed test and rerun the same sequence
- once again after the problem has been fixed.
-
- =item 2
-
- Second is to be able to understand the cause of the problem. If during
- the random test the failure has happened after running 400 tests, how
- can we possibly know which previously running tests has caused to the
- failure of the test 401. Chances are that most of the tests were clean
- and don't have inter-dependency problem. Therefore it'd be very
- helpful if we could reduce the long sequence to a minimum. Preferably
- 1 or 2 tests. That's when we can try to understand the cause of the
- detected problem.
-
- =back
-
- This utility attempts to solve both problems, and at the end of each
- iteration print a minimal sequence of tests causing to a failure. This
- doesn't always succeed, but works in many cases.
-
- This utility:
-
- =over
-
- =item 1
-
- Runs the tests randomly until the first failure is detected. Or
- non-randomly if the option I<-order> is set to I<repeat> or I<rotate>.
-
- =item 2
-
- Then it tries to reduce that sequence of tests to a minimum, and this
- sequence still causes to the same failure.
-
- =item 3
-
- (XXX: todo): then it reruns the minimal sequence in the verbose mode
- and saves the output.
-
- =item 4
-
- It reports all the successful reductions as it goes to STDOUT and
- report file of the format: smoke-report-<date>.txt.
-
- In addition the systems build parameters are logged into the report
- file, so the detected problems could be reproduced.
-
- =item 5
-
- Goto 1 and run again using a new random seed, which potentially should
- detect different failures.
-
- =back
-
- =head1 Reduction Algorithm
-
- Currently for each reduction path, the following reduction algorithms
- get applied:
-
- =over
-
- =item 1
-
- Binary search: first try the upper half then the lower.
-
- =item 2
-
- Random window: randomize the left item, then the right item and return
- the items between these two points.
-
- =back
-
- =head1 t/SMOKE.PL
-
- I<t/SMOKE.PL> is driving this module, if you don't have it, create it:
-
- #!perl
-
- use strict;
- use warnings FATAL => 'all';
-
- use FindBin;
- use lib "$FindBin::Bin/../Apache-Test/lib";
- use lib "$FindBin::Bin/../lib";
-
- use Apache::TestSmoke ();
-
- Apache::TestSmoke->new(@ARGV)->run;
-
- usually I<Makefile.PL> converts it into I<t/SMOKE> while adjusting the
- perl path, but you create I<t/SMOKE> in first place as well.
-
- =head1 AUTHOR
-
- Stas Bekman
-
- =cut
-