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

Add experimental unit test harness written in C.

Having the test harness in C will allow us to remove duplicated Perl code and test on systems where Perl support is not present.

Custom harnesses and shims are currently not implemented, which means only the following tests in the common module will run: error, stack-trace, type-convert, assert-on, mem-context, time, encode, type-object, type-string, type-list, type-buffer, type-variant, reg-exp, log.

The experimental test harness is being committed with partial functionality so it can be used in Windows development. The remaining features will follow as needed.
This commit is contained in:
David Steele 2022-06-23 12:20:56 -04:00
parent b7a1b3ec2c
commit f863fc9888
21 changed files with 1667 additions and 20 deletions

View File

@ -169,3 +169,4 @@ configuration.set('FN_NO_RETURN', '__attribute__((__noreturn__))', description:
# Include subdirs
####################################################################################################################################
subdir('src')
subdir('test/src')

View File

@ -101,6 +101,38 @@ yamlFree(Yaml *const this)
objFree(this);
}
/***********************************************************************************************************************************
Helper macros for iterating sequences and maps
***********************************************************************************************************************************/
#define YAML_ITER_BEGIN(yamlParam, eventBegin) \
do \
{ \
Yaml *const YAML_ITER_yaml = yaml; \
yamlEventNextCheck(YAML_ITER_yaml, eventBegin); \
\
do \
{
#define YAML_ITER_END(eventEnd) \
} \
while (yamlEventPeek(YAML_ITER_yaml).type != eventEnd); \
\
yamlEventNextCheck(YAML_ITER_yaml, eventEnd); \
} \
while (0)
#define YAML_SEQ_BEGIN(yaml) \
YAML_ITER_BEGIN(yaml, yamlEventTypeSeqBegin)
#define YAML_SEQ_END() \
YAML_ITER_END(yamlEventTypeSeqEnd);
#define YAML_MAP_BEGIN(yaml) \
YAML_ITER_BEGIN(yaml, yamlEventTypeMapBegin)
#define YAML_MAP_END() \
YAML_ITER_END(yamlEventTypeMapEnd);
/***********************************************************************************************************************************
Macros for function logging
***********************************************************************************************************************************/

View File

@ -15,30 +15,49 @@ int
main(int argListSize, const char *argList[])
{
// Check parameters
CHECK(ParamInvalidError, argListSize <= 2, "only one parameter allowed");
CHECK(ParamInvalidError, argListSize <= 3, "only two parameters allowed");
// Initialize logging
logInit(logLevelWarn, logLevelError, logLevelOff, false, 0, 1, false);
// If the path was specified
const String *pathRepo;
// Get current working dir
char currentWorkDir[1024];
THROW_ON_SYS_ERROR(getcwd(currentWorkDir, sizeof(currentWorkDir)) == NULL, FormatError, "unable to get cwd");
// If the repo path was specified
const String *pathRepo = strPath(STR(currentWorkDir));
String *const pathOut = strCatZ(strNew(), currentWorkDir);
if (argListSize >= 2)
{
pathRepo = strPath(STR(argList[1]));
}
// Else use current working directory
else
{
char currentWorkDir[1024];
THROW_ON_SYS_ERROR(getcwd(currentWorkDir, sizeof(currentWorkDir)) == NULL, FormatError, "unable to get cwd");
const String *const pathArg = STR(argList[1]);
pathRepo = strPath(STR(currentWorkDir));
if (strBeginsWith(pathArg, FSLASH_STR))
pathRepo = strPath(pathArg);
else
{
pathRepo = strPathAbsolute(pathArg, STR(currentWorkDir));
strCatZ(pathOut, "/src");
}
}
// If the build path was specified
const String *pathBuild = pathRepo;
if (argListSize >= 3)
{
const String *const pathArg = STR(argList[2]);
if (strBeginsWith(pathArg, FSLASH_STR))
pathBuild = strPath(pathArg);
else
pathBuild = strPathAbsolute(pathArg, STR(currentWorkDir));
}
// Render config
const Storage *const storageRepo = storagePosixNewP(pathRepo, .write = true);
bldCfgRender(storageRepo, bldCfgParse(storageRepo), true);
const Storage *const storageRepo = storagePosixNewP(pathRepo);
const Storage *const storageBuild = storagePosixNewP(pathBuild, .write = true);
bldCfgRender(storageBuild, bldCfgParse(storageRepo), true);
return 0;
}

View File

