summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy Schwerin <schwerin@mongodb.com>2015-06-16 22:12:48 -0400
committerAndy Schwerin <schwerin@mongodb.com>2015-06-17 18:24:52 -0400
commitf7d2f46438617bc40aa404e5606f2334d33ab65f (patch)
tree3885476dbfbfb5a4bc89291a6c95dff28c29c6bb
parentc3f9d7755bb1d6d801bab0d5d5586b2ae8e35f48 (diff)
downloadmongo-f7d2f46438617bc40aa404e5606f2334d33ab65f.tar.gz
SERVER-19005 Support DEATH_TEST and DEATH_TEST_F
-rw-r--r--src/mongo/unittest/SConscript3
-rw-r--r--src/mongo/unittest/death_test.cpp122
-rw-r--r--src/mongo/unittest/death_test.h109
-rw-r--r--src/mongo/unittest/unittest.h11
-rw-r--r--src/mongo/unittest/unittest_test.cpp17
5 files changed, 254 insertions, 8 deletions
diff --git a/src/mongo/unittest/SConscript b/src/mongo/unittest/SConscript
index c69b12c8d96..9815734cc9f 100644
--- a/src/mongo/unittest/SConscript
+++ b/src/mongo/unittest/SConscript
@@ -4,8 +4,9 @@ Import("env")
env.Library(target="unittest",
source=[
- 'unittest.cpp',
+ 'death_test.cpp',
'temp_dir.cpp',
+ 'unittest.cpp',
'unittest_helpers.cpp',
],
LIBDEPS=['$BUILD_DIR/mongo/util/foundation',
diff --git a/src/mongo/unittest/death_test.cpp b/src/mongo/unittest/death_test.cpp
new file mode 100644
index 00000000000..a4df41a475d
--- /dev/null
+++ b/src/mongo/unittest/death_test.cpp
@@ -0,0 +1,122 @@
+/**
+ * Copyright (C) 2015 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/unittest/death_test.h"
+
+#ifndef _WIN32
+#include <cstdio>
+#include <sys/wait.h>
+#include <unistd.h>
+#endif
+
+#include <sstream>
+
+#include "mongo/util/assert_util.h"
+#include "mongo/util/log.h"
+#include "mongo/util/quick_exit.h"
+
+#define checkSyscall(EXPR) do { \
+ if (-1 == (EXPR)) { \
+ const int err = errno; \
+ severe() << #EXPR " failed: " << errnoWithDescription(err); \
+ invariantFailed("-1 != (" #EXPR ")", __FILE__, __LINE__); \
+ } \
+ } while(false)
+
+namespace mongo {
+namespace unittest {
+
+DeathTestImpl::DeathTestImpl(std::unique_ptr<Test> test) : _test(std::move(test)) {}
+
+void DeathTestImpl::_doTest() {
+#ifdef _WIN32
+ log() << "Skipping death test on Windows";
+ return;
+#else
+ int pipes[2];
+ checkSyscall(pipe(pipes));
+ pid_t child;
+ checkSyscall(child = fork());
+ if (child) {
+ checkSyscall(close(pipes[1]));
+ char buf[1000];
+ std::ostringstream os;
+ ssize_t bytesRead;
+ while (0 < (bytesRead = read(pipes[0], buf, sizeof(buf)))) {
+ os.write(buf, bytesRead);
+ invariant(os);
+ }
+ checkSyscall(bytesRead);
+ pid_t pid;
+ int stat;
+ while (child != (pid = waitpid(child, &stat, 0))) {
+ invariant(pid == -1);
+ const int err = errno;
+ switch (err) {
+ case EINTR:
+ continue;
+ default:
+ severe() << "Unrecoverable error while waiting for " << child <<
+ ": " << errnoWithDescription(err);
+ MONGO_UNREACHABLE;
+ }
+ }
+ if (WIFSIGNALED(stat) || (WIFEXITED(stat) && WEXITSTATUS(stat) != 0)) {
+ // Exited with a signal or non-zero code. Should check the pattern, here,
+ // but haven't figured out how, so just return.
+ ASSERT_STRING_CONTAINS(os.str(), getPattern());
+ return;
+ }
+ else {
+ invariant(!WIFSTOPPED(stat));
+ }
+ FAIL("Expected death, found life\n\n") << os.str();
+ }
+
+ // This code only executes in the child process.
+ checkSyscall(close(pipes[0]));
+ checkSyscall(dup2(pipes[1], 1));
+ checkSyscall(dup2(1, 2));
+ try {
+ _test->run();
+ }
+ catch (const TestAssertionFailureException& tafe) {
+ log() << "Caught test exception while expecting death: " << tafe;
+ // To fail the test, we must exit with a successful error code, because the parent process
+ // is checking for the child to die with an exit code indicating an error.
+ quickExit(EXIT_SUCCESS);
+ }
+ quickExit(EXIT_SUCCESS);
+#endif
+}
+
+} // namespace unittest
+} // namespace mongo
diff --git a/src/mongo/unittest/death_test.h b/src/mongo/unittest/death_test.h
new file mode 100644
index 00000000000..5aff0c39cd4
--- /dev/null
+++ b/src/mongo/unittest/death_test.h
@@ -0,0 +1,109 @@
+/**
+ * Copyright (C) 2015 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include "mongo/base/disallow_copying.h"
+#include "mongo/stdx/memory.h"
+#include "mongo/unittest/unittest.h"
+
+/**
+ * Constructs a single death test named "TEST_NAME" within the test case "CASE_NAME".
+ *
+ * The test only succeeds if the process terminates abnormally, e.g. through an fassert
+ * failure or deadly signal.
+ *
+ * Death tests are incompatible with already-running threads. If you need multiple threads
+ * in your death test, start them in the test body, or use DEATH_TEST_F and start them
+ * in the setUp() method of the fixture.
+ */
+#define DEATH_TEST(CASE_NAME, TEST_NAME, MATCH_EXPR) \
+ class _TEST_TYPE_NAME(CASE_NAME, TEST_NAME) : public ::mongo::unittest::Test { \
+ private: \
+ virtual void _doTest(); \
+ \
+ static const RegistrationAgent<::mongo::unittest::DeathTest< \
+ _TEST_TYPE_NAME(CASE_NAME, TEST_NAME)>> _agent; \
+ }; \
+ const ::mongo::unittest::Test::RegistrationAgent< \
+ ::mongo::unittest::DeathTest<_TEST_TYPE_NAME(CASE_NAME, TEST_NAME)>> \
+ _TEST_TYPE_NAME(CASE_NAME, TEST_NAME)::_agent(#CASE_NAME, #TEST_NAME); \
+ std::string getDeathTestPattern(_TEST_TYPE_NAME(CASE_NAME, TEST_NAME)*) { return MATCH_EXPR; } \
+ void _TEST_TYPE_NAME(CASE_NAME, TEST_NAME)::_doTest()
+
+/**
+ * Constructs a single test named TEST_NAME that has access to a common fixture
+ * named "FIXTURE_NAME".
+ *
+ * See description of DEATH_TEST for more details on death tests.
+ */
+#define DEATH_TEST_F(FIXTURE_NAME, TEST_NAME, MATCH_EXPR) \
+ class _TEST_TYPE_NAME(FIXTURE_NAME, TEST_NAME) : public FIXTURE_NAME { \
+ private: \
+ virtual void _doTest(); \
+ \
+ static const RegistrationAgent<::mongo::unittest::DeathTest< \
+ _TEST_TYPE_NAME(FIXTURE_NAME, TEST_NAME)>> _agent; \
+ }; \
+ const ::mongo::unittest::Test::RegistrationAgent< \
+ ::mongo::unittest::DeathTest<_TEST_TYPE_NAME(FIXTURE_NAME, TEST_NAME)>> \
+ _TEST_TYPE_NAME(FIXTURE_NAME, TEST_NAME)::_agent(#FIXTURE_NAME, #TEST_NAME); \
+ std::string getDeathTestPattern(_TEST_TYPE_NAME(FIXTURE_NAME, TEST_NAME)*) { \
+ return MATCH_EXPR; \
+ } \
+ void _TEST_TYPE_NAME(FIXTURE_NAME, TEST_NAME)::_doTest()
+
+namespace mongo {
+namespace unittest {
+
+class DeathTestImpl : public Test {
+ MONGO_DISALLOW_COPYING(DeathTestImpl);
+protected:
+ DeathTestImpl(std::unique_ptr<Test> test);
+
+private:
+ void _doTest() override;
+ virtual std::string getPattern() = 0;
+ std::unique_ptr<Test> _test;
+};
+
+template <typename T>
+class DeathTest : public DeathTestImpl {
+public:
+ static const std::string pattern;
+
+ DeathTest() : DeathTestImpl(stdx::make_unique<T>()) {}
+private:
+ std::string getPattern() override { return getDeathTestPattern(static_cast<T*>(nullptr)); }
+};
+
+} // namespace unittest
+} // namespace mongo
diff --git a/src/mongo/unittest/unittest.h b/src/mongo/unittest/unittest.h
index 53cfb0204a3..030dd06681b 100644
--- a/src/mongo/unittest/unittest.h
+++ b/src/mongo/unittest/unittest.h
@@ -162,12 +162,13 @@
} \
} while (false)
-#define ASSERT_STRING_CONTAINS(BIG_STRING, CONTAINS ) do { \
+#define ASSERT_STRING_CONTAINS(BIG_STRING, CONTAINS) do { \
std::string myString( BIG_STRING ); \
- if ( myString.find(CONTAINS) == std::string::npos ) { \
- std::string err( "Expected " #BIG_STRING " (" ); \
- err += myString; \
- err += std::string(") to contain " #CONTAINS ); \
+ std::string myContains(CONTAINS); \
+ if ( myString.find(myContains) == std::string::npos ) { \
+ str::stream err; \
+ err << "Expected to find " #CONTAINS " (" << myContains << \
+ ") in " #BIG_STRING " (" << myString << ")"; \
::mongo::unittest::TestAssertionFailure(__FILE__, \
__LINE__, \
err).stream(); \
diff --git a/src/mongo/unittest/unittest_test.cpp b/src/mongo/unittest/unittest_test.cpp
index 1b10883200f..2fb8a4abd97 100644
--- a/src/mongo/unittest/unittest_test.cpp
+++ b/src/mongo/unittest/unittest_test.cpp
@@ -32,12 +32,13 @@
#include "mongo/platform/basic.h"
-#include "mongo/unittest/unittest.h"
-
#include <limits>
#include <string>
#include "mongo/stdx/functional.h"
+#include "mongo/util/assert_util.h"
+#include "mongo/unittest/death_test.h"
+#include "mongo/unittest/unittest.h"
namespace {
namespace stdx = mongo::stdx;
@@ -131,4 +132,16 @@ namespace {
ASSERT_TEST_FAILS_MATCH(ASSERT_EQ(0, ++i), "(0 == 1)");
}
+ DEATH_TEST(DeathTestSelfTest, TestDeath, "Invariant failure false") { invariant(false); }
+
+ class DeathTestSelfTestFixture : public ::mongo::unittest::Test {
+ public:
+ void setUp() override {}
+ void tearDown() override {
+ mongo::unittest::log() << "Died in tear-down";
+ invariant(false);
+ }
+ };
+
+ DEATH_TEST_F(DeathTestSelfTestFixture, DieInTearDown, "Died in tear-down") {}
} // namespace