Revision control

Copy as Markdown

#!/usr/bin/env perl
use 5.010;
use strict;
use warnings;
=head1 NAME
20-testrunner-core.t - test testrunner's coredump handling
=head1 SYNOPSIS
perl ./20-testrunner-core.t
This test will run the testrunner.pl script with some crashing processes
and do a basic verification that a backtrace of some kind was captured.
=cut
use Capture::Tiny qw( capture );
use English qw( -no_match_vars );
use File::Basename;
use File::Slurp qw( read_file );
use File::Temp qw( tempdir );
use FindBin;
use Readonly;
use Test::More;
use lib "$FindBin::Bin/../../lib/perl5";
use QtQA::Test::More qw( is_or_like );
# Matches backtrace text expected when a segfault occurs
Readonly my $SIGSEGV_BACKTRACE => qr{
\A
\QQtQA::App::TestRunner: Process exited due to signal 11; dumped core\E\n
# We can't feasibly code the expected backtrace, so we just test that...
# (1) the core plugin claims to be producing a backtrace (backtrace follows:)
QtQA::App::TestRunner: [^\n]+ backtrace[ ]follows:[^\n]+ \n
.*
# (2) some message which is usually generated by gdb is present in the output
\QQtQA::App::TestRunner: Thread 1\E
.*
\z
}xms;
# Testdata for core_pattern_to_glob_pattern
Readonly my @CORE_TO_GLOB_TESTDATA => (
# trivial and default
{
core_pattern => 'core',
core_uses_pid => 0,
glob_pattern => 'core',
},
# pid on the end
{
core_pattern => 'core',
core_uses_pid => 1,
glob_pattern => 'core.*',
},
# pid on the end via %p
{
core_pattern => 'core-%p',
core_uses_pid => 0,
glob_pattern => 'core-*',
},
# literal %
{
core_pattern => '/tmp/core%%foo/%e/bar',
core_uses_pid => 0,
glob_pattern => '/tmp/core%foo/*',
},
# core_uses_pid makes no difference past the variable portion
{
core_pattern => 'my-core-file:%t',
core_uses_pid => 1,
glob_pattern => 'my-core-file:*',
},
# "piping core" behaves like `core'
{
core_pattern => '|/usr/bin/some-cool-handler',
core_uses_pid => 0,
glob_pattern => 'core',
},
# "piping core" with pid behaves like `core.%p'
{
core_pattern => '|/usr/bin/some-cool-handler',
core_uses_pid => 1,
glob_pattern => 'core.*',
},
);
sub test_run
{
my ($params_ref) = @_;
my @args = @{$params_ref->{ args }};
my $expected_stdout = $params_ref->{ expected_stdout };
my $expected_stderr = $params_ref->{ expected_stderr };
my $expected_success = $params_ref->{ expected_success };
my $expected_logfile = $params_ref->{ expected_logfile };
my $expected_logtext = $params_ref->{ expected_logtext } // "";
my $testname = $params_ref->{ testname } // q{};
my $status;
my ($output, $error) = capture {
$status = system( 'perl', "$FindBin::Bin/../testrunner.pl", @args );
};
if ($expected_success) {
is ( $status, 0, "$testname exits zero" );
}
else {
isnt( $status, 0, "$testname exits non-zero" );
}
is_or_like( $output, $expected_stdout, "$testname output looks correct" );
is_or_like( $error, $expected_stderr, "$testname error looks correct" );
# The rest of the verification steps are only applicable if a log file is expected and created
return if (!$expected_logfile);
return if (!ok( -e $expected_logfile, "$testname created $expected_logfile" ));
my $logtext = read_file( $expected_logfile ); # dies on error
is_or_like( $logtext, $expected_logtext, "$testname logtext is as expected" );
return;
}
sub test_testrunner
{
# control; check that `--plugin core' can load OK
test_run({
testname => 'plugin loads OK 0 exitcode',
args => [ '--plugin', 'core', '--', 'true' ],
expected_success => 1,
expected_stdout => q{},
expected_stderr => q{},
});
# another control; check that it doesn't break non-zero exit code
test_run({
testname => 'plugin loads OK !0 exitcode',
args => [ '--plugin', 'core', '--', 'false' ],
expected_success => 0,
expected_stdout => q{},
expected_stderr => q{},
});
# check that a backtrace is generated if process crashes
test_run({
testname => 'simple backtrace',
args => [ '--plugin', 'core', '--', 'perl', '-e', 'kill 11, $$' ],
expected_success => 0,
expected_stdout => q{},
expected_stderr => $SIGSEGV_BACKTRACE,
});
# check that the backtrace is captured to log OK
my $tempdir = tempdir( basename($0).'.XXXXXX', TMPDIR => 1, CLEANUP => 1 );
test_run({
testname => 'backtrace to log',
args => [
'--capture-logs',
$tempdir,
'--plugin',
'core',
'--sync-output',
'--',
'perl',
'-e',
'kill 11, $$'
],
expected_success => 0,
expected_stdout => q{},
expected_stderr => q{},
expected_logfile => "$tempdir/perl-00.txt",
expected_logtext => $SIGSEGV_BACKTRACE,
});
# And again, with --tee
test_run({
testname => 'backtrace to log',
args => [
'--tee-logs',
$tempdir,
'--plugin',
'core',
'--',
'perl',
'-e',
'kill 11, $$'
],
expected_success => 0,
expected_stdout => q{},
expected_stderr => $SIGSEGV_BACKTRACE,
expected_logfile => "$tempdir/perl-01.txt",
expected_logtext => $SIGSEGV_BACKTRACE,
});
return;
}
sub test_core_pattern_to_glob_pattern
{
ok( do("$FindBin::Bin/../testrunner-plugins/core.pm"), "plugin loaded OK" );
my $plugin = QtQA::App::TestRunner::Plugin::core->new( );
foreach my $row (@CORE_TO_GLOB_TESTDATA) {
my $testname = "$row->{ core_pattern }, core_uses_pid $row->{ core_uses_pid }";
my $expected_glob_pattern = $row->{ glob_pattern };
my $actual_glob_pattern = $plugin->_core_pattern_to_glob_pattern(
$row->{ core_pattern },
$row->{ core_uses_pid },
);
is( $actual_glob_pattern, $expected_glob_pattern, "glob pattern for $testname" );
}
return;
}
sub run
{
if ($OSNAME !~ m{linux|darwin}i) {
plan 'skip_all', "test is not relevant on $OSNAME";
}
if ($OSNAME =~ m{darwin}i && ! -w '/cores') {
plan 'skip_all', 'you must make /cores directory writable to run this test on mac';
}
test_core_pattern_to_glob_pattern;
test_testrunner;
done_testing;
return;
}
run if (!caller);
1;