1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-07-17 01:12:23 +02:00

Added integration for testing coverage with Devel::Cover.

This commit is contained in:
David Steele
2017-01-09 20:49:04 -05:00
parent 39744a4f1d
commit 0e4f51c271
14 changed files with 188 additions and 25 deletions

1
.gitignore vendored
View File

@ -6,5 +6,6 @@ test/.vagrant
test/package
test/nytprof.out
test/nytprof/*
test/coverage*
doc/output/*
doc/doc/output/*

View File

@ -254,9 +254,9 @@ local $EVAL_ERROR = undef; eval
lockRelease();
exitSafe(0);
# It shouldn't be possible to get here
# uncoverable statement - exit should happen above
&log(ASSERT, 'execution reached invalid location in ' . __FILE__ . ', line ' . __LINE__);
exit ERROR_ASSERT;
exit ERROR_ASSERT; # uncoverable statement
}
####################################################################################################################################
@ -267,6 +267,6 @@ or do
exitSafe(undef, $EVAL_ERROR);
};
# It shouldn't be possible to get here
# uncoverable statement - errors should be handled in the do block above
&log(ASSERT, 'execution reached invalid location in ' . __FILE__ . ', line ' . __LINE__);
exit ERROR_ASSERT;
exit ERROR_ASSERT; # uncoverable statement

View File

@ -222,6 +222,10 @@
<release-test-list>
<release-feature-list>
<release-item>
<p>Added integration for testing coverage with <code>Devel::Cover</code>.</p>
</release-item>
<release-item>
<p>Added unit tests for low-level functions in the <code>File</code> and <code>BackupCommon</code> modules.</p>
</release-item>

View File

@ -66,6 +66,7 @@ sub exitSafe
{
lockRelease(false);
}
# uncoverable branch false - this eval exists only to suppress lock errors so original error will not be lost
or do {};
# If exit code is not defined then try to get it from the exception

View File

@ -708,7 +708,7 @@ sub remoteTypeTest
my $self = shift;
my $strRemoteType = shift;
return $self->{strRemoteType} eq $strRemoteType ? true : false;
return $self->remoteType() eq $strRemoteType ? true : false;
}
####################################################################################################################################

2
test/Vagrantfile vendored
View File

@ -35,7 +35,7 @@ Vagrant.configure(2) do |config|
# Install Perl modules
echo 'Install Perl Modules' && date
apt-get install -y libdbd-pg-perl libxml-checker-perl libperl-critic-perl
apt-get install -y libdbd-pg-perl libxml-checker-perl libperl-critic-perl libdevel-nytprof-perl libdevel-cover-perl
# Install utilities
echo 'Install Utilities' && date

View File

@ -286,7 +286,8 @@ sub perlInstall
elsif ($strOS eq VM_U16 || $strOS eq VM_D8)
{
return $strImage .
"RUN apt-get install -y libdbd-pg-perl libdbi-perl";
"RUN apt-get install -y libdbd-pg-perl libdbi-perl" .
($strOS eq VM_U16 ? ' libdevel-cover-perl' : '');
}
confess &log(ERROR, "unable to install perl for os '${strOS}'");
@ -413,7 +414,9 @@ sub containerBuild
# Create test user
$strScript .=
"\n\n# Create test user\n" .
userCreate($strOS, TEST_USER, TEST_USER_ID, TEST_GROUP);
userCreate($strOS, TEST_USER, TEST_USER_ID, TEST_GROUP) . "\n" .
'RUN mkdir -m 750 /home/' . TEST_USER . "/test\n" .
'RUN chown ' . TEST_USER . ':' . TEST_GROUP . ' /home/' . TEST_USER . '/test';
# Suppress dpkg interactive output
if ($$oVm{$strOS}{&VM_OS_BASE} eq VM_OS_BASE_DEBIAN)

View File

@ -27,6 +27,10 @@ use constant TESTDEF_EXPECT => 'expect';
push @EXPORT, qw(TESTDEF_EXPECT);
use constant TESTDEF_TEST => 'test';
push @EXPORT, qw(TESTDEF_TEST);
use constant TESTDEF_TEST_ALL => 'all';
push @EXPORT, qw(TESTDEF_TEST_ALL);
use constant TESTDEF_TEST_COVERAGE => 'coverage';
push @EXPORT, qw(TESTDEF_TEST_COVERAGE);
use constant TESTDEF_TEST_INDIVIDUAL => 'individual';
push @EXPORT, qw(TESTDEF_TEST_INDIVIDUAL);
use constant TESTDEF_TEST_NAME => 'name';
@ -40,6 +44,16 @@ use constant TESTDEF_TEST_PROCESS => 'process'
use constant TESTDEF_TEST_DB => 'db';
push @EXPORT, qw(TESTDEF_TEST_DB);
use constant TESTDEF_COVERAGE_FULL => true;
push @EXPORT, qw(TESTDEF_COVERAGE_FULL);
use constant TESTDEF_COVERAGE_PARTIAL => false;
push @EXPORT, qw(TESTDEF_COVERAGE_PARTIAL);
use constant TESTDEF_MODULE_FILE => 'File';
push @EXPORT, qw(TESTDEF_MODULE_FILE);
use constant TESTDEF_MODULE_FILE_COMMON => TESTDEF_MODULE_FILE . 'Common';
push @EXPORT, qw(TESTDEF_MODULE_FILE_COMMON);
################################################################################################################################
# Define tests
################################################################################################################################
@ -86,6 +100,12 @@ my $oTestDef =
&TESTDEF_MODULE_NAME => 'file',
&TESTDEF_TEST_CONTAINER => true,
&TESTDEF_TEST_COVERAGE =>
{
&TESTDEF_MODULE_FILE => TESTDEF_COVERAGE_FULL,
&TESTDEF_MODULE_FILE_COMMON => TESTDEF_COVERAGE_FULL,
},
&TESTDEF_TEST =>
[
{

View File

@ -66,7 +66,8 @@ sub new
my $self = $class->SUPER::new(
$strName, $strContainer, $$oParam{strImage}, $$oParam{strUser}, testRunGet()->vm(),
["${strProjectPath}:${strProjectPath}", "${strTestPath}:${strTestPath}"]);
["${strProjectPath}:${strProjectPath}", "${strTestPath}:${strTestPath}"
,dirname(dirname($strTestPath)) . '/cover_db:' . dirname(dirname($strTestPath)) . '/cover_db']);
bless $self, $class;
# Set test path

View File

@ -51,6 +51,7 @@ sub testListGet
my $iyModuleTestRun = shift;
my $strDbVersion = shift;
my $iProcessMax = shift;
my $bCoverage = shift;
my $oTestDef = testDefGet();
my $oyVm = vmGet();
@ -122,6 +123,13 @@ sub testListGet
$bTestIndividual && @{$iyModuleTestRun} != 0 &&
!grep(/^$iTestRunIdx$/i, @{$iyModuleTestRun}));
# Skip this run if coverage is requested and this test does not provide coverage
next if (
$bCoverage &&
(($bTestIndividual && !defined($oTest->{&TESTDEF_TEST_COVERAGE}{$iTestRunIdx})) ||
(!$bTestIndividual && !defined($oTest->{&TESTDEF_TEST_COVERAGE}{&TESTDEF_TEST_ALL}))) &&
!defined($oModule->{&TESTDEF_TEST_COVERAGE}));
my $iyProcessMax = [defined($iProcessMax) ? $iProcessMax : 1];
if (defined($$oTest{&TESTDEF_TEST_PROCESS}) && $$oTest{&TESTDEF_TEST_PROCESS} &&

View File

@ -13,6 +13,7 @@ use English '-no_match_vars';
use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(dirname);
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
@ -26,7 +27,6 @@ use pgBackRestTest::Common::DefineTest;
use constant BOGUS => 'bogus';
push @EXPORT, qw(BOGUS);
####################################################################################################################################
# The current test run that is executung. Only a single run should ever occur in a process to prevent various cleanup issues from
# affecting the next run. Of course multiple subtests can be executed in a single run.
@ -86,7 +86,7 @@ sub process
$self->{iVmId},
$self->{strBasePath},
$self->{strTestPath},
$self->{strBackRestExe},
$self->{strBackRestExeOriginal},
$self->{strPgBinPath},
$self->{strPgVersion},
$self->{strModule},
@ -97,6 +97,7 @@ sub process
$self->{bDryRun},
$self->{bCleanup},
$self->{bLogForce},
$self->{bCoverage},
$self->{strPgUser},
$self->{strBackRestUser},
$self->{strGroup},
@ -108,7 +109,7 @@ sub process
{name => 'iVmId'},
{name => 'strBasePath'},
{name => 'strTestPath'},
{name => 'strBackRestExe'},
{name => 'strBackRestExeOriginal'},
{name => 'strPgBinPath', required => false},
{name => 'strPgVersion', required => false},
{name => 'strModule'},
@ -119,6 +120,7 @@ sub process
{name => 'bDryRun'},
{name => 'bCleanup'},
{name => 'bLogForce'},
{name => 'bCoverage'},
{name => 'strPgUser'},
{name => 'strBackRestUser'},
{name => 'strGroup'},
@ -206,16 +208,27 @@ sub begin
return false;
}
# If the module is defined then create a ExpectTest object
if ($self->doExpect())
my $strExe = $self->backrestExeOriginal();
# If coverage is requested then prepend the coverage code
if ($self->coverage())
{
$strExe = testRunExe(
$strExe, dirname($self->testPath()), $self->basePath(), $self->module(), $self->moduleTest(), $self->runCurrent(),
true);
}
# Else if the module is defined then create a ExpectTest object
elsif ($self->doExpect())
{
$self->{oExpect} = new pgBackRestTest::Common::LogTest(
$self->module(), $self->moduleTest(), $self->runCurrent(), $self->doLogForce(), $strDescription, $self->backrestExe(),
$self->module(), $self->moduleTest(), $self->runCurrent(), $self->doLogForce(), $strDescription, $strExe,
$self->pgBinPath(), $self->testPath());
&log(INFO, ' expect log: ' . $self->{oExpect}->{strFileName});
}
$self->{strBackRestExe} = $strExe;
return true;
}
@ -365,12 +378,80 @@ sub testRunGet
push @EXPORT, qw(testRunGet);
####################################################################################################################################
# testExe
####################################################################################################################################
sub testRunExe
{
my $strExe = shift;
my $strCoveragePath = shift;
my $strBackRestBasePath = shift;
my $strModule = shift;
my $strTest = shift;
my $iRun = shift;
my $bLog = shift;
# Limit Perl modules tested to what is defined in the test coverage (if it exists)
my $strPerlModule;
my $strPerlModuleLog;
my $hTestCoverage;
my $hTestDef = testDefGet();
foreach my $hTestModule (@{$hTestDef->{&TESTDEF_MODULE}})
{
if ($hTestModule->{&TESTDEF_MODULE_NAME} eq $strModule)
{
$hTestCoverage = $hTestModule->{&TESTDEF_TEST_COVERAGE};
foreach my $hTest (@{$hTestModule->{&TESTDEF_TEST}})
{
if (defined($strTest) && $hTest->{&TESTDEF_TEST_NAME} eq $strTest)
{
$hTestCoverage =
defined($hTest->{&TESTDEF_TEST_COVERAGE}{$iRun}) ? $hTest->{&TESTDEF_TEST_COVERAGE}{$iRun}: $hTestCoverage;
}
}
}
}
if (defined($hTestCoverage))
{
foreach my $strCoverageModule (sort(keys(%{$hTestCoverage})))
{
$strPerlModule .= ',.*/' . $strCoverageModule . '\.p.$';
$strPerlModuleLog .= (defined($strPerlModuleLog) ? ', ' : '') . $strCoverageModule;
}
}
# Build the exe
if (defined($strPerlModule))
{
$strExe =
'perl -MDevel::Cover=-silent,1,-dir,' . $strCoveragePath . ',-subs_only,1' .
",-select${strPerlModule},+inc," . $strBackRestBasePath .
',-coverage,statement,branch,condition,path,subroutine' . " ${strExe}";
if (defined($bLog) && $bLog)
{
&log(INFO, " coverage: ${strPerlModuleLog}");
}
}
return $strExe;
}
push(@EXPORT, qw(testRunExe));
####################################################################################################################################
####################################################################################################################################
# Getters
####################################################################################################################################
sub backrestExe {return shift->{strBackRestExe}}
sub backrestExeOriginal {return shift->{strBackRestExeOriginal}}
sub backrestUser {return shift->{strBackRestUser}}
sub basePath {return shift->{strBasePath}}
sub coverage {return shift->{bCoverage}}
sub dataPath {return shift->basePath() . '/test/data'}
sub doCleanup {return shift->{bCleanup}}
sub doExpect {return shift->{bExpect}}

