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

Add HARNESS_FORK for tests that require fork().

A standard pattern for tests makes fork() easier to use and should help prevent some common mistakes.
This commit is contained in:
David Steele 2018-05-06 08:56:42 -04:00
parent 790f7c7312
commit 4d6a51ac47
8 changed files with 190 additions and 111 deletions

View File

@ -254,6 +254,10 @@
<p>Add <code>harnessCfgLoad()</code> test function, which allows a new config to be loaded for unit testing without resetting log functions, opening a log file, or taking locks.</p>
</release-item>
<release-item>
<p>Add <code>HARNESS_FORK</code> for tests that require fork(). A standard pattern for tests makes fork() easier to use and should help prevent some common mistakes.</p>
</release-item>
<release-item>
<p>Add <code>TEST_ERROR_FMT</code> macro to simplify testing of formatted error messages.</p>
</release-item>

View File

@ -0,0 +1,103 @@
/***********************************************************************************************************************************
Harness for Testing Using Fork
Sometimes it is useful to use a child process for testing. This can be to test interaction with another process or to avoid
polluting memory in the main process with something that can't easily be undone.
The general form of the fork harness is:
HARNESS_FORK_BEGIN()
{
// This block is required
HARNESS_FORK_CHILD()
{
// Child test code goes here
}
// This block is optional
HARNESS_FORK_PARENT()
{
// Parent test code goes here
}
// If the exit result from the child process is expected to be non-zero then that must be set with this optional statement
HARNESS_FORK_CHILD_EXPECTED_EXIT_STATUS_SET(<non-zero exit status>);
}
HARNESS_FORK_END()
If the child process does not explicitly exit in HARNESS_FORK_CHILD() then it will exit with 0 at HARNESS_FORK_END(). This harness
is not intended for long-lived child processes.
There should not be any code outside the HARNESS_FORK_CHILD() and HARNESS_FORK_PARENT() blocks except the
HARNESS_FORK_CHILD_EXPECTED_EXIT_STATUS_SET() macro unless the code is intended to run in both the child and parent process which is
rare.
***********************************************************************************************************************************/
#include <sys/wait.h>
#include <unistd.h>
/***********************************************************************************************************************************
HARNESS_FORK_PROCESS_ID()
Return the id of the child process, 0 if in the child process.
***********************************************************************************************************************************/
#define HARNESS_FORK_PROCESS_ID() \
HARNESS_FORK_processId
/***********************************************************************************************************************************
HARNESS_FORK_CHILD_EXPECTED_EXIT_STATUS()
At the end of the HARNESS_FORK block the parent will wait for the child to exit. By default an exit code of 0 is expected but that
can be modified with the _SET macro
***********************************************************************************************************************************/
#define HARNESS_FORK_CHILD_EXPECTED_EXIT_STATUS() \
HARNESS_FORK_expectedExitCode
#define HARNESS_FORK_CHILD_EXPECTED_EXIT_STATUS_SET(expectedExitStatus) \
HARNESS_FORK_CHILD_EXPECTED_EXIT_STATUS() = expectedExitStatus;
/***********************************************************************************************************************************
HARNESS_FORK_BEGIN()
Performs the fork and stores the process id.
***********************************************************************************************************************************/
#define HARNESS_FORK_BEGIN() \
{ \
pid_t HARNESS_FORK_PROCESS_ID() = fork(); \
int HARNESS_FORK_CHILD_EXPECTED_EXIT_STATUS() = 0;
/***********************************************************************************************************************************
HARNESS_FORK_CHILD()
Is this the child process?
***********************************************************************************************************************************/
#define HARNESS_FORK_CHILD() \
if (HARNESS_FORK_PROCESS_ID() == 0)
/***********************************************************************************************************************************
HARNESS_FORK_PARENT()
Is this the parent process?
***********************************************************************************************************************************/
#define HARNESS_FORK_PARENT() \
if (HARNESS_FORK_PROCESS_ID() != 0)
/***********************************************************************************************************************************
HARNESS_FORK_END()
Finish the fork block by waiting for the child to exit.
***********************************************************************************************************************************/
#define HARNESS_FORK_END() \
HARNESS_FORK_CHILD() \
exit(0); \
\
HARNESS_FORK_PARENT() \
{ \
int processStatus; \
\
if (waitpid(HARNESS_FORK_PROCESS_ID(), &processStatus, 0) != HARNESS_FORK_PROCESS_ID()) \
THROW_SYS_ERROR(AssertError, "unable to find child process"); \
\
if (WEXITSTATUS(processStatus) != HARNESS_FORK_CHILD_EXPECTED_EXIT_STATUS()) \
THROW_FMT(AssertError, "child exited with error %d", WEXITSTATUS(processStatus)); \
} \
}

View File