@ -16,7 +16,7 @@ int
main(int argListSize, const char *argList[])
{
// Check parameters
CHECK(ParamInvalidError, argListSize <= 2, "only one parameter allowed");
CHECK(ParamInvalidError, argListSize <= 3, "only two parameters allowed");
// Initialize logging
logInit(logLevelWarn, logLevelError, logLevelOff, false, 0, 1, false);
@ -27,7 +27,7 @@ main(int argListSize, const char *argList[])
// Get repo path (cwd if it was not passed)
const String *pathRepo = strPath(STR(currentWorkDir));
String *const pathOut = strCatZ(strNew(), currentWorkDir);
String *pathBuild = strCatZ(strNew(), currentWorkDir);
if (argListSize >= 2)
{
@ -38,13 +38,24 @@ main(int argListSize, const char *argList[])
else
{
pathRepo = strPathAbsolute(pathArg, STR(currentWorkDir));
strCatZ(pathOut, "/src");
strCatZ(pathBuild, "/src");
}
}
// Render config
// If the build path was specified
if (argListSize >= 3)
{
const String *const pathArg = STR(argList[2]);
if (strBeginsWith(pathArg, FSLASH_STR))
pathBuild = strPath(pathArg);
else
pathBuild = strPathAbsolute(pathArg, STR(currentWorkDir));
}
// Render help
const Storage *const storageRepo = storagePosixNewP(pathRepo);
const Storage *const storageBuild = storagePosixNewP(pathOut, .write = true);
const Storage *const storageBuild = storagePosixNewP(pathBuild, .write = true);
const BldCfg bldCfg = bldCfgParse(storageRepo);
bldHlpRender(storageBuild, bldCfg, bldHlpParse(storageRepo, bldCfg));

View File

@ -23,7 +23,7 @@ configure_file(output: 'build.auto.h', configuration: configuration)
####################################################################################################################################
# Common source used by all targets
####################################################################################################################################
src_common = [
src_common = files(
'common/debug.c',
'common/encode.c',
'common/error.c',
@ -62,7 +62,7 @@ src_common = [
'storage/read.c',
'storage/storage.c',
'storage/write.c',
]
)
####################################################################################################################################
# Build config target

View File

@ -0,0 +1,148 @@
####################################################################################################################################
# Configuration Definition
####################################################################################################################################
####################################################################################################################################
# Commands
####################################################################################################################################
command:
help:
log-level-default: DEBUG
parameter-allowed: true
test: {}
noop:
internal: true
version:
log-level-default: DEBUG
####################################################################################################################################
# Option groups that are not used but must be present for modules to compile
####################################################################################################################################
optionGroup:
pg: {}
repo: {}
####################################################################################################################################
# Options
####################################################################################################################################
option:
# General options
#---------------------------------------------------------------------------------------------------------------------------------
buffer-size:
type: size
internal: true
default: 64KiB
allow-list:
- 16KiB
- 32KiB
- 64KiB
- 128KiB
- 256KiB
- 512KiB
- 1MiB
module:
type: string
command:
test: {}
neutral-umask:
type: boolean
internal: true
default: true
repo-path:
type: string
default: pgbackrest
command:
test: {}
scale:
type: integer
internal: true
default: 1
allow-range: [1, 1000000000000]
command:
test: {}
test:
type: integer
internal: true
required: false
allow-range: [1, 999]
command:
test: {}
test-path:
type: string
default: test
command:
test: {}
tz:
type: string
internal: true
required: false
command:
test: {}
vm:
type: string
internal: true
default: none
command:
test: {}
vm-id:
type: integer
internal: true
default: 0
allow-range: [0, 1024]
command:
test: {}
# Logging options
#---------------------------------------------------------------------------------------------------------------------------------
log-level:
type: string-id
default: info
allow-list:
- off
- error
- warn
- info
- detail
- debug
- trace
log-level-test:
inherit: log-level
default: off
log-timestamp:
type: boolean
default: true
negate: true
command: log-level
# Options that are not used but must be present for modules to compile. All must have a default or not be required.
#---------------------------------------------------------------------------------------------------------------------------------
compress-level-network: {type: string, required: false, command: {noop: {}}}
config: {
type: string, internal: true, default: CFGOPTDEF_CONFIG_PATH "/" PROJECT_CONFIG_FILE, default-literal: true, negate: true}
config-path: {type: string, required: false, command: {noop: {}}}
config-include-path: {
type: string, default: CFGOPTDEF_CONFIG_PATH "/" PROJECT_CONFIG_INCLUDE_PATH, default-literal: true, command: {noop: {}}}
job-retry: {type: string, required: false, deprecate: {job-retry-old: {}}, command: {noop: {}}}
job-retry-interval: {type: string, required: false, command: {noop: {}}}
log-level-file: {type: string, required: false, command: {noop: {}}}
log-level-stderr: {type: string, required: false, command: {noop: {}}}
pg: {type: string, required: false, command: {noop: {}}}
pg-path: {type: string, required: false, command: {noop: {}}}
repo-type: {type: string, required: false, command: {noop: {}}}
repo: {type: string, required: false, command: {noop: {}}}
spool-path: {type: string, required: false, command: {noop: {}}}
stanza: {type: string, required: false, command: {noop: {}}}

View File

@ -0,0 +1,205 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE doc SYSTEM "doc.dtd">
<doc>
<config><config-section-list></config-section-list></config>
<operation>
<operation-general>
<option-list>
<option id="config"><summary></summary><text><p></p></text></option>
<option id="config-path"><summary></summary><text><p></p></text></option>
<option id="config-include-path"><summary></summary><text><p></p></text></option>
<option id="pg"><summary></summary><text><p></p></text></option>
<option id="pg-path"><summary></summary><text><p></p></text></option>
<option id="compress-level-network"><summary></summary><text><p></p></text></option>
<option id="job-retry"><summary></summary><text><p></p></text></option>
<option id="job-retry-interval"><summary></summary><text><p></p></text></option>
<option id="log-level-file"><summary></summary><text><p></p></text></option>
<option id="log-level-stderr"><summary></summary><text><p></p></text></option>
<option id="repo"><summary></summary><text><p></p></text></option>
<option id="repo-type"><summary></summary><text><p></p></text></option>
<option id="spool-path"><summary></summary><text><p></p></text></option>
<option id="stanza"><summary></summary><text><p></p></text></option>
<option id="buffer-size">
<summary>Buffer size for I/O operations.</summary>
<text>
<p>Buffer size used for copy, compress, encrypt, and other operations. The number of buffers used depends on options and each operation may use additional memory, e.g. <id>gz</id> compression may use an additional 256KiB of memory.</p>
<p>Allowed values are <id>16KiB</id>, <id>32KiB</id>, <id>64KiB</id>, <id>128KiB</id>, <id>256KiB</id>, <id>512KiB</id>, and <id>1MiB</id>.</p>
</text>
<example>1MiB</example>
</option>
<option id="log-level">
<summary>Level for harness logging.</summary>
<text>
<p>Log level for the test harness (as opposed to the test being run).</p>
<p>The following log levels are supported:</p>
<list>
<list-item><id>off</id> - No logging at all (not recommended)</list-item>
<list-item><id>error</id> - Log only errors</list-item>
<list-item><id>warn</id> - Log warnings and errors</list-item>
<list-item><id>info</id> - Log info, warnings, and errors</list-item>
<list-item><id>detail</id> - Log detail, info, warnings, and errors</list-item>
<list-item><id>debug</id> - Log debug, detail, info, warnings, and errors</list-item>
<list-item><id>trace</id> - Log trace (very verbose debugging), debug, info, warnings, and errors</list-item>
</list>
</text>
<example>error</example>
</option>
<option id="log-level-test">
<summary>Level for test logging.</summary>
<text>
<p>Log level for the test being run (as opposed to the test harness).</p>
<p>Supported log levels are the same as <br-option>log-level</br-option></p>
</text>
<example>error</example>
</option>
<option id="log-timestamp">
<summary>Enable timestamp in logging.</summary>
<text>
<p>Enables the timestamp in console and file logging. This option is disabled in special situations such as generating documentation.</p>
</text>
<example>n</example>
</option>
<option id="module">
<summary>Module to test.</summary>
<text>
<p>Unit test module to be tested.</p>
</text>
<example>common/error</example>
</option>
<option id="neutral-umask">
<summary>Use a neutral umask.</summary>
<text>
<p>Sets the umask to 0000 so modes are created in a sensible way. The default directory mode is 0750 and default file mode is 0640. The lock and log directories set the directory and file mode to 0770 and 0660 respectively.</p>
<p>To use the executing user's umask instead specify <setting>neutral-umask=n</setting> in the config file or <setting>--no-neutral-umask</setting> on the command line.</p>
</text>
<example>n</example>
</option>
<option id="repo-path">
<summary>Path to code repository.</summary>
<text>
<p>Path to the original code repository or a copy.</p>
</text>
<example>n</example>
</option>
<option id="scale">
<summary>Scale performance test.</summary>
<text>
<p>Allows performance tests to be scaled from the default.</p>
</text>
<example>10</example>
</option>
<option id="test">
<summary>Module test to run.</summary>
<text>
<p>Run an individual test in a module.</p>
</text>
<example>2</example>
</option>
<option id="test-path">
<summary>Path where test will run.</summary>
<text>
<p>This path will be managed entirely by the test harness.</p>
</text>
<example>n</example>
</option>
<option id="tz">
<summary>Timezone for testing.</summary>
<text>
<p>Used to ensure that different timezones do not change test results.</p>
</text>
<example>America/New_York</example>
</option>
<option id="vm">
<summary>VM to run test on.</summary>
<text>
<p>Name of VM to run the test on.</p>
</text>
<example>rh7</example>
</option>
<option id="vm-id">
<summary>VM id to run test on.</summary>
<text>
<p>Allows more than one test to run simultaneously.</p>
</text>
<example>3</example>
</option>
</option-list>
</operation-general>
<command-list>
<command id="help">
<summary>Get help.</summary>
<text>
<p>Three levels of help are provided. If no command is specified then general help will be displayed. If a command is specified (e.g. <cmd>pgbackrest help backup</cmd>) then a full description of the command will be displayed along with a list of valid options. If an option is specified in addition to a command (e.g. <cmd>pgbackrest help backup type</cmd>) then a full description of the option as it applies to the command will be displayed.</p>
</text>
</command>
<!-- Noop command holds options that must be defined but we don't need -->
<command id="noop"><summary></summary><text><p></p></text></command>
<command id="test">
<summary>Run a test.</summary>
<text>
<p>By default tests expect to run with the code repository in a subpath named <path>pgbackrest</path>. Tests will be run in a subpath named <path>test</path>.</p>
<p>A test can be run by using the module option, e.g. <br-option>--module=common/error</br-option>.</p>
</text>
</command>
<command id="version">
<summary>Get version.</summary>
<text>
<p>Displays installed <backrest/> version.</p>
</text>
</command>
</command-list>
</operation>
</doc>

View File

@ -0,0 +1,16 @@
####################################################################################################################################
# Generate help
####################################################################################################################################
test_help_auto_c_inc = custom_target(
'help.auto.c.inc',
output: 'help.auto.c.inc',
depend_files: [
'../../build/config/config.yaml',
'../../build/help/help.xml',
],
command: [
build_help,
'@CURRENT_SOURCE_DIR@/../../..',
'@BUILD_ROOT@/test/src',
],
)

View File

@ -0,0 +1,329 @@
/***********************************************************************************************************************************
Test Build Handler
***********************************************************************************************************************************/
#include "build.auto.h"
#include "build/common/render.h"
#include "command/test/build.h"
#include "common/debug.h"
#include "common/log.h"
#include "common/user.h"
#include "storage/posix/storage.h"
#include "version.h"
/***********************************************************************************************************************************
Object type
***********************************************************************************************************************************/
struct TestBuild
{
TestBuildPub pub; // Publicly accessible variables
};
/***********************************************************************************************************************************
Constants
***********************************************************************************************************************************/
#define MESON_COMMENT_BLOCK \
"############################################################################################################################" \
"########"
/**********************************************************************************************************************************/
TestBuild *
testBldNew(
const String *const pathRepo, const String *const pathTest, const String *const vm, const unsigned int vmId,
const String *const moduleName, const unsigned int test, const uint64_t scale, const LogLevel logLevel, const bool logTime,
const String *const timeZone)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STRING, pathRepo);
FUNCTION_LOG_PARAM(STRING, pathTest);
FUNCTION_LOG_PARAM(STRING, vm);
FUNCTION_LOG_PARAM(UINT, vmId);
FUNCTION_LOG_PARAM(STRING, moduleName);
FUNCTION_LOG_PARAM(UINT, test);
FUNCTION_LOG_PARAM(UINT64, scale);
FUNCTION_LOG_PARAM(ENUM, logLevel);
FUNCTION_LOG_PARAM(BOOL, logTime);
FUNCTION_LOG_PARAM(STRING, timeZone);
FUNCTION_LOG_END();
ASSERT(pathRepo != NULL);
ASSERT(pathTest != NULL);
ASSERT(vm != NULL);
ASSERT(moduleName != NULL);
ASSERT(scale != 0);
TestBuild *this = NULL;
OBJ_NEW_BEGIN(TestBuild, .childQty = MEM_CONTEXT_QTY_MAX)
{
// Create object
this = OBJ_NEW_ALLOC();
*this = (TestBuild)
{
.pub =
{
.pathRepo = strDup(pathRepo),
.pathTest = strDup(pathTest),
.vm = strDup(vm),
.vmId = vmId,
.moduleName = strDup(moduleName),
.test = test,
.scale = scale,
.logLevel = logLevel,
.logTime = logTime,
.timeZone = strDup(timeZone),
},
};
this->pub.storageRepo = storagePosixNewP(testBldPathRepo(this));
this->pub.storageTest = storagePosixNewP(testBldPathTest(this));
// Find the module to test
this->pub.module = lstFind(testDefParse(testBldStorageRepo(this)).moduleList, &this->pub.moduleName);
CHECK(AssertError, this->pub.module != NULL, "unable to find module");
}
OBJ_NEW_END();
FUNCTION_LOG_RETURN(TEST_BUILD, this);
}
/***********************************************************************************************************************************
Write files into the test path and keep a list of files written
***********************************************************************************************************************************/
static void
testBldWrite(const Storage *const storage, StringList *const fileList, const char *const file, const Buffer *const content)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STORAGE, storage);
FUNCTION_LOG_PARAM(STRING_LIST, fileList);
FUNCTION_LOG_PARAM(STRINGZ, file);
FUNCTION_LOG_PARAM(BUFFER, content);
FUNCTION_LOG_END();
MEM_CONTEXT_TEMP_BEGIN()
{
// Only write if the content has changed
const Buffer *const current = storageGetP(storageNewReadP(storage, STR(file), .ignoreMissing = true));
if (current == NULL || !bufEq(content, current))
storagePutP(storageNewWriteP(storage, STR(file)), content);
strLstAddZ(fileList, file);
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
void
testBldUnit(TestBuild *const this)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(TEST_BUILD, this);
FUNCTION_LOG_END();
ASSERT(this != NULL);
userInit();
MEM_CONTEXT_TEMP_BEGIN()
{
const Storage *const storageUnit = storagePosixNewP(
strNewFmt("%s/unit-%u", strZ(testBldPathTest(this)), testBldVmId(this)), .write = true);
const Storage *const storageTestId = storagePosixNewP(
strNewFmt("%s/test-%u", strZ(testBldPathTest(this)), testBldVmId(this)), .write = true);
StringList *const storageUnitList = strLstNew();
// Copy meson_options.txt
// -----------------------------------------------------------------------------------------------------------------------------
testBldWrite(
storageUnit, storageUnitList, "meson_options.txt",
storageGetP(storageNewReadP(testBldStorageRepo(this), STRDEF("meson_options.txt"))));
// Build meson.build
// -----------------------------------------------------------------------------------------------------------------------------
String *const mesonBuild = strCatBuf(
strNew(), storageGetP(storageNewReadP(testBldStorageRepo(this), STRDEF("meson.build"))));
// Comment out subdirs that are not used for testing
strReplace(mesonBuild, STRDEF("subdir('"), STRDEF("# subdir('"));
// Write build.auto.in
strCatZ(
mesonBuild,
"\n"
MESON_COMMENT_BLOCK "\n"
"# Write configuration\n"
MESON_COMMENT_BLOCK "\n"
"configure_file(output: 'build.auto.h', configuration: configuration)\n"
"\n"
"add_global_arguments('-DERROR_MESSAGE_BUFFER_SIZE=131072', language : 'c')\n");
// Configure features
if (testBldModule(this)->feature != NULL)
strCatFmt(mesonBuild, "add_global_arguments('-DHRN_INTEST_%s', language : 'c')\n", strZ(testBldModule(this)->feature));
if (testBldModule(this)->featureList != NULL)
{
for (unsigned int featureIdx = 0; featureIdx < strLstSize(testBldModule(this)->featureList); featureIdx++)
{
strCatFmt(
mesonBuild, "add_global_arguments('-DHRN_FEATURE_%s', language : 'c')\n",
strZ(strLstGet(testBldModule(this)->featureList, featureIdx)));
}
}
// Add compiler flags
if (testBldModule(this)->flag != NULL)
strCatFmt(mesonBuild, "add_global_arguments('%s', language : 'c')\n", strZ(testBldModule(this)->flag));
// Build unit test
strCatZ(
mesonBuild,
"\n"
MESON_COMMENT_BLOCK "\n"
"# Unit test\n"
MESON_COMMENT_BLOCK "\n"
"executable(\n"
" 'test-unit',\n");
for (unsigned int dependIdx = 0; dependIdx < strLstSize(testBldModule(this)->dependList); dependIdx++)
{
strCatFmt(
mesonBuild, " '%s/src/%s.c',\n", strZ(testBldPathRepo(this)),
strZ(strLstGet(testBldModule(this)->dependList, dependIdx)));
}
strCatFmt(
mesonBuild,
" '%s/test/src/common/harnessTest.c',\n"
" 'test.c',\n"
" include_directories:\n"
" include_directories(\n"
" '.',\n"
" '%s/test/src',\n"
" '%s/src',\n"
" ),\n"
")\n",
strZ(testBldPathRepo(this)), strZ(testBldPathRepo(this)), strZ(testBldPathRepo(this)));
testBldWrite(storageUnit, storageUnitList, "meson.build", BUFSTR(mesonBuild));
// Build test.c
// -----------------------------------------------------------------------------------------------------------------------------
String *const testC = strCatBuf(
strNew(), storageGetP(storageNewReadP(testBldStorageRepo(this), STRDEF("test/src/test.c"))));
// Files to test/include
StringList *const testIncludeFileList = strLstNew();
if (testBldModule(this)->coverageList != NULL)
{
for (unsigned int coverageIdx = 0; coverageIdx < lstSize(testBldModule(this)->coverageList); coverageIdx++)
{
const TestDefCoverage *const coverage = lstGet(testBldModule(this)->coverageList, coverageIdx);
if (coverage->coverable)
strLstAdd(testIncludeFileList, coverage->name);
}
}
if (testBldModule(this)->includeList != NULL)
{
for (unsigned int includeIdx = 0; includeIdx < strLstSize(testBldModule(this)->includeList); includeIdx++)
strLstAdd(testIncludeFileList, strLstGet(testBldModule(this)->includeList, includeIdx));
}
String *const testIncludeFile = strNew();
for (unsigned int testIncludeFileIdx = 0; testIncludeFileIdx < strLstSize(testIncludeFileList); testIncludeFileIdx++)
{
if (testIncludeFileIdx != 0)
strCatChr(testIncludeFile, '\n');
strCatFmt(
testIncludeFile, "#include \"%s/src/%s.c\"", strZ(testBldPathRepo(this)),
strZ(strLstGet(testIncludeFileList, testIncludeFileIdx)));
}
strReplace(testC, STRDEF("{[C_INCLUDE]}"), testIncludeFile);
// Test path
strReplace(testC, STRDEF("{[C_TEST_PATH]}"), storagePathP(storageTestId, NULL));
// Harness data path
const String *const pathHarnessData = strNewFmt("%s/data-%u", strZ(testBldPathTest(this)), testBldVmId(this));
strReplace(testC, STRDEF("{[C_HRN_PATH]}"), pathHarnessData);
// Harness repo path
strReplace(testC, STRDEF("{[C_HRN_PATH_REPO]}"), testBldPathRepo(this));
// Path to the project exe when it exists
const String *const pathProjectExe = storagePathP(
testBldStorageTest(this),
strNewFmt("bin/%s%s/" PROJECT_BIN, strZ(testBldVm(this)), strEqZ(testBldVm(this), "none") ? "/src" : ""));
strReplace(testC, STRDEF("{[C_TEST_PROJECT_EXE]}"), pathProjectExe);
// Path to source -- used to construct __FILENAME__ tests
strReplace(testC, STRDEF("{[C_TEST_PGB_PATH]}"), testBldPathRepo(this));
// Test log level
strReplace(
testC, STRDEF("{[C_LOG_LEVEL_TEST]}"),
bldEnum("logLevel", strLower(strNewZ(logLevelStr(testBldLogLevel(this))))));
// Log time/timestamp
strReplace(testC, STRDEF("{[C_TEST_TIMING]}"), STR(cvtBoolToConstZ(testBldLogTime(this))));
// Test timezone
strReplace(
testC, STRDEF("{[C_TEST_TZ]}"),
testBldTimeZone(this) == NULL ?
STRDEF("// No timezone specified") : strNewFmt("setenv(\"TZ\", \"%s\", true);", strZ(testBldTimeZone(this))));
// Scale performance test
strReplace(testC, STRDEF("{[C_TEST_SCALE]}"), strNewFmt("%" PRIu64, testBldScale(this)));
// Does this test run in a container?
strReplace(testC, STRDEF("{[C_TEST_CONTAINER]}"), STR(cvtBoolToConstZ(testBldModule(this)->containerRequired)));
// User/group info
strReplace(testC, STRDEF("{[C_TEST_GROUP]}"), groupName());
strReplace(testC, STRDEF("{[C_TEST_GROUP_ID]}"), strNewFmt("%u", groupId()));
strReplace(testC, STRDEF("{[C_TEST_USER]}"), userName());
strReplace(testC, STRDEF("{[C_TEST_USER_ID]}"), strNewFmt("%u", userId()));
// Test id
strReplace(testC, STRDEF("{[C_TEST_IDX]}"), strNewFmt("%u", testBldVmId(this)));
// Include test file
strReplace(
testC, STRDEF("{[C_TEST_INCLUDE]}"),
strNewFmt(
"#include \"%s/test/src/module/%sTest.c\"", strZ(testBldPathRepo(this)),
strZ(bldEnum(NULL, testBldModuleName(this)))));
// Test list
String *const testList = strNew();
for (unsigned int testIdx = 0; testIdx < testBldModule(this)->total; testIdx++)
{
if (testIdx != 0)
strCatZ(testList, "\n ");
strCatFmt(
testList, "hrnAdd(%3u, %8s);", testIdx + 1,
cvtBoolToConstZ(testBldTest(this) == 0 || testBldTest(this) == testIdx + 1));
}
strReplace(testC, STRDEF("{[C_TEST_LIST]}"), testList);
// Write file
testBldWrite(storageUnit, storageUnitList, "test.c", BUFSTR(testC));
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_VOID();
}

View File

@ -0,0 +1,160 @@
/***********************************************************************************************************************************
Test Build Handler
***********************************************************************************************************************************/
#ifndef TEST_COMMAND_TEST_BUILD_H
#define TEST_COMMAND_TEST_BUILD_H
/***********************************************************************************************************************************
Object type
***********************************************************************************************************************************/
typedef struct TestBuild TestBuild;
#include "command/test/define.h"
#include "common/logLevel.h"
#include "common/type/object.h"
#include "common/type/string.h"
#include "storage/storage.h"
/***********************************************************************************************************************************
Constructors
***********************************************************************************************************************************/
TestBuild *testBldNew(
const String *pathRepo, const String *pathTest, const String *const vm, unsigned int vmId, const String *moduleName,
unsigned int test, uint64_t scale, LogLevel logLevel, bool logTime, const String *timeZone);
/***********************************************************************************************************************************
Getters/Setters
***********************************************************************************************************************************/
typedef struct TestBuildPub
{
const String *pathRepo; // Code repository path
const String *pathTest; // Test path
const Storage *storageRepo; // Repository storage
const Storage *storageTest; // Test storage
const String *vm; // Vm to run the test on
unsigned int vmId; // Vm id (0-based) to run the test on
const String *moduleName; // Module to run
const TestDefModule *module; // Module definition
unsigned int test; // Specific test to run (0 if all)
LogLevel logLevel; // Test log level
bool logTime; // Log times/timestamps
uint64_t scale; // Scale performance test
const String *timeZone; // Test in timezone
TestDef tstDef; // Test definitions
} TestBuildPub;
// Repository path
__attribute__((always_inline)) static inline const String *
testBldPathRepo(const TestBuild *const this)
{
return THIS_PUB(TestBuild)->pathRepo;
}
// Test path
__attribute__((always_inline)) static inline const String *
testBldPathTest(const TestBuild *const this)
{
return THIS_PUB(TestBuild)->pathTest;
}
// Repository storage
__attribute__((always_inline)) static inline const Storage *
testBldStorageRepo(const TestBuild *const this)
{
return THIS_PUB(TestBuild)->storageRepo;
}
// Test storage
__attribute__((always_inline)) static inline const Storage *
testBldStorageTest(const TestBuild *const this)
{
return THIS_PUB(TestBuild)->storageTest;
}
// Vm
__attribute__((always_inline)) static inline const String *
testBldVm(const TestBuild *const this)
{
return THIS_PUB(TestBuild)->vm;
}
// Vm id
__attribute__((always_inline)) static inline unsigned int
testBldVmId(const TestBuild *const this)
{
return THIS_PUB(TestBuild)->vmId;
}
// Test Definition
__attribute__((always_inline)) static inline const TestDefModule *
testBldModule(const TestBuild *const this)
{
return THIS_PUB(TestBuild)->module;
}
// Module to run
__attribute__((always_inline)) static inline const String *
testBldModuleName(const TestBuild *const this)
{
return THIS_PUB(TestBuild)->moduleName;
}
// Specific test to run
__attribute__((always_inline)) static inline unsigned int
testBldTest(const TestBuild *const this)
{
return THIS_PUB(TestBuild)->test;
}
// Log level
__attribute__((always_inline)) static inline LogLevel
testBldLogLevel(const TestBuild *const this)
{
return THIS_PUB(TestBuild)->logLevel;
}
// Log time/timestamps
__attribute__((always_inline)) static inline bool
testBldLogTime(const TestBuild *const this)
{
return THIS_PUB(TestBuild)->logTime;
}
// Test in timezone
__attribute__((always_inline)) static inline const String *
testBldTimeZone(const TestBuild *const this)
{
return THIS_PUB(TestBuild)->timeZone;
}
// Scale
__attribute__((always_inline)) static inline uint64_t
testBldScale(const TestBuild *const this)
{
return THIS_PUB(TestBuild)->scale;
}
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
void testBldUnit(TestBuild *this);
/***********************************************************************************************************************************
Destructor
***********************************************************************************************************************************/
__attribute__((always_inline)) static inline void
testBuildFree(TestBuild *const this)
{
objFree(this);
}
/***********************************************************************************************************************************
Macros for function logging
***********************************************************************************************************************************/
#define FUNCTION_LOG_TEST_BUILD_TYPE \
TestBuild *
#define FUNCTION_LOG_TEST_BUILD_FORMAT(value, buffer, bufferSize) \
objToLog(value, "TestBuild", buffer, bufferSize)
#endif

View File

@ -0,0 +1,256 @@
/***********************************************************************************************************************************
Parse Define Yaml
***********************************************************************************************************************************/
#include "build.auto.h"
#include <yaml.h>
#include "common/debug.h"
#include "common/log.h"
#include "storage/posix/storage.h"
#include "build/common/yaml.h"
#include "command/test/define.h"
/***********************************************************************************************************************************
Parse module list
***********************************************************************************************************************************/
static void
testDefParseModuleList(Yaml *const yaml, List *const moduleList)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(YAML, yaml);
FUNCTION_LOG_PARAM(LIST, moduleList);
FUNCTION_LOG_END();
// Global lists to be copied to next test
StringList *const globalDependList = strLstNew();
StringList *const globalFeatureList = strLstNew();
// Module list
YAML_SEQ_BEGIN(yaml)
{
// Module
YAML_MAP_BEGIN(yaml)
{
// Module name
yamlScalarNextCheckZ(yaml, "name");
const String *const moduleName = yamlScalarNext(yaml).value;
// Submodule List
yamlScalarNextCheckZ(yaml, "test");
YAML_SEQ_BEGIN(yaml)
{
TestDefModule testDefModule = {0};
StringList *includeList = NULL;
List *coverageList = NULL;
// Submodule
YAML_MAP_BEGIN(yaml)
{
YamlEvent subModuleDef = yamlEventNext(yaml);
if (strEqZ(subModuleDef.value, "binReq"))
{
testDefModule.binRequired = yamlBoolParse(yamlScalarNext(yaml));
}
else if (strEqZ(subModuleDef.value, "containerReq"))
{
testDefModule.containerRequired = yamlBoolParse(yamlScalarNext(yaml));
}
else if (strEqZ(subModuleDef.value, "coverage"))
{
coverageList = lstNewP(sizeof(TestDefCoverage), .comparator = lstComparatorStr);
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();
}
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)
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"))
{
if (yamlEventPeek(yaml).type == yamlEventTypeScalar)
{
yamlScalarNext(yaml);
}
else
{
YAML_MAP_BEGIN(yaml)
{
yamlScalarNextCheckZ(yaml, "name");
yamlScalarNext(yaml);
yamlScalarNextCheckZ(yaml, "shim");
YAML_MAP_BEGIN(yaml)
{
yamlScalarNext(yaml);
if (yamlEventPeek(yaml).type == yamlEventTypeScalar)
{
yamlScalarNext(yaml);
}
else
{
YAML_MAP_BEGIN(yaml)
{
yamlScalarNextCheckZ(yaml, "function");
YAML_SEQ_BEGIN(yaml)
{
yamlScalarNext(yaml);
}
YAML_SEQ_END();
}
YAML_MAP_END();
}
}
YAML_MAP_END();
}
YAML_MAP_END();
}
}
else if (strEqZ(subModuleDef.value, "include"))
{
includeList = strLstNew();
YAML_SEQ_BEGIN(yaml)
{
strLstAdd(includeList, yamlEventNext(yaml).value);
}
YAML_SEQ_END();
}
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();
// 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);
}
MEM_CONTEXT_OBJ_END();
lstAdd(moduleList, &testDefModule);
// Add feature to global list
if (testDefModule.feature != NULL)
strLstAdd(globalFeatureList, testDefModule.feature);
}
YAML_SEQ_END();
}
YAML_MAP_END();
}
YAML_SEQ_END();
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
TestDef
testDefParse(const Storage *const storageRepo)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STORAGE, storageRepo);
FUNCTION_LOG_END();
// Module list
List *const moduleList = lstNewP(sizeof(TestDefModule), .comparator = lstComparatorStr);
MEM_CONTEXT_TEMP_BEGIN()
{
// Initialize yaml
Yaml *const yaml = yamlNew(storageGetP(storageNewReadP(storageRepo, STRDEF("test/define.yaml"))));
yamlEventNextCheck(yaml, yamlEventTypeMapBegin);
// Parse unit tests
yamlScalarNextCheckZ(yaml, "unit");
testDefParseModuleList(yaml, moduleList);
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_STRUCT((TestDef){.moduleList = moduleList});
}

