1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2024-12-12 10:04:14 +02:00

Add profiling, performance, and optimization to C test harness.

All unit and performance tests are now built by the C harness.

Remove all unit/performance test build code from Perl.

Remove code from C harness that is no longer used. This code was included so the C harness could be run separately, but that is no longer needed with this full integration.
This commit is contained in:
David Steele 2022-07-29 10:31:36 -04:00
parent 1e83f2a022
commit eb287b18c8
14 changed files with 456 additions and 1121 deletions

View File

@ -61,34 +61,6 @@ jobs:
- name: Run Test
run: cd ${HOME?} && ${GITHUB_WORKSPACE?}/pgbackrest/test/ci.pl ${{matrix.param}} --param=build-max=2
# C test harness. Run inside a container so tests that require socket binding work correctly.
testc:
runs-on: ubuntu-22.04
container:
image: ubuntu:18.04
steps:
- name: Checkout Code
uses: actions/checkout@v2
with:
path: pgbackrest
- name: Install
run: |
apt-get update
DEBCONF_NONINTERACTIVE_SEEN=true DEBIAN_FRONTEND=noninteractive apt-get install -y sudo zlib1g-dev libssl-dev libxml2-dev libpq-dev libyaml-dev pkg-config gcc ccache meson liblz4-dev liblz4-tool zstd libzstd-dev bzip2 libbz2-dev tzdata valgrind
adduser --disabled-password --gecos \"\" runner
- name: Build
run: |
sudo -iu runner cp -rp ${GITHUB_WORKSPACE?}/pgbackrest /home/runner
sudo -iu runner meson -Dwerror=true -Dfatal-errors=true -Dbuildtype=debug /home/runner/test/build/none /home/runner/pgbackrest
sudo -iu runner ninja -C /home/runner/test/build/none test/src/test-pgbackrest
- name: Test
run: |
sudo -iu runner /home/runner/test/build/none/test/src/test-pgbackrest --repo-path=/home/runner/pgbackrest --test-path=/home/runner/test --no-repo-copy test
# Basic tests on other architectures using emulation. The emulation is so slow that running all the unit tests would be too
# expensive, but this at least shows that the build works and some of the more complex tests run. In particular, it is good to
# test on one big-endian architecture to be sure that checksums are correct.

View File

