supports a protocol for catching tests that prematurely exit

This commit is contained in:
zhanyong.wan 2013-09-06 22:50:25 +00:00
parent 492986a5d0
commit c306ef2e14
8 changed files with 246 additions and 21 deletions

View File

@ -2,6 +2,10 @@ Changes for 1.7.0:
* New feature: death tests are supported on OpenBSD and in iOS * New feature: death tests are supported on OpenBSD and in iOS
simulator now. simulator now.
* New feature: Google Test now implements a protocol to allow
a test runner to detect that a test program has exited
prematurely and report it as a failure (before it would be
falsely reported as a success if the exit code is 0).
* New feature: Test::RecordProperty() can now be used outside of the * New feature: Test::RecordProperty() can now be used outside of the
lifespan of a test method, in which case it will be attributed to lifespan of a test method, in which case it will be attributed to
the current test case or the test program in the XML report. the current test case or the test program in the XML report.

View File

@ -124,6 +124,8 @@ if (gtest_build_tests)
test/gtest-param-test2_test.cc) test/gtest-param-test2_test.cc)
cxx_test(gtest-port_test gtest_main) cxx_test(gtest-port_test gtest_main)
cxx_test(gtest_pred_impl_unittest gtest_main) cxx_test(gtest_pred_impl_unittest gtest_main)
cxx_test(gtest_premature_exit_test gtest
test/gtest_premature_exit_test.cc)
cxx_test(gtest-printers_test gtest_main) cxx_test(gtest-printers_test gtest_main)
cxx_test(gtest_prod_test gtest_main cxx_test(gtest_prod_test gtest_main
test/production.cc) test/production.cc)

View File

@ -58,6 +58,7 @@ EXTRA_DIST += \
test/gtest-param-test_test.cc \ test/gtest-param-test_test.cc \
test/gtest-param-test_test.h \ test/gtest-param-test_test.h \
test/gtest-port_test.cc \ test/gtest-port_test.cc \
test/gtest_premature_exit_test.cc \
test/gtest-printers_test.cc \ test/gtest-printers_test.cc \
test/gtest-test-part_test.cc \ test/gtest-test-part_test.cc \
test/gtest-tuple_test.cc \ test/gtest-tuple_test.cc \

View File