@ -1,12 +1,12 @@
/***********************************************************************************************************************************
Test Archive Get Command
***********************************************************************************************************************************/
#include <sys/wait.h>
#include "common/harnessConfig.h"
#include "postgres/type.h"
#include "postgres/version.h"
#include "common/harnessConfig.h"
#include "common/harnessFork.h"
/***********************************************************************************************************************************
Test Run
***********************************************************************************************************************************/
@ -120,25 +120,15 @@ testRun()
strLstAdd(argListTemp, strNewFmt("--pg1-path=%s/db", testPath()));
harnessCfgLoad(strLstSize(argListTemp), strLstPtr(argListTemp));
int processId = fork();
// Test this in a fork so we can use different Perl options in later tests
if (processId == 0)
HARNESS_FORK_BEGIN()
{
TEST_ERROR(cmdArchiveGet(), FileMissingError, "!!!EMBEDDEDPERLERROR!!!");
exit(0);
}
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) != 0) // {uncoverable - correct error code}
THROW_FMT( // {uncoverable+}
AssertError, "perl exited with error %d", WEXITSTATUS(processStatus));
HARNESS_FORK_CHILD()
{
TEST_ERROR(cmdArchiveGet(), FileMissingError, "!!!EMBEDDEDPERLERROR!!!");
}
}
HARNESS_FORK_END();
// -------------------------------------------------------------------------------------------------------------------------
argListTemp = strLstDup(argList);
@ -147,25 +137,16 @@ testRun()
strLstAdd(argListTemp, walFile);
strLstAddZ(argListTemp, "--archive-async");
harnessCfgLoad(strLstSize(argListTemp), strLstPtr(argListTemp));
processId = fork();
// Test this in a fork so we can use different Perl options in later tests
if (processId == 0)
HARNESS_FORK_BEGIN()
{
TEST_ERROR(cmdArchiveGet(), FileMissingError, "!!!EMBEDDEDPERLERROR!!!");
exit(0);
}
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) != 0) // {uncoverable - correct error code}
THROW_FMT( // {uncoverable+}
AssertError, "perl exited with error %d", WEXITSTATUS(processStatus));
HARNESS_FORK_CHILD()
{
TEST_ERROR(cmdArchiveGet(), FileMissingError, "!!!EMBEDDEDPERLERROR!!!");
}
}
HARNESS_FORK_END();
// Make sure the process times out when there is nothing to get
// -------------------------------------------------------------------------------------------------------------------------

View File

@ -2,8 +2,8 @@
Test Error Handling
***********************************************************************************************************************************/
#include <assert.h>
#include <sys/wait.h>
#include <unistd.h>
#include "common/harnessFork.h"
/***********************************************************************************************************************************
Declare some error locally because real errors won't work for some tests -- they could also break as errors change
@ -272,22 +272,16 @@ testRun()
// *****************************************************************************************************************************
if (testBegin("Uncaught error"))
{
int processId = fork();
// Test in a fork so the process does not actually exit
if (processId == 0)
HARNESS_FORK_BEGIN()
{
THROW(TestChildError, "does not get caught!");
}
else
{
int processStatus;
HARNESS_FORK_CHILD()
{
THROW(TestChildError, "does not get caught!");
}
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));
HARNESS_FORK_CHILD_EXPECTED_EXIT_STATUS_SET(UnhandledError.code);
}
HARNESS_FORK_END();
}
}

View File

@ -1,12 +1,11 @@
/***********************************************************************************************************************************
Test Exit Routines
***********************************************************************************************************************************/
#include <sys/wait.h>
#include <unistd.h>
#include "common/error.h"
#include "config/config.h"
#include "common/harnessFork.h"
/***********************************************************************************************************************************
Test Run
***********************************************************************************************************************************/
@ -25,23 +24,17 @@ testRun()
// *****************************************************************************************************************************
if (testBegin("exitInit() and exitOnSignal()"))
{
int processId = fork();
// If this is the fork
if (processId == 0)
HARNESS_FORK_BEGIN()
{
exitInit();
raise(SIGTERM);
}
else
{
int processStatus;
HARNESS_FORK_CHILD()
{
exitInit();
raise(SIGTERM);
}
if (waitpid(processId, &processStatus, 0) != processId) // {uncoverable - fork() does not fail}
THROW_SYS_ERROR(AssertError, "unable to find child process"); // {uncoverable+}
TEST_RESULT_INT(WEXITSTATUS(processStatus), errorTypeCode(&TermError), "test error result");
HARNESS_FORK_CHILD_EXPECTED_EXIT_STATUS_SET(errorTypeCode(&TermError));
}
HARNESS_FORK_END();
}
// *****************************************************************************************************************************

View File