View File

@ -61,6 +61,9 @@ use constant VM_OS_UBUNTU => 'ubuntu';
####################################################################################################################################
# Valid VM list
####################################################################################################################################
use constant VM_ALL => 'all';
push @EXPORT, qw(VM_ALL);
use constant VM_CO6 => 'co6';
push @EXPORT, qw(VM_CO6);
use constant VM_CO7 => 'co7';

View File

@ -47,7 +47,7 @@ sub init
$self->{oRemote} = new pgBackRest::Protocol::RemoteMaster(
BACKUP,
OPTION_DEFAULT_CMD_SSH,
$self->backrestExe() . ' --stanza=' . $self->stanza() .
$self->backrestExeOriginal() . ' --stanza=' . $self->stanza() .
" --type=backup --repo-path=${strRepoPath} --no-config --command=test remote",
262144,
OPTION_DEFAULT_COMPRESS_LEVEL,

View File

@ -65,8 +65,9 @@ test.pl [options]
--no-cleanup don't cleaup after the last test is complete - useful for debugging
--db-version version of postgres to test (all, defaults to minimal)
--log-force force overwrite of current test log files
--no-lint Disable static source code analysis
--libc-only Compile the C library and run tests only
--no-lint disable static source code analysis
--libc-only compile the C library and run tests only
--coverage perform coverage analysis
Configuration Options:
--psql-bin path to the psql executables (e.g. /usr/lib/postgresql/9.3/bin/)
@ -106,11 +107,12 @@ my $bHelp = false;
my $bQuiet = false;
my $strDbVersion = 'minimal';
my $bLogForce = false;
my $strVm = 'all';
my $strVm = VM_ALL;
my $bVmBuild = false;
my $bVmForce = false;
my $bNoLint = false;
my $bLibCOnly = false;
my $bCoverage = false;
GetOptions ('q|quiet' => \$bQuiet,
'version' => \$bVersion,
@ -133,7 +135,8 @@ GetOptions ('q|quiet' => \$bQuiet,
'db-version=s' => \$strDbVersion,
'log-force' => \$bLogForce,
'no-lint' => \$bNoLint,
'libc-only' => \$bLibCOnly)
'libc-only' => \$bLibCOnly,
'coverage' => \$bCoverage)
or pod2usage(2);
####################################################################################################################################
@ -197,6 +200,20 @@ eval
$strTestPath = cwd() . '/test';
}
# Coverage can only be run with u16 containers due to version compatibility issues
if ($bCoverage)
{
if ($strVm eq VM_ALL)
{
&log(INFO, 'Set --vm=' . VM_U16 . ' for coverage testing');
$strVm = VM_U16;
}
elsif ($strVm ne VM_U16)
{
confess &log(ERROR, 'only --vm=' . VM_U16 . ' can be used for coverage testing');
}
}
# Get the base backrest path
my $strBackRestBase = dirname(dirname(abs_path($0)));
@ -310,7 +327,8 @@ eval
# Determine which tests to run
#-----------------------------------------------------------------------------------------------------------------------
my $oyTestRun = testListGet($strVm, \@stryModule, \@stryModuleTest, \@iyModuleTestRun, $strDbVersion, $iProcessMax);
my $oyTestRun = testListGet(
$strVm, \@stryModule, \@stryModuleTest, \@iyModuleTestRun, $strDbVersion, $iProcessMax, $bCoverage);
if (@{$oyTestRun} == 0)
{
@ -328,6 +346,7 @@ eval
#-----------------------------------------------------------------------------------------------------------------------
my $iTestFail = 0;
my $oyProcess = [];
my $strCoveragePath = "${strTestPath}/cover_db";
if (!$bDryRun || $bVmOut)
{
@ -339,7 +358,7 @@ eval
}
executeTest("sudo rm -rf ${strTestPath}/*");
filePathCreate($strTestPath, undef, true);
filePathCreate($strCoveragePath, '0770', true, true);
}
# Build the C Library in container
@ -347,7 +366,7 @@ eval
if (!$bDryRun)
{
my $bLogDetail = $strLogLevel eq 'detail';
my @stryBuildVm = $strVm eq 'all' ? (VM_CO6, VM_U16, VM_D8, VM_CO7, VM_U14, VM_U12) : ($strVm);
my @stryBuildVm = $strVm eq VM_ALL ? (VM_CO6, VM_U16, VM_D8, VM_CO7, VM_U14, VM_U12) : ($strVm);
foreach my $strBuildVM (sort(@stryBuildVm))
{
@ -490,8 +509,10 @@ eval
{
executeTest(
'docker run -itd -h ' . $$oTest{&TEST_VM} . "-test --name=${strImage}" .
" -v ${strCoveragePath}:${strCoveragePath} " .
" -v ${strHostTestPath}:${strVmTestPath}" .
" -v ${strBackRestBase}:${strBackRestBase} " . containerNamespace() . '/' . $$oTest{&TEST_VM} .
" -v ${strBackRestBase}:${strBackRestBase} " .
containerNamespace() . '/' . $$oTest{&TEST_VM} .
"-loop-test-pre");
}
}
@ -506,7 +527,11 @@ eval
# Create command
my $strCommand =
($$oTest{&TEST_CONTAINER} ? 'docker exec -i -u ' . TEST_USER . " ${strImage} " : '') . abs_path($0) .
($$oTest{&TEST_CONTAINER} ? 'docker exec -i -u ' . TEST_USER . " ${strImage} " : '') .
($bCoverage ? testRunExe(
abs_path($0), dirname($strCoveragePath), $strBackRestBase, $$oTest{&TEST_MODULE},
$$oTest{&TEST_NAME}, defined($$oTest{&TEST_RUN}) ? $$oTest{&TEST_RUN} : 'all') :
abs_path($0)) .
" --test-path=${strVmTestPath}" .
" --vm=$$oTest{&TEST_VM}" .
" --vm-id=${iVmIdx}" .
@ -517,6 +542,7 @@ eval
(defined($$oTest{&TEST_PROCESS}) ? ' --process-max=' . $$oTest{&TEST_PROCESS} : '') .
($strLogLevel ne lc(INFO) ? " --log-level=${strLogLevel}" : '') .
' --pgsql-bin=' . $$oTest{&TEST_PGSQL_BIN} .
($bCoverage ? ' --coverage' : '') .
($bLogForce ? ' --log-force' : '') .
($bDryRun ? ' --dry-run' : '') .
($bVmOut ? ' --vm-out' : '') .
@ -559,6 +585,20 @@ eval
}
while ($iVmTotal > 0);
# Write out coverage info
#-----------------------------------------------------------------------------------------------------------------------
if ($bCoverage)
{
&log(INFO, 'Writing coverage report');
executeTest("rm -rf ${strBackRestBase}/test/coverage");
executeTest("cp -rp ${strCoveragePath} ${strCoveragePath}_temp");
executeTest("cover -report json -outputdir ${strBackRestBase}/test/coverage ${strCoveragePath}_temp");
executeTest("rm -rf ${strCoveragePath}_temp");
executeTest("cp -rp ${strCoveragePath} ${strCoveragePath}_temp");
executeTest("cover -outputdir ${strBackRestBase}/test/coverage ${strCoveragePath}_temp");
executeTest("rm -rf ${strCoveragePath}_temp");
}
# Print test info and exit
#-----------------------------------------------------------------------------------------------------------------------
if ($bDryRun)
@ -592,6 +632,7 @@ eval
$strDbVersion ne 'minimal' ? $strDbVersion: undef, # Db version
$stryModule[0], $stryModuleTest[0], \@iyModuleTestRun, # Module info
$iProcessMax, $bVmOut, $bDryRun, $bNoCleanup, $bLogForce, # Test options
$bCoverage, # Test options
TEST_USER, BACKREST_USER, TEST_GROUP); # User/group info
if (!$bNoCleanup)