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:
parent
790f7c7312
commit
4d6a51ac47
@ -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>
|
||||
|
103
test/src/common/harnessFork.h
Normal file
103
test/src/common/harnessFork.h
Normal 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)); \
|
||||
} \
|
||||
}
|
@ -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
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
// *****************************************************************************************************************************
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
// *****************************************************************************************************************************
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user