1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-03-03 14:52:21 +02:00

Full branch coverage for command/help/help, common/error, common/ini, and common/log modules.

This commit is contained in:
David Steele 2018-05-05 09:38:09 -04:00
parent 90aadc6534
commit 0a860e0b60
11 changed files with 161 additions and 70 deletions

View File

@ -148,7 +148,7 @@
</release-item>
<release-item>
<p>Improve branch coverage in C code.</p>
<p>Full branch coverage in C code.</p>
</release-item>
<release-item>

View File

@ -334,15 +334,15 @@ helpRender()
{
strCat(result, "\ndeprecated name");
if (cfgDefOptionHelpNameAltValueTotal(optionDefId) > 1)
strCat(result, "s"); // {uncovered - no option has more than one alt name}
if (cfgDefOptionHelpNameAltValueTotal(optionDefId) > 1) // {uncovered - no option has more than one alt name}
strCat(result, "s"); // {+uncovered}
strCat(result, ": ");
for (uint nameAltIdx = 0; nameAltIdx < cfgDefOptionHelpNameAltValueTotal(optionDefId); nameAltIdx++)
{
if (nameAltIdx != 0)
strCat(result, ", "); // {uncovered - no option has more than one alt name}
if (nameAltIdx != 0) // {uncovered - no option has more than one alt name}
strCat(result, ", "); // {+uncovered}
strCat(result, cfgDefOptionHelpNameAltValue(optionDefId, nameAltIdx));
}

View File

@ -134,12 +134,17 @@ Does the child error type extend the parent error type?
bool
errorTypeExtends(const ErrorType *child, const ErrorType *parent)
{
// Search for the parent
for (; child && errorTypeParent(child) != child; child = (ErrorType *)errorTypeParent(child))
const ErrorType *find = child;
do
{
if (errorTypeParent(child) == parent)
find = errorTypeParent(find);
// Parent was found
if (find == parent)
return true;
}
while (find != errorTypeParent(find));
// Parent was not found
return false;
@ -232,7 +237,10 @@ True when in catch state and the expected error matches
bool
errorInternalStateCatch(const ErrorType *errorTypeCatch)
{
return errorInternalState() == errorStateCatch && errorInstanceOf(errorTypeCatch) && errorInternalProcess(true);
if (errorInternalState() == errorStateCatch && errorInstanceOf(errorTypeCatch))
return errorInternalProcess(true);
return false;
}
/***********************************************************************************************************************************
@ -288,15 +296,11 @@ errorInternalPropagate()
longjmp(errorContext.jumpList[errorContext.tryTotal - 1], 1);
// If there was no try to catch this error then output to stderr
if (fprintf( // {uncovered - output to stderr is a problem for test harness}
stderr, "\nUncaught %s: %s\n thrown at %s:%d\n\n",
errorName(), errorMessage(), errorFileName(), errorFileLine()) > 0)
{
fflush(stderr); // {+uncovered}
}
fprintf(stderr, "\nUncaught %s: %s\n thrown at %s:%d\n\n", errorName(), errorMessage(), errorFileName(), errorFileLine());
fflush(stderr);
// Exit with failure
exit(EXIT_FAILURE); // {uncovered - exit failure is a problem for test harness}
exit(UnhandledError.code);
}
/***********************************************************************************************************************************

View File

@ -132,9 +132,7 @@ iniParse(Ini *this, const String *content)
{
MEM_CONTEXT_BEGIN(this->memContext)
{
if (this->store != NULL)
kvFree(this->store);
kvFree(this->store);
this->store = kvNew();
if (content != NULL)

View File

@ -168,6 +168,16 @@ logWill(LogLevel logLevel)
return logWillStdOut(logLevel) || logWillStdErr(logLevel) || logWillFile(logLevel);
}
/***********************************************************************************************************************************
Internal write function that handles errors
***********************************************************************************************************************************/
static void
logWrite(int handle, const char *message, size_t messageSize, const char *errorDetail)
{
if ((size_t)write(handle, message, messageSize) != messageSize)
THROW_SYS_ERROR_FMT(FileWriteError, "unable to write %s", errorDetail);
}
/***********************************************************************************************************************************
General log function
***********************************************************************************************************************************/
@ -251,18 +261,9 @@ logInternal(LogLevel logLevel, const char *fileName, const char *functionName, i
// Determine where to log the message based on log-level-stderr
if (logWillStdErr(logLevel))
{
if (write(
logHandleStdErr, logBuffer + messageStdErrPos, bufferPos - messageStdErrPos) != (int)(bufferPos - messageStdErrPos))
{
THROW_SYS_ERROR(FileWriteError, "unable to write log to stderr");
}
}
logWrite(logHandleStdErr, logBuffer + messageStdErrPos, bufferPos - messageStdErrPos, "log to stderr");
else if (logWillStdOut(logLevel))
{
if (write(logHandleStdOut, logBuffer, bufferPos) != (int)bufferPos) // {uncovered - write does not fail}
THROW_SYS_ERROR(FileWriteError, "unable to write log to stdout"); // {uncovered+}
}
logWrite(logHandleStdOut, logBuffer, bufferPos, "log to stdout");
// Log to file
if (logWillFile(logLevel))
@ -272,22 +273,17 @@ logInternal(LogLevel logLevel, const char *fileName, const char *functionName, i
{
// Add a blank line if the file already has content
if (lseek(logHandleFile, 0, SEEK_END) > 0)
{
if (write(logHandleFile, "\n", 1) != 1) // {uncovered - write does not fail}
THROW_SYS_ERROR(FileWriteError, "unable to write banner spacing to file"); // {uncovered +}
}
logWrite(logHandleFile, "\n", 1, "banner spacing to file");
// Write process start banner
const char *banner = "-------------------PROCESS START-------------------\n";
if (write(logHandleFile, banner, strlen(banner)) != (int)strlen(banner)) // {uncovered - write does not fail}
THROW_SYS_ERROR(FileWriteError, "unable to write banner to file"); // {uncovered+}
logWrite(logHandleFile, banner, strlen(banner), "banner to file");
// Mark banner as written
logFileBanner = true;
}
if (write(logHandleFile, logBuffer, bufferPos) != (int)bufferPos) // {uncovered - write does not fail}
THROW_SYS_ERROR(FileWriteError, "unable to write log to file"); // {uncovered+}
logWrite(logHandleFile, logBuffer, bufferPos, "log to file");
}
}

View File

@ -43,7 +43,7 @@ unit:
# ----------------------------------------------------------------------------------------------------------------------------
- name: error
total: 6
total: 8
define: -DNO_ERROR -DNO_LOG
coverage:
@ -85,7 +85,7 @@ unit:
# ----------------------------------------------------------------------------------------------------------------------------
- name: log
total: 4
total: 5
define: -DNO_LOG
coverage:

View File

@ -9,6 +9,9 @@ Test Run
void
testRun()
{
// Create default storage object for testing
Storage *storageTest = storageNewP(strNew(testPath()), .write = true);
// *****************************************************************************************************************************
if (testBegin("cmdArchivePush()"))
{
@ -29,10 +32,12 @@ testRun()
// Make sure the process times out when there is nothing to archive
// -------------------------------------------------------------------------------------------------------------------------
storagePathCreateNP(storageTest, strNewFmt("%s/db/archive_status", testPath()));
strLstAdd(argList, strNewFmt("--spool-path=%s", testPath()));
strLstAddZ(argList, "--archive-async");
strLstAddZ(argList, "--log-level-console=off");
strLstAddZ(argList, "--log-level-stderr=off");
strLstAdd(argList, strNewFmt("--log-path=%s", testPath()));
strLstAdd(argList, strNewFmt("--log-level-file=debug"));
strLstAdd(argList, strNewFmt("--pg1-path=%s/db", testPath()));
harnessCfgLoad(strLstSize(argList), strLstPtr(argList));
@ -40,28 +45,21 @@ testRun()
cmdArchivePush(), ArchiveTimeoutError,
"unable to push WAL segment '000000010000000100000001' asynchronously after 1 second(s)");
// Make sure the process times out when there is nothing to archive and it can't get a lock
// -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_VOID(
lockAcquire(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptStanza), cfgLockType(), 30, true), "acquire lock");
TEST_RESULT_VOID(lockClear(true), "clear lock");
TEST_ERROR(
cmdArchivePush(), ArchiveTimeoutError,
"unable to push WAL segment '000000010000000100000001' asynchronously after 1 second(s)");
// Wait for the lock to release
lockAcquire(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptStanza), cfgLockType(), 30, true);
lockRelease(true);
// Write out a bogus .error file to make sure it is ignored on the first loop
// -------------------------------------------------------------------------------------------------------------------------
String *errorFile = storagePathNP(storageSpool(), strNew(STORAGE_SPOOL_ARCHIVE_OUT "/000000010000000100000001.error"));
// Remove the archive status path so async will error and not overwrite the bogus error file
storagePathRemoveNP(storageTest, strNewFmt("%s/db/archive_status", testPath()));
mkdir(strPtr(strNewFmt("%s/archive", testPath())), 0750);
mkdir(strPtr(strNewFmt("%s/archive/db", testPath())), 0750);
mkdir(strPtr(strNewFmt("%s/archive/db/out", testPath())), 0750);
String *errorFile = storagePathNP(storageSpool(), strNew(STORAGE_SPOOL_ARCHIVE_OUT "/000000010000000100000001.error"));
storagePutNP(storageNewWriteNP(storageSpool(), errorFile), bufNewStr(strNew("25\n" BOGUS_STR)));
TEST_ERROR(cmdArchivePush(), AssertError, BOGUS_STR);
unlink(strPtr(errorFile));
storageRemoveP(storageTest, errorFile, .errorOnMissing = true);
// Write out a valid ok file and test for success
// -------------------------------------------------------------------------------------------------------------------------
@ -71,5 +69,18 @@ testRun()
TEST_RESULT_VOID(cmdArchivePush(), "successful push");
testLogResult("P00 INFO: pushed WAL segment 000000010000000100000001 asynchronously");
storageRemoveP(storageSpool(), strNew(STORAGE_SPOOL_ARCHIVE_OUT "/000000010000000100000001.ok"), .errorOnMissing = true);
// Make sure the process times out when there is nothing to archive and it can't get a lock. This test MUST go last since
// the lock is lost and cannot be closed by the main process.
// -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_VOID(
lockAcquire(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptStanza), cfgLockType(), 30, true), "acquire lock");
TEST_RESULT_VOID(lockClear(true), "clear lock");
TEST_ERROR(
cmdArchivePush(), ArchiveTimeoutError,
"unable to push WAL segment '000000010000000100000001' asynchronously after 1 second(s)");
}
}

