diff options
Diffstat (limited to 'src/mongo/unittest/death_test.cpp')
-rw-r--r-- | src/mongo/unittest/death_test.cpp | 119 |
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) { |