From 092d0885332316ca679ac77e0f8fe1f5c368fb4f Mon Sep 17 00:00:00 2001
From: Alexey Sokolov <sokolov@google.com>
Date: Sat, 3 Feb 2018 23:36:19 +0000
Subject: [PATCH] Add ability to throw from ASSERT

while not losing benefits of EXPECT, and not killing the whole test,
as with --gtest_throw_on_failure.

183822976
---
 googletest/docs/AdvancedGuide.md              |  30 ++++-
 googletest/include/gtest/gtest.h              |  16 ++-
 googletest/src/gtest.cc                       |   7 +-
 googletest/test/BUILD.bazel                   |   7 ++
 .../test/gtest_assert_by_exception_test.cc    | 119 ++++++++++++++++++
 5 files changed, 171 insertions(+), 8 deletions(-)
 create mode 100644 googletest/test/gtest_assert_by_exception_test.cc

diff --git a/googletest/docs/AdvancedGuide.md b/googletest/docs/AdvancedGuide.md
index e1df20d5..6605f448 100644
--- a/googletest/docs/AdvancedGuide.md
+++ b/googletest/docs/AdvancedGuide.md
@@ -872,13 +872,33 @@ TEST(FooTest, Bar) {
 }
 ```
 
-Since we don't use exceptions, it is technically impossible to
-implement the intended behavior here.  To alleviate this, Google Test
-provides two solutions.  You could use either the
-`(ASSERT|EXPECT)_NO_FATAL_FAILURE` assertions or the
-`HasFatalFailure()` function.  They are described in the following two
+To alleviate this, gUnit provides three different solutions. You could use
+either exceptions, the `(ASSERT|EXPECT)_NO_FATAL_FAILURE` assertions or the
+`HasFatalFailure()` function. They are described in the following two
 subsections.
 
+#### Asserting on Subroutines with an exception
+
+The following code can turn ASSERT-failure into an exception:
+
+```c++
+class ThrowListener : public testing::EmptyTestEventListener {
+  void OnTestPartResult(const testing::TestPartResult& result) override {
+    if (result.type() == testing::TestPartResult::kFatalFailure) {
+      throw testing::AssertionException(result);
+    }
+  }
+};
+int main(int argc, char** argv) {
+  ...
+  testing::UnitTest::GetInstance()->listeners().Append(new ThrowListener);
+  return RUN_ALL_TESTS();
+}
+```
+
+This listener should be added after other listeners if you have any, otherwise
+they won't see failed `OnTestPartResult`.
+
 ### Asserting on Subroutines ###
 
 As shown above, if your test calls a subroutine that has an `ASSERT_*`
diff --git a/googletest/include/gtest/gtest.h b/googletest/include/gtest/gtest.h
index 01994e6d..26e787d9 100644
--- a/googletest/include/gtest/gtest.h
+++ b/googletest/include/gtest/gtest.h
@@ -138,7 +138,7 @@ GTEST_DECLARE_int32_(stack_trace_depth);
 
 // When this flag is specified, a failed assertion will throw an
 // exception if exceptions are enabled, or exit the program with a
-// non-zero code otherwise.
+// non-zero code otherwise. For use with an external test framework.
 GTEST_DECLARE_bool_(throw_on_failure);
 
 // When this flag is set with a "host:port" string, on supported
@@ -1004,6 +1004,18 @@ class Environment {
   virtual Setup_should_be_spelled_SetUp* Setup() { return NULL; }
 };
 
+#if GTEST_HAS_EXCEPTIONS
+
+// Exception which can be thrown from TestEventListener::OnTestPartResult.
+class GTEST_API_ AssertionException
+    : public internal::GoogleTestFailureException {
+ public:
+  explicit AssertionException(const TestPartResult& result)
+      : GoogleTestFailureException(result) {}
+};
+
+#endif  // GTEST_HAS_EXCEPTIONS
+
 // The interface for tracing execution of tests. The methods are organized in
 // the order the corresponding events are fired.
 class TestEventListener {
@@ -1032,6 +1044,8 @@ class TestEventListener {
   virtual void OnTestStart(const TestInfo& test_info) = 0;
 
   // Fired after a failed assertion or a SUCCEED() invocation.
+  // If you want to throw an exception from this function to skip to the next
+  // TEST, it must be AssertionException defined above, or inherited from it.
   virtual void OnTestPartResult(const TestPartResult& test_part_result) = 0;
 
   // Fired after the test ends.
diff --git a/googletest/src/gtest.cc b/googletest/src/gtest.cc
index 2c25f838..54e25b2d 100644
--- a/googletest/src/gtest.cc
+++ b/googletest/src/gtest.cc
@@ -293,7 +293,7 @@ GTEST_DEFINE_bool_(
     internal::BoolFromGTestEnv("throw_on_failure", false),
     "When this flag is specified, a failed assertion will throw an exception "
     "if exceptions are enabled or exit the program with a non-zero code "
-    "otherwise.");
+    "otherwise. For use with an external test framework.");
 
 #if GTEST_USE_OWN_FLAGFILE_FLAG_
 GTEST_DEFINE_string_(
@@ -2435,6 +2435,8 @@ Result HandleExceptionsInMethodIfSupported(
 #if GTEST_HAS_EXCEPTIONS
     try {
       return HandleSehExceptionsInMethodIfSupported(object, method, location);
+    } catch (const AssertionException&) {  // NOLINT
+      // This failure was reported already.
     } catch (const internal::GoogleTestFailureException&) {  // NOLINT
       // This exception type can only be thrown by a failed Google
       // Test assertion with the intention of letting another testing
@@ -5201,7 +5203,8 @@ static const char kColorEncodedHelpMessage[] =
 "  @G--" GTEST_FLAG_PREFIX_ "break_on_failure@D\n"
 "      Turn assertion failures into debugger break-points.\n"
 "  @G--" GTEST_FLAG_PREFIX_ "throw_on_failure@D\n"
-"      Turn assertion failures into C++ exceptions.\n"
+"      Turn assertion failures into C++ exceptions for use by an external\n"
+"      test framework.\n"
 "  @G--" GTEST_FLAG_PREFIX_ "catch_exceptions=0@D\n"
 "      Do not report exceptions as test failures. Instead, allow them\n"
 "      to crash the program or throw a pop-up (on Windows).\n"
diff --git a/googletest/test/BUILD.bazel b/googletest/test/BUILD.bazel
index 3c700b15..1b81133c 100644
--- a/googletest/test/BUILD.bazel
+++ b/googletest/test/BUILD.bazel
@@ -219,6 +219,13 @@ py_test(
     deps = [":gtest_test_utils"],
 )
 
+cc_test(
+    name = "gtest_assert_by_exception_test",
+    size = "small",
+    srcs = ["gtest_assert_by_exception_test.cc"],
+    deps = ["//:gtest"],
+)
+
 cc_binary(
     name = "gtest_throw_on_failure_test_",
     testonly = 1,
diff --git a/googletest/test/gtest_assert_by_exception_test.cc b/googletest/test/gtest_assert_by_exception_test.cc
new file mode 100644
index 00000000..2f0e34af
--- /dev/null
+++ b/googletest/test/gtest_assert_by_exception_test.cc
@@ -0,0 +1,119 @@
+// Copyright 2009, 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 Google Test's assert-by-exception mode with exceptions enabled.
+
+#include "gtest/gtest.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdexcept>
+
+class ThrowListener : public testing::EmptyTestEventListener {
+  void OnTestPartResult(const testing::TestPartResult& result) override {
+    if (result.type() == testing::TestPartResult::kFatalFailure) {
+      throw testing::AssertionException(result);
+    }
+  }
+};
+
+// Prints the given failure message and exits the program with
+// non-zero.  We use this instead of a Google Test assertion to
+// indicate a failure, as the latter is been tested and cannot be
+// relied on.
+void Fail(const char* msg) {
+  printf("FAILURE: %s\n", msg);
+  fflush(stdout);
+  exit(1);
+}
+
+static void AssertFalse() {
+  ASSERT_EQ(2, 3) << "Expected failure";
+}
+
+// Tests that an assertion failure throws a subclass of
+// std::runtime_error.
+TEST(Test, Test) {
+  // A successful assertion shouldn't throw.
+  try {
+    EXPECT_EQ(3, 3);
+  } catch(...) {
+    Fail("A successful assertion wrongfully threw.");
+  }
+
+  // A successful assertion shouldn't throw.
+  try {
+    EXPECT_EQ(3, 4);
+  } catch(...) {
+    Fail("A failed non-fatal assertion wrongfully threw.");
+  }
+
+  // A failed assertion should throw.
+  try {
+    AssertFalse();
+  } catch(const testing::AssertionException& e) {
+    if (strstr(e.what(), "Expected failure") != NULL)
+      throw;
+
+    printf("%s",
+           "A failed assertion did throw an exception of the right type, "
+           "but the message is incorrect.  Instead of containing \"Expected "
+           "failure\", it is:\n");
+    Fail(e.what());
+  } catch(...) {
+    Fail("A failed assertion threw the wrong type of exception.");
+  }
+  Fail("A failed assertion should've thrown but didn't.");
+}
+
+int kTestForContinuingTest = 0;
+
+TEST(Test, Test2) {
+  // FIXME(sokolov): how to force Test2 to be after Test?
+  kTestForContinuingTest = 1;
+}
+
+int main(int argc, char** argv) {
+  testing::InitGoogleTest(&argc, argv);
+  testing::UnitTest::GetInstance()->listeners().Append(new ThrowListener);
+
+  int result = RUN_ALL_TESTS();
+  if (result == 0) {
+    printf("RUN_ALL_TESTS returned %d\n", result);
+    Fail("Expected failure instead.");
+  }
+
+  if (kTestForContinuingTest == 0) {
+    Fail("Should have continued with other tests, but did not.");
+  }
+  return 0;
+}