@ -39,35 +39,14 @@ use constant TESTDEF_TEST => 'test';
use constant TESTDEF_DB => 'db';
push @EXPORT, qw(TESTDEF_DB);
use constant TESTDEF_DEPEND => 'depend';
use constant TESTDEF_CONTAINER => 'container';
push @EXPORT, qw(TESTDEF_CONTAINER);
use constant TESTDEF_CONTAINER_REQUIRED => 'containerReq';
push @EXPORT, qw(TESTDEF_CONTAINER_REQUIRED);
use constant TESTDEF_COVERAGE => 'coverage';
push @EXPORT, qw(TESTDEF_COVERAGE);
use constant TESTDEF_CORE => 'core';
push @EXPORT, qw(TESTDEF_CORE);
use constant TESTDEF_C => 'c';
push @EXPORT, qw(TESTDEF_C);
use constant TESTDEF_DEFINE => 'define';
push @EXPORT, qw(TESTDEF_DEFINE);
use constant TESTDEF_FEATURE => 'feature';
push @EXPORT, qw(TESTDEF_FEATURE);
use constant TESTDEF_HARNESS => 'harness';
push @EXPORT, qw(TESTDEF_HARNESS);
# Harness name which must match the harness implementation file name
use constant TESTDEF_HARNESS_NAME => 'name';
push @EXPORT, qw(TESTDEF_HARNESS_NAME);
# The harness contains shimmed elements
use constant TESTDEF_HARNESS_SHIM => 'shim';
push @EXPORT, qw(TESTDEF_HARNESS_SHIM);
# The harness shim was first defined in the module/test
use constant TESTDEF_HARNESS_SHIM_DEF => 'harnessShimDef';
push @EXPORT, qw(TESTDEF_HARNESS_SHIM_DEF);
# List of shimmed functions for a C module
use constant TESTDEF_HARNESS_SHIM_FUNCTION => 'function';
push @EXPORT, qw(TESTDEF_HARNESS_SHIM_FUNCTION);
use constant TESTDEF_INCLUDE => 'include';
push @EXPORT, qw(TESTDEF_INCLUDE);
use constant TESTDEF_INDIVIDUAL => 'individual';
@ -146,7 +125,7 @@ sub testDefLoad
# Resolve variables that can be set in the module or the test
foreach my $strVar (
TESTDEF_DEFINE, TESTDEF_DB, TESTDEF_BIN_REQ, TESTDEF_VM, TESTDEF_CONTAINER_REQUIRED)
TESTDEF_DB, TESTDEF_BIN_REQ, TESTDEF_VM, TESTDEF_CONTAINER_REQUIRED)
{
$hTestDefHash->{$strModule}{$strTest}{$strVar} = coalesce(
$hModuleTest->{$strVar}, $hModule->{$strVar}, $strVar eq TESTDEF_VM ? undef : false);
@ -166,78 +145,6 @@ sub testDefLoad
$hTestDefHash->{$strModule}{$strTest}{&TESTDEF_CONTAINER} = $bContainer;
$hTestDefHash->{$strModule}{$strTest}{&TESTDEF_INDIVIDUAL} = $bIndividual;
if (!$hTestDefHash->{$strModule}{$strTest}{&TESTDEF_INTEGRATION})
{
# Add depends to core files
if (defined($hModuleTest->{&TESTDEF_DEPEND}))
{
foreach my $strDepend (@{$hModuleTest->{&TESTDEF_DEPEND}})
{
if (!grep(/$strDepend/i, @stryCoreFile))
{
push(@stryCoreFile, $strDepend);
}
}
}
# Add core files
push(@{$hTestDefHash->{$strModule}{$strTest}{&TESTDEF_CORE}}, @stryCoreFile);
# Add harness files
my $rhHarnessShimDef = {};
if (defined($hModuleTest->{&TESTDEF_HARNESS}))
{
my $rhHarness = {};
# If the harness is a hash then it contains shims
if (ref($hModuleTest->{&TESTDEF_HARNESS}))
{
# Harness name must be set
if (!defined($hModuleTest->{&TESTDEF_HARNESS}{&TESTDEF_HARNESS_NAME}))
{
confess &log(ERROR, "must define 'name' for harness in test '$strTest'");
}
# Don't use hash syntax when there are no shims
if (!defined($hModuleTest->{&TESTDEF_HARNESS}{&TESTDEF_HARNESS_SHIM}))
{
confess &log(
ERROR,
"use 'harness: $hModuleTest->{&TESTDEF_HARNESS}{&TESTDEF_HARNESS_NAME}' if there are no shims");
}
# Note that this shim is defined in the module
$rhHarnessShimDef->{$hModuleTest->{&TESTDEF_HARNESS}{&TESTDEF_HARNESS_NAME}} = true;
# Set the harness
$rhHarness = $hModuleTest->{&TESTDEF_HARNESS};
}
# Else set the harness with just a name
else
{
$rhHarness->{&TESTDEF_HARNESS_NAME} = $hModuleTest->{&TESTDEF_HARNESS};
}
push(@rhyHarnessFile, $rhHarness);
}
push(@{$hTestDefHash->{$strModule}{$strTest}{&TESTDEF_HARNESS}}, @rhyHarnessFile);
$hTestDefHash->{$strModule}{$strTest}{&TESTDEF_HARNESS_SHIM_DEF} = $rhHarnessShimDef;
# Add test defines
$hTestDefHash->{$strModule}{$strTest}{&TESTDEF_FEATURE} =
(defined($hModuleTest->{&TESTDEF_FEATURE}) ?
"-DHRN_INTEST_" . uc($hModuleTest->{&TESTDEF_FEATURE}) . ' ' : '') .
$strTestDefine;
if (defined($hModuleTest->{&TESTDEF_FEATURE}))
{
$strTestDefine .=
($strTestDefine eq '' ? '' : ' ') . "-DHRN_FEATURE_" . uc($hModuleTest->{&TESTDEF_FEATURE});
}
}
# Set test count
$hTestDefHash->{$strModule}{$strTest}{&TESTDEF_TOTAL} = $hModuleTest->{&TESTDEF_TOTAL};

View File

@ -114,12 +114,8 @@ sub new
# Set try to 0
$self->{iTry} = 0;
# Use the new C test harness?
$self->{bTestC} = $self->{oTest}->{&TEST_C} && !$self->{bProfile} && $self->{oTest}->{&TEST_TYPE} ne TESTDEF_PERFORMANCE;
# Setup the path where unit test will be built
$self->{strUnitPath} =
"$self->{strTestPath}/" . ($self->{bTestC} ? 'unit' : 'gcov') . "-$self->{iVmIdx}/$self->{oTest}->{&TEST_VM}";
$self->{strUnitPath} = "$self->{strTestPath}/unit-$self->{iVmIdx}/$self->{oTest}->{&TEST_VM}";
$self->{strDataPath} = "$self->{strTestPath}/data-$self->{iVmIdx}";
$self->{strRepoPath} = "$self->{strTestPath}/repo";
@ -219,14 +215,27 @@ sub run
" -v $self->{strBackRestBase}:$self->{strBackRestBase}" .
" -v $self->{strRepoPath}:$self->{strRepoPath}" .
($self->{oTest}->{&TEST_BIN_REQ} ? " -v ${strBinPath}:${strBinPath}:ro" : '') .
($self->{bTestC} ? " -v ${strBuildPath}:${strBuildPath}:ro" : '') .
($self->{bTestC} ? " -v ${strCCachePath}:/home/${\TEST_USER}/.ccache" : '') .
($self->{oTest}->{&TEST_C} ? " -v ${strBuildPath}:${strBuildPath}:ro" : '') .
($self->{oTest}->{&TEST_C} ? " -v ${strCCachePath}:/home/${\TEST_USER}/.ccache" : '') .
' ' . containerRepo() . ':' . $self->{oTest}->{&TEST_VM} . '-test',
{bSuppressStdErr => true});
}
}
}
# Disable debug/coverage for performance and profile tests
my $bPerformance = $self->{oTest}->{&TEST_TYPE} eq TESTDEF_PERFORMANCE;
if ($bPerformance || $self->{bProfile})
{
$self->{bDebug} = false;
$self->{bDebugTestTrace} = false;
$self->{bCoverageUnit} = false;
}
# Is coverage being tested?
my $bCoverage = vmCoverageC($self->{oTest}->{&TEST_VM}) && $self->{bCoverageUnit};
# Create run parameters
my $strCommandRunParam = '';
@ -240,528 +249,22 @@ sub run
my $strCommand = undef; # Command to run test
# If testing with C harness
if ($self->{bTestC})
if ($self->{oTest}->{&TEST_C})
{
# Create command
# ------------------------------------------------------------------------------------------------------------------
# Build filename for valgrind suppressions
my $strValgrindSuppress = $self->{strRepoPath} . '/test/src/valgrind.suppress.' . $self->{oTest}->{&TEST_VM};
# Is coverage enabled?
my $bCoverage = vmCoverageC($self->{oTest}->{&TEST_VM}) && $self->{bCoverageUnit};
$strCommand =
($self->{oTest}->{&TEST_VM} ne VM_NONE ? "docker exec -i -u ${\TEST_USER} ${strImage} bash -l -c '" : '') .
" \\\n" .
$self->{strTestPath} . '/build/' . $self->{oTest}->{&TEST_VM} . '/test/src/test-pgbackrest --no-run' .
' --no-repo-copy --repo-path=' . $self->{strTestPath} . '/repo' . ' --test-path=' . $self->{strTestPath} .
' --vm=' . $self->{oTest}->{&TEST_VM} . ' --vm-id=' . $self->{iVmIdx} .
($bCoverage ? '' : ' --no-coverage') . ' test ' . $self->{oTest}->{&TEST_MODULE} . '/' .
$self->{oTest}->{&TEST_NAME} . " && \\\n" .
# Allow stderr to be copied to stderr and stdout
"exec 3>&1 && \\\n" .
# Test with valgrind when requested
($self->{bValgrindUnit} && $self->{oTest}->{&TEST_TYPE} ne TESTDEF_PERFORMANCE ?
'valgrind -q --gen-suppressions=all' .
($self->{oStorageTest}->exists($strValgrindSuppress) ? " --suppressions=${strValgrindSuppress}" : '') .
" --exit-on-first-error=yes --leak-check=full --leak-resolution=high --error-exitcode=25" . ' ' : '') .
$self->{strUnitPath} . '/build/test-unit 2>&1 1>&3 | tee /dev/stderr' .
($self->{oTest}->{&TEST_VM} ne VM_NONE ? "'" : '');
}
# Else still testing with Perl harness
elsif ($self->{oTest}->{&TEST_C})
{
my $strRepoCopyPath = $self->{strTestPath} . '/repo'; # Path to repo copy
my $strRepoCopySrcPath = $strRepoCopyPath . '/src'; # Path to repo copy src
my $strRepoCopyTestSrcPath = $strRepoCopyPath . '/test/src'; # Path to repo copy test src
my $strShimSrcPath = $self->{strUnitPath} . '/src'; # Path to shim src
my $strShimTestSrcPath = $self->{strUnitPath} . '/test/src'; # Path to shim test src
my $bCleanAll = false; # Do all object files need to be cleaned?
my $bConfigure = false; # Does configure need to be run?
# If the build.processing file exists then wipe the path to start clean
# ------------------------------------------------------------------------------------------------------------------
my $strBuildProcessingFile = $self->{strUnitPath} . "/build.processing";
# If the file exists then processing terminated before test.bin was run in the last test and the path might be in a
# bad state.
if ($self->{oStorageTest}->exists($strBuildProcessingFile))
{
executeTest("find $self->{strUnitPath} -mindepth 1 -print0 | xargs -0 rm -rf");
}
# Write build.processing to track processing of this test
$self->{oStorageTest}->put($strBuildProcessingFile);
# Create Makefile.in
# ------------------------------------------------------------------------------------------------------------------
my $strMakefileIn =
"CC_CONFIG = \@CC\@\n" .
"CFLAGS_CONFIG = \@CFLAGS\@\n" .
"CPPFLAGS_CONFIG = \@CPPFLAGS\@\n" .
"LDFLAGS_CONFIG = \@LDFLAGS\@\n" .
"LIBS_CONFIG = \@LIBS\@ \@LIBS_BUILD\@\n";
# If Makefile.in has changed then configure needs to be run and all files cleaned
if (buildPutDiffers($self->{oStorageTest}, $self->{strUnitPath} . "/Makefile.in", $strMakefileIn))
{
$bConfigure = true;
$bCleanAll = true;
}
# Create Makefile.param
# ------------------------------------------------------------------------------------------------------------------
# Disable debug/coverage for performance and profile tests
my $bPerformance = $self->{oTest}->{&TEST_TYPE} eq TESTDEF_PERFORMANCE;
if ($bPerformance || $self->{bProfile})
{
$self->{bDebug} = false;
$self->{bDebugTestTrace} = false;
$self->{bCoverageUnit} = false;
}
# Is coverage being tested?
my $bCoverage = vmCoverageC($self->{oTest}->{&TEST_VM}) && $self->{bCoverageUnit};
# Generate Makefile.param
my $strMakefileParam =
"CFLAGS =" .
" \\\n\t-DERROR_MESSAGE_BUFFER_SIZE=131072" .
($self->{bProfile} ? " \\\n\t-pg" : '') .
(vmArchBits($self->{oTest}->{&TEST_VM}) == 32 ? " \\\n\t-D_FILE_OFFSET_BITS=64" : '') .
($self->{bDebug} ? '' : " \\\n\t-DNDEBUG") .
($self->{oTest}->{&TEST_VM} eq VM_RH7 ? " \\\n\t-DDEBUG_EXEC_TIME" : '') .
($bCoverage ? " \\\n\t-DDEBUG_COVERAGE" : '') .
($self->{bDebugTestTrace} && $self->{bDebug} ? " \\\n\t-DDEBUG_TEST_TRACE" : '') .
(vmWithBackTrace($self->{oTest}->{&TEST_VM}) && $self->{bBackTrace} ? " \\\n\t-DWITH_BACKTRACE" : '') .
($self->{oTest}->{&TEST_CDEF} ? " \\\n\t$self->{oTest}->{&TEST_CDEF}" : '') .
"\n" .
"\n" .
"CFLAGS_TEST =" .
" \\\n\t" . (($self->{bOptimize} && ($self->{bProfile} || $bPerformance)) ? '-O2' : '-O0') .
(!$self->{bDebugTestTrace} && $self->{bDebug} ? " \\\n\t-DDEBUG_TEST_TRACE" : '') .
($bCoverage ? " \\\n\t-fprofile-arcs -ftest-coverage" : '') .
($self->{oTest}->{&TEST_VM} eq VM_NONE ? '' : " \\\n\t-DTEST_CONTAINER_REQUIRED") .
($self->{oTest}->{&TEST_CTESTDEF} ? " \\\n\t$self->{oTest}->{&TEST_CTESTDEF}" : '') .
"\n" .
"\n" .
"CFLAGS_HARNESS =" .
" \\\n\t" . ($self->{bOptimize} ? '-O2' : '-O0') .
($self->{oTest}->{&TEST_CTESTDEF} ? " \\\n\t$self->{oTest}->{&TEST_CTESTDEF}" : '') .
"\n" .
"\n" .
"CFLAGS_CORE =" .
" \\\n\t" . ($self->{bOptimize} ? '-O2' : '-O0') .
"\n" .
"\n" .
"LDFLAGS =" .
($self->{bProfile} ? " \\\n\t-pg" : '') .
"\n" .
"\n" .
"LIBS =" .
($bCoverage ? " \\\n\t-lgcov" : '') .
(vmWithBackTrace($self->{oTest}->{&TEST_VM}) && $self->{bBackTrace} ? " \\\n\t-lbacktrace" : '') .
"\n" .
"\n" .
"INCLUDE =" .
" \\\n\t-I\"${strShimSrcPath}\"" .
" \\\n\t-I\"${strShimTestSrcPath}\"" .
" \\\n\t-I\"${strRepoCopySrcPath}\"" .
" \\\n\t-I\"${strRepoCopyTestSrcPath}\"" .
"\n" .
"\n" .
"vpath \%.c ${strShimSrcPath}:${strShimTestSrcPath}:${strRepoCopySrcPath}:${strRepoCopyTestSrcPath}\n";
# If Makefile.param has changed then clean all files
if (buildPutDiffers($self->{oStorageTest}, $self->{strUnitPath} . "/Makefile.param", $strMakefileParam))
{
$bCleanAll = true;
}
# Generate list of harness files
# ------------------------------------------------------------------------------------------------------------------
my $hTest = (testDefModuleTest($self->{oTest}->{&TEST_MODULE}, $self->{oTest}->{&TEST_NAME}));
my $strRepoCopyTestSrcHarnessPath = $strRepoCopyTestSrcPath . '/common';
# C modules included in harness files that should not be added to the make list
my $rhHarnessCModule = {};
# List of harness files to include in make
my @stryHarnessFile = ('common/harnessTest');
foreach my $rhHarness (@{$hTest->{&TESTDEF_HARNESS}})
{
my $bFound = false;
my $strFile = "common/harness" . ucfirst($rhHarness->{&TESTDEF_HARNESS_NAME});
# Include harness file if present
my $strHarnessSrcFile = "${strRepoCopyTestSrcPath}/${strFile}.c";
if ($self->{oStorageTest}->exists($strHarnessSrcFile))
{
$bFound = true;
if (!defined($hTest->{&TESTDEF_HARNESS_SHIM_DEF}{$rhHarness->{&TESTDEF_HARNESS_NAME}}))
{
push(@stryHarnessFile, $strFile);
}
# Install shim
my $rhShim = $rhHarness->{&TESTDEF_HARNESS_SHIM};
if (defined($rhShim))
{
my $strHarnessSrc = ${$self->{oStorageTest}->get($strHarnessSrcFile)};
# Error if there is no placeholder for the shimmed modules
if ($strHarnessSrc !~ /\{\[SHIM\_MODULE\]\}/)
{
confess &log(ERROR, "{[SHIM_MODULE]} tag not found in '${strFile}' harness with shims");
}
# Build list of shimmed C modules
my $strShimModuleList = undef;
foreach my $strShimModule (sort(keys(%{$rhShim})))
{
# If there are shimmed elements the C module will need to be updated and saved to the test path
if (defined($rhShim->{$strShimModule}))
{
my $strShimModuleSrc = ${$self->{oStorageTest}->get(
"${strRepoCopySrcPath}/${strShimModule}.c")};
my @stryShimModuleSrcRenamed;
my $strFunctionDeclaration = undef;
my $strFunctionShim = undef;
foreach my $strLine (split("\n", $strShimModuleSrc))
{
# If shimmed function declaration construction is in progress
if (defined($strFunctionShim))
{
# When the beginning of the function block is found, output both the constructed
# declaration and the renamed implementation.
if ($strLine =~ /^{/)
{
push(
@stryShimModuleSrcRenamed,
trim($strFunctionDeclaration) . "; " . $strFunctionShim);
push(@stryShimModuleSrcRenamed, $strLine);
$strFunctionShim = undef;
}
# Else keep constructing the declaration and implementation
else
{
$strFunctionDeclaration .= trim($strLine);
$strFunctionShim .= "${strLine}\n";
}
}
# Else search for shimmed functions
else
{
# Rename shimmed functions
my $bFound = false;
foreach my $strFunction (@{$rhShim->{$strShimModule}{&TESTDEF_HARNESS_SHIM_FUNCTION}})
{
# If the function to shim is static then we need to create a declaration with the
# original name so references to the original name in the C module will compile.
# This is not necessary for externed functions since they should already have a
# declaration in the header file.
if ($strLine =~ /^${strFunction}\(/ && $stryShimModuleSrcRenamed[-1] =~ /^static /)
{
my $strLineLast = pop(@stryShimModuleSrcRenamed);
$strFunctionDeclaration = "${strLineLast} ${strLine}";
$strLine =~ s/^${strFunction}\(/${strFunction}_SHIMMED\(/;
$strFunctionShim = "${strLineLast}\n${strLine}";
$bFound = true;
last;
}
}
# If the function was not found then just append the line
if (!$bFound)
{
push(@stryShimModuleSrcRenamed, $strLine);
}
}
}
buildPutDiffers(
$self->{oStorageTest}, "${strShimSrcPath}/${strShimModule}.c",
join("\n", @stryShimModuleSrcRenamed));
}
# Build list to include in the harness
if (defined($strShimModuleList))
{
$strShimModuleList .= "\n";
}
$rhHarnessCModule->{$strShimModule} = true;
$strShimModuleList .= "#include \"${strShimModule}.c\"";
}
# Replace modules and save
$strHarnessSrc =~ s/\{\[SHIM\_MODULE\]\}/${strShimModuleList}/g;
buildPutDiffers($self->{oStorageTest}, "${strShimTestSrcPath}/${strFile}.c", $strHarnessSrc);
}
}
# Include files in the harness directory if present
for my $strFileSub (
$self->{oStorageTest}->list("${strRepoCopyTestSrcPath}/${strFile}",
{bIgnoreMissing => true, strExpression => '\.c$'}))
{
push(@stryHarnessFile, "${strFile}/" . substr($strFileSub, 0, length($strFileSub) - 2));
$bFound = true;
}
# Error when no harness files were found
if (!$bFound)
{
confess &log(ERROR, "no files found for harness '$rhHarness->{&TESTDEF_HARNESS_NAME}'");
}
}
# Generate list of core files (files to be tested/included in this unit will be excluded)
# ------------------------------------------------------------------------------------------------------------------
my $hTestCoverage = $hTest->{&TESTDEF_COVERAGE};
my @stryCoreFile;
foreach my $strFile (@{$hTest->{&TESTDEF_CORE}})
{
# Skip all files except .c files (including .auto.c and .vendor.c)
next if $strFile !~ /(?<!\.auto)$/ || $strFile !~ /(?<!\.vendor)$/;
# Skip if no C file exists
next if !$self->{oStorageTest}->exists("${strRepoCopySrcPath}/${strFile}.c");
# Skip if the C file is included in the harness
next if defined($rhHarnessCModule->{$strFile});
if (!defined($hTestCoverage->{$strFile}) && !grep(/^$strFile$/, @{$hTest->{&TESTDEF_INCLUDE}}))
{
push(@stryCoreFile, $strFile);
}
}
# Create Makefile
# ------------------------------------------------------------------------------------------------------------------
my $strMakefile =
"include Makefile.config\n" .
"include Makefile.param\n" .
"\n" .
"SRCS = test.c \\\n" .
"\t" . join('.c ', @stryHarnessFile) . ".c \\\n" .
(@stryCoreFile > 0 ? "\t" . join('.c ', @stryCoreFile) . ".c\n" : '').
"\n" .
".build/test.o: CFLAGS += \$(CFLAGS_TEST)\n" .
"\n" .
".build/" . join(".o: CFLAGS += \$(CFLAGS_HARNESS)\n.build/", @stryHarnessFile) .
".o: CFLAGS += \$(CFLAGS_HARNESS)\n" .
"\n" .
".build/" . join(".o: CFLAGS += \$(CFLAGS_CORE)\n.build/", @stryCoreFile) .
".o: CFLAGS += \$(CFLAGS_CORE)\n" .
"\n" .
".build/\%.o : \%.c\n" .
" \@if test ! -d \$(\@D); then mkdir -p \$(\@D); fi\n" .
" \$(CC_CONFIG) \$(INCLUDE) \$(CFLAGS_CONFIG) \$(CPPFLAGS_CONFIG) \$(CFLAGS)" .
" -c -o \$\@ \$< -MMD -MP -MF .build/\$*.dep\n" .
"\n" .
"OBJS = \$(patsubst \%.c,.build/\%.o,\$(SRCS))\n" .
"\n" .
"test: \$(OBJS)\n" .
" \$(CC_CONFIG) -o test.bin \$(OBJS) \$(LDFLAGS_CONFIG) \$(LDFLAGS) \$(LIBS_CONFIG) \$(LIBS)\n" .
"\n" .
"rwildcard = \$(wildcard \$1\$2) \$(foreach d,\$(wildcard \$1*),\$(call rwildcard,\$d/,\$2))\n" .
"DEP_FILES = \$(call rwildcard,.build,*.dep)\n" .
"include \$(DEP_FILES)\n";
buildPutDiffers($self->{oStorageTest}, $self->{strUnitPath} . "/Makefile", $strMakefile);
# Create test.c
# ------------------------------------------------------------------------------------------------------------------
# Generate list of C files to include for testing
my $strTestDepend = '';
my $strTestFile =
"module/$self->{oTest}->{&TEST_MODULE}/" . testRunName($self->{oTest}->{&TEST_NAME}, false) . 'Test.c';
my $strCInclude;
foreach my $strFile (sort(keys(%{$hTestCoverage}), @{$hTest->{&TESTDEF_INCLUDE}}))
{
# Don't include the test file as it is already included below
next if $strFile =~ /Test$/;
# Don't include auto files as they are included in their companion C files
next if $strFile =~ /auto$/;
# Don't include vendor files as they are included in regular C files
next if $strFile =~ /vendor$/;
# Skip if the C file is included in the harness
next if defined($rhHarnessCModule->{$strFile});
# 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("${strRepoCopySrcPath}/${strCIncludeFile}"))
{
# Error if code was expected
if ($hTestCoverage->{$strFile} ne TESTDEF_COVERAGE_NOCODE)
{
confess &log(ERROR, "unable to find source file '${strRepoCopySrcPath}/${strCIncludeFile}'");
}
$strCIncludeFile = "${strFile}.h";
}
$strCInclude .= (defined($strCInclude) ? "\n" : '') . "#include \"${strCIncludeFile}\"";
$strTestDepend .= " ${strCIncludeFile}";
}
# Add harnesses with shims that are first defined in this module
foreach my $strHarness (sort(keys(%{$hTest->{&TESTDEF_HARNESS_SHIM_DEF}})))
{
$strCInclude .=
(defined($strCInclude) ? "\n" : '') . "#include \"common/harness" . ucfirst($strHarness) . ".c\"";
}
# Update C test file with test module
my $strTestC = ${$self->{oStorageTest}->get("${strRepoCopyTestSrcPath}/test.c")};
if (defined($strCInclude))
{
$strTestC =~ s/\{\[C\_INCLUDE\]\}/$strCInclude/g;
}
else
{
$strTestC =~ s/\{\[C\_INCLUDE\]\}//g;
}
$strTestC =~ s/\{\[C\_TEST\_INCLUDE\]\}/\#include \"$strTestFile\"/g;
$strTestDepend .= " ${strTestFile}";
# Determine where the project exe is located
my $strProjectExePath =
"$self->{strTestPath}/bin/$self->{oTest}->{&TEST_VM}/" . ($self->{oTest}->{&TEST_VM} eq VM_NONE ? 'src/' : '') .
PROJECT_EXE;
# Is this test running in a container?
my $strContainer = $self->{oTest}->{&TEST_VM} eq VM_NONE ? 'false' : 'true';
# What test path should be passed to C? Containers always have their test path at ~/test but when running with
# vm=none it should be in a subdirectory of the current directory.
my $strTestPathC = $self->{oTest}->{&TEST_VM} eq VM_NONE ? $strHostTestPath : $strVmTestPath;
# Set globals
$strTestC =~ s/\{\[C\_TEST\_CONTAINER\]\}/$strContainer/g;
$strTestC =~ s/\{\[C\_TEST\_PROJECT\_EXE\]\}/$strProjectExePath/g;
$strTestC =~ s/\{\[C\_TEST\_PATH\]\}/$strTestPathC/g;
$strTestC =~ s/\{\[C\_TEST\_USER\]\}/${\TEST_USER}/g;
$strTestC =~ s/\{\[C\_TEST\_USER\_ID\]\}/${\TEST_USER_ID}/g;
$strTestC =~ s/\{\[C\_TEST\_GROUP\]\}/${\TEST_GROUP}/g;
$strTestC =~ s/\{\[C\_TEST\_GROUP\_ID\]\}/${\TEST_GROUP_ID}/g;
$strTestC =~ s/\{\[C\_TEST\_PGB\_PATH\]\}/$strRepoCopyPath/g;
$strTestC =~ s/\{\[C\_HRN\_PATH\]\}/$self->{strDataPath}/g;
$strTestC =~ s/\{\[C\_TEST\_IDX\]\}/$self->{iVmIdx}/g;
$strTestC =~ s/\{\[C\_HRN\_PATH\_REPO\]\}/$self->{strBackRestBase}/g;
$strTestC =~ s/\{\[C\_TEST\_SCALE\]\}/$self->{iScale}/g;
my $strLogTimestampC = $self->{bLogTimestamp} ? 'true' : 'false';
$strTestC =~ s/\{\[C\_TEST\_TIMING\]\}/$strLogTimestampC/g;
# Set timezone
if (defined($self->{strTimeZone}))
{
$strTestC =~ s/\{\[C\_TEST\_TZ\]\}/setenv\("TZ", "$self->{strTimeZone}", true\);/g;
}
else
{
$strTestC =~ s/\{\[C\_TEST\_TZ\]\}/\/\/ No timezone specified/g;
}
# Set default log level
my $strLogLevelTestC = "logLevel" . ucfirst($self->{strLogLevelTest});
$strTestC =~ s/\{\[C\_LOG\_LEVEL\_TEST\]\}/$strLogLevelTestC/g;
# Initialize tests
my $strTestInit;
for (my $iTestIdx = 1; $iTestIdx <= $hTest->{&TESTDEF_TOTAL}; $iTestIdx++)
{
my $bSelected = false;
if (!defined($self->{oTest}->{&TEST_RUN}) || @{$self->{oTest}->{&TEST_RUN}} == 0 ||
grep(/^$iTestIdx$/, @{$self->{oTest}->{&TEST_RUN}}))
{
$bSelected = true;
}
$strTestInit .=
(defined($strTestInit) ? "\n " : '') .
sprintf("hrnAdd(%3d, %8s);" , $iTestIdx, ($bSelected ? 'true' : 'false'));
}
$strTestC =~ s/\{\[C\_TEST\_LIST\]\}/$strTestInit/g;
# Save test.c and make sure it gets a new timestamp
# ------------------------------------------------------------------------------------------------------------------
my $strTestCFile = "$self->{strUnitPath}/test.c";
if (buildPutDiffers($self->{oStorageTest}, "$self->{strUnitPath}/test.c", $strTestC))
{
# Get timestamp for test.bin if it existss
my $oTestBinInfo = $self->{oStorageTest}->info("$self->{strUnitPath}/test.bin", {bIgnoreMissing => true});
my $iTestBinOriginalTime = defined($oTestBinInfo) ? $oTestBinInfo->mtime : 0;
# Get timestamp for test.c
my $iTestCNewTime = $self->{oStorageTest}->info($strTestCFile)->mtime;
# Is the timestamp for test.c newer than test.bin?
while ($iTestCNewTime <= $iTestBinOriginalTime)
{
# If not then sleep until the next second
my $iTimeToSleep = ($iTestBinOriginalTime + 1) - gettimeofday();
if ($iTimeToSleep > 0)
{
usleep($iTimeToSleep * 1000000);
}
# Save the file again
$self->{oStorageTest}->put($self->{oStorageTest}->openWrite($strTestCFile), $strTestC);
$iTestCNewTime = $self->{oStorageTest}->info($strTestCFile)->mtime;
}
}
# Create command
# ------------------------------------------------------------------------------------------------------------------
# Build filename for valgrind suppressions
my $strValgrindSuppress = $self->{strRepoPath} . '/test/src/valgrind.suppress.' . $self->{oTest}->{&TEST_VM};
$strCommand =
($self->{oTest}->{&TEST_VM} ne VM_NONE ? "docker exec -i -u ${\TEST_USER} ${strImage} bash -l -c '" : '') .
" \\\n" .
"cd $self->{strUnitPath} && \\\n" .
# Clean build
($bCleanAll ? "rm -rf .build && \\\n" : '') .
# Remove coverage data
(!$bCleanAll && $bCoverage ? "rm -rf .build/test.gcda && \\\n" : '') .
# Configure when required
($bConfigure ?
"mv Makefile Makefile.tmp && ${strRepoCopySrcPath}/configure -q --enable-test" .
" && mv Makefile Makefile.config && mv Makefile.tmp Makefile && \\\n" :
'') .
$self->{strMakeCmd} . " -j $self->{iBuildMax} -s 2>&1 && \\\n" .
"rm ${strBuildProcessingFile} && \\\n" .
$self->{strTestPath} . '/build/' . $self->{oTest}->{&TEST_VM} . '/test/src/test-pgbackrest' .
' --repo-path=' . $self->{strTestPath} . '/repo' . ' --test-path=' . $self->{strTestPath} .
" --log-level=$self->{strLogLevel}" . ' --vm=' . $self->{oTest}->{&TEST_VM} .
' --vm-id=' . $self->{iVmIdx} . ($self->{bProfile} ? ' --profile' : '') .
($bCoverage ? '' : ' --no-coverage') . ' test ' .
$self->{oTest}->{&TEST_MODULE} . '/' . $self->{oTest}->{&TEST_NAME} . " && \\\n" .
# Allow stderr to be copied to stderr and stdout
"exec 3>&1 && \\\n" .
# Test with valgrind when requested
@ -769,7 +272,7 @@ sub run
'valgrind -q --gen-suppressions=all' .
($self->{oStorageTest}->exists($strValgrindSuppress) ? " --suppressions=${strValgrindSuppress}" : '') .
" --exit-on-first-error=yes --leak-check=full --leak-resolution=high --error-exitcode=25" . ' ' : '') .
"./test.bin 2>&1 1>&3 | tee /dev/stderr" .
"$self->{strUnitPath}/build/test-unit 2>&1 1>&3 | tee /dev/stderr" .
($self->{oTest}->{&TEST_VM} ne VM_NONE ? "'" : '');
}
else
@ -852,7 +355,8 @@ sub end
{
executeTest(
($self->{oTest}->{&TEST_VM} ne VM_NONE ? 'docker exec -i -u ' . TEST_USER . " ${strImage} " : '') .
"gprof $self->{strUnitPath}/test.bin $self->{strUnitPath}/gmon.out > $self->{strUnitPath}/gprof.txt");
"gprof $self->{strUnitPath}/build/test-unit $self->{strUnitPath}/build/gmon.out >" .
" $self->{strUnitPath}/gprof.txt");
$self->{oStorageTest}->pathCreate(
"$self->{strBackRestBase}/test/result/profile", {strMode => '0750', bIgnoreExists => true, bCreateParent => true});

View File

@ -26,10 +26,6 @@ use constant TEST_DB => 'db';
push @EXPORT, qw(TEST_DB);
use constant TEST_C => 'c';
push @EXPORT, qw(TEST_C);
use constant TEST_CDEF => 'cdef';
push @EXPORT, qw(TEST_CDEF);
use constant TEST_CTESTDEF => 'ctestdef';
push @EXPORT, qw(TEST_CTESTDEF);
use constant TEST_CONTAINER => 'container';
push @EXPORT, qw(TEST_CONTAINER);
use constant TEST_MODULE => 'module';
@ -168,8 +164,6 @@ sub testListGet
&TEST_TYPE => $hTest->{&TESTDEF_TYPE},
&TEST_VM => $strTestOS,
&TEST_C => coalesce($hTest->{&TESTDEF_C}, $hModule->{&TESTDEF_C}, false),
&TEST_CDEF => $hTest->{&TESTDEF_DEFINE},
&TEST_CTESTDEF => $hTest->{&TESTDEF_FEATURE},
&TEST_CONTAINER => defined($hTest->{&TESTDEF_CONTAINER}) ?
$hTest->{&TESTDEF_CONTAINER} : $hModule->{&TESTDEF_CONTAINER},
&TEST_PGSQL_BIN => $strPgSqlBin,

View File

@ -57,10 +57,15 @@ option:
internal: true
default: true
repo-copy:
optimize:
type: boolean
default: true
negate: true
default: false
command:
test: {}
profile:
type: boolean
default: false
command:
test: {}
@ -70,13 +75,6 @@ option:
command:
test: {}
run:
type: boolean
default: true
negate: true
command:
test: {}
scale:
type: integer
internal: true
@ -106,13 +104,6 @@ option:
command:
test: {}
valgrind:
type: boolean
default: true
negate: true
command:
test: {}
vm:
type: string
internal: true

View File

@ -99,11 +99,21 @@
<example>n</example>
</option>
<option id="repo-copy">
<summary>Make copy of code repository.</summary>
<option id="optimize">
<summary>Optimize test.</summary>
<text>
<p>Make a copy of the code repository for testing so changes to the code repository do not affect tests.</p>
<p>Compile test with optimizations.</p>
</text>
<example>n</example>
</option>
<option id="profile">
<summary>Generate profile report.</summary>
<text>
<p>Profiling helps identify bottlenecks in the code.</p>
</text>
<example>n</example>
@ -119,16 +129,6 @@
<example>/path/to/pgbackrest</example>
</option>
<option id="run">
<summary>Run the test?</summary>
<text>
<p>When negated the test will only be built.</p>
</text>
<example>n</example>
</option>
<option id="scale">
<summary>Scale performance test.</summary>
@ -169,16 +169,6 @@
<example>America/New_York</example>
</option>
<option id="valgrind">
<summary>Run test with valgrind.</summary>
<text>
<p>Valgrind helps find various memory issues.</p>
</text>
<example>n</example>
</option>
<option id="vm">
<summary>VM to run test on.</summary>

View File

@ -31,7 +31,7 @@ TestBuild *
testBldNew(
const String *const pathRepo, const String *const pathTest, const String *const vm, const unsigned int vmId,
const TestDefModule *const module, const unsigned int test, const uint64_t scale, const LogLevel logLevel, const bool logTime,
const String *const timeZone, const bool coverage)
const String *const timeZone, const bool coverage, const bool profile, const bool optimize)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STRING, pathRepo);
@ -45,6 +45,8 @@ testBldNew(
FUNCTION_LOG_PARAM(BOOL, logTime);
FUNCTION_LOG_PARAM(STRING, timeZone);
FUNCTION_LOG_PARAM(BOOL, coverage);
FUNCTION_LOG_PARAM(BOOL, profile);
FUNCTION_LOG_PARAM(BOOL, optimize);
FUNCTION_LOG_END();
ASSERT(pathRepo != NULL);
@ -75,6 +77,8 @@ testBldNew(
.logTime = logTime,
.timeZone = strDup(timeZone),
.coverage = coverage,
.profile = profile,
.optimize = optimize,
},
};
@ -434,7 +438,54 @@ testBldUnit(TestBuild *const this)
"\n"
"executable(\n"
" 'test-unit',\n"
" sources: src_unit,\n"
" sources: src_unit,\n",
strZ(pathRepoRel));
// Add C args
String *const cArg = strNew();
if (testBldOptimize(this) || module->type == testDefTypePerformance)
strCatZ(cArg, "\n '-O2',");
if (testBldProfile(this))
{
strCatZ(
cArg,
"\n '-pg',"
"\n '-no-pie',");
}
if (!strEmpty(cArg))
{
strCatFmt(
mesonBuild,
" c_args: [%s\n"
" ],\n",
strZ(cArg));
}
// Add linker args
String *const linkArg = strNew();
if (testBldProfile(this))
{
strCatZ(
linkArg,
"\n '-pg',"
"\n '-no-pie',");
}
if (!strEmpty(linkArg))
{
strCatFmt(
mesonBuild,
" link_args: [%s\n"
" ],\n",
strZ(linkArg));
}
strCatFmt(
mesonBuild,
" include_directories:\n"
" include_directories(\n"
" '.',\n"
@ -452,7 +503,7 @@ testBldUnit(TestBuild *const this)
" lib_zstd,\n"
" ],\n"
")\n",
strZ(pathRepoRel), strZ(pathRepoRel), strZ(pathRepoRel));
strZ(pathRepoRel), strZ(pathRepoRel));
testBldWrite(storageUnit, storageUnitList, "meson.build", BUFSTR(mesonBuild));
@ -461,6 +512,12 @@ testBldUnit(TestBuild *const this)
String *const testC = strCatBuf(
strNew(), storageGetP(storageNewReadP(testBldStorageRepo(this), STRDEF("test/src/test.c"))));
// Enable debug test trace
if (!testBldProfile(this) && module->type != testDefTypePerformance)
strReplace(testC, STRDEF("{[C_TEST_DEBUG_TEST_TRACE]}"), STRDEF("#define DEBUG_TEST_TRACE"));
else
strReplace(testC, STRDEF("{[C_TEST_DEBUG_TEST_TRACE]}"), STRDEF("// Debug test trace not enabled"));
// Files to test/include
StringList *const testIncludeFileList = strLstNew();
@ -577,6 +634,10 @@ testBldUnit(TestBuild *const this)
strReplace(testC, STRDEF("{[C_TEST_LIST]}"), testList);
// Profiling
strReplace(testC, STRDEF("{[C_TEST_PROFILE]}"), STR(cvtBoolToConstZ(testBldProfile(this))));
strReplace(testC, STRDEF("{[C_TEST_PATH_BUILD]}"), strNewFmt("%s/build", strZ(pathUnit)));
// Write file
testBldWrite(storageUnit, storageUnitList, "test.c", BUFSTR(testC));