@ -3523,6 +3523,35 @@ const char* const
OsStackTraceGetter::kElidedFramesMarker = OsStackTraceGetter::kElidedFramesMarker =
"... " GTEST_NAME_ " internal frames ..."; "... " GTEST_NAME_ " internal frames ...";
// A helper class that creates the premature-exit file in its
// constructor and deletes the file in its destructor.
class ScopedPrematureExitFile {
public:
explicit ScopedPrematureExitFile(const char* premature_exit_filepath)
: premature_exit_filepath_(premature_exit_filepath) {
// If a path to the premature-exit file is specified...
if (premature_exit_filepath != NULL && *premature_exit_filepath != '\0') {
// create the file with a single "0" character in it. I/O
// errors are ignored as there's nothing better we can do and we
// don't want to fail the test because of this.
FILE* pfile = posix::FOpen(premature_exit_filepath, "w");
fwrite("0", 1, 1, pfile);
fclose(pfile);
}
}
~ScopedPrematureExitFile() {
if (premature_exit_filepath_ != NULL && *premature_exit_filepath_ != '\0') {
remove(premature_exit_filepath_);
}
}
private:
const char* const premature_exit_filepath_;
GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedPrematureExitFile);
};
} // namespace internal } // namespace internal
// class TestEventListeners // class TestEventListeners
@ -3823,14 +3852,39 @@ void UnitTest::RecordProperty(const std::string& key,
// We don't protect this under mutex_, as we only support calling it // We don't protect this under mutex_, as we only support calling it
// from the main thread. // from the main thread.
int UnitTest::Run() { int UnitTest::Run() {
const bool in_death_test_child_process =
internal::GTEST_FLAG(internal_run_death_test).length() > 0;
// Google Test implements this protocol for catching that a test
// program exits before returning control to Google Test:
//
// 1. Upon start, Google Test creates a file whose absolute path
// is specified by the environment variable
// TEST_PREMATURE_EXIT_FILE.
// 2. When Google Test has finished its work, it deletes the file.
//
// This allows a test runner to set TEST_PREMATURE_EXIT_FILE before
// running a Google-Test-based test program and check the existence
// of the file at the end of the test execution to see if it has
// exited prematurely.
// If we are in the child process of a death test, don't
// create/delete the premature exit file, as doing so is unnecessary
// and will confuse the parent process. Otherwise, create/delete
// the file upon entering/leaving this function. If the program
// somehow exits before this function has a chance to return, the
// premature-exit file will be left undeleted, causing a test runner
// that understands the premature-exit-file protocol to report the
// test as having failed.
const internal::ScopedPrematureExitFile premature_exit_file(
in_death_test_child_process ?
NULL : internal::posix::GetEnv("TEST_PREMATURE_EXIT_FILE"));
// Captures the value of GTEST_FLAG(catch_exceptions). This value will be // Captures the value of GTEST_FLAG(catch_exceptions). This value will be
// used for the duration of the program. // used for the duration of the program.
impl()->set_catch_exceptions(GTEST_FLAG(catch_exceptions)); impl()->set_catch_exceptions(GTEST_FLAG(catch_exceptions));
#if GTEST_HAS_SEH #if GTEST_HAS_SEH
const bool in_death_test_child_process =
internal::GTEST_FLAG(internal_run_death_test).length() > 0;
// Either the user wants Google Test to catch exceptions thrown by the // Either the user wants Google Test to catch exceptions thrown by the
// tests or this is executing in the context of death test child // tests or this is executing in the context of death test child
// process. In either case the user does not want to see pop-up dialogs // process. In either case the user does not want to see pop-up dialogs

View File

@ -66,21 +66,15 @@ EXE_PATH = gtest_test_utils.GetTestExecutablePath(
'gtest_break_on_failure_unittest_') 'gtest_break_on_failure_unittest_')
# Utilities. environ = gtest_test_utils.environ
SetEnvVar = gtest_test_utils.SetEnvVar
# Tests in this file run a Google-Test-based test program and expect it
environ = os.environ.copy() # to terminate prematurely. Therefore they are incompatible with
# the premature-exit-file protocol by design. Unset the
# premature-exit filepath to prevent Google Test from creating
def SetEnvVar(env_var, value): # the file.
"""Sets an environment variable to a given value; unsets it when the SetEnvVar(gtest_test_utils.PREMATURE_EXIT_FILE_ENV_VAR, None)
given value is None.
"""
if value is not None:
environ[env_var] = value
elif env_var in environ:
del environ[env_var]
def Run(command): def Run(command):

View File

@ -57,14 +57,27 @@ EX_EXE_PATH = gtest_test_utils.GetTestExecutablePath(
EXE_PATH = gtest_test_utils.GetTestExecutablePath( EXE_PATH = gtest_test_utils.GetTestExecutablePath(
'gtest_catch_exceptions_no_ex_test_') 'gtest_catch_exceptions_no_ex_test_')
TEST_LIST = gtest_test_utils.Subprocess([EXE_PATH, LIST_TESTS_FLAG]).output environ = gtest_test_utils.environ
SetEnvVar = gtest_test_utils.SetEnvVar
# Tests in this file run a Google-Test-based test program and expect it
# to terminate prematurely. Therefore they are incompatible with
# the premature-exit-file protocol by design. Unset the
# premature-exit filepath to prevent Google Test from creating
# the file.
SetEnvVar(gtest_test_utils.PREMATURE_EXIT_FILE_ENV_VAR, None)
TEST_LIST = gtest_test_utils.Subprocess(
[EXE_PATH, LIST_TESTS_FLAG], env=environ).output
SUPPORTS_SEH_EXCEPTIONS = 'ThrowsSehException' in TEST_LIST SUPPORTS_SEH_EXCEPTIONS = 'ThrowsSehException' in TEST_LIST
if SUPPORTS_SEH_EXCEPTIONS: if SUPPORTS_SEH_EXCEPTIONS:
BINARY_OUTPUT = gtest_test_utils.Subprocess([EXE_PATH]).output BINARY_OUTPUT = gtest_test_utils.Subprocess([EXE_PATH], env=environ).output
EX_BINARY_OUTPUT = gtest_test_utils.Subprocess(
[EX_EXE_PATH], env=environ).output
EX_BINARY_OUTPUT = gtest_test_utils.Subprocess([EX_EXE_PATH]).output
# The tests. # The tests.
if SUPPORTS_SEH_EXCEPTIONS: if SUPPORTS_SEH_EXCEPTIONS:
@ -212,7 +225,8 @@ class CatchCxxExceptionsTest(gtest_test_utils.TestCase):
uncaught_exceptions_ex_binary_output = gtest_test_utils.Subprocess( uncaught_exceptions_ex_binary_output = gtest_test_utils.Subprocess(
[EX_EXE_PATH, [EX_EXE_PATH,
NO_CATCH_EXCEPTIONS_FLAG, NO_CATCH_EXCEPTIONS_FLAG,
FITLER_OUT_SEH_TESTS_FLAG]).output FITLER_OUT_SEH_TESTS_FLAG],
env=environ).output
self.assert_('Unhandled C++ exception terminating the program' self.assert_('Unhandled C++ exception terminating the program'
in uncaught_exceptions_ex_binary_output) in uncaught_exceptions_ex_binary_output)

View File

@ -0,0 +1,141 @@
// Copyright 2013, 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: wan@google.com (Zhanyong Wan)
//
// Tests that Google Test manipulates the premature-exit-detection
// file correctly.
#include <stdio.h>
#include "gtest/gtest.h"
using ::testing::InitGoogleTest;
using ::testing::Test;
using ::testing::internal::posix::GetEnv;
using ::testing::internal::posix::Stat;
using ::testing::internal::posix::StatStruct;
namespace {
// Is the TEST_PREMATURE_EXIT_FILE environment variable expected to be
// set?
const bool kTestPrematureExitFileEnvVarShouldBeSet = false;
class PrematureExitTest : public Test {
public:
// Returns true iff the given file exists.
static bool FileExists(const char* filepath) {
StatStruct stat;
return Stat(filepath, &stat) == 0;
}
protected:
PrematureExitTest() {
premature_exit_file_path_ = GetEnv("TEST_PREMATURE_EXIT_FILE");
// Normalize NULL to "" for ease of handling.
if (premature_exit_file_path_ == NULL) {
premature_exit_file_path_ = "";
}
}
// Returns true iff the premature-exit file exists.
bool PrematureExitFileExists() const {
return FileExists(premature_exit_file_path_);
}
const char* premature_exit_file_path_;
};
typedef PrematureExitTest PrematureExitDeathTest;
// Tests that:
// - the premature-exit file exists during the execution of a
// death test (EXPECT_DEATH*), and
// - a death test doesn't interfere with the main test process's
// handling of the premature-exit file.
TEST_F(PrematureExitDeathTest, FileExistsDuringExecutionOfDeathTest) {
if (*premature_exit_file_path_ == '\0') {
return;
}
EXPECT_DEATH_IF_SUPPORTED({
// If the file exists, crash the process such that the main test
// process will catch the (expected) crash and report a success;
// otherwise don't crash, which will cause the main test process
// to report that the death test has failed.
if (PrematureExitFileExists()) {
exit(1);
}
}, "");
}
// Tests that TEST_PREMATURE_EXIT_FILE is set where it's expected to
// be set.
TEST_F(PrematureExitTest, TestPrematureExitFileEnvVarIsSet) {
if (kTestPrematureExitFileEnvVarShouldBeSet) {
const char* const filepath = GetEnv("TEST_PREMATURE_EXIT_FILE");
ASSERT_TRUE(filepath != NULL);
ASSERT_NE(*filepath, '\0');
}
}
// Tests that the premature-exit file exists during the execution of a
// normal (non-death) test.
TEST_F(PrematureExitTest, PrematureExitFileExistsDuringTestExecution) {
if (*premature_exit_file_path_ == '\0') {
return;
}
EXPECT_TRUE(PrematureExitFileExists())
<< " file " << premature_exit_file_path_
<< " should exist during test execution, but doesn't.";
}
} // namespace
int main(int argc, char **argv) {
InitGoogleTest(&argc, argv);
const int exit_code = RUN_ALL_TESTS();
// Test that the premature-exit file is deleted upon return from
// RUN_ALL_TESTS().
const char* const filepath = GetEnv("TEST_PREMATURE_EXIT_FILE");
if (filepath != NULL && *filepath != '\0') {
if (PrematureExitTest::FileExists(filepath)) {
printf(
"File %s shouldn't exist after the test program finishes, but does.",
filepath);
return 1;
}
}
return exit_code;
}

View File

@ -56,6 +56,21 @@ GTEST_OUTPUT_VAR_NAME = 'GTEST_OUTPUT'
IS_WINDOWS = os.name == 'nt' IS_WINDOWS = os.name == 'nt'
IS_CYGWIN = os.name == 'posix' and 'CYGWIN' in os.uname()[0] IS_CYGWIN = os.name == 'posix' and 'CYGWIN' in os.uname()[0]
# The environment variable for specifying the path to the premature-exit file.
PREMATURE_EXIT_FILE_ENV_VAR = 'TEST_PREMATURE_EXIT_FILE'
environ = os.environ.copy()
def SetEnvVar(env_var, value):
"""Sets/unsets an environment variable to a given value."""
if value is not None:
environ[env_var] = value
elif env_var in environ:
del environ[env_var]
# Here we expose a class from a particular module, depending on the # Here we expose a class from a particular module, depending on the
# environment. The comment suppresses the 'Invalid variable name' lint # environment. The comment suppresses the 'Invalid variable name' lint
# complaint. # complaint.