diff options
author | Andy Schwerin <schwerin@mongodb.com> | 2015-06-16 22:12:48 -0400 |
---|---|---|
committer | Andy Schwerin <schwerin@mongodb.com> | 2015-06-17 18:24:52 -0400 |
commit | f7d2f46438617bc40aa404e5606f2334d33ab65f (patch) | |
tree | 3885476dbfbfb5a4bc89291a6c95dff28c29c6bb | |
parent | c3f9d7755bb1d6d801bab0d5d5586b2ae8e35f48 (diff) | |
download | mongo-f7d2f46438617bc40aa404e5606f2334d33ab65f.tar.gz |
SERVER-19005 Support DEATH_TEST and DEATH_TEST_F
-rw-r--r-- | src/mongo/unittest/SConscript | 3 | ||||
-rw-r--r-- | src/mongo/unittest/death_test.cpp | 122 | ||||
-rw-r--r-- | src/mongo/unittest/death_test.h | 109 | ||||
-rw-r--r-- | src/mongo/unittest/unittest.h | 11 | ||||
-rw-r--r-- | src/mongo/unittest/unittest_test.cpp | 17 |
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 |