View File

@ -20,7 +20,8 @@ Constructors
***********************************************************************************************************************************/
TestBuild *testBldNew(
const String *pathRepo, const String *pathTest, const String *const vm, unsigned int vmId, const TestDefModule *module,
unsigned int test, uint64_t scale, LogLevel logLevel, bool logTime, const String *timeZone, bool coverage);
unsigned int test, uint64_t scale, LogLevel logLevel, bool logTime, const String *timeZone, bool coverage, bool profile,
bool optimize);
/***********************************************************************************************************************************
Getters/Setters
@ -40,6 +41,8 @@ typedef struct TestBuildPub
uint64_t scale; // Scale performance test
const String *timeZone; // Test in timezone
bool coverage; // Generate coverage?
bool profile; // Generate profile report?
bool optimize; // Optimize code?
TestDef tstDef; // Test definitions
} TestBuildPub;
@ -128,6 +131,20 @@ testBldCoverage(const TestBuild *const this)
return THIS_PUB(TestBuild)->coverage;
}
// Generate profile repo?
__attribute__((always_inline)) static inline bool
testBldProfile(const TestBuild *const this)
{
return THIS_PUB(TestBuild)->profile;
}
// Optimize code?
__attribute__((always_inline)) static inline bool
testBldOptimize(const TestBuild *const this)
{
return THIS_PUB(TestBuild)->optimize;
}
// Scale
__attribute__((always_inline)) static inline uint64_t
testBldScale(const TestBuild *const this)

View File

@ -28,263 +28,279 @@ testDefParseModuleList(Yaml *const yaml, List *const moduleList)
StringList *const globalFeatureList = strLstNew();
List *const globalHarnessList = lstNewP(sizeof(TestDefHarness), .comparator = lstComparatorStr);
List *const globalShimList = lstNewP(sizeof(TestDefShim), .comparator = lstComparatorStr);
const char *typeList[] = {"unit", "integration", "performance"};
// Module list
YAML_SEQ_BEGIN(yaml)
for (TestDefType type = 0; type < LENGTH_OF(typeList); type++)
{
// Module
YAML_MAP_BEGIN(yaml)
yamlScalarNextCheckZ(yaml, typeList[type]);
// Module list
YAML_SEQ_BEGIN(yaml)
{
// Module name
yamlScalarNextCheckZ(yaml, "name");
const String *const moduleName = yamlScalarNext(yaml).value;
// Submodule List
yamlScalarNextCheckZ(yaml, "test");
YAML_SEQ_BEGIN(yaml)
// Module
YAML_MAP_BEGIN(yaml)
{
TestDefModule testDefModule = {0};
StringList *const includeList = strLstNew();
List *const coverageList = lstNewP(sizeof(TestDefCoverage), .comparator = lstComparatorStr);
// Module name
yamlScalarNextCheckZ(yaml, "name");
const String *const moduleName = yamlScalarNext(yaml).value;
// Submodule
YAML_MAP_BEGIN(yaml)
// Check if next is db for integration tests
bool pgRequired = false;
if (type == testDefTypeIntegration && yamlEventPeek(yaml).type == yamlEventTypeScalar &&
strEqZ(yamlEventPeek(yaml).value, "db"))
{
YamlEvent subModuleDef = yamlEventNext(yaml);
yamlScalarNextCheckZ(yaml, "db");
pgRequired = yamlBoolParse(yamlScalarNext(yaml));
}
if (strEqZ(subModuleDef.value, "binReq"))
// Submodule List
yamlScalarNextCheckZ(yaml, "test");
YAML_SEQ_BEGIN(yaml)
{
TestDefModule testDefModule = {.type = type, .pgRequired = pgRequired};
StringList *const includeList = strLstNew();
List *const coverageList = lstNewP(sizeof(TestDefCoverage), .comparator = lstComparatorStr);
// Submodule
YAML_MAP_BEGIN(yaml)
{
testDefModule.binRequired = yamlBoolParse(yamlScalarNext(yaml));
}
else if (strEqZ(subModuleDef.value, "containerReq"))
{
testDefModule.containerRequired = yamlBoolParse(yamlScalarNext(yaml));
}
else if (strEqZ(subModuleDef.value, "coverage"))
{
YAML_SEQ_BEGIN(yaml)
YamlEvent subModuleDef = yamlEventNext(yaml);
if (strEqZ(subModuleDef.value, "binReq"))
{
TestDefCoverage testDefCoverage = {0};
testDefModule.binRequired = yamlBoolParse(yamlScalarNext(yaml));
}
else if (strEqZ(subModuleDef.value, "containerReq"))
{
testDefModule.containerRequired = yamlBoolParse(yamlScalarNext(yaml));
}
else if (strEqZ(subModuleDef.value, "coverage"))
{
YAML_SEQ_BEGIN(yaml)
{
TestDefCoverage testDefCoverage = {0};
if (yamlEventPeek(yaml).type == yamlEventTypeScalar)
{
testDefCoverage.name = yamlScalarNext(yaml).value;
testDefCoverage.coverable = true;
}
else
{
YAML_MAP_BEGIN(yaml)
{
testDefCoverage.name = yamlScalarNext(yaml).value;
yamlScalarNextCheckZ(yaml, "noCode");
}
YAML_MAP_END();
}
testDefCoverage.include =
strEndsWithZ(testDefCoverage.name, ".vendor") || strEndsWithZ(testDefCoverage.name, ".auto");
MEM_CONTEXT_OBJ_BEGIN(coverageList)
{
testDefCoverage.name = strDup(testDefCoverage.name);
lstAdd(coverageList, &testDefCoverage);
}
MEM_CONTEXT_OBJ_END();
// Also add to the global depend list
if (testDefCoverage.coverable && !testDefCoverage.include)
strLstAddIfMissing(globalDependList, testDefCoverage.name);
}
YAML_SEQ_END();
}
else if (strEqZ(subModuleDef.value, "define"))
{
testDefModule.flag = yamlScalarNext(yaml).value;
}
else if (strEqZ(subModuleDef.value, "depend"))
{
YAML_SEQ_BEGIN(yaml)
{
strLstAddIfMissing(globalDependList, yamlEventNext(yaml).value);
}
YAML_SEQ_END();
}
else if (strEqZ(subModuleDef.value, "feature"))
{
testDefModule.feature = yamlScalarNext(yaml).value;
}
else if (strEqZ(subModuleDef.value, "harness"))
{
TestDefHarness testDefHarness = {0};
StringList *harnessIncludeList = strLstNew();
if (yamlEventPeek(yaml).type == yamlEventTypeScalar)
{
testDefCoverage.name = yamlScalarNext(yaml).value;
testDefCoverage.coverable = true;
testDefHarness.name = yamlScalarNext(yaml).value;
}
else
{
YAML_MAP_BEGIN(yaml)
{
testDefCoverage.name = yamlScalarNext(yaml).value;
yamlScalarNextCheckZ(yaml, "noCode");
yamlScalarNextCheckZ(yaml, "name");
testDefHarness.name = yamlScalarNext(yaml).value;
yamlScalarNextCheckZ(yaml, "shim");
YAML_MAP_BEGIN(yaml)
{
const String *const shim = yamlScalarNext(yaml).value;
strLstAdd(harnessIncludeList, shim);
if (yamlEventPeek(yaml).type == yamlEventTypeScalar)
{
yamlScalarNext(yaml);
}
else
{
TestDefShim testDefShim = {.name = shim, .functionList = strLstNew()};
YAML_MAP_BEGIN(yaml)
{
yamlScalarNextCheckZ(yaml, "function");
StringList *const functionList = strLstNew();
YAML_SEQ_BEGIN(yaml)
{
strLstAdd(functionList, yamlScalarNext(yaml).value);
}
YAML_SEQ_END();
testDefShim.functionList = functionList;
}
YAML_MAP_END();
lstAdd(globalShimList, &testDefShim);
}
}
YAML_MAP_END();
}
YAML_MAP_END();
}
testDefCoverage.include =
strEndsWithZ(testDefCoverage.name, ".vendor") || strEndsWithZ(testDefCoverage.name, ".auto");
MEM_CONTEXT_OBJ_BEGIN(coverageList)
testDefHarness.includeList = harnessIncludeList;
lstAdd(globalHarnessList, &testDefHarness);
}
else if (strEqZ(subModuleDef.value, "include"))
{
YAML_SEQ_BEGIN(yaml)
{
testDefCoverage.name = strDup(testDefCoverage.name);
lstAdd(coverageList, &testDefCoverage);
strLstAdd(includeList, yamlEventNext(yaml).value);
}
MEM_CONTEXT_OBJ_END();
// Also add to the global depend list
if (testDefCoverage.coverable && !testDefCoverage.include)
strLstAddIfMissing(globalDependList, testDefCoverage.name);
YAML_SEQ_END();
}
YAML_SEQ_END();
}
else if (strEqZ(subModuleDef.value, "define"))
{
testDefModule.flag = yamlScalarNext(yaml).value;
}
else if (strEqZ(subModuleDef.value, "depend"))
{
YAML_SEQ_BEGIN(yaml)
else if (strEqZ(subModuleDef.value, "name"))
{
strLstAddIfMissing(globalDependList, yamlEventNext(yaml).value);
testDefModule.name = strNewFmt("%s/%s", strZ(moduleName), strZ(yamlScalarNext(yaml).value));
}
YAML_SEQ_END();
}
else if (strEqZ(subModuleDef.value, "feature"))
{
testDefModule.feature = yamlScalarNext(yaml).value;
}
else if (strEqZ(subModuleDef.value, "harness"))
{
TestDefHarness testDefHarness = {0};
StringList *harnessIncludeList = strLstNew();
if (yamlEventPeek(yaml).type == yamlEventTypeScalar)
else if (strEqZ(subModuleDef.value, "total"))
{
testDefHarness.name = yamlScalarNext(yaml).value;
testDefModule.total = cvtZToUInt(strZ(yamlScalarNext(yaml).value));
}
else
{
YAML_MAP_BEGIN(yaml)
THROW_FMT(
FormatError, "unexpected scalar '%s' at line %zu, column %zu", strZ(subModuleDef.value),
subModuleDef.line, subModuleDef.column);
}
}
YAML_MAP_END();
// Depend list is the global list minus the coverage and include lists
StringList *const dependList = strLstNew();
for (unsigned int dependIdx = 0; dependIdx < strLstSize(globalDependList); dependIdx++)
{
const String *const depend = strLstGet(globalDependList, dependIdx);
if ((coverageList == NULL || !lstExists(coverageList, &depend)) &&
(includeList == NULL || !strLstExists(includeList, depend)))
{
strLstAdd(dependList, depend);
}
}
// Add test module
MEM_CONTEXT_OBJ_BEGIN(moduleList)
{
testDefModule.name = strDup(testDefModule.name);
testDefModule.coverageList = lstMove(coverageList, memContextCurrent());
testDefModule.flag = strDup(testDefModule.flag);
testDefModule.includeList = strLstMove(includeList, memContextCurrent());
if (strLstSize(dependList) > 0)
testDefModule.dependList = strLstMove(dependList, memContextCurrent());
if (testDefModule.feature != NULL)
testDefModule.feature = strUpper(strDup(testDefModule.feature));
if (strLstSize(globalFeatureList) > 0)
testDefModule.featureList = strLstDup(globalFeatureList);
// Copy harness list
List *const harnessList = lstNewP(sizeof(TestDefHarness), .comparator = lstComparatorStr);
MEM_CONTEXT_OBJ_BEGIN(harnessList)
{
for (unsigned int harnessIdx = 0; harnessIdx < lstSize(globalHarnessList); harnessIdx++)
{
yamlScalarNextCheckZ(yaml, "name");
testDefHarness.name = yamlScalarNext(yaml).value;
const TestDefHarness *const globalHarness = lstGet(globalHarnessList, harnessIdx);
yamlScalarNextCheckZ(yaml, "shim");
YAML_MAP_BEGIN(yaml)
{
const String *const shim = yamlScalarNext(yaml).value;
strLstAdd(harnessIncludeList, shim);
if (yamlEventPeek(yaml).type == yamlEventTypeScalar)
lstAdd(
harnessList,
&(TestDefHarness)
{
yamlScalarNext(yaml);
}
else
{
TestDefShim testDefShim = {.name = shim, .functionList = strLstNew()};
YAML_MAP_BEGIN(yaml)
{
yamlScalarNextCheckZ(yaml, "function");
StringList *const functionList = strLstNew();
YAML_SEQ_BEGIN(yaml)
{
strLstAdd(functionList, yamlScalarNext(yaml).value);
}
YAML_SEQ_END();
testDefShim.functionList = functionList;
}
YAML_MAP_END();
lstAdd(globalShimList, &testDefShim);
}
}
YAML_MAP_END();
.name = strDup(globalHarness->name),
.includeList = strLstDup(globalHarness->includeList),
});
}
YAML_MAP_END();
}
MEM_CONTEXT_OBJ_END();
testDefHarness.includeList = harnessIncludeList;
lstAdd(globalHarnessList, &testDefHarness);
}
else if (strEqZ(subModuleDef.value, "include"))
{
YAML_SEQ_BEGIN(yaml)
testDefModule.harnessList = harnessList;
// Copy shim list
List *const shimList = lstNewP(sizeof(TestDefShim), .comparator = lstComparatorStr);
MEM_CONTEXT_OBJ_BEGIN(shimList)
{
strLstAdd(includeList, yamlEventNext(yaml).value);
for (unsigned int shimIdx = 0; shimIdx < lstSize(globalShimList); shimIdx++)
{
const TestDefShim *const globalShim = lstGet(globalShimList, shimIdx);
lstAdd(
shimList,
&(TestDefShim)
{
.name = strDup(globalShim->name),
.functionList = strLstDup(globalShim->functionList),
});
}
}
YAML_SEQ_END();
MEM_CONTEXT_OBJ_END();
testDefModule.shimList = shimList;
}
else if (strEqZ(subModuleDef.value, "name"))
{
testDefModule.name = strNewFmt("%s/%s", strZ(moduleName), strZ(yamlScalarNext(yaml).value));
}
else if (strEqZ(subModuleDef.value, "total"))
{
testDefModule.total = cvtZToUInt(strZ(yamlScalarNext(yaml).value));
}
else
{
THROW_FMT(
FormatError, "unexpected scalar '%s' at line %zu, column %zu", strZ(subModuleDef.value),
subModuleDef.line, subModuleDef.column);
}
}
YAML_MAP_END();
MEM_CONTEXT_OBJ_END();
// Depend list is the global list minus the coverage and include lists
StringList *const dependList = strLstNew();
for (unsigned int dependIdx = 0; dependIdx < strLstSize(globalDependList); dependIdx++)
{
const String *const depend = strLstGet(globalDependList, dependIdx);
if ((coverageList == NULL || !lstExists(coverageList, &depend)) &&
(includeList == NULL || !strLstExists(includeList, depend)))
{
strLstAdd(dependList, depend);
}
}
// Add test module
MEM_CONTEXT_OBJ_BEGIN(moduleList)
{
testDefModule.name = strDup(testDefModule.name);
testDefModule.coverageList = lstMove(coverageList, memContextCurrent());
testDefModule.flag = strDup(testDefModule.flag);
testDefModule.includeList = strLstMove(includeList, memContextCurrent());
if (strLstSize(dependList) > 0)
testDefModule.dependList = strLstMove(dependList, memContextCurrent());
lstAdd(moduleList, &testDefModule);
// Add feature to global list
if (testDefModule.feature != NULL)
testDefModule.feature = strUpper(strDup(testDefModule.feature));
if (strLstSize(globalFeatureList) > 0)
testDefModule.featureList = strLstDup(globalFeatureList);
// Copy harness list
List *const harnessList = lstNewP(sizeof(TestDefHarness), .comparator = lstComparatorStr);
MEM_CONTEXT_OBJ_BEGIN(harnessList)
{
for (unsigned int harnessIdx = 0; harnessIdx < lstSize(globalHarnessList); harnessIdx++)
{
const TestDefHarness *const globalHarness = lstGet(globalHarnessList, harnessIdx);
lstAdd(
harnessList,
&(TestDefHarness)
{
.name = strDup(globalHarness->name),
.includeList = strLstDup(globalHarness->includeList),
});
}
}
MEM_CONTEXT_OBJ_END();
testDefModule.harnessList = harnessList;
// Copy shim list
List *const shimList = lstNewP(sizeof(TestDefShim), .comparator = lstComparatorStr);
MEM_CONTEXT_OBJ_BEGIN(shimList)
{
for (unsigned int shimIdx = 0; shimIdx < lstSize(globalShimList); shimIdx++)
{
const TestDefShim *const globalShim = lstGet(globalShimList, shimIdx);
lstAdd(
shimList,
&(TestDefShim)
{
.name = strDup(globalShim->name),
.functionList = strLstDup(globalShim->functionList),
});
}
}
MEM_CONTEXT_OBJ_END();
testDefModule.shimList = shimList;
strLstAdd(globalFeatureList, testDefModule.feature);
}
MEM_CONTEXT_OBJ_END();
lstAdd(moduleList, &testDefModule);
// Add feature to global list
if (testDefModule.feature != NULL)
strLstAdd(globalFeatureList, testDefModule.feature);
YAML_SEQ_END();
}
YAML_SEQ_END();
YAML_MAP_END();
}
YAML_MAP_END();
YAML_SEQ_END();
}
YAML_SEQ_END();
FUNCTION_LOG_RETURN_VOID();
}
@ -307,7 +323,6 @@ testDefParse(const Storage *const storageRepo)
yamlEventNextCheck(yaml, yamlEventTypeMapBegin);
// Parse unit tests
yamlScalarNextCheckZ(yaml, "unit");
testDefParseModuleList(yaml, moduleList);
}
MEM_CONTEXT_TEMP_END();

View File

@ -8,6 +8,14 @@ Parse Define Yaml
#include "common/type/string.h"
#include "storage/storage.h"
// Test definition types
typedef enum
{
testDefTypeUnit,
testDefTypeIntegration,
testDefTypePerformance,
} TestDefType;
// Covered code modules
typedef struct TestDefCoverage
{
@ -33,7 +41,9 @@ typedef struct TestDefShim
typedef struct TestDefModule
{
const String *name; // Test module name
TestDefType type; // Module type (unit, performance, etc.)
unsigned int total; // Total sub-tests
bool pgRequired; // Is PostgreSQL required?
bool binRequired; // Is a binary required to run this test?
bool containerRequired; // Is a container required to run this test?
const String *flag; // Compilation flags

View File

@ -74,7 +74,7 @@ void
cmdTest(
const String *const pathRepo, const String *const pathTest, const String *const vm, const unsigned int vmId,
const StringList *moduleFilterList, const unsigned int test, const uint64_t scale, const LogLevel logLevel,
const bool logTime, const String *const timeZone, const bool repoCopy, const bool valgrind, const bool coverage, const bool run)
const bool logTime, const String *const timeZone, const bool coverage, const bool profile, const bool optimize)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STRING, pathRepo);
@ -87,10 +87,9 @@ cmdTest(
FUNCTION_LOG_PARAM(ENUM, logLevel);
FUNCTION_LOG_PARAM(BOOL, logTime);
FUNCTION_LOG_PARAM(STRING, timeZone);
FUNCTION_LOG_PARAM(BOOL, repoCopy);
FUNCTION_LOG_PARAM(BOOL, valgrind);
FUNCTION_LOG_PARAM(BOOL, coverage);
FUNCTION_LOG_PARAM(BOOL, run);
FUNCTION_LOG_PARAM(BOOL, profile);
FUNCTION_LOG_PARAM(BOOL, optimize);
FUNCTION_LOG_END();
MEM_CONTEXT_TEMP_BEGIN()
@ -98,231 +97,98 @@ cmdTest(
// Log file name
cmdTestExecLog = strNewFmt("%s/exec-%u.log", strZ(pathTest), vmId);
// Create data path
if (run)
{
const Storage *const storageHrnId = storagePosixNewP(strNewFmt("%s/data-%u", strZ(pathTest), vmId), .write = true);
cmdTestPathCreate(storageHrnId, NULL);
}
// Find test
ASSERT(!strLstEmpty(moduleFilterList));
// Copy the source repository if requested (otherwise defaults to source code repository)
const String *pathRepoCopy = pathRepo;
const TestDef testDef = testDefParse(storagePosixNewP(pathRepo));
const String *const moduleName = strLstGet(moduleFilterList, 0);
const TestDefModule *const module = lstFind(testDef.moduleList, &moduleName);
if (repoCopy)
{
pathRepoCopy = strNewFmt("%s/repo", strZ(pathTest));
const Storage *const storageRepoCopy = storagePosixNewP(pathRepoCopy, .write = true);
if (module == NULL)
THROW_FMT(ParamInvalidError, "'%s' is not a valid test", strZ(moduleName));
LOG_DETAIL_FMT("sync repo to %s", strZ(pathRepoCopy));
storagePathCreateP(storageRepoCopy, NULL, .mode = 0770);
cmdTestExec(
strNewFmt(
"git -C %s ls-files -c --others --exclude-standard | rsync -LtW --files-from=- %s/ %s", strZ(pathRepo),
strZ(pathRepo), strZ(pathRepoCopy)));
}
// Build code (??? better to do this only when it is needed)
if (run)
cmdTestExec(strNewFmt("%s/build/%s/src/build-code postgres %s/extra", strZ(pathTest), strZ(vm), strZ(pathRepoCopy)));
// Build test list
const TestDef testDef = testDefParse(storagePosixNewP(pathRepoCopy));
StringList *const moduleList = strLstNew();
bool binRequired = false;
if (strLstEmpty(moduleFilterList))
{
StringList *const moduleFilterListEmpty = strLstNew();
strLstAddZ(moduleFilterListEmpty, "");
moduleFilterList = moduleFilterListEmpty;
}
for (unsigned int moduleFilterIdx = 0; moduleFilterIdx < strLstSize(moduleFilterList); moduleFilterIdx++)
{
const String *const moduleFilter = strLstGet(moduleFilterList, moduleFilterIdx);
if (strEmpty(moduleFilter) || strEndsWithZ(moduleFilter, "/"))
{
bool found = false;
for (unsigned int moduleIdx = 0; moduleIdx < lstSize(testDef.moduleList); moduleIdx++)
{
const TestDefModule *const module = lstGet(testDef.moduleList, moduleIdx);
// ??? Container tests don not run yet
if (module->containerRequired)
continue;
if (strEmpty(moduleFilter) || strBeginsWith(module->name, moduleFilter))
{
strLstAddIfMissing(moduleList, module->name);
found = true;
if (module->binRequired)
binRequired = true;
}
}
if (!found)
THROW_FMT(ParamInvalidError, "'%s' prefix does not match any tests", strZ(moduleFilter));
}
else
{
const TestDefModule *const module = lstFind(testDef.moduleList, &moduleFilter);
if (module == NULL)
THROW_FMT(ParamInvalidError, "'%s' is not a valid test", strZ(moduleFilter));
strLstAddIfMissing(moduleList, module->name);
}
}
// Build pgbackrest exe
if (run && binRequired)
{
LOG_INFO("build pgbackrest");
cmdTestExec(strNewFmt("ninja -C %s/build/none src/pgbackrest", strZ(pathTest)));
}
// Process test list
unsigned int errorTotal = 0;
// Build test
bool buildRetry = false;
String *const mesonSetupLast = strNew();
const String *const pathUnit = strNewFmt("%s/unit-%u/%s", strZ(pathTest), vmId, strZ(vm));
const String *const pathUnitBuild = strNewFmt("%s/build", strZ(pathUnit));
const Storage *const storageUnitBuild = storagePosixNewP(pathUnitBuild, .write = true);
for (unsigned int moduleIdx = 0; moduleIdx < strLstSize(moduleList); moduleIdx++)
do
{
const String *const moduleName = strLstGet(moduleList, moduleIdx);
const TestDefModule *const module = lstFind(testDef.moduleList, &moduleName);
CHECK(AssertError, module != NULL, "unable to find module");
TRY_BEGIN()
{
// Build unit test
const String *const pathUnit = strNewFmt("%s/unit-%u/%s", strZ(pathTest), vmId, strZ(vm));
const String *const pathUnitBuild = strNewFmt("%s/build", strZ(pathUnit));
const Storage *const storageUnitBuild = storagePosixNewP(pathUnitBuild, .write = true);
const TimeMSec buildTimeBegin = timeMSec();
TimeMSec buildTimeEnd;
TestBuild *testBld;
// Build unit
TestBuild *const testBld = testBldNew(
pathRepo, pathTest, vm, vmId, module, test, scale, logLevel, logTime, timeZone, coverage, profile, optimize);
testBldUnit(testBld);
TRY_BEGIN()
// Meson setup
String *const mesonSetup = strCatZ(strNew(), "-Dbuildtype=");
if (module->flag != NULL || profile || module->type == testDefTypePerformance)
{
// Build unit
testBld = testBldNew(
pathRepoCopy, pathTest, vm, vmId, module, test, scale, logLevel, logTime, timeZone, coverage);
testBldUnit(testBld);
// Meson setup
String *const mesonSetup = strCatZ(strNew(), "-Dbuildtype=");
if (module->flag != NULL)
{
ASSERT(strEqZ(module->flag, "-DNDEBUG"));
strCatZ(mesonSetup, "release");
}
else
strCatZ(mesonSetup, "debug");
strCatFmt(mesonSetup, " -Db_coverage=%s", cvtBoolToConstZ(coverage));
if (!storageExistsP(testBldStorageTest(testBld), strNewFmt("%s/build.ninja", strZ(pathUnitBuild))))
{
LOG_DETAIL("meson setup");
cmdTestExec(
strNewFmt(
"meson setup -Dwerror=true -Dfatal-errors=true %s %s %s", strZ(mesonSetup), strZ(pathUnitBuild),
strZ(pathUnit)));
}
// Else reconfigure as needed
else if (!strEq(mesonSetup, mesonSetupLast))
{
LOG_DETAIL("meson configure");
cmdTestExec(strNewFmt("meson configure %s %s", strZ(mesonSetup), strZ(pathUnitBuild)));
}
strCat(strTrunc(mesonSetupLast), mesonSetup);
// Remove old coverage data. Note that coverage can be in different paths depending on the meson version.
const String *const pathCoverage = storagePathExistsP(storageUnitBuild, STRDEF("test-unit.p")) ?
STRDEF("test-unit.p") : STRDEF("test-unit@exe");
StorageIterator *const storageItr = storageNewItrP(
storageUnitBuild, pathCoverage, .expression = STRDEF("\\.gcda$"));
while (storageItrMore(storageItr))
{
storageRemoveP(
storageUnitBuild, strNewFmt("%s/%s", strZ(pathCoverage), strZ(storageItrNext(storageItr).name)));
}
// Ninja build
cmdTestExec(strNewFmt("ninja -C %s", strZ(pathUnitBuild)));
buildTimeEnd = timeMSec();
buildRetry = false;
ASSERT(module->flag == NULL || strEqZ(module->flag, "-DNDEBUG"));
strCatZ(mesonSetup, "release");
}
CATCH_ANY()
else
strCatZ(mesonSetup, "debug");
strCatFmt(mesonSetup, " -Db_coverage=%s", cvtBoolToConstZ(coverage));
if (!storageExistsP(testBldStorageTest(testBld), strNewFmt("%s/build.ninja", strZ(pathUnitBuild))))
{
// If this is the first build failure then clean the build path a retry
if (buildRetry == false)
{
buildRetry = true;
moduleIdx--;
LOG_DETAIL("meson setup");
LOG_WARN_FMT("build failed for unit %s -- will retry: %s", strZ(moduleName), errorMessage());
cmdTestPathCreate(storagePosixNewP(pathTest, .write = true), pathUnit);
}
// Else error
else
{
buildRetry = false;
RETHROW();
}
cmdTestExec(
strNewFmt(
"meson setup -Dwerror=true -Dfatal-errors=true %s %s %s", strZ(mesonSetup), strZ(pathUnitBuild),
strZ(pathUnit)));
}
TRY_END();
// Skip test if build needs to be retried
if (run && !buildRetry)
// Else reconfigure
else
{
// Create test path
const Storage *const storageTestId = storagePosixNewP(
strNewFmt("%s/test-%u", strZ(testBldPathTest(testBld)), testBldVmId(testBld)), .write = true);
LOG_DETAIL("meson configure");
cmdTestPathCreate(storageTestId, NULL);
// Unit test
const TimeMSec runTimeBegin = timeMSec();
String *const command = strNew();
if (valgrind)
strCatZ(command, "valgrind -q ");
strCatFmt(command, "%s/test-unit", strZ(pathUnitBuild));
cmdTestExec(command);
const TimeMSec runTimeEnd = timeMSec();
LOG_INFO_FMT(
"test unit %s (bld=%.3fs, run=%.3fs)", strZ(moduleName),
(double)(buildTimeEnd - buildTimeBegin) / (double)MSEC_PER_SEC,
(double)(runTimeEnd - runTimeBegin) / (double)MSEC_PER_SEC);
cmdTestExec(strNewFmt("meson configure %s %s", strZ(mesonSetup), strZ(pathUnitBuild)));
}
// Remove old coverage data. Note that coverage can be in different paths depending on the meson version.
const String *const pathCoverage = storagePathExistsP(storageUnitBuild, STRDEF("test-unit.p")) ?
STRDEF("test-unit.p") : STRDEF("test-unit@exe");
StorageIterator *const storageItr = storageNewItrP(
storageUnitBuild, pathCoverage, .expression = STRDEF("\\.gcda$"));
while (storageItrMore(storageItr))
{
storageRemoveP(
storageUnitBuild, strNewFmt("%s/%s", strZ(pathCoverage), strZ(storageItrNext(storageItr).name)));
}
// Remove old profile data
storageRemoveP(storageUnitBuild, STRDEF("gmon.out"));
// Ninja build
cmdTestExec(strNewFmt("ninja -C %s", strZ(pathUnitBuild)));
buildRetry = false;
}
CATCH_ANY()
{
LOG_ERROR_FMT(errorCode(), "test unit %s failed: %s", strZ(moduleName), errorMessage());
errorTotal++;
// If this is the first build failure then clean the build path a retry
if (buildRetry == false)
{
buildRetry = true;
LOG_WARN_FMT("build failed for unit %s -- will retry: %s", strZ(moduleName), errorMessage());
cmdTestPathCreate(storagePosixNewP(pathTest, .write = true), pathUnit);
}
// Else error
else
THROWP_FMT(errorType(), "build failed for unit %s: %s", strZ(moduleName), errorMessage());
}
TRY_END();
}
// Report errors
if (errorTotal > 0)
THROW_FMT(CommandError, "%u test failure(s)", errorTotal);
while (buildRetry);
}
MEM_CONTEXT_TEMP_END();

View File

@ -14,7 +14,7 @@ Functions
***********************************************************************************************************************************/
void cmdTest(
const String *pathRepo, const String *pathTest, const String *const vm, unsigned int vmId, const StringList *moduleFilterList,
unsigned int test, uint64_t scale, LogLevel logLevel, bool logTime, const String *timeZone, bool repoCopy, bool valgrind,
bool coverage, bool run);
unsigned int test, uint64_t scale, LogLevel logLevel, bool logTime, const String *timeZone, bool coverage, bool profile,
bool optimize);
#endif

