mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2025-03-03 14:52:21 +02:00
Add C unit test infrastructure.
This commit is contained in:
parent
08d6f14603
commit
10dfbd90b5
@ -32,6 +32,12 @@
|
||||
</release-core-list>
|
||||
|
||||
<release-test-list>
|
||||
<release-feature-list>
|
||||
<release-item>
|
||||
<p>Add C unit test infrastructure.</p>
|
||||
</release-item>
|
||||
</release-feature-list>
|
||||
|
||||
<release-refactor-list>
|
||||
<release-item>
|
||||
<p>Warnings in C builds treated as errors.</p>
|
||||
|
@ -312,12 +312,12 @@ use ExtUtils::MakeMaker;
|
||||
# Create C files array
|
||||
my @stryCFile = qw(LibC.c);
|
||||
|
||||
foreach my $strFile (sort(keys(%{$oStorage->manifest('')})))
|
||||
foreach my $strFile (sort(keys(%{$oStorage->manifest('src')})))
|
||||
{
|
||||
# Skip all files except .c files (including .auto.c)
|
||||
next if $strFile !~ /(?<!\.auto)\.c$/;
|
||||
|
||||
push(@stryCFile, "../${strFile}");
|
||||
push(@stryCFile, "../src/${strFile}");
|
||||
}
|
||||
|
||||
WriteMakefile
|
||||
|
@ -1,7 +1,7 @@
|
||||
####################################################################################################################################
|
||||
# Sanity Tests for C Library
|
||||
#
|
||||
# Basic test to ensure the C library loads and has been compiled correctly.
|
||||
# Test to ensure the C library loads and is compiled correctly. Unit and integration tests are performed by test/test.pl.
|
||||
####################################################################################################################################
|
||||
use strict;
|
||||
use warnings;
|
||||
|
@ -339,7 +339,7 @@ sub containerBuild
|
||||
{
|
||||
$strScript .=
|
||||
" apt-get update && \\\n" .
|
||||
" apt-get -y install openssh-server wget sudo python-pip git \\\n" .
|
||||
" apt-get -y install openssh-server wget sudo python-pip build-essential git \\\n" .
|
||||
" libdbd-pg-perl libhtml-parser-perl libio-socket-ssl-perl libxml-libxml-perl";
|
||||
|
||||
if ($strOS eq VM_U14)
|
||||
|
@ -40,6 +40,12 @@ use constant TESTDEF_COVERAGE => 'coverage
|
||||
# Should expect log tests be run
|
||||
use constant TESTDEF_EXPECT => 'expect';
|
||||
push @EXPORT, qw(TESTDEF_EXPECT);
|
||||
# Is this a C test (instead of Perl)?
|
||||
use constant TESTDEF_C => 'c';
|
||||
push @EXPORT, qw(TESTDEF_C);
|
||||
# Is the C library required? This only applies to unit tests, the C library is always supplied for integration tests.
|
||||
use constant TESTDEF_CLIB => 'clib';
|
||||
push @EXPORT, qw(TESTDEF_CLIB);
|
||||
# Determines if each run in a test will be run in a new container
|
||||
use constant TESTDEF_INDIVIDUAL => 'individual';
|
||||
push @EXPORT, qw(TESTDEF_INDIVIDUAL);
|
||||
@ -51,11 +57,14 @@ use constant TESTDEF_VM => 'vm';
|
||||
push @EXPORT, qw(TESTDEF_VM);
|
||||
|
||||
# The test provides full coverage for the module
|
||||
use constant TESTDEF_COVERAGE_FULL => true;
|
||||
use constant TESTDEF_COVERAGE_FULL => 'full';
|
||||
push @EXPORT, qw(TESTDEF_COVERAGE_FULL);
|
||||
# The test provides partial coverage for the module
|
||||
use constant TESTDEF_COVERAGE_PARTIAL => false;
|
||||
use constant TESTDEF_COVERAGE_PARTIAL => 'partial';
|
||||
push @EXPORT, qw(TESTDEF_COVERAGE_PARTIAL);
|
||||
# The module does not have any code so does not have any coverage. An error will be thrown if the module has code in the future.
|
||||
use constant TESTDEF_COVERAGE_NOCODE => 'nocode';
|
||||
push @EXPORT, qw(TESTDEF_COVERAGE_NOCODE);
|
||||
|
||||
################################################################################################################################
|
||||
# Code modules
|
||||
@ -88,6 +97,16 @@ my $oTestDef =
|
||||
|
||||
&TESTDEF_TEST =>
|
||||
[
|
||||
{
|
||||
&TESTDEF_NAME => 'type',
|
||||
&TESTDEF_TOTAL => 2,
|
||||
&TESTDEF_C => true,
|
||||
|
||||
&TESTDEF_COVERAGE =>
|
||||
{
|
||||
'common/type' => TESTDEF_COVERAGE_NOCODE,
|
||||
},
|
||||
},
|
||||
{
|
||||
&TESTDEF_NAME => 'http-client',
|
||||
&TESTDEF_TOTAL => 2,
|
||||
@ -252,6 +271,7 @@ my $oTestDef =
|
||||
{
|
||||
&TESTDEF_NAME => 'local',
|
||||
&TESTDEF_TOTAL => 9,
|
||||
&TESTDEF_CLIB => true,
|
||||
|
||||
&TESTDEF_COVERAGE =>
|
||||
{
|
||||
@ -494,7 +514,7 @@ foreach my $hModule (@{$oTestDef->{&TESTDEF_MODULE}})
|
||||
|
||||
# Resolve variables that can be set in the module or the test
|
||||
foreach my $strVar (
|
||||
TESTDEF_CONTAINER, TESTDEF_EXPECT, TESTDEF_DB, TESTDEF_INDIVIDUAL, TESTDEF_VM)
|
||||
TESTDEF_C, TESTDEF_CLIB, TESTDEF_CONTAINER, TESTDEF_EXPECT, TESTDEF_DB, TESTDEF_INDIVIDUAL, TESTDEF_VM)
|
||||
{
|
||||
$hTestDefHash->{$strModule}{$strTest}{$strVar} = coalesce(
|
||||
$hModuleTest->{$strVar}, $hModule->{$strVar}, $strVar eq TESTDEF_VM ? undef : false);
|
||||
@ -503,6 +523,14 @@ foreach my $hModule (@{$oTestDef->{&TESTDEF_MODULE}})
|
||||
# Set test count
|
||||
$hTestDefHash->{$strModule}{$strTest}{&TESTDEF_TOTAL} = $hModuleTest->{&TESTDEF_TOTAL};
|
||||
|
||||
# If this is a C test then add the test module to coverage
|
||||
if ($hModuleTest->{&TESTDEF_C})
|
||||
{
|
||||
my $strTestFile = "module/${strModule}/${strTest}Test";
|
||||
|
||||
$hModuleTest->{&TESTDEF_COVERAGE}{$strTestFile} = TESTDEF_COVERAGE_FULL;
|
||||
}
|
||||
|
||||
# Concatenate coverage for modules and tests
|
||||
foreach my $hCoverage ($hModule->{&TESTDEF_COVERAGE}, $hModuleTest->{&TESTDEF_COVERAGE})
|
||||
{
|
||||
@ -521,9 +549,9 @@ foreach my $hModule (@{$oTestDef->{&TESTDEF_MODULE}})
|
||||
{
|
||||
$hCoverageType->{$strCodeModule} = $hCoverage->{$strCodeModule};
|
||||
}
|
||||
elsif ($hCoverageType->{$strCodeModule} != $hCoverage->{$strCodeModule})
|
||||
elsif ($hCoverageType->{$strCodeModule} ne $hCoverage->{$strCodeModule})
|
||||
{
|
||||
confess &log(ASSERT, "cannot mix full/partial coverage for ${strCodeModule}");
|
||||
confess &log(ASSERT, "cannot mix coverage types for ${strCodeModule}");
|
||||
}
|
||||
|
||||
# Add to coverage list
|
||||
|
@ -14,7 +14,7 @@ use English '-no_match_vars';
|
||||
use Cwd qw(abs_path);
|
||||
use Exporter qw(import);
|
||||
our @EXPORT = qw();
|
||||
use File::Basename qw(dirname);
|
||||
use File::Basename qw(dirname basename);
|
||||
use POSIX qw(ceil);
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
||||
@ -24,6 +24,7 @@ use pgBackRest::Common::Log;
|
||||
use pgBackRest::Common::String;
|
||||
|
||||
use pgBackRestTest::Common::ContainerTest;
|
||||
use pgBackRestTest::Common::DefineTest;
|
||||
use pgBackRestTest::Common::ExecuteTest;
|
||||
use pgBackRestTest::Common::ListTest;
|
||||
use pgBackRestTest::Common::RunTest;
|
||||
@ -84,6 +85,9 @@ sub new
|
||||
# Set try to 0
|
||||
$self->{iTry} = 0;
|
||||
|
||||
# Setup the path where gcc coverage will be performed
|
||||
$self->{strGCovPath} = "$self->{strTestPath}/gcov-$self->{iVmIdx}";
|
||||
|
||||
# Return from function and log return values if any
|
||||
return logDebugReturn
|
||||
(
|
||||
@ -102,6 +106,9 @@ sub run
|
||||
# Assign function parameters, defaults, and log debug info
|
||||
(my $strOperation) = logDebugParam (__PACKAGE__ . '->run', \@_,);
|
||||
|
||||
# Start the test timer
|
||||
my $fTestStartTime = gettimeofday();
|
||||
|
||||
# Was the job run?
|
||||
my $bRun = false;
|
||||
|
||||
@ -115,14 +122,15 @@ sub run
|
||||
$self->{strLogLevel} = lc(DEBUG);
|
||||
}
|
||||
|
||||
my $strTest = sprintf('P%0' . length($self->{iVmMax}) . 'd-T%0' . length($self->{iTestMax}) . 'd/%0' .
|
||||
length($self->{iTestMax}) . "d - ", $self->{iVmIdx} + 1, $self->{iTestIdx} + 1, $self->{iTestMax}) .
|
||||
'vm=' . $self->{oTest}->{&TEST_VM} .
|
||||
', module=' . $self->{oTest}->{&TEST_MODULE} .
|
||||
', test=' . $self->{oTest}->{&TEST_NAME} .
|
||||
(defined($self->{oTest}->{&TEST_RUN}) ? ', run=' . join(',', @{$self->{oTest}->{&TEST_RUN}}) : '') .
|
||||
(defined($self->{oTest}->{&TEST_DB}) ? ', db=' . $self->{oTest}->{&TEST_DB} : '') .
|
||||
($self->{iTry} > 1 ? ' (retry ' . ($self->{iTry} - 1) . ')' : '');
|
||||
my $strTest = sprintf(
|
||||
'P%0' . length($self->{iVmMax}) . 'd-T%0' . length($self->{iTestMax}) . 'd/%0' .
|
||||
length($self->{iTestMax}) . "d - ", $self->{iVmIdx} + 1, $self->{iTestIdx} + 1, $self->{iTestMax}) .
|
||||
'vm=' . $self->{oTest}->{&TEST_VM} .
|
||||
', module=' . $self->{oTest}->{&TEST_MODULE} .
|
||||
', test=' . $self->{oTest}->{&TEST_NAME} .
|
||||
(defined($self->{oTest}->{&TEST_RUN}) ? ', run=' . join(',', sort(@{$self->{oTest}->{&TEST_RUN}})) : '') .
|
||||
(defined($self->{oTest}->{&TEST_DB}) ? ', db=' . $self->{oTest}->{&TEST_DB} : '') .
|
||||
($self->{iTry} > 1 ? ' (retry ' . ($self->{iTry} - 1) . ')' : '');
|
||||
|
||||
my $strImage = 'test-' . $self->{iVmIdx};
|
||||
my $strDbVersion = (defined($self->{oTest}->{&TEST_DB}) ? $self->{oTest}->{&TEST_DB} : PG_VERSION_94);
|
||||
@ -141,31 +149,38 @@ sub run
|
||||
# Create host test directory
|
||||
$self->{oStorageTest}->pathCreate($strHostTestPath, {strMode => '0770'});
|
||||
|
||||
# Create gcov directory
|
||||
$self->{oStorageTest}->pathCreate($self->{strGCovPath}, {strMode => '0770'});
|
||||
|
||||
if ($self->{oTest}->{&TEST_CONTAINER})
|
||||
{
|
||||
executeTest(
|
||||
'docker run -itd -h ' . $self->{oTest}->{&TEST_VM} . "-test --name=${strImage}" .
|
||||
" -v $self->{strCoveragePath}:$self->{strCoveragePath} " .
|
||||
" -v ${strHostTestPath}:${strVmTestPath}" .
|
||||
" -v $self->{strGCovPath}:$self->{strGCovPath}" .
|
||||
" -v $self->{strBackRestBase}:$self->{strBackRestBase} " .
|
||||
containerRepo() . ':' . $self->{oTest}->{&TEST_VM} .
|
||||
"-test",
|
||||
{bSuppressStdErr => true});
|
||||
|
||||
# Install Perl C Library
|
||||
# my $oVm = vmGet();
|
||||
# my $strOS = $self->{oTest}->{&TEST_VM};
|
||||
# my $strBuildPath = $self->{strBackRestBase} . "/test/.vagrant/libc/$strOS/libc/";
|
||||
# my $strPerlAutoPath = $$oVm{$strOS}{&VMDEF_PERL_ARCH_PATH} . '/auto/pgBackRest/LibC';
|
||||
# my $strPerlModulePath = $$oVm{$strOS}{&VMDEF_PERL_ARCH_PATH} . '/pgBackRest';
|
||||
#
|
||||
# executeTest(
|
||||
# "docker exec -i -u root ${strImage} bash -c '" .
|
||||
# "mkdir -p -m 755 ${strPerlAutoPath} && " .
|
||||
# "cp ${strBuildPath}/blib/arch/auto/pgBackRest/LibC/LibC.so ${strPerlAutoPath} && " .
|
||||
# "cp ${strBuildPath}/blib/lib/auto/pgBackRest/LibC/autosplit.ix ${strPerlAutoPath} && " .
|
||||
# "mkdir -p -m 755 ${strPerlModulePath} && " .
|
||||
# "cp ${strBuildPath}/blib/lib/pgBackRest/LibC.pm ${strPerlModulePath}'");
|
||||
if ($self->{oTest}->{&TEST_CLIB})
|
||||
{
|
||||
my $oVm = vmGet();
|
||||
my $strOS = $self->{oTest}->{&TEST_VM};
|
||||
my $strBuildPath = $self->{strBackRestBase} . "/test/.vagrant/libc/$strOS/libc/";
|
||||
my $strPerlAutoPath = $$oVm{$strOS}{&VMDEF_PERL_ARCH_PATH} . '/auto/pgBackRest/LibC';
|
||||
my $strPerlModulePath = $$oVm{$strOS}{&VMDEF_PERL_ARCH_PATH} . '/pgBackRest';
|
||||
|
||||
executeTest(
|
||||
"docker exec -i -u root ${strImage} bash -c '" .
|
||||
"mkdir -p -m 755 ${strPerlAutoPath} && " .
|
||||
"cp ${strBuildPath}/blib/arch/auto/pgBackRest/LibC/LibC.so ${strPerlAutoPath} && " .
|
||||
"cp ${strBuildPath}/blib/lib/auto/pgBackRest/LibC/autosplit.ix ${strPerlAutoPath} && " .
|
||||
"mkdir -p -m 755 ${strPerlModulePath} && " .
|
||||
"cp ${strBuildPath}/blib/lib/pgBackRest/LibC.pm ${strPerlModulePath}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,32 +193,39 @@ sub run
|
||||
}
|
||||
|
||||
# Create command
|
||||
my $strCommand =
|
||||
($self->{oTest}->{&TEST_CONTAINER} ? 'docker exec -i -u ' . TEST_USER . " ${strImage} " : '') .
|
||||
(vmCoverage($self->{oTest}->{&TEST_VM}) ? testRunExe(
|
||||
abs_path($0), dirname($self->{strCoveragePath}), $self->{strBackRestBase}, $self->{oTest}->{&TEST_MODULE},
|
||||
$self->{oTest}->{&TEST_NAME}, defined($self->{oTest}->{&TEST_RUN}) ? $self->{oTest}->{&TEST_RUN} : 'all') :
|
||||
abs_path($0)) .
|
||||
" --test-path=${strVmTestPath}" .
|
||||
" --vm=$self->{oTest}->{&TEST_VM}" .
|
||||
" --vm-id=$self->{iVmIdx}" .
|
||||
" --module=" . $self->{oTest}->{&TEST_MODULE} .
|
||||
' --test=' . $self->{oTest}->{&TEST_NAME} .
|
||||
$strCommandRunParam .
|
||||
(defined($self->{oTest}->{&TEST_DB}) ? ' --db-version=' . $self->{oTest}->{&TEST_DB} : '') .
|
||||
($self->{strLogLevel} ne lc(INFO) ? " --log-level=$self->{strLogLevel}" : '') .
|
||||
' --pgsql-bin=' . $self->{oTest}->{&TEST_PGSQL_BIN} .
|
||||
($self->{bLogForce} ? ' --log-force' : '') .
|
||||
($self->{bDryRun} ? ' --dry-run' : '') .
|
||||
($self->{bDryRun} ? ' --vm-out' : '') .
|
||||
($self->{bNoCleanup} ? " --no-cleanup" : '');
|
||||
my $strCommand;
|
||||
|
||||
if ($self->{oTest}->{&TEST_C})
|
||||
{
|
||||
$strCommand = 'docker exec -i -u ' . TEST_USER . " ${strImage} $self->{strGCovPath}/test";
|
||||
}
|
||||
else
|
||||
{
|
||||
$strCommand =
|
||||
($self->{oTest}->{&TEST_CONTAINER} ? 'docker exec -i -u ' . TEST_USER . " ${strImage} " : '') .
|
||||
(vmCoverage($self->{oTest}->{&TEST_VM}) ? testRunExe(
|
||||
abs_path($0), dirname($self->{strCoveragePath}), $self->{strBackRestBase}, $self->{oTest}->{&TEST_MODULE},
|
||||
$self->{oTest}->{&TEST_NAME}, defined($self->{oTest}->{&TEST_RUN}) ? $self->{oTest}->{&TEST_RUN} : 'all') :
|
||||
abs_path($0)) .
|
||||
" --test-path=${strVmTestPath}" .
|
||||
" --vm=$self->{oTest}->{&TEST_VM}" .
|
||||
" --vm-id=$self->{iVmIdx}" .
|
||||
" --module=" . $self->{oTest}->{&TEST_MODULE} .
|
||||
' --test=' . $self->{oTest}->{&TEST_NAME} .
|
||||
$strCommandRunParam .
|
||||
(defined($self->{oTest}->{&TEST_DB}) ? ' --db-version=' . $self->{oTest}->{&TEST_DB} : '') .
|
||||
($self->{strLogLevel} ne lc(INFO) ? " --log-level=$self->{strLogLevel}" : '') .
|
||||
' --pgsql-bin=' . $self->{oTest}->{&TEST_PGSQL_BIN} .
|
||||
($self->{bLogForce} ? ' --log-force' : '') .
|
||||
($self->{bDryRun} ? ' --dry-run' : '') .
|
||||
($self->{bDryRun} ? ' --vm-out' : '') .
|
||||
($self->{bNoCleanup} ? " --no-cleanup" : '');
|
||||
}
|
||||
|
||||
&log(DETAIL, $strCommand);
|
||||
|
||||
if (!$self->{bDryRun} || $self->{bVmOut})
|
||||
{
|
||||
my $fTestStartTime = gettimeofday();
|
||||
|
||||
# Set permissions on the Docker test directory. This can be removed once users/groups are sync'd between
|
||||
# Docker and the host VM.
|
||||
if ($self->{oTest}->{&TEST_CONTAINER})
|
||||
@ -211,9 +233,104 @@ sub run
|
||||
executeTest("docker exec ${strImage} chown " . TEST_USER . ':' . TEST_GROUP . " -R ${strVmTestPath}");
|
||||
}
|
||||
|
||||
if ($self->{oTest}->{&TEST_C})
|
||||
{
|
||||
my $strCSrcPath = "$self->{strBackRestBase}/src";
|
||||
my $hTest = (testDefModuleTest($self->{oTest}->{&TEST_MODULE}, $self->{oTest}->{&TEST_NAME}));
|
||||
my $hTestCoverage = $hTest->{&TESTDEF_COVERAGE};
|
||||
|
||||
my @stryCFile;
|
||||
|
||||
foreach my $strFile (sort(keys(%{$self->{oStorageTest}->manifest($strCSrcPath)})))
|
||||
{
|
||||
# Skip all files except .c files (including .auto.c)
|
||||
next if $strFile !~ /(?<!\.auto)\.c$/;
|
||||
|
||||
# ??? Temporarily skip because it has Perl includes which will be removed in a later commit
|
||||
next if $strFile =~ /pageChecksum.c$/;
|
||||
|
||||
if (!defined($hTestCoverage->{substr($strFile, 0, length($strFile) - 2)}))
|
||||
{
|
||||
push(@stryCFile, "${strCSrcPath}/${strFile}");
|
||||
}
|
||||
}
|
||||
|
||||
# Generate list of C files to include for testing
|
||||
my $strTestFile =
|
||||
"module/$self->{oTest}->{&TEST_MODULE}/" . testRunName($self->{oTest}->{&TEST_NAME}, false) . 'Test.c';
|
||||
my $strCInclude;
|
||||
|
||||
foreach my $strFile (sort(keys(%{$hTestCoverage})))
|
||||
{
|
||||
# Don't include the test file as it is already included below
|
||||
next if $strFile =~ /Test$/;
|
||||
|
||||
# Don't any auto files as they are included in their companion C files
|
||||
next if $strFile =~ /auto$/;
|
||||
|
||||
# Include the C file if it exists
|
||||
my $strCIncludeFile = "${strFile}.c";
|
||||
|
||||
# If the C file does not exist use the header file instead
|
||||
if (!$self->{oStorageTest}->exists("${strCSrcPath}/${strCIncludeFile}"))
|
||||
{
|
||||
# Error if code was expected
|
||||
if ($hTestCoverage->{$strFile} ne TESTDEF_COVERAGE_NOCODE)
|
||||
{
|
||||
confess &log(ERROR, "unable to file source file '${strCIncludeFile}'");
|
||||
}
|
||||
|
||||
$strCIncludeFile = "${strFile}.h";
|
||||
}
|
||||
|
||||
$strCInclude .= (defined($strCInclude) ? "\n" : '') . "#include \"${strCIncludeFile}\"";
|
||||
}
|
||||
|
||||
# Update C test file with test module
|
||||
my $strTestC = ${$self->{oStorageTest}->get("$self->{strBackRestBase}/test/src/test.c")};
|
||||
$strTestC =~ s/\{\[C\_INCLUDE\]\}/$strCInclude/g;
|
||||
$strTestC =~ s/\{\[C\_TEST\_INCLUDE\]\}/\#include \"$strTestFile\"/g;
|
||||
|
||||
# Initialize tests
|
||||
my $strTestInit;
|
||||
|
||||
for (my $iTestIdx = 1; $iTestIdx <= $hTest->{&TESTDEF_TOTAL}; $iTestIdx++)
|
||||
{
|
||||
my $bSelected = false;
|
||||
|
||||
# use Data::Dumper; confess "RUN: " . Dumper($self->{oTest}->{&TEST_RUN});
|
||||
|
||||
if (!defined($self->{oTest}->{&TEST_RUN}) || @{$self->{oTest}->{&TEST_RUN}} == 0 ||
|
||||
grep(/^$iTestIdx$/, @{$self->{oTest}->{&TEST_RUN}}))
|
||||
{
|
||||
$bSelected = true;
|
||||
}
|
||||
|
||||
$strTestInit .=
|
||||
(defined($strTestInit) ? "\n " : '') .
|
||||
sprintf("testAdd(%3d, %8s);" , $iTestIdx, ($bSelected ? 'true' : 'false'));
|
||||
}
|
||||
|
||||
$strTestC =~ s/\{\[C\_TEST\_LIST\]\}/$strTestInit/g;
|
||||
|
||||
# Save C test file
|
||||
$self->{oStorageTest}->put("$self->{strGCovPath}/test.c", $strTestC);
|
||||
|
||||
my $strGccCommand =
|
||||
'gcc -std=c99 -fprofile-arcs -ftest-coverage -fPIC -O0 ' .
|
||||
"-I/$self->{strBackRestBase}/src -I/$self->{strBackRestBase}/test/src test.c " .
|
||||
"/$self->{strBackRestBase}/test/src/common/harnessTest.c " .
|
||||
join(' ', @stryCFile) . ' -o test';
|
||||
|
||||
executeTest(
|
||||
'docker exec -i -u ' . TEST_USER . " ${strImage} bash -l -c '" .
|
||||
"cd $self->{strGCovPath} && " .
|
||||
"${strGccCommand}'");
|
||||
}
|
||||
|
||||
my $oExec = new pgBackRestTest::Common::ExecuteTest(
|
||||
$strCommand,
|
||||
{bSuppressError => true, bShowOutputAsync => $self->{bShowOutputAsync}});
|
||||
{bSuppressError => !$self->{oTest}->{&TEST_C}, bShowOutputAsync => $self->{bShowOutputAsync}});
|
||||
|
||||
$oExec->begin();
|
||||
|
||||
@ -261,13 +378,96 @@ sub end
|
||||
|
||||
if (defined($iExitStatus))
|
||||
{
|
||||
my $strImage = 'test-' . $self->{iVmIdx};
|
||||
|
||||
if ($self->{bShowOutputAsync})
|
||||
{
|
||||
syswrite(*STDOUT, "\n");
|
||||
}
|
||||
|
||||
# If C library generate coverage info
|
||||
if ($iExitStatus == 0 && $self->{oTest}->{&TEST_C})
|
||||
{
|
||||
# Generate a list of files to cover
|
||||
my $hTestCoverage =
|
||||
(testDefModuleTest($self->{oTest}->{&TEST_MODULE}, $self->{oTest}->{&TEST_NAME}))->{&TESTDEF_COVERAGE};
|
||||
|
||||
my @stryCoveredModule;
|
||||
|
||||
foreach my $strModule (sort(keys(%{$hTestCoverage})))
|
||||
{
|
||||
# Skip modules that have no code
|
||||
next if ($hTestCoverage->{$strModule} eq TESTDEF_COVERAGE_NOCODE);
|
||||
|
||||
push (@stryCoveredModule, testRunName(basename($strModule), false) . ".c.gcov");
|
||||
}
|
||||
|
||||
# Generate coverage reports for the modules
|
||||
executeTest(
|
||||
'docker exec -i -u ' . TEST_USER . " ${strImage} bash -l -c '" .
|
||||
"cd $self->{strGCovPath} && " .
|
||||
"gcov test.c'", {bSuppressStdErr => true});
|
||||
|
||||
# Mark uncoverable statements as successful
|
||||
foreach my $strModule (sort(keys(%{$hTestCoverage})))
|
||||
{
|
||||
# File that contains coverage info for the module
|
||||
my $strCoverageFile = "$self->{strGCovPath}/" . testRunName(basename($strModule), false) . ".c.gcov";
|
||||
|
||||
# If marked as no code then error if there is code
|
||||
if ($hTestCoverage->{$strModule} eq TESTDEF_COVERAGE_NOCODE)
|
||||
{
|
||||
if ($self->{oStorageTest}->exists($strCoverageFile))
|
||||
{
|
||||
confess &log(ERROR, "module '${strModule}' is marked 'no code' but has code");
|
||||
}
|
||||
|
||||
next;
|
||||
}
|
||||
|
||||
# Load the coverage file
|
||||
my $strCoverage = ${$self->{oStorageTest}->get($strCoverageFile)};
|
||||
|
||||
# Go line by line and update uncovered statements
|
||||
my $strUpdatedCoverage;
|
||||
|
||||
foreach my $strLine (split("\n", trim($strCoverage)))
|
||||
{
|
||||
# If the statement is marked uncoverable
|
||||
if ($strLine =~ /\/\/ \{(uncoverable - [^\}]+|\+uncoverable|uncovered - [^\}]+|\+uncovered)\}\s*$/)
|
||||
{
|
||||
# Error if the statement is marked uncoverable but is in fact covered
|
||||
if ($strLine !~ /^ \#\#\#\#\#/)
|
||||
{
|
||||
&log(ERROR, "line in ${strModule}.c is marked as uncoverable but is covered:\n${strLine}");
|
||||
}
|
||||
|
||||
# Mark the statement as covered with 0 executions
|
||||
$strLine =~ s/^ \#\#\#\#\#/^ 0/;
|
||||
}
|
||||
|
||||
# Add to updated file
|
||||
$strUpdatedCoverage .= "${strLine}\n";
|
||||
}
|
||||
|
||||
# Store the updated file
|
||||
$self->{oStorageTest}->put($strCoverageFile, $strUpdatedCoverage);
|
||||
}
|
||||
|
||||
executeTest(
|
||||
'docker exec -i -u ' . TEST_USER . " ${strImage} bash -l -c '" .
|
||||
"cd $self->{strGCovPath} && " .
|
||||
# Only generate coverage files for VMs that support coverage
|
||||
(vmCoverage($self->{oTest}->{&TEST_VM}) ?
|
||||
"gcov2perl -db ../cover_db " . join(' ', @stryCoveredModule) . " && " : '') .
|
||||
# If these aren't deleted then cover automagically finds them and includes them in the report
|
||||
"rm *.gcda *.c.gcov'");
|
||||
}
|
||||
|
||||
# Record elapsed time
|
||||
my $fTestElapsedTime = ceil((gettimeofday() - $self->{oProcess}{start_time}) * 100) / 100;
|
||||
|
||||
# Output error
|
||||
if ($iExitStatus != 0)
|
||||
{
|
||||
&log(ERROR, "${strTestDone} (err${iExitStatus}-${fTestElapsedTime}s)" .
|
||||
@ -275,6 +475,7 @@ sub end
|
||||
":\n\n" . trim($oExecDone->{strOutLog}) . "\n" : ''), undef, undef, 4);
|
||||
$bFail = true;
|
||||
}
|
||||
# Output success
|
||||
else
|
||||
{
|
||||
&log(INFO, "${strTestDone} (${fTestElapsedTime}s)".
|
||||
@ -284,11 +485,11 @@ sub end
|
||||
|
||||
if (!$self->{bNoCleanup})
|
||||
{
|
||||
my $strImage = 'test-' . $self->{iVmIdx};
|
||||
my $strHostTestPath = "$self->{strTestPath}/${strImage}";
|
||||
|
||||
containerRemove("test-$self->{iVmIdx}");
|
||||
executeTest("sudo rm -rf ${strHostTestPath}");
|
||||
executeTest("sudo rm -rf $self->{strGCovPath}");
|
||||
}
|
||||
|
||||
$bDone = true;
|
||||
|
@ -14,6 +14,7 @@ use Exporter qw(import);
|
||||
our @EXPORT = qw();
|
||||
|
||||
use pgBackRest::Common::Log;
|
||||
use pgBackRest::Common::String;
|
||||
|
||||
use pgBackRestTest::Common::DefineTest;
|
||||
use pgBackRestTest::Common::VmTest;
|
||||
@ -23,6 +24,10 @@ use pgBackRestTest::Common::VmTest;
|
||||
################################################################################################################################
|
||||
use constant TEST_DB => 'db';
|
||||
push @EXPORT, qw(TEST_DB);
|
||||
use constant TEST_C => 'c';
|
||||
push @EXPORT, qw(TEST_C);
|
||||
use constant TEST_CLIB => 'clib';
|
||||
push @EXPORT, qw(TEST_CLIB);
|
||||
use constant TEST_CONTAINER => 'container';
|
||||
push @EXPORT, qw(TEST_CONTAINER);
|
||||
use constant TEST_MODULE => 'module';
|
||||
@ -49,6 +54,7 @@ sub testListGet
|
||||
my $iyModuleTestRun = shift;
|
||||
my $strDbVersion = shift;
|
||||
my $bCoverageOnly = shift;
|
||||
my $bCOnly = shift;
|
||||
|
||||
my $oyVm = vmGet();
|
||||
my $oyTestRun = [];
|
||||
@ -94,6 +100,9 @@ sub testListGet
|
||||
# Skip this test if it can't run on this VM
|
||||
next if (defined($hTest->{&TESTDEF_VM}) && grep(/^$strTestOS$/i, @{$hTest->{&TESTDEF_VM}}) == 0);
|
||||
|
||||
# Skip this test if only C tests are requested and this is not a C test
|
||||
next if ($bCOnly && !$hTest->{&TESTDEF_C});
|
||||
|
||||
for (my $iDbVersionIdx = $iDbVersionMax; $iDbVersionIdx >= $iDbVersionMin; $iDbVersionIdx--)
|
||||
{
|
||||
if ($iDbVersionIdx == -1 || $strDbVersion eq 'all' || $strDbVersion eq 'minimal' ||
|
||||
@ -134,6 +143,8 @@ sub testListGet
|
||||
my $oTestRun =
|
||||
{
|
||||
&TEST_VM => $strTestOS,
|
||||
&TEST_C => coalesce($hTest->{&TESTDEF_C}, $hModule->{&TESTDEF_C}, false),
|
||||
&TEST_CLIB => coalesce($hTest->{&TESTDEF_CLIB}, $hModule->{&TESTDEF_CLIB}, false),
|
||||
&TEST_CONTAINER => defined($hTest->{&TESTDEF_CONTAINER}) ?
|
||||
$hTest->{&TESTDEF_CONTAINER} : $hModule->{&TESTDEF_CONTAINER},
|
||||
&TEST_PGSQL_BIN => $strPgSqlBin,
|
||||
|
@ -454,18 +454,25 @@ sub testException
|
||||
sub testRunName
|
||||
{
|
||||
my $strName = shift;
|
||||
my $bInitCapFirst = shift;
|
||||
|
||||
$bInitCapFirst = defined($bInitCapFirst) ? $bInitCapFirst : true;
|
||||
my $bFirst = true;
|
||||
|
||||
my @stryName = split('\-', $strName);
|
||||
$strName = undef;
|
||||
|
||||
foreach my $strPart (@stryName)
|
||||
{
|
||||
$strName .= ucfirst($strPart);
|
||||
$strName .= ($bFirst && $bInitCapFirst) || !$bFirst ? ucfirst($strPart) : $strPart;
|
||||
$bFirst = false;
|
||||
}
|
||||
|
||||
return $strName;
|
||||
}
|
||||
|
||||
push @EXPORT, qw(testRunName);
|
||||
|
||||
####################################################################################################################################
|
||||
# testRun
|
||||
####################################################################################################################################
|
||||
|
66
test/src/common/harnessTest.c
Normal file
66
test/src/common/harnessTest.c
Normal file
@ -0,0 +1,66 @@
|
||||
/***********************************************************************************************************************************
|
||||
C Test Harness
|
||||
***********************************************************************************************************************************/
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common/harnessTest.h"
|
||||
|
||||
#define TEST_LIST_SIZE 64
|
||||
|
||||
typedef struct TestData
|
||||
{
|
||||
bool selected;
|
||||
} TestData;
|
||||
|
||||
static TestData testList[TEST_LIST_SIZE];
|
||||
|
||||
static int testRun = 0;
|
||||
static int testTotal = 0;
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
testAdd - add a new test
|
||||
***********************************************************************************************************************************/
|
||||
void
|
||||
testAdd(int run, bool selected)
|
||||
{
|
||||
testList[testTotal].selected = selected;
|
||||
testTotal++;
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
testBegin - should this test run?
|
||||
***********************************************************************************************************************************/
|
||||
bool
|
||||
testBegin(const char *name)
|
||||
{
|
||||
testRun++;
|
||||
|
||||
if (testList[testRun - 1].selected)
|
||||
{
|
||||
if (testRun != 1)
|
||||
{
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
printf("run %03d - %s\n", testRun, name);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
testComplete - make sure all expected tests ran
|
||||
***********************************************************************************************************************************/
|
||||
void
|
||||
testComplete()
|
||||
{
|
||||
// Check that all tests ran
|
||||
if (testRun != testTotal)
|
||||
{
|
||||
fprintf(stderr, "ERROR: expected %d tests but %d were run\n", testTotal, testRun);
|
||||
fflush(stderr);
|
||||
exit(255);
|
||||
}
|
||||
}
|
9
test/src/common/harnessTest.h
Normal file
9
test/src/common/harnessTest.h
Normal file
@ -0,0 +1,9 @@
|
||||
/***********************************************************************************************************************************
|
||||
C Test Harness
|
||||
***********************************************************************************************************************************/
|
||||
#include "common/type.h"
|
||||
|
||||
// Functions
|
||||
void testAdd(int run, bool selected);
|
||||
bool testBegin(const char *name);
|
||||
void testComplete();
|
20
test/src/module/common/typeTest.c
Normal file
20
test/src/module/common/typeTest.c
Normal file
@ -0,0 +1,20 @@
|
||||
/***********************************************************************************************************************************
|
||||
Test Types
|
||||
***********************************************************************************************************************************/
|
||||
void testRun()
|
||||
{
|
||||
// -----------------------------------------------------------------------------------------------------------------------------
|
||||
if (testBegin("test int size"))
|
||||
{
|
||||
// Ensure that int is at least 4 bytes
|
||||
assert(sizeof(int) >= 4);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------------------
|
||||
if (testBegin("test boolean values"))
|
||||
{
|
||||
// Ensure false and true are defined
|
||||
assert(true);
|
||||
assert(!false);
|
||||
}
|
||||
}
|
43
test/src/test.c
Normal file
43
test/src/test.c
Normal file
@ -0,0 +1,43 @@
|
||||
/***********************************************************************************************************************************
|
||||
C Test Wrapper
|
||||
|
||||
This wrapper runs the the C unit tests.
|
||||
***********************************************************************************************************************************/
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common/harnessTest.h"
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
C files to be tested
|
||||
|
||||
The files are included directly so the test can see and manipulate variables and functions in the module without the need to extern.
|
||||
If a .c file does not exist for a module then the header file will be included instead.
|
||||
***********************************************************************************************************************************/
|
||||
{[C_INCLUDE]}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
The test code is included directly so it can freely interact with the included C files
|
||||
***********************************************************************************************************************************/
|
||||
{[C_TEST_INCLUDE]}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
main - run the tests
|
||||
***********************************************************************************************************************************/
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
// Initialize tests
|
||||
// run, selected
|
||||
{[C_TEST_LIST]}
|
||||
|
||||
// Run the tests
|
||||
testRun();
|
||||
|
||||
// End test run an make sure all tests ran
|
||||
testComplete();
|
||||
|
||||
printf("\nTESTS COMPLETED SUCCESSFULLY (DESPITE ANY ERROR MESSAGES YOU SAW)\n");
|
||||
fflush(stdout);
|
||||
}
|
71
test/test.pl
71
test/test.pl
@ -72,6 +72,7 @@ test.pl [options]
|
||||
--no-lint disable static source code analysis
|
||||
--build-only compile the C library / packages and run tests only
|
||||
--coverage-only only run coverage tests (as a subset of selected tests)
|
||||
--c-only only run C tests
|
||||
--smart perform libc/package builds only when source timestamps have changed
|
||||
--no-package do not build packages
|
||||
--no-ci-config don't overwrite the current continuous integration config
|
||||
@ -122,6 +123,7 @@ my $bVmForce = false;
|
||||
my $bNoLint = false;
|
||||
my $bBuildOnly = false;
|
||||
my $bCoverageOnly = false;
|
||||
my $bCOnly = false;
|
||||
my $bSmart = false;
|
||||
my $bNoPackage = false;
|
||||
my $bNoCiConfig = false;
|
||||
@ -154,6 +156,7 @@ GetOptions ('q|quiet' => \$bQuiet,
|
||||
'no-package' => \$bNoPackage,
|
||||
'no-ci-config' => \$bNoCiConfig,
|
||||
'coverage-only' => \$bCoverageOnly,
|
||||
'c-only' => \$bCOnly,
|
||||
'smart' => \$bSmart,
|
||||
'dev' => \$bDev,
|
||||
'expect' => \$bExpect,
|
||||
@ -639,7 +642,7 @@ eval
|
||||
# Determine which tests to run
|
||||
#---------------------------------------------------------------------------------------------------------------------------
|
||||
my $oyTestRun = testListGet(
|
||||
$strVm, \@stryModule, \@stryModuleTest, \@iyModuleTestRun, $strDbVersion, $bCoverageOnly);
|
||||
$strVm, \@stryModule, \@stryModuleTest, \@iyModuleTestRun, $strDbVersion, $bCoverageOnly, $bCOnly);
|
||||
|
||||
if (@{$oyTestRun} == 0)
|
||||
{
|
||||
@ -801,7 +804,7 @@ eval
|
||||
|
||||
if (@{$hCoverageList->{$strCodeModule}} == $iCoverageTotal)
|
||||
{
|
||||
$hCoverageActual->{$strCodeModule} = $hCoverageType->{$strCodeModule};
|
||||
$hCoverageActual->{testRunName($strCodeModule, false)} = $hCoverageType->{$strCodeModule};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -815,12 +818,53 @@ eval
|
||||
&log(INFO, 'no code modules had all tests run required for coverage');
|
||||
}
|
||||
|
||||
my $strPartialCoverage;
|
||||
|
||||
foreach my $strCodeModule (sort(keys(%{$hCoverageActual})))
|
||||
{
|
||||
my $strCodeModulePath = "${strBackRestBase}/";
|
||||
|
||||
# If the first char of the module is lower case when this is a c module
|
||||
if (substr($strCodeModule, 0, 1) eq lc(substr($strCodeModule, 0, 1)))
|
||||
{
|
||||
# If it ends with Test then it is a test modile
|
||||
if ($strCodeModule =~ /Test$/)
|
||||
{
|
||||
$strCodeModulePath .= "test/src/${strCodeModule}.c";
|
||||
}
|
||||
else
|
||||
{
|
||||
$strCodeModulePath .= "src/${strCodeModule}.c";
|
||||
}
|
||||
}
|
||||
# Else a Perl module
|
||||
else
|
||||
{
|
||||
$strCodeModulePath .= "lib/" . BACKREST_NAME . "/${strCodeModule}.pm"
|
||||
}
|
||||
|
||||
# Get summary results (??? Need to fix this for coverage testing on bin/pgbackrest since .pm is required)
|
||||
my $hCoverageResultAll =
|
||||
$hCoverageResult->{'summary'}
|
||||
{"${strBackRestBase}/lib/" . BACKREST_NAME . "/${strCodeModule}.pm"}{total};
|
||||
my $hCoverageResultAll = $hCoverageResult->{'summary'}{$strCodeModulePath}{total};
|
||||
|
||||
# Try an extra / if the module is not found
|
||||
if (!defined($hCoverageResultAll))
|
||||
{
|
||||
$strCodeModulePath = "/${strCodeModulePath}";
|
||||
$hCoverageResultAll = $hCoverageResult->{'summary'}{$strCodeModulePath}{total};
|
||||
}
|
||||
|
||||
# If module is marked as having no code
|
||||
if ($hCoverageActual->{$strCodeModule} eq TESTDEF_COVERAGE_NOCODE)
|
||||
{
|
||||
# Error if it really does have coverage
|
||||
if ($hCoverageResultAll)
|
||||
{
|
||||
confess &log(ERROR, "module ${strCodeModule} is marked 'no code' but has code");
|
||||
}
|
||||
|
||||
# Skip to next module
|
||||
next;
|
||||
}
|
||||
|
||||
if (!defined($hCoverageResultAll))
|
||||
{
|
||||
@ -830,9 +874,9 @@ eval
|
||||
# Check that all code has been covered
|
||||
my $iCoverageTotal = $hCoverageResultAll->{total};
|
||||
my $iCoverageUncoverable = coalesce($hCoverageResultAll->{uncoverable}, 0);
|
||||
my $iCoverageCovered = $hCoverageResultAll->{covered};
|
||||
my $iCoverageCovered = coalesce($hCoverageResultAll->{covered}, 0);
|
||||
|
||||
if ($hCoverageActual->{$strCodeModule} == TESTDEF_COVERAGE_FULL)
|
||||
if ($hCoverageActual->{$strCodeModule} eq TESTDEF_COVERAGE_FULL)
|
||||
{
|
||||
my $iUncoveredLines = $iCoverageTotal - $iCoverageCovered - $iCoverageUncoverable;
|
||||
|
||||
@ -842,8 +886,8 @@ eval
|
||||
$iUncoveredCodeModuleTotal++;
|
||||
}
|
||||
}
|
||||
# Else test how much partial coverage where was
|
||||
else
|
||||
# Else test how much partial coverage there was
|
||||
elsif ($hCoverageActual->{$strCodeModule} eq TESTDEF_COVERAGE_PARTIAL)
|
||||
{
|
||||
my $iCoveragePercent = int(($iCoverageCovered + $iCoverageUncoverable) * 100 / $iCoverageTotal);
|
||||
|
||||
@ -854,10 +898,17 @@ eval
|
||||
}
|
||||
else
|
||||
{
|
||||
&log(INFO, "code module ${strCodeModule} has (expected) partial coverage of ${iCoveragePercent}%");
|
||||
$strPartialCoverage .=
|
||||
(defined($strPartialCoverage) ? ', ' : '') . "${strCodeModule} (${iCoveragePercent}%)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# If any modules had partial coverage then display them
|
||||
if (defined($strPartialCoverage))
|
||||
{
|
||||
&log(INFO, "module (expected) partial coverage: ${strPartialCoverage}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user