1
0
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:
David Steele 2017-10-12 12:55:48 -04:00
parent 08d6f14603
commit 10dfbd90b5
13 changed files with 507 additions and 65 deletions

View File

@ -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>

View File

@ -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

View File

@ -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;

View File

@ -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)

View File

@ -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

View File

@ -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;

View File

@ -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,

View File

@ -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
####################################################################################################################################

View 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);
}
}

View 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();

View 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
View 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);
}

View File

@ -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}");
}
}
}