@ -1,7 +1,7 @@
/***********************************************************************************************************************************
Test Fork Handler
***********************************************************************************************************************************/
#include <sys/wait.h>
#include "common/harnessFork.h"
/***********************************************************************************************************************************
Test Run
@ -13,32 +13,22 @@ testRun()
if (testBegin("forkAndDetach()"))
{
int sessionId = getsid(0);
int processId = fork();
// If this is the fork
if (processId == 0)
HARNESS_FORK_BEGIN()
{
char buffer[1024];
HARNESS_FORK_CHILD()
{
char buffer[1024];
forkDetach();
forkDetach();
TEST_RESULT_BOOL(getsid(0) != sessionId, true, "new session id has been created");
TEST_RESULT_STR(getcwd(buffer, sizeof(buffer)), "/", "current working directory is '/'");
TEST_RESULT_INT(write(STDIN_FILENO, buffer, strlen(buffer)), -1, "write to stdin fails");
TEST_RESULT_INT(write(STDOUT_FILENO, buffer, strlen(buffer)), -1, "write to stdout fails");
TEST_RESULT_INT(write(STDERR_FILENO, buffer, strlen(buffer)), -1, "write to stderr fails");
exit(0);
}
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) != 0)
THROW_FMT(AssertError, "perl exited with error %d", WEXITSTATUS(processStatus));
TEST_RESULT_BOOL(getsid(0) != sessionId, true, "new session id has been created");
TEST_RESULT_STR(getcwd(buffer, sizeof(buffer)), "/", "current working directory is '/'");
TEST_RESULT_INT(write(STDIN_FILENO, buffer, strlen(buffer)), -1, "write to stdin fails");
TEST_RESULT_INT(write(STDOUT_FILENO, buffer, strlen(buffer)), -1, "write to stdout fails");
TEST_RESULT_INT(write(STDERR_FILENO, buffer, strlen(buffer)), -1, "write to stderr fails");
}
}
HARNESS_FORK_END();
}
}

View File

@ -3,6 +3,8 @@ Test Lock Handler
***********************************************************************************************************************************/
#include "common/time.h"
#include "common/harnessFork.h"
/***********************************************************************************************************************************
Test Run
***********************************************************************************************************************************/
@ -74,24 +76,27 @@ testRun()
// -------------------------------------------------------------------------------------------------------------------------
String *backupLock = strNewFmt("%s/main-backup.lock", testPath());
if (fork() == 0)
HARNESS_FORK_BEGIN()
{
TEST_RESULT_BOOL(
lockAcquireFile(backupLock, 0, true), true, "lock on fork");
sleepMSec(500);
exit(0);
}
else
{
sleepMSec(250);
TEST_ERROR(
lockAcquireFile(backupLock, 0, true),
LockAcquireError,
strPtr(
strNewFmt(
"unable to acquire lock on file '%s': Resource temporarily unavailable\n"
"HINT: is another pgBackRest process running?", strPtr(backupLock))));
HARNESS_FORK_CHILD()
{
TEST_RESULT_BOOL(lockAcquireFile(backupLock, 0, true), true, "lock on fork");
sleepMSec(500);
}
HARNESS_FORK_PARENT()
{
sleepMSec(250);
TEST_ERROR(
lockAcquireFile(backupLock, 0, true),
LockAcquireError,
strPtr(
strNewFmt(
"unable to acquire lock on file '%s': Resource temporarily unavailable\n"
"HINT: is another pgBackRest process running?", strPtr(backupLock))));
}
}
HARNESS_FORK_END();
}
// *****************************************************************************************************************************

View File

@ -5,6 +5,8 @@ Test Storage Manager
#include "storage/fileRead.h"
#include "storage/fileWrite.h"
#include "common/harnessFork.h"
/***********************************************************************************************************************************
Test function for path expression
***********************************************************************************************************************************/
@ -93,14 +95,21 @@ testRun()
TEST_RESULT_INT(system(strPtr(strNewFmt("sudo rm %s", strPtr(fileExists)))), 0, "remove exists file");
// -------------------------------------------------------------------------------------------------------------------------
if (fork() == 0)
HARNESS_FORK_BEGIN()
{
sleepMSec(250);
TEST_RESULT_INT(system(strPtr(strNewFmt("touch %s", strPtr(fileExists)))), 0, "create exists file");
exit(0);
}
HARNESS_FORK_CHILD()
{
sleepMSec(250);
TEST_RESULT_INT(system(strPtr(strNewFmt("touch %s", strPtr(fileExists)))), 0, "create exists file");
}
HARNESS_FORK_PARENT()
{
TEST_RESULT_BOOL(storageExistsP(storageTest, fileExists, .timeout = 1), true, "file exists after wait");
}
}
HARNESS_FORK_END();
TEST_RESULT_BOOL(storageExistsP(storageTest, fileExists, .timeout = 1), true, "file exists after wait");
TEST_RESULT_INT(system(strPtr(strNewFmt("sudo rm %s", strPtr(fileExists)))), 0, "remove exists file");
}