summaryrefslogtreecommitdiff
path: root/src/mongo/unittest/death_test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/unittest/death_test.cpp')
-rw-r--r--src/mongo/unittest/death_test.cpp119
1 files changed, 81 insertions, 38 deletions
diff --git a/src/mongo/unittest/death_test.cpp b/src/mongo/unittest/death_test.cpp
index 31add6149ac..b33900aff90 100644
--- a/src/mongo/unittest/death_test.cpp
+++ b/src/mongo/unittest/death_test.cpp
@@ -30,6 +30,10 @@
#include "mongo/platform/basic.h"
+#include <fmt/format.h>
+#include <stdio.h>
+
+#include "mongo/bson/json.h"
#include "mongo/unittest/death_test.h"
#ifndef _WIN32
@@ -47,23 +51,36 @@
#include "mongo/logv2/log.h"
#include "mongo/util/assert_util.h"
+#include "mongo/util/debugger.h"
#include "mongo/util/quick_exit.h"
-#define checkSyscall(EXPR) \
- do { \
- if (-1 == (EXPR)) { \
- const int err = errno; \
- LOGV2_ERROR(24138, \
- "{expr} failed: {errno}", \
- "expr"_attr = #EXPR, \
- "errno"_attr = errnoWithDescription(err)); \
- invariantFailed("-1 != (" #EXPR ")", __FILE__, __LINE__); \
- } \
- } while (false)
-
namespace mongo {
namespace unittest {
+class DeathTestSyscallException : public std::runtime_error {
+public:
+ using std::runtime_error::runtime_error;
+};
+
+#define logAndThrowWithErrno(expr) logAndThrowWithErrnoAt(expr, __FILE__, __LINE__, errno)
+
+void logAndThrowWithErrnoAt(const StringData expr,
+ const StringData file,
+ const unsigned line,
+ const int err) {
+ using namespace fmt::literals;
+ LOGV2_ERROR(24138,
+ "{expr} failed: {error} @{file}:{line}",
+ "expression failed",
+ "expr"_attr = expr,
+ "error"_attr = errnoWithDescription(err),
+ "file"_attr = file,
+ "line"_attr = line);
+ breakpoint();
+ throw DeathTestSyscallException(
+ "{} failed: {} @{}:{}"_format(expr, errnoWithDescription(err), file, line));
+}
+
void DeathTestBase::_doTest() {
#if defined(_WIN32)
LOGV2(24133, "Skipping death test on Windows");
@@ -73,23 +90,47 @@ void DeathTestBase::_doTest() {
return;
#else
int pipes[2];
- checkSyscall(pipe(pipes));
+ if (pipe(pipes) == -1)
+ logAndThrowWithErrno("pipe()");
pid_t child;
- checkSyscall(child = fork());
+ if ((child = fork()) == -1)
+ logAndThrowWithErrno("fork()");
if (child) {
- checkSyscall(close(pipes[1]));
- char buf[1000];
+ if (close(pipes[1]) == -1)
+ logAndThrowWithErrno("close(pipe[1])");
std::ostringstream os;
+ FILE* pf = 0;
+ if ((pf = fdopen(pipes[0], "r")) == NULL)
+ logAndThrowWithErrno("fdopen(pipe[0], \"r\")");
+ auto pfGuard = makeGuard([&] {
+ if (fclose(pf) != 0)
+ logAndThrowWithErrno("fclose(pf)");
+ });
+ char* lineBuf = nullptr;
+ size_t lineBufSize = 0;
+ auto lineBufGuard = makeGuard([&] { free(lineBuf); });
ssize_t bytesRead;
- LOGV2(24135, "========== Beginning of interleaved output of death test ==========");
- while (0 < (bytesRead = read(pipes[0], buf, sizeof(buf)))) {
- std::cout.write(buf, bytesRead);
- invariant(std::cout);
- os.write(buf, bytesRead);
+ while ((bytesRead = getline(&lineBuf, &lineBufSize, pf)) != -1) {
+ StringData line(lineBuf, bytesRead);
+ if (line.empty())
+ continue;
+ if (line[line.size() - 1] == '\n')
+ line = line.substr(0, line.size() - 1);
+ if (line.empty())
+ continue;
+ int parsedLen;
+ auto parsedChildLog = fromjson(lineBuf, &parsedLen);
+ if (static_cast<size_t>(parsedLen) == line.size()) {
+ LOGV2(20165, "child", "json"_attr = parsedChildLog);
+ } else {
+ LOGV2(20169, "child", "text"_attr = line);
+ }
+ os.write(lineBuf, bytesRead);
invariant(os);
}
- LOGV2(24136, "========== End of interleaved output of death test ==========");
- checkSyscall(bytesRead);
+ if (!feof(pf))
+ logAndThrowWithErrno("getline(&buf, &bufSize, pf)");
+
pid_t pid;
int stat;
while (child != (pid = waitpid(child, &stat, 0))) {
@@ -99,20 +140,17 @@ void DeathTestBase::_doTest() {
case EINTR:
continue;
default:
- LOGV2_FATAL(
- 24139,
- "Unrecoverable error while waiting for {child}: {errnoWithDescription_err}",
- "child"_attr = child,
- "errnoWithDescription_err"_attr = errnoWithDescription(err));
- MONGO_UNREACHABLE;
+ logAndThrowWithErrno("waitpid(child, &stat, 0)");
}
}
if (WIFSIGNALED(stat) || (WIFEXITED(stat) && WEXITSTATUS(stat) != 0)) {
// Exited with a signal or non-zero code. Validate the expected message.
if (_isRegex()) {
- ASSERT_STRING_SEARCH_REGEX(os.str(), _doGetPattern());
+ ASSERT_STRING_SEARCH_REGEX(os.str(), _doGetPattern())
+ << " @" << _getFile() << ":" << _getLine();
} else {
- ASSERT_STRING_CONTAINS(os.str(), _doGetPattern());
+ ASSERT_STRING_CONTAINS(os.str(), _doGetPattern())
+ << " @" << _getFile() << ":" << _getLine();
}
return;
} else {
@@ -122,18 +160,23 @@ void DeathTestBase::_doTest() {
}
// This code only executes in the child process.
- checkSyscall(close(pipes[0]));
- checkSyscall(dup2(pipes[1], 1));
- checkSyscall(dup2(1, 2));
+ if (close(pipes[0]) == -1)
+ logAndThrowWithErrno("close(pipes[0])");
+ if (dup2(pipes[1], 1) == -1)
+ logAndThrowWithErrno("dup2(pipes[1], 1)");
+ if (dup2(1, 2) == -1)
+ logAndThrowWithErrno("dup2(1, 2)");
- // We disable the creation of core dump files in the child process since the child process is
- // expected to exit uncleanly. This avoids unnecessarily creating core dump files when the child
- // process calls std::abort() or std::terminate().
+ // We disable the creation of core dump files in the child process since the child process
+ // is expected to exit uncleanly. This avoids unnecessarily creating core dump files when
+ // the child process calls std::abort() or std::terminate().
const struct rlimit kNoCoreDump { 0U, 0U };
- checkSyscall(setrlimit(RLIMIT_CORE, &kNoCoreDump));
+ if (setrlimit(RLIMIT_CORE, &kNoCoreDump) == -1)
+ logAndThrowWithErrno("setrlimit(RLIMIT_CORE, &kNoCoreDump)");
try {
auto test = _doMakeTest();
+ LOGV2(23515, "Running DeathTest in child");
test->run();
LOGV2(20166, "Death test failed to die");
} catch (const TestAssertionFailureException& tafe) {