Revision control
++ #!/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;
++