View File

@ -2,6 +2,19 @@
Test Error Handling
***********************************************************************************************************************************/
#include <assert.h>
#include <sys/wait.h>
#include <unistd.h>
/***********************************************************************************************************************************
Declare some error locally because real errors won't work for some tests -- they could also break as errors change
***********************************************************************************************************************************/
ERROR_DECLARE(TestParent1Error);
ERROR_DECLARE(TestParent2Error);
ERROR_DECLARE(TestChildError);
ERROR_DEFINE(101, TestParent1Error, TestParent1Error);
ERROR_DEFINE(102, TestParent2Error, TestParent1Error);
ERROR_DEFINE(200, TestChildError, TestParent2Error);
/***********************************************************************************************************************************
testTryRecurse - test to blow up try stack
@ -37,13 +50,22 @@ Test Run
void
testRun()
{
// -----------------------------------------------------------------------------------------------------------------------------
// *****************************************************************************************************************************
if (testBegin("check that try stack is initialized correctly"))
{
assert(errorContext.tryTotal == 0);
}
// -----------------------------------------------------------------------------------------------------------------------------
// *****************************************************************************************************************************
if (testBegin("errorTypeExtends"))
{
assert(errorTypeExtends(&TestParent1Error, &TestParent1Error));
assert(errorTypeExtends(&TestChildError, &TestParent1Error));
assert(errorTypeExtends(&TestChildError, &TestParent2Error));
assert(!errorTypeExtends(&TestChildError, &TestChildError));
}
// *****************************************************************************************************************************
if (testBegin("TRY with no errors"))
{
volatile bool tryDone = false;
@ -72,7 +94,7 @@ testRun()
assert(errorContext.tryTotal == 0);
}
// -----------------------------------------------------------------------------------------------------------------------------
// *****************************************************************************************************************************
if (testBegin("TRY with multiple catches"))
{
volatile bool tryDone = false;
@ -136,7 +158,7 @@ testRun()
assert(errorContext.tryTotal == 0);
}
// -----------------------------------------------------------------------------------------------------------------------------
// *****************************************************************************************************************************
if (testBegin("too deep recursive TRY_ERROR()"))
{
volatile bool tryDone = false;
@ -177,7 +199,7 @@ testRun()
assert(testTryRecurseFinally);
}
// -----------------------------------------------------------------------------------------------------------------------------
// *****************************************************************************************************************************
if (testBegin("THROW_CODE() and THROW_CODE_FMT()"))
{
TRY_BEGIN()
@ -216,7 +238,7 @@ testRun()
TRY_END();
}
// -----------------------------------------------------------------------------------------------------------------------------
// *****************************************************************************************************************************
if (testBegin("THROW_SYS_ERROR() and THROW_SYS_ERROR_FMT()"))
{
TRY_BEGIN()
@ -246,4 +268,26 @@ testRun()
}
TRY_END();
}
// *****************************************************************************************************************************
if (testBegin("Uncaught error"))
{
int processId = fork();
// Test in a fork so the process does not actually exit
if (processId == 0)
{
THROW(TestChildError, "does not get caught!");
}
else
{
int processStatus;
if (waitpid(processId, &processStatus, 0) != processId) // {uncoverable - fork() does not fail}
THROW_SYS_ERROR(AssertError, "unable to find child process"); // {uncoverable+}
if (WEXITSTATUS(processStatus) != UnhandledError.code)
THROW_FMT(AssertError, "fork exited with error %d", WEXITSTATUS(processStatus));
}
}
}

View File

@ -15,7 +15,7 @@ testRun()
TEST_ASSIGN(ini, iniNew(), "new ini");
TEST_RESULT_PTR_NE(ini->memContext, NULL, "mem context is set");
TEST_RESULT_PTR_NE(ini->store, NULL, "stores is set");
TEST_RESULT_PTR_NE(ini->store, NULL, "store is set");
TEST_RESULT_VOID(iniFree(ini), "free ini");
}
@ -33,6 +33,7 @@ testRun()
TEST_ERROR(iniGet(ini, strNew("section2"), strNew("key2")), FormatError, "section 'section2', key 'key2' does not exist");
TEST_RESULT_INT(varInt(iniGetDefault(ini, strNew("section1"), strNew("key1"), NULL)), 11, "get section, key, int");
TEST_RESULT_PTR(iniGetDefault(ini, strNew("section2"), strNew("key2"), NULL), NULL, "get section, key, NULL");
TEST_RESULT_BOOL(
varBool(iniGetDefault(ini, strNew("section3"), strNew("key3"), varNewBool(true))), true, "get section, key, bool");
@ -50,6 +51,7 @@ testRun()
String *content = NULL;
// -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_VOID(iniParse(iniNew(), NULL), "no content");
TEST_ERROR(
iniParse(iniNew(), strNew("compress=y\n")), FormatError, "key/value found outside of section at line 1: compress=y");
TEST_ERROR(iniParse(iniNew(), strNew("[section\n")), FormatError, "ini section should end with ] at line 1: [section");

View File

@ -71,10 +71,23 @@ testRun()
logHandleFile = -1;
}
// *****************************************************************************************************************************
if (testBegin("logWrite()"))
{
// Just test the error here -- success is well tested elsewhere
TEST_ERROR(
logWrite(-1, "message", 7, "invalid handle"), FileWriteError,
"unable to write invalid handle: [9] Bad file descriptor");
}
// *****************************************************************************************************************************
if (testBegin("logInternal()"))
{
TEST_RESULT_VOID(logInit(logLevelOff, logLevelOff, logLevelOff, false), "init logging to off");
TEST_RESULT_VOID(logInternal(logLevelWarn, NULL, NULL, 0, "format"), "message not logged anywhere");
TEST_RESULT_VOID(logInit(logLevelWarn, logLevelOff, logLevelOff, true), "init logging to warn (timestamp on)");
TEST_RESULT_VOID(logFileSet(BOGUS_STR), "ignore bogus filename because file logging is off");
TEST_RESULT_VOID(logInternal(logLevelWarn, NULL, NULL, 0, "TEST"), "log timestamp");
String *logTime = strNewN(logBuffer, 23);
@ -120,6 +133,8 @@ testRun()
logBuffer[0] = 0;
TEST_RESULT_VOID(logInternal(logLevelInfo, "test.c", "test_func", 0, "info message"), "log info");
TEST_RESULT_STR(logBuffer, "P00 INFO: info message\n", " check log");
TEST_RESULT_VOID(logInternal(logLevelInfo, "test.c", "test_func", 0, "info message 2"), "log info");
TEST_RESULT_STR(logBuffer, "P00 INFO: info message 2\n", " check log");
// Reopen invalid log file
logFileSet("/" BOGUS_STR);
@ -140,6 +155,7 @@ testRun()
strPtr(strNewBuf(storageGetNP(storageNewReadNP(storage, stderrFile)))),
"DEBUG: test.c:test_func(): message\n"
"INFO: info message\n"
"INFO: info message 2\n"
"WARN: unable to open log file '/BOGUS': Permission denied\n"
"NOTE: process will continue without log file.\n",
"checkout stderr output");
@ -151,7 +167,8 @@ testRun()
"P00 DEBUG: test.c:test_func(): message\n"
"\n"
"-------------------PROCESS START-------------------\n"
"P00 INFO: info message\n",
"P00 INFO: info message\n"
"P00 INFO: info message 2\n",
"checkout file output");
}
}

View File

@ -91,9 +91,26 @@ testRun()
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), "help from help command");
TEST_RESULT_STR(strPtr(helpRender()), generalHelp, " check text");
// This test is broken up into multiple strings because C99 does not require compilers to support const strings > 4095 bytes
// -------------------------------------------------------------------------------------------------------------------------
const char *commandHelp = strPtr(strNewFmt(
"%s%s",
helpVersion,
" - 'version' command help\n"
"\n"
"Get version.\n"
"\n"
"Displays installed pgBackRest version.\n"));
argList = strLstNew();
strLstAddZ(argList, "/path/to/pgbackrest");
strLstAddZ(argList, "help");
strLstAddZ(argList, "version");
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), "help for version command");
TEST_RESULT_STR(strPtr(helpRender()), commandHelp, " check text");
// This test is broken up into multiple strings because C99 does not require compilers to support const strings > 4095 bytes
// -------------------------------------------------------------------------------------------------------------------------
commandHelp = strPtr(strNewFmt(
"%s%s%s",
helpVersion,
" - 'restore' command help\n"
@ -251,11 +268,6 @@ testRun()
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), "help for archive-push command, buffer-size option");
TEST_RESULT_STR(strPtr(helpRender()), strPtr(strNewFmt("%s\ndefault: 4194304\n", optionHelp)), " check text");
argList = strLstNew();
strLstAddZ(argList, "/path/to/pgbackrest");
strLstAddZ(argList, "help");
strLstAddZ(argList, "archive-push");
strLstAddZ(argList, "buffer-size");
strLstAddZ(argList, "--buffer-size=32768");
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), "help for archive-push command, buffer-size option");
TEST_RESULT_STR(
@ -279,6 +291,13 @@ testRun()
configParse(strLstSize(argList), strLstPtr(argList)), "help for archive-push command, repo1-s3-host option");
TEST_RESULT_STR(strPtr(helpRender()), optionHelp, " check text");
strLstAddZ(argList, "--repo1-type=s3");
strLstAddZ(argList, "--repo1-s3-host=s3-host");
TEST_RESULT_VOID(
configParse(strLstSize(argList), strLstPtr(argList)), "help for archive-push command, repo1-s3-host option");
TEST_RESULT_STR(
strPtr(helpRender()), strPtr(strNewFmt("%s\ncurrent: s3-host\n", optionHelp)), " check text");
// -------------------------------------------------------------------------------------------------------------------------
optionHelp = strPtr(strNewFmt(
"%s - 'backup' command - 'repo-hardlink' option help\n"