summaryrefslogtreecommitdiff
path: root/absl/base/internal/exception_safety_testing.h
diff options
context:
space:
mode:
authorGravatar Abseil Team <absl-team@google.com>2018-04-18 05:56:39 -0700
committerGravatar Jon Cohen <cohenjon@google.com>2018-04-19 10:19:32 -0400
commit5b535401665cc6aa96d54a5c9b0901153d97210f (patch)
tree8d45863656540c8ec81eb7d2742c9191c33ca935 /absl/base/internal/exception_safety_testing.h
parenta7e522daf1ec9cda69b356472f662142dd0c1215 (diff)
- ed0ba496fe01eb8edfa86beade8a37768e7c12ef Updates the API for Exception Safety testing to use build... by Abseil Team <absl-team@google.com>
- c4b7a4e517c9404932c45f2f9f92eb7dc694e45d Internal change by Abseil Team <absl-team@google.com> - 76c78ed9385f65d881511645446e0bb8ababf6ec Add missing ABSL_PREDICT_FALSE to one of FixedArray::at()... by Abseil Team <absl-team@google.com> - 1204fb1c46f007dd9dfb7d9abf3e96c58835d193 Internal change. by Greg Falcon <gfalcon@google.com> - f1f47c98a026bc5e425ae83ff4a2eb391bbd3d9b Add internal-only functionality to examine the stack, to ... by Derek Mauro <dmauro@google.com> - 30d63097cd268d912f917526f6511005580465c4 fix typo by Abseil Team <absl-team@google.com> - 942d7efa6cf54cd248ca57dcaf3c245188b52a76 Remove unnecessary semicolons from comment examples. by Abseil Team <absl-team@google.com> - 7db0669cf23a06d934d3ed8c76aee4e4e23b7e04 Remove malloc_hook and malloc_extension from our internal... by Greg Falcon <gfalcon@google.com> - 0190f1063d101b1ded355019df2e1d325931f6c7 Make the maximum length of a string view equal difference... by Abseil Team <absl-team@google.com> - c8ae37cbce29449b02115a0ebd435ddc3d7ab062 Add namespace qualification. by Shaindel Schwartz <shaindel@google.com> - ff70afe2e6e3dd39f51ce9829e3e1f18231bf4d7 Fix internal/direct_mmap.h for non-linux builds. by Greg Falcon <gfalcon@google.com> GitOrigin-RevId: ed0ba496fe01eb8edfa86beade8a37768e7c12ef Change-Id: I7595ee3480d1d6724fd3797c15ba9d9be0d17e62
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 a0cd33b4..3493b5e2 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