View File

@ -74,8 +74,8 @@ main(int argListSize, const char *argList[])
cfgOptionStr(cfgOptRepoPath), cfgOptionStr(cfgOptTestPath), cfgOptionStr(cfgOptVm),
cfgOptionUInt(cfgOptVmId), cfgCommandParam(), cfgOptionTest(cfgOptTest) ? cfgOptionUInt(cfgOptTest) : 0,
cfgOptionUInt64(cfgOptScale), logLevelEnum(cfgOptionStrId(cfgOptLogLevelTest)),
cfgOptionBool(cfgOptLogTimestamp), cfgOptionStrNull(cfgOptTz), cfgOptionBool(cfgOptRepoCopy),
cfgOptionBool(cfgOptValgrind), cfgOptionBool(cfgOptCoverage), cfgOptionBool(cfgOptRun));
cfgOptionBool(cfgOptLogTimestamp), cfgOptionStrNull(cfgOptTz), cfgOptionBool(cfgOptCoverage),
cfgOptionBool(cfgOptProfile), cfgOptionBool(cfgOptOptimize));
break;
}

View File

@ -6,12 +6,10 @@ This wrapper runs the C unit tests.
#include "build.auto.h"
// Enable FUNCTION_TEST*() macros for enhanced debugging
#ifndef DEBUG_TEST_TRACE
#define DEBUG_TEST_TRACE
#endif
{[C_TEST_DEBUG_TEST_TRACE]}
// This must be before all includes except build.auto.h
#ifdef HRN_FEATURE_MEMCONTEXT
// Enable memory debugging
#if defined(HRN_FEATURE_MEMCONTEXT) && defined(DEBUG)
#define DEBUG_MEM
#endif
@ -290,5 +288,15 @@ main(int argListSize, const char *argList[])
TRY_END();
#endif
// Switch to build path when profiling so profile data gets written to a predictable location
#if {[C_TEST_PROFILE]}
if (chdir("{[C_TEST_PATH_BUILD]}") != 0)
{
fprintf(stderr, "unable to chdir to '{[C_TEST_PATH_BUILD]}'");
fflush(stderr);
result = 25;
}
#endif
FUNCTION_HARNESS_RETURN(INT, result);
}