From 50f4deb1cf3ef32282c13b7cb84a81b1bf61e0d8 Mon Sep 17 00:00:00 2001 From: vladlosev Date: Mon, 18 Oct 2010 22:09:55 +0000 Subject: [PATCH] Modifies handling of C++ exceptions in death tests to treat exceptions escaping them as failures. --- CMakeLists.txt | 7 ++ .../internal/gtest-death-test-internal.h | 35 ++++++- src/gtest-death-test.cc | 33 +++++-- test/gtest-death-test_ex_test.cc | 93 +++++++++++++++++++ test/gtest-death-test_test.cc | 7 +- 5 files changed, 160 insertions(+), 15 deletions(-) create mode 100644 test/gtest-death-test_ex_test.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 130719fa..0fe26540 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,6 +147,13 @@ if (gtest_build_tests) cxx_library(gtest_main_no_rtti "${cxx_no_rtti}" src/gtest-all.cc src/gtest_main.cc) + cxx_test_with_flags(gtest-death-test_ex_nocatch_test + "${cxx_exception} -DGTEST_ENABLE_CATCH_EXCEPTIONS_=0" + gtest test/gtest-death-test_ex_test.cc) + cxx_test_with_flags(gtest-death-test_ex_catch_test + "${cxx_exception} -DGTEST_ENABLE_CATCH_EXCEPTIONS_=1" + gtest test/gtest-death-test_ex_test.cc) + cxx_test_with_flags(gtest_no_rtti_unittest "${cxx_no_rtti}" gtest_main_no_rtti test/gtest_unittest.cc) diff --git a/include/gtest/internal/gtest-death-test-internal.h b/include/gtest/internal/gtest-death-test-internal.h index 9242bd38..2b2c98d6 100644 --- a/include/gtest/internal/gtest-death-test-internal.h +++ b/include/gtest/internal/gtest-death-test-internal.h @@ -39,6 +39,8 @@ #include "gtest/internal/gtest-internal.h" +#include + namespace testing { namespace internal { @@ -96,8 +98,12 @@ class GTEST_API_ DeathTest { // test, then wait for it to complete. enum TestRole { OVERSEE_TEST, EXECUTE_TEST }; - // An enumeration of the two reasons that a test might be aborted. - enum AbortReason { TEST_ENCOUNTERED_RETURN_STATEMENT, TEST_DID_NOT_DIE }; + // An enumeration of the three reasons that a test might be aborted. + enum AbortReason { + TEST_ENCOUNTERED_RETURN_STATEMENT, + TEST_THREW_EXCEPTION, + TEST_DID_NOT_DIE + }; // Assumes one of the above roles. virtual TestRole AssumeRole() = 0; @@ -149,6 +155,29 @@ class DefaultDeathTestFactory : public DeathTestFactory { // by a signal, or exited normally with a nonzero exit code. GTEST_API_ bool ExitedUnsuccessfully(int exit_status); +// Traps C++ exceptions escaping statement and reports them as test +// failures. Note that trapping SEH exceptions is not implemented here. +#if GTEST_HAS_EXCEPTIONS +#define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } catch (const ::std::exception& gtest_exception) { \ + fprintf(\ + stderr, \ + "\n%s: Caught std::exception-derived exception escaping the " \ + "death test statement. Exception message: %s\n", \ + ::testing::internal::FormatFileLocation(__FILE__, __LINE__).c_str(), \ + gtest_exception.what()); \ + fflush(stderr); \ + death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \ + } catch (...) { \ + death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \ + } +#else +#define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) +#endif + // This macro is for implementing ASSERT_DEATH*, EXPECT_DEATH*, // ASSERT_EXIT*, and EXPECT_EXIT*. #define GTEST_DEATH_TEST_(statement, predicate, regex, fail) \ @@ -172,7 +201,7 @@ GTEST_API_ bool ExitedUnsuccessfully(int exit_status); case ::testing::internal::DeathTest::EXECUTE_TEST: { \ ::testing::internal::DeathTest::ReturnSentinel \ gtest_sentinel(gtest_dt); \ - GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, gtest_dt); \ gtest_dt->Abort(::testing::internal::DeathTest::TEST_DID_NOT_DIE); \ break; \ } \ diff --git a/src/gtest-death-test.cc b/src/gtest-death-test.cc index e11f5041..3ab9cf9e 100644 --- a/src/gtest-death-test.cc +++ b/src/gtest-death-test.cc @@ -182,15 +182,19 @@ static String DeathTestThreadWarning(size_t thread_count) { // Flag characters for reporting a death test that did not die. static const char kDeathTestLived = 'L'; static const char kDeathTestReturned = 'R'; +static const char kDeathTestThrew = 'T'; static const char kDeathTestInternalError = 'I'; -// An enumeration describing all of the possible ways that a death test -// can conclude. DIED means that the process died while executing the -// test code; LIVED means that process lived beyond the end of the test -// code; and RETURNED means that the test statement attempted a "return," -// which is not allowed. IN_PROGRESS means the test has not yet -// concluded. -enum DeathTestOutcome { IN_PROGRESS, DIED, LIVED, RETURNED }; +// An enumeration describing all of the possible ways that a death test can +// conclude. DIED means that the process died while executing the test +// code; LIVED means that process lived beyond the end of the test code; +// RETURNED means that the test statement attempted to execute a return +// statement, which is not allowed; THREW means that the test statement +// returned control by throwing an exception. IN_PROGRESS means the test +// has not yet concluded. +// TODO(vladl@google.com): Unify names and possibly values for +// AbortReason, DeathTestOutcome, and flag characters above. +enum DeathTestOutcome { IN_PROGRESS, DIED, LIVED, RETURNED, THREW }; // Routine for aborting the program which is safe to call from an // exec-style death test child process, in which case the error @@ -388,6 +392,9 @@ void DeathTestImpl::ReadAndInterpretStatusByte() { case kDeathTestReturned: set_outcome(RETURNED); break; + case kDeathTestThrew: + set_outcome(THREW); + break; case kDeathTestLived: set_outcome(LIVED); break; @@ -416,7 +423,9 @@ void DeathTestImpl::Abort(AbortReason reason) { // it finds any data in our pipe. So, here we write a single flag byte // to the pipe, then exit. const char status_ch = - reason == TEST_DID_NOT_DIE ? kDeathTestLived : kDeathTestReturned; + reason == TEST_DID_NOT_DIE ? kDeathTestLived : + reason == TEST_THREW_EXCEPTION ? kDeathTestThrew : kDeathTestReturned; + GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Write(write_fd(), &status_ch, 1)); // We are leaking the descriptor here because on some platforms (i.e., // when built as Windows DLL), destructors of global objects will still @@ -434,8 +443,8 @@ void DeathTestImpl::Abort(AbortReason reason) { // // Private data members: // outcome: An enumeration describing how the death test -// concluded: DIED, LIVED, or RETURNED. The death test fails -// in the latter two cases. +// concluded: DIED, LIVED, THREW, or RETURNED. The death test +// fails in the latter three cases. // status: The exit status of the child process. On *nix, it is in the // in the format specified by wait(2). On Windows, this is the // value supplied to the ExitProcess() API or a numeric code @@ -466,6 +475,10 @@ bool DeathTestImpl::Passed(bool status_ok) { buffer << " Result: failed to die.\n" << " Error msg: " << error_message; break; + case THREW: + buffer << " Result: threw an exception.\n" + << " Error msg: " << error_message; + break; case RETURNED: buffer << " Result: illegal return in test statement.\n" << " Error msg: " << error_message; diff --git a/test/gtest-death-test_ex_test.cc b/test/gtest-death-test_ex_test.cc new file mode 100644 index 00000000..71cc7a36 --- /dev/null +++ b/test/gtest-death-test_ex_test.cc @@ -0,0 +1,93 @@ +// Copyright 2010, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: vladl@google.com (Vlad Losev) +// +// Tests that verify interaction of exceptions and death tests. + +#include "gtest/gtest-death-test.h" +#include "gtest/gtest.h" + +#if GTEST_HAS_DEATH_TEST + +#if GTEST_HAS_SEH +#include // For RaiseException(). +#endif + +#include "gtest/gtest-spi.h" + +#if GTEST_HAS_EXCEPTIONS + +#include // For std::exception. + +// Tests that death tests report thrown exceptions as failures and that the +// exceptions do not escape death test macros. +TEST(CxxExceptionDeathTest, ExceptionIsFailure) { + try { + EXPECT_NONFATAL_FAILURE(EXPECT_DEATH(throw 1, ""), "threw an exception"); + } catch (...) { // NOLINT + FAIL() << "An exception escaped a death test macro invocation " + << "with catch_exceptions " + << (testing::GTEST_FLAG(catch_exceptions) ? "enabled" : "disabled"); + } +} + +class TestException : public std::exception { + public: + virtual const char* what() const throw() { return "exceptional message"; } +}; + +TEST(CxxExceptionDeathTest, PrintsMessageForStdExceptions) { + // Verifies that the exception message is quoted in the failure text. + EXPECT_NONFATAL_FAILURE(EXPECT_DEATH(throw TestException(), ""), + "exceptional message"); + // Verifies that the location is mentioned in the failure text. + EXPECT_NONFATAL_FAILURE(EXPECT_DEATH(throw TestException(), ""), + "gtest-death-test_ex_test.cc"); +} +#endif // GTEST_HAS_EXCEPTIONS + +#if GTEST_HAS_SEH +// Tests that enabling interception of SEH exceptions with the +// catch_exceptions flag does not interfere with SEH exceptions being +// treated as death by death tests. +TEST(SehExceptionDeasTest, CatchExceptionsDoesNotInterfere) { + EXPECT_DEATH(RaiseException(42, 0x0, 0, NULL), "") + << "with catch_exceptions " + << (testing::GTEST_FLAG(catch_exceptions) ? "enabled" : "disabled"); +} +#endif + +#endif // GTEST_HAS_DEATH_TEST + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + testing::GTEST_FLAG(catch_exceptions) = GTEST_ENABLE_CATCH_EXCEPTIONS_ != 0; + return RUN_ALL_TESTS(); +} diff --git a/test/gtest-death-test_test.cc b/test/gtest-death-test_test.cc index 2f1d3859..b83b0db2 100644 --- a/test/gtest-death-test_test.cc +++ b/test/gtest-death-test_test.cc @@ -538,15 +538,18 @@ TEST_F(TestForDeathTest, SingleEvaluation) { } // Tests that run-away death tests are reported as failures. -TEST_F(TestForDeathTest, Runaway) { +TEST_F(TestForDeathTest, RunawayIsFailure) { EXPECT_NONFATAL_FAILURE(EXPECT_DEATH(static_cast(0), "Foo"), "failed to die."); +} +// Tests that death tests report executing 'return' in the statement as +// failure. +TEST_F(TestForDeathTest, ReturnIsFailure) { EXPECT_FATAL_FAILURE(ASSERT_DEATH(return, "Bar"), "illegal return in test statement."); } - // Tests that EXPECT_DEBUG_DEATH works as expected, // that is, in debug mode, it: // 1. Asserts on death.