View File

@ -0,0 +1,42 @@
/***********************************************************************************************************************************
Parse Define Yaml
***********************************************************************************************************************************/
#ifndef TEST_COMMAND_TEST_DEFINE_H
#define TEST_COMMAND_TEST_DEFINE_H
#include "common/type/list.h"
#include "common/type/string.h"
#include "storage/storage.h"
typedef struct TestDefCoverage
{
const String *name; // Code module name
bool coverable; // Does this code module include coverable code?
} TestDefCoverage;
typedef struct TestDefModule
{
const String *name; // Test module name
unsigned int total; // Total sub-tests
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
const String *feature; // Does this module introduce a feature?
const StringList *featureList; // Features to include in this module
const List *coverageList; // Code modules covered by this test module
const StringList *dependList; // Code modules that this test module depends on
const StringList *includeList; // Additional code modules to include in the test module
} TestDefModule;
typedef struct TestDef
{
const List *moduleList; // Module list
} TestDef;
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
// Parse define.yaml
TestDef testDefParse(const Storage *const storageRepo);
#endif

View File

@ -0,0 +1,74 @@
/***********************************************************************************************************************************
Test Command
***********************************************************************************************************************************/
#include "build.auto.h"
#include <stdlib.h>
#include "command/test/build.h"
#include "common/debug.h"
#include "common/log.h"
#include "config/config.h"
#include "storage/posix/storage.h"
/**********************************************************************************************************************************/
void
cmdTest(
const String *const pathRepo, const String *const pathTest, const String *const vm, const unsigned int vmId,
const String *const moduleName, const unsigned int test, const uint64_t scale, const LogLevel logLevel, const bool logTime,
const String *const timeZone)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STRING, pathRepo);
FUNCTION_LOG_PARAM(STRING, pathTest);
FUNCTION_LOG_PARAM(STRING, vm);
FUNCTION_LOG_PARAM(UINT, vmId);
FUNCTION_LOG_PARAM(STRING, moduleName);
FUNCTION_LOG_PARAM(UINT, test);
FUNCTION_LOG_PARAM(UINT64, scale);
FUNCTION_LOG_PARAM(ENUM, logLevel);
FUNCTION_LOG_PARAM(BOOL, logTime);
FUNCTION_LOG_PARAM(STRING, timeZone);
FUNCTION_LOG_END();
MEM_CONTEXT_TEMP_BEGIN()
{
// Build unit test
TestBuild *const testBld = testBldNew(pathRepo, pathTest, vm, vmId, moduleName, test, scale, logLevel, logTime, timeZone);
testBldUnit(testBld);
// Remove and recreate the test path
const Storage *const storageTestId = storagePosixNewP(
strNewFmt("%s/test-%u", strZ(testBldPathTest(testBld)), testBldVmId(testBld)), .write = true);
storagePathRemoveP(storageTestId, NULL, .recurse = true);
storagePathCreateP(storageTestId, NULL);
// Meson setup
const String *const pathUnit = strNewFmt("%s/unit-%u", strZ(testBldPathTest(testBld)), testBldVmId(testBld));
const String *const pathUnitBuild = strNewFmt("%s/build", strZ(pathUnit));
if (!storageExistsP(testBldStorageTest(testBld), strNewFmt("%s/build.ninja", strZ(pathUnitBuild))))
{
const char *const mesonSetup = zNewFmt(
"meson setup -Dwerror=true -Dfatal-errors=true -Dbuildtype=debug %s %s", strZ(pathUnitBuild), strZ(pathUnit));
if (system(mesonSetup) != 0)
THROW_FMT(ExecuteError, "unable to execute: %s", mesonSetup);
}
// Ninja build
const char *const ninjaBuild = zNewFmt("ninja -C %s", strZ(pathUnitBuild));
if (system(ninjaBuild) != 0)
THROW_FMT(ExecuteError, "unable to execute: %s", ninjaBuild);
// Unit test
const char *const unitTest = zNewFmt("%s/test-unit", strZ(pathUnitBuild));
if (system(unitTest) != 0)
THROW_FMT(ExecuteError, "unable to execute: %s", unitTest);
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_VOID();
}

