about summary refs log tree commit diff
path: root/absl/base/internal/exception_safety_testing.h
diff options
context:
space:
mode:
Diffstat (limited to 'absl/base/internal/exception_safety_testing.h')
-rw-r--r--absl/base/internal/exception_safety_testing.h392
1 files changed, 284 insertions, 108 deletions
diff --git a/absl/base/internal/exception_safety_testing.h b/absl/base/internal/exception_safety_testing.h
index a0cd33b4d00a..3493b5e2325d 100644
--- a/absl/base/internal/exception_safety_testing.h
+++ b/absl/base/internal/exception_safety_testing.h
@@ -23,6 +23,7 @@
 #include <initializer_list>
 #include <iosfwd>
 #include <string>
+#include <tuple>
 #include <unordered_map>
 
 #include "gtest/gtest.h"
@@ -35,7 +36,6 @@
 #include "absl/types/optional.h"
 
 namespace absl {
-struct InternalAbslNamespaceFinder {};
 
 struct ConstructorTracker;
 
@@ -63,6 +63,7 @@ constexpr NoThrow operator&(NoThrow a, NoThrow b) {
 
 namespace exceptions_internal {
 struct NoThrowTag {};
+struct StrongGuaranteeTagType {};
 
 constexpr bool ThrowingAllowed(NoThrow flags, NoThrow flag) {
   return !static_cast<bool>(flags & flag);
@@ -87,8 +88,7 @@ class TestException {
 // bad_alloc exception in TestExceptionSafety.
 class TestBadAllocException : public std::bad_alloc, public TestException {
  public:
-  explicit TestBadAllocException(absl::string_view msg)
-      : TestException(msg) {}
+  explicit TestBadAllocException(absl::string_view msg) : TestException(msg) {}
   using TestException::what;
 };
 
@@ -128,75 +128,73 @@ class TrackedObject {
   friend struct ::absl::ConstructorTracker;
 };
 
-template <typename Factory>
-using FactoryType = typename absl::result_of_t<Factory()>::element_type;
-
-// Returns an optional with the result of the check if op fails, or an empty
-// optional if op passes
-template <typename Factory, typename Op, typename Checker>
-absl::optional<testing::AssertionResult> TestCheckerAtCountdown(
-    Factory factory, const Op& op, int count, const Checker& check) {
+template <typename Factory, typename Operation, typename Invariant>
+absl::optional<testing::AssertionResult> TestSingleInvariantAtCountdownImpl(
+    const Factory& factory, const Operation& operation, int count,
+    const Invariant& invariant) {
   auto t_ptr = factory();
-  absl::optional<testing::AssertionResult> out;
+  absl::optional<testing::AssertionResult> current_res;
+  exceptions_internal::countdown = count;
   try {
-    exceptions_internal::countdown = count;
-    op(t_ptr.get());
+    operation(t_ptr.get());
   } catch (const exceptions_internal::TestException& e) {
-    out.emplace(check(t_ptr.get()));
-    if (!*out) {
-      *out << " caused by exception thrown by " << e.what();
+    current_res.emplace(invariant(t_ptr.get()));
+    if (!current_res.value()) {
+      *current_res << e.what() << " failed invariant check";
     }
   }
-  return out;
+  exceptions_internal::countdown = -1;
+  return current_res;
+}
+
+template <typename Factory, typename Operation>
+absl::optional<testing::AssertionResult> TestSingleInvariantAtCountdownImpl(
+    const Factory& factory, const Operation& operation, int count,
+    StrongGuaranteeTagType) {
+  using TPtr = typename decltype(factory())::pointer;
+  auto t_is_strong = [&](TPtr t) { return *t == *factory(); };
+  return TestSingleInvariantAtCountdownImpl(factory, operation, count,
+                                            t_is_strong);
 }
 
-template <typename Factory, typename Op, typename Checker>
-int UpdateOut(Factory factory, const Op& op, int count, const Checker& checker,
-              testing::AssertionResult* out) {
-  if (*out) *out = *TestCheckerAtCountdown(factory, op, count, checker);
+template <typename Factory, typename Operation, typename Invariant>
+int TestSingleInvariantAtCountdown(
+    const Factory& factory, const Operation& operation, int count,
+    const Invariant& invariant,
+    absl::optional<testing::AssertionResult>* reduced_res) {
+  // If reduced_res is empty, it means the current call to
+  // TestSingleInvariantAtCountdown(...) is the first test being run so we do
+  // want to run it. Alternatively, if it's not empty (meaning a previous test
+  // has run) we want to check if it passed. If the previous test did pass, we
+  // want to contine running tests so we do want to run the current one. If it
+  // failed, we want to short circuit so as not to overwrite the AssertionResult
+  // output. If that's the case, we do not run the current test and instead we
+  // simply return.
+  if (!reduced_res->has_value() || reduced_res->value()) {
+    *reduced_res = TestSingleInvariantAtCountdownImpl(factory, operation, count,
+                                                      invariant);
+  }
   return 0;
 }
 
-// Declare AbslCheckInvariants so that it can be found eventually via ADL.
-// Taking `...` gives it the lowest possible precedence.
-void AbslCheckInvariants(...);
-
-// Returns an optional with the result of the check if op fails, or an empty
-// optional if op passes
-template <typename Factory, typename Op, typename... Checkers>
-absl::optional<testing::AssertionResult> TestAtCountdown(
-    Factory factory, const Op& op, int count, const Checkers&... checkers) {
-  // Don't bother with the checkers if the class invariants are already broken.
-  auto out = TestCheckerAtCountdown(
-      factory, op, count, [](FactoryType<Factory>* t_ptr) {
-        return AbslCheckInvariants(t_ptr, InternalAbslNamespaceFinder());
-      });
-  if (!out.has_value()) return out;
+template <typename Factory, typename Operation, typename... Invariants>
+inline absl::optional<testing::AssertionResult> TestAllInvariantsAtCountdown(
+    const Factory& factory, const Operation& operation, int count,
+    const Invariants&... invariants) {
+  absl::optional<testing::AssertionResult> reduced_res;
 
   // Run each checker, short circuiting after the first failure
-  int dummy[] = {0, (UpdateOut(factory, op, count, checkers, &*out))...};
+  int dummy[] = {
+      0, (TestSingleInvariantAtCountdown(factory, operation, count, invariants,
+                                         &reduced_res))...};
   static_cast<void>(dummy);
-  return out;
+  return reduced_res;
 }
 
-template <typename T, typename EqualTo>
-class StrongGuaranteeTester {
- public:
-  explicit StrongGuaranteeTester(std::unique_ptr<T> t_ptr, EqualTo eq) noexcept
-      : val_(std::move(t_ptr)), eq_(eq) {}
-
-  testing::AssertionResult operator()(T* other) const {
-    return eq_(*val_, *other) ? testing::AssertionSuccess()
-                              : testing::AssertionFailure() << "State changed";
-  }
-
- private:
-  std::unique_ptr<T> val_;
-  EqualTo eq_;
-};
 }  // namespace exceptions_internal
 
 extern exceptions_internal::NoThrowTag no_throw_ctor;
+extern exceptions_internal::StrongGuaranteeTagType strong_guarantee;
 
 // These are useful for tests which just construct objects and make sure there
 // are no leaks.
@@ -208,7 +206,7 @@ inline void UnsetCountdown() { exceptions_internal::countdown = -1; }
 class ThrowingBool {
  public:
   ThrowingBool(bool b) noexcept : b_(b) {}  // NOLINT(runtime/explicit)
-  operator bool() const {  // NOLINT(runtime/explicit)
+  operator bool() const {                   // NOLINT
     exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
     return b_;
   }
@@ -734,10 +732,9 @@ template <typename T, typename... Args>
 T TestThrowingCtor(Args&&... args) {
   struct Cleanup {
     ~Cleanup() { UnsetCountdown(); }
-  };
-  Cleanup c;
-  for (int countdown = 0;; ++countdown) {
-    exceptions_internal::countdown = countdown;
+  } c;
+  for (int count = 0;; ++count) {
+    exceptions_internal::countdown = count;
     try {
       return T(std::forward<Args>(args)...);
     } catch (const exceptions_internal::TestException&) {
@@ -745,58 +742,237 @@ T TestThrowingCtor(Args&&... args) {
   }
 }
 
-// Tests that performing operation Op on a T follows exception safety
-// guarantees.  By default only tests the basic guarantee. There must be a
-// function, AbslCheckInvariants(T*, absl::InternalAbslNamespaceFinder) which
-// returns anything convertible to bool and which makes sure the invariants of
-// the type are upheld.  This is called before any of the checkers.  The
-// InternalAbslNamespaceFinder is unused, and just helps find
-// AbslCheckInvariants for absl types which become aliases to std::types in
-// C++17.
-//
-// Parameters:
-//   * TFactory: operator() returns a unique_ptr to the type under test (T).  It
-//   should always return pointers to values which compare equal.
-//   * FunctionFromTPtrToVoid: A functor exercising the function under test.  It
-//   should take a T* and return void.
-//   * Checkers: Any number of functions taking a T* and returning
-//   anything contextually convertible to bool.  If a testing::AssertionResult
-//   is used then the error message is kept.  These test invariants related to
-//   the operation. To test the strong guarantee, pass
-//   absl::StrongGuarantee(factory).  A checker may freely modify the passed-in
-//   T, for example to make sure the T can be set to a known state.
-template <typename TFactory, typename FunctionFromTPtrToVoid,
-          typename... Checkers>
-testing::AssertionResult TestExceptionSafety(TFactory factory,
-                                             FunctionFromTPtrToVoid&& op,
-                                             const Checkers&... checkers) {
-  struct Cleanup {
-    ~Cleanup() { UnsetCountdown(); }
-  } c;
-  for (int countdown = 0;; ++countdown) {
-    auto out = exceptions_internal::TestAtCountdown(factory, op, countdown,
-                                                    checkers...);
-    if (!out.has_value()) {
-      return testing::AssertionSuccess();
+namespace exceptions_internal {
+
+// Dummy struct for ExceptionSafetyTester<> partial state.
+struct UninitializedT {};
+
+template <typename T>
+class DefaultFactory {
+ public:
+  explicit DefaultFactory(const T& t) : t_(t) {}
+  std::unique_ptr<T> operator()() const { return absl::make_unique<T>(t_); }
+
+ private:
+  T t_;
+};
+
+template <size_t LazyInvariantsCount, typename LazyFactory,
+          typename LazyOperation>
+using EnableIfTestable = typename absl::enable_if_t<
+    LazyInvariantsCount != 0 &&
+    !std::is_same<LazyFactory, UninitializedT>::value &&
+    !std::is_same<LazyOperation, UninitializedT>::value>;
+
+template <typename Factory = UninitializedT,
+          typename Operation = UninitializedT, typename... Invariants>
+class ExceptionSafetyTester;
+
+}  // namespace exceptions_internal
+
+exceptions_internal::ExceptionSafetyTester<> MakeExceptionSafetyTester();
+
+namespace exceptions_internal {
+
+/*
+ * Builds a tester object that tests if performing a operation on a T follows
+ * exception safety guarantees. Verification is done via invariant assertion
+ * callbacks applied to T instances post-throw.
+ *
+ * Template parameters for ExceptionSafetyTester:
+ *
+ * - Factory: The factory object (passed in via tester.WithFactory(...) or
+ *   tester.WithInitialValue(...)) must be invocable with the signature
+ *   `std::unique_ptr<T> operator()() const` where T is the type being tested.
+ *   It is used for reliably creating identical T instances to test on.
+ *
+ * - Operation: The operation object (passsed in via tester.WithOperation(...)
+ *   or tester.Test(...)) must be invocable with the signature
+ *   `void operator()(T*) const` where T is the type being tested. It is used
+ *   for performing steps on a T instance that may throw and that need to be
+ *   checked for exception safety. Each call to the operation will receive a
+ *   fresh T instance so it's free to modify and destroy the T instances as it
+ *   pleases.
+ *
+ * - Invariants...: The invariant assertion callback objects (passed in via
+ *   tester.WithInvariants(...)) must be invocable with the signature
+ *   `testing::AssertionResult operator()(T*) const` where T is the type being
+ *   tested. Invariant assertion callbacks are provided T instances post-throw.
+ *   They must return testing::AssertionSuccess when the type invariants of the
+ *   provided T instance hold. If the type invariants of the T instance do not
+ *   hold, they must return testing::AssertionFailure. Execution order of
+ *   Invariants... is unspecified. They will each individually get a fresh T
+ *   instance so they are free to modify and destroy the T instances as they
+ *   please.
+ */
+template <typename Factory, typename Operation, typename... Invariants>
+class ExceptionSafetyTester {
+ public:
+  /*
+   * Returns a new ExceptionSafetyTester with an included T factory based on the
+   * provided T instance. The existing factory will not be included in the newly
+   * created tester instance. The created factory returns a new T instance by
+   * copy-constructing the provided const T& t.
+   *
+   * Preconditions for tester.WithInitialValue(const T& t):
+   *
+   * - The const T& t object must be copy-constructible where T is the type
+   *   being tested. For non-copy-constructible objects, use the method
+   *   tester.WithFactory(...).
+   */
+  template <typename T>
+  ExceptionSafetyTester<DefaultFactory<T>, Operation, Invariants...>
+  WithInitialValue(const T& t) const {
+    return WithFactory(DefaultFactory<T>(t));
+  }
+
+  /*
+   * Returns a new ExceptionSafetyTester with the provided T factory included.
+   * The existing factory will not be included in the newly-created tester
+   * instance. This method is intended for use with types lacking a copy
+   * constructor. Types that can be copy-constructed should instead use the
+   * method tester.WithInitialValue(...).
+   */
+  template <typename NewFactory>
+  ExceptionSafetyTester<absl::decay_t<NewFactory>, Operation, Invariants...>
+  WithFactory(const NewFactory& new_factory) const {
+    return {new_factory, operation_, invariants_};
+  }
+
+  /*
+   * Returns a new ExceptionSafetyTester with the provided testable operation
+   * included. The existing operation will not be included in the newly created
+   * tester.
+   */
+  template <typename NewOperation>
+  ExceptionSafetyTester<Factory, absl::decay_t<NewOperation>, Invariants...>
+  WithOperation(const NewOperation& new_operation) const {
+    return {factory_, new_operation, invariants_};
+  }
+
+  /*
+   * Returns a new ExceptionSafetyTester with the provided MoreInvariants...
+   * combined with the Invariants... that were already included in the instance
+   * on which the method was called. Invariants... cannot be removed or replaced
+   * once added to an ExceptionSafetyTester instance. A fresh object must be
+   * created in order to get an empty Invariants... list.
+   *
+   * In addition to passing in custom invariant assertion callbacks, this method
+   * accepts `absl::strong_guarantee` as an argument which checks T instances
+   * post-throw against freshly created T instances via operator== to verify
+   * that any state changes made during the execution of the operation were
+   * properly rolled back.
+   */
+  template <typename... MoreInvariants>
+  ExceptionSafetyTester<Factory, Operation, Invariants...,
+                        absl::decay_t<MoreInvariants>...>
+  WithInvariants(const MoreInvariants&... more_invariants) const {
+    return {factory_, operation_,
+            std::tuple_cat(invariants_,
+                           std::tuple<absl::decay_t<MoreInvariants>...>(
+                               more_invariants...))};
+  }
+
+  /*
+   * Returns a testing::AssertionResult that is the reduced result of the
+   * exception safety algorithm. The algorithm short circuits and returns
+   * AssertionFailure after the first invariant callback returns an
+   * AssertionFailure. Otherwise, if all invariant callbacks return an
+   * AssertionSuccess, the reduced result is AssertionSuccess.
+   *
+   * The passed-in testable operation will not be saved in a new tester instance
+   * nor will it modify/replace the existing tester instance. This is useful
+   * when each operation being tested is unique and does not need to be reused.
+   *
+   * Preconditions for tester.Test(const NewOperation& new_operation):
+   *
+   * - May only be called after at least one invariant assertion callback and a
+   *   factory or initial value have been provided.
+   */
+  template <
+      typename NewOperation,
+      typename = EnableIfTestable<sizeof...(Invariants), Factory, NewOperation>>
+  testing::AssertionResult Test(const NewOperation& new_operation) const {
+    return TestImpl(new_operation, absl::index_sequence_for<Invariants...>());
+  }
+
+  /*
+   * Returns a testing::AssertionResult that is the reduced result of the
+   * exception safety algorithm. The algorithm short circuits and returns
+   * AssertionFailure after the first invariant callback returns an
+   * AssertionFailure. Otherwise, if all invariant callbacks return an
+   * AssertionSuccess, the reduced result is AssertionSuccess.
+   *
+   * Preconditions for tester.Test():
+   *
+   * - May only be called after at least one invariant assertion callback, a
+   *   factory or initial value and a testable operation have been provided.
+   */
+  template <typename LazyOperation = Operation,
+            typename =
+                EnableIfTestable<sizeof...(Invariants), Factory, LazyOperation>>
+  testing::AssertionResult Test() const {
+    return TestImpl(operation_, absl::index_sequence_for<Invariants...>());
+  }
+
+ private:
+  template <typename, typename, typename...>
+  friend class ExceptionSafetyTester;
+
+  friend ExceptionSafetyTester<> absl::MakeExceptionSafetyTester();
+
+  ExceptionSafetyTester() {}
+
+  ExceptionSafetyTester(const Factory& f, const Operation& o,
+                        const std::tuple<Invariants...>& i)
+      : factory_(f), operation_(o), invariants_(i) {}
+
+  template <typename SelectedOperation, size_t... Indices>
+  testing::AssertionResult TestImpl(const SelectedOperation& selected_operation,
+                                    absl::index_sequence<Indices...>) const {
+    // Starting from 0 and counting upwards until one of the exit conditions is
+    // hit...
+    for (int count = 0;; ++count) {
+      // Run the full exception safety test algorithm for the current countdown
+      auto reduced_res =
+          TestAllInvariantsAtCountdown(factory_, selected_operation, count,
+                                       std::get<Indices>(invariants_)...);
+      // If there is no value in the optional, no invariants were run because no
+      // exception was thrown. This means that the test is complete and the loop
+      // can exit successfully.
+      if (!reduced_res.has_value()) {
+        return testing::AssertionSuccess();
+      }
+      // If the optional is not empty and the value is falsy, an invariant check
+      // failed so the test must exit to propegate the failure.
+      if (!reduced_res.value()) {
+        return reduced_res.value();
+      }
+      // If the optional is not empty and the value is not falsy, it means
+      // exceptions were thrown but the invariants passed so the test must
+      // continue to run.
     }
-    if (!*out) return *out;
   }
-}
 
-// Returns a functor to test for the strong exception-safety guarantee.
-// Equality comparisons are made against the T provided by the factory and
-// default to using operator==.
-//
-// Parameters:
-//   * TFactory: operator() returns a unique_ptr to the type under test.  It
-//   should always return pointers to values which compare equal.
-template <typename TFactory, typename EqualTo = std::equal_to<
-                                 exceptions_internal::FactoryType<TFactory>>>
-exceptions_internal::StrongGuaranteeTester<
-    exceptions_internal::FactoryType<TFactory>, EqualTo>
-StrongGuarantee(TFactory factory, EqualTo eq = EqualTo()) {
-  return exceptions_internal::StrongGuaranteeTester<
-      exceptions_internal::FactoryType<TFactory>, EqualTo>(factory(), eq);
+  Factory factory_;
+  Operation operation_;
+  std::tuple<Invariants...> invariants_;
+};
+
+}  // namespace exceptions_internal
+
+/*
+ * Constructs an empty ExceptionSafetyTester. All ExceptionSafetyTester
+ * objects are immutable and all With[thing] mutation methods return new
+ * instances of ExceptionSafetyTester.
+ *
+ * In order to test a T for exception safety, a factory for that T, a testable
+ * operation, and at least one invariant callback returning an assertion
+ * result must be applied using the respective methods.
+ */
+inline exceptions_internal::ExceptionSafetyTester<>
+MakeExceptionSafetyTester() {
+  return {};
 }
 
 }  // namespace absl