View File

@ -0,0 +1,19 @@
/***********************************************************************************************************************************
Test Command
Perform a test.
***********************************************************************************************************************************/
#ifndef TEST_COMMAND_TEST_TEST_H
#define TEST_COMMAND_TEST_TEST_H
#include "common/logLevel.h"
#include "common/type/string.h"
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
void cmdTest(
const String *pathRepo, const String *pathTest, const String *const vm, unsigned int vmId, const String *moduleName,
unsigned int test, uint64_t scale, LogLevel logLevel, bool logTime, const String *timeZone);
#endif

View File

@ -33,6 +33,8 @@ There should not be any code outside the HRN_FORK_CHILD_BEGIN/END() and HRN_FORK
#include <sys/wait.h>
#include <unistd.h>
#include <common/type/param.h>
#include <common/harnessLog.h>
/***********************************************************************************************************************************

129
test/src/config/load.c Normal file
View File

@ -0,0 +1,129 @@
/***********************************************************************************************************************************
Configuration Load
***********************************************************************************************************************************/
#include "build.auto.h"
#include <unistd.h>
#include "command/command.h"
#include "common/debug.h"
#include "common/io/io.h"
#include "common/log.h"
#include "config/config.intern.h"
#include "storage/posix/storage.h"
/***********************************************************************************************************************************
Load log settings
***********************************************************************************************************************************/
static void
cfgLoadLogSetting(void)
{
FUNCTION_LOG_VOID(logLevelTrace);
// Initialize logging
LogLevel logLevelConsole = logLevelOff;
bool logTimestamp = true;
if (cfgOptionValid(cfgOptLogLevel))
logLevelConsole = logLevelEnum(cfgOptionStrId(cfgOptLogLevel));
if (cfgOptionValid(cfgOptLogTimestamp))
logTimestamp = cfgOptionBool(cfgOptLogTimestamp);
logInit(logLevelConsole, logLevelOff, logLevelOff, logTimestamp, 0, 1, false);
FUNCTION_LOG_RETURN_VOID();
}
/***********************************************************************************************************************************
Update options that have complex rules
***********************************************************************************************************************************/
static void
cfgLoadUpdateOption(void)
{
FUNCTION_LOG_VOID(logLevelTrace);
// Get current working dir
char currentWorkDir[1024];
THROW_ON_SYS_ERROR(getcwd(currentWorkDir, sizeof(currentWorkDir)) == NULL, FormatError, "unable to get cwd");
// Invalidate config option so it does not show up in option list
cfgOptionInvalidate(cfgOptConfig);
// If repo-path is relative then make it absolute
const String *const repoPath = cfgOptionStr(cfgOptRepoPath);
if (!strBeginsWithZ(repoPath, "/"))
cfgOptionSet(cfgOptRepoPath, cfgOptionSource(cfgOptRepoPath), VARSTR(strNewFmt("%s/%s", currentWorkDir, strZ(repoPath))));
// If test-path is relative then make it absolute
const String *const testPath = cfgOptionStr(cfgOptTestPath);
if (!strBeginsWithZ(testPath, "/"))
cfgOptionSet(cfgOptTestPath, cfgOptionSource(cfgOptTestPath), VARSTR(strNewFmt("%s/%s", currentWorkDir, strZ(testPath))));
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
void
cfgLoad(unsigned int argListSize, const char *argList[])
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(UINT, argListSize);
FUNCTION_LOG_PARAM(CHARPY, argList);
FUNCTION_LOG_END();
MEM_CONTEXT_TEMP_BEGIN()
{
// Make a copy of the arguments so they can be manipulated
StringList *const argListNew = strLstNew();
for (unsigned int argListIdx = 0; argListIdx < argListSize; argListIdx++)
strLstAddZ(argListNew, argList[argListIdx]);
// Explicitly set --no-config so a stray config file will not be loaded
strLstAddZ(argListNew, "--no-" CFGOPT_CONFIG);
// Parse config from command line
TRY_BEGIN()
{
configParse(storagePosixNewP(FSLASH_STR), strLstSize(argListNew), strLstPtr(argListNew), true);
}
CATCH(CommandRequiredError)
{
strLstAddZ(argListNew, CFGCMD_TEST);
configParse(storagePosixNewP(FSLASH_STR), strLstSize(argListNew), strLstPtr(argListNew), true);
}
TRY_END();
// Error on noop command. This command is required to hold options that must be declared but are unused by test.
if (cfgCommand() == cfgCmdNoop)
THROW(CommandInvalidError, "invalid command '" CFGCMD_NOOP "'");
// If a command is set
if (cfgCommand() != cfgCmdNone && cfgCommand() != cfgCmdHelp && cfgCommand() != cfgCmdVersion)
{
// Load the log settings
if (!cfgCommandHelp())
cfgLoadLogSetting();
// Neutralize the umask to make the repository file/path modes more consistent
if (cfgOptionValid(cfgOptNeutralUmask) && cfgOptionBool(cfgOptNeutralUmask))
umask(0000);
// Set IO buffer size
if (cfgOptionValid(cfgOptBufferSize))
ioBufferSizeSet(cfgOptionUInt(cfgOptBufferSize));
// Update options that have complex rules
cfgLoadUpdateOption();
// Begin the command
cmdBegin();
}
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_VOID();
}

13
test/src/config/load.h Normal file
View File

@ -0,0 +1,13 @@
/***********************************************************************************************************************************
Test Configuration Load
***********************************************************************************************************************************/
#ifndef TEST_CONFIG_LOAD_H
#define TEST_CONFIG_LOAD_H
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
// Load the configuration
void cfgLoad(unsigned int argListSize, const char *argList[]);
#endif

View File

@ -0,0 +1,15 @@
####################################################################################################################################
# Generate config
####################################################################################################################################
test_parse_auto_c_inc = custom_target(
'parse.auto.c.inc',
output : 'parse.auto.c.inc',
depend_files: [
'../build/config/config.yaml',
],
command : [
build_config,
'@CURRENT_SOURCE_DIR@/../..',
'@BUILD_ROOT@/test',
],
)

108
test/src/main.c Normal file
View File

@ -0,0 +1,108 @@
/***********************************************************************************************************************************
Main
***********************************************************************************************************************************/
#include "build.auto.h"
#include <stdio.h>
#include "command/command.h"
#include "command/exit.h"
#include "command/help/help.h"
#include "command/test/test.h"
#include "common/debug.h"
#include "common/error.h"
#include "common/log.h"
#include "common/macro.h"
#include "common/memContext.h"
#include "common/stat.h"
#include "config/load.h"
#include "config/parse.h"
#include "storage/posix/storage.h"
#include "version.h"
/***********************************************************************************************************************************
Include automatically generated help data
***********************************************************************************************************************************/
#include "command/help/help.auto.c.inc"
int
main(int argListSize, const char *argList[])
{
// Set stack trace and mem context error cleanup handlers
static const ErrorHandlerFunction errorHandlerList[] = {stackTraceClean, memContextClean};
errorHandlerSet(errorHandlerList, LENGTH_OF(errorHandlerList));
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(INT, argListSize);
FUNCTION_LOG_PARAM(CHARPY, argList);
FUNCTION_LOG_END();
// Initialize command with the start time
cmdInit();
// Initialize statistics collector
statInit();
// Initialize exit handler
exitInit();
// Process commands
volatile int result = 0;
volatile bool error = false;
TRY_BEGIN()
{
// Load the configuration
// -------------------------------------------------------------------------------------------------------------------------
cfgLoad((unsigned int)argListSize, argList);
// Display help
// -------------------------------------------------------------------------------------------------------------------------
if (cfgCommandHelp())
{
cmdHelp(BUF(helpData, sizeof(helpData)));
}
else
{
switch (cfgCommand())
{
// Test
// -----------------------------------------------------------------------------------------------------------------
case cfgCmdTest:
{
cmdTest(
cfgOptionStr(cfgOptRepoPath), cfgOptionStr(cfgOptTestPath), cfgOptionStr(cfgOptVm),
cfgOptionUInt(cfgOptVmId), cfgOptionStr(cfgOptModule),
cfgOptionTest(cfgOptTest) ? cfgOptionUInt(cfgOptTest) : 0, cfgOptionUInt64(cfgOptScale),
logLevelEnum(cfgOptionStrId(cfgOptLogLevelTest)), cfgOptionBool(cfgOptLogTimestamp),
cfgOptionStrNull(cfgOptTz));
break;
}
// Display version
// -----------------------------------------------------------------------------------------------------------------
case cfgCmdVersion:
printf(PROJECT_NAME " Test " PROJECT_VERSION "\n");
fflush(stdout);
break;
// Error on commands that should have already been handled
// -----------------------------------------------------------------------------------------------------------------
case cfgCmdHelp:
case cfgCmdNone:
case cfgCmdNoop:
THROW_FMT(AssertError, "'%s' command should have been handled", cfgCommandName());
break;
}
}
}
CATCH_FATAL()
{
error = true;
result = exitSafe(result, true, 0);
}
TRY_END();
FUNCTION_LOG_RETURN(INT, error ? result : exitSafe(result, false, 0));
}

63
test/src/meson.build Normal file
View File

@ -0,0 +1,63 @@
####################################################################################################################################
# Write configuration
####################################################################################################################################
configure_file(output: 'build.auto.h', configuration: configuration)
####################################################################################################################################
# Build config target
####################################################################################################################################
# build parse.auto.c.inc
subdir('config')
alias_target('test-build-config', test_parse_auto_c_inc)
####################################################################################################################################
# Build help target
####################################################################################################################################
# build help.auto.c.inc
subdir('command/help')
alias_target('test-build-help', test_help_auto_c_inc)
####################################################################################################################################
# test target
####################################################################################################################################
src_test = [
'../../src/build/common/render.c',
'../../src/build/common/yaml.c',
'../../src/command/command.c',
'../../src/command/exit.c',
'../../src/command/help/help.c',
'../../src/common/compress/bz2/common.c',
'../../src/common/compress/bz2/decompress.c',
'../../src/common/ini.c',
'../../src/common/io/fd.c',
'../../src/common/io/fdRead.c',
'../../src/common/io/fdWrite.c',
'../../src/common/lock.c',
'../../src/common/stat.c',
'../../src/common/type/json.c',
'../../src/config/config.c',
'../../src/config/parse.c',
'command/test/build.c',
'command/test/define.c',
'command/test/test.c',
'config/load.c',
'main.c',
]
test_pgbackrest = executable(
'test-pgbackrest',
src_common,
src_test,
test_help_auto_c_inc,
test_parse_auto_c_inc,
include_directories: include_directories('.', '../../src'),
dependencies: [
lib_bz2,
lib_yaml,
],
build_by_default: false,
)
alias_target('test-pgbackrest', test_pgbackrest)

View File

@ -5,6 +5,11 @@ 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
// This must be before all includes except build.auto.h
#ifdef HRN_FEATURE_MEMCONTEXT
#define DEBUG_MEM