aboutsummaryrefslogtreecommitdiff
path: root/third_party/abseil-cpp/absl/base/exception_safety_testing_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/abseil-cpp/absl/base/exception_safety_testing_test.cc')
-rw-r--r--third_party/abseil-cpp/absl/base/exception_safety_testing_test.cc956
1 files changed, 956 insertions, 0 deletions
diff --git a/third_party/abseil-cpp/absl/base/exception_safety_testing_test.cc b/third_party/abseil-cpp/absl/base/exception_safety_testing_test.cc
new file mode 100644
index 0000000000..a59be29e91
--- /dev/null
+++ b/third_party/abseil-cpp/absl/base/exception_safety_testing_test.cc
@@ -0,0 +1,956 @@
+// Copyright 2017 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/base/internal/exception_safety_testing.h"
+
+#ifdef ABSL_HAVE_EXCEPTIONS
+
+#include <cstddef>
+#include <exception>
+#include <iostream>
+#include <list>
+#include <type_traits>
+#include <vector>
+
+#include "gtest/gtest-spi.h"
+#include "gtest/gtest.h"
+#include "absl/memory/memory.h"
+
+namespace testing {
+
+namespace {
+
+using ::testing::exceptions_internal::SetCountdown;
+using ::testing::exceptions_internal::TestException;
+using ::testing::exceptions_internal::UnsetCountdown;
+
+// EXPECT_NO_THROW can't inspect the thrown inspection in general.
+template <typename F>
+void ExpectNoThrow(const F& f) {
+ try {
+ f();
+ } catch (const TestException& e) {
+ ADD_FAILURE() << "Unexpected exception thrown from " << e.what();
+ }
+}
+
+TEST(ThrowingValueTest, Throws) {
+ SetCountdown();
+ EXPECT_THROW(ThrowingValue<> bomb, TestException);
+
+ // It's not guaranteed that every operator only throws *once*. The default
+ // ctor only throws once, though, so use it to make sure we only throw when
+ // the countdown hits 0
+ SetCountdown(2);
+ ExpectNoThrow([]() { ThrowingValue<> bomb; });
+ ExpectNoThrow([]() { ThrowingValue<> bomb; });
+ EXPECT_THROW(ThrowingValue<> bomb, TestException);
+
+ UnsetCountdown();
+}
+
+// Tests that an operation throws when the countdown is at 0, doesn't throw when
+// the countdown doesn't hit 0, and doesn't modify the state of the
+// ThrowingValue if it throws
+template <typename F>
+void TestOp(const F& f) {
+ ExpectNoThrow(f);
+
+ SetCountdown();
+ EXPECT_THROW(f(), TestException);
+ UnsetCountdown();
+}
+
+TEST(ThrowingValueTest, ThrowingCtors) {
+ ThrowingValue<> bomb;
+
+ TestOp([]() { ThrowingValue<> bomb(1); });
+ TestOp([&]() { ThrowingValue<> bomb1 = bomb; });
+ TestOp([&]() { ThrowingValue<> bomb1 = std::move(bomb); });
+}
+
+TEST(ThrowingValueTest, ThrowingAssignment) {
+ ThrowingValue<> bomb, bomb1;
+
+ TestOp([&]() { bomb = bomb1; });
+ TestOp([&]() { bomb = std::move(bomb1); });
+
+ // Test that when assignment throws, the assignment should fail (lhs != rhs)
+ // and strong guarantee fails (lhs != lhs_copy).
+ {
+ ThrowingValue<> lhs(39), rhs(42);
+ ThrowingValue<> lhs_copy(lhs);
+ SetCountdown();
+ EXPECT_THROW(lhs = rhs, TestException);
+ UnsetCountdown();
+ EXPECT_NE(lhs, rhs);
+ EXPECT_NE(lhs_copy, lhs);
+ }
+ {
+ ThrowingValue<> lhs(39), rhs(42);
+ ThrowingValue<> lhs_copy(lhs), rhs_copy(rhs);
+ SetCountdown();
+ EXPECT_THROW(lhs = std::move(rhs), TestException);
+ UnsetCountdown();
+ EXPECT_NE(lhs, rhs_copy);
+ EXPECT_NE(lhs_copy, lhs);
+ }
+}
+
+TEST(ThrowingValueTest, ThrowingComparisons) {
+ ThrowingValue<> bomb1, bomb2;
+ TestOp([&]() { return bomb1 == bomb2; });
+ TestOp([&]() { return bomb1 != bomb2; });
+ TestOp([&]() { return bomb1 < bomb2; });
+ TestOp([&]() { return bomb1 <= bomb2; });
+ TestOp([&]() { return bomb1 > bomb2; });
+ TestOp([&]() { return bomb1 >= bomb2; });
+}
+
+TEST(ThrowingValueTest, ThrowingArithmeticOps) {
+ ThrowingValue<> bomb1(1), bomb2(2);
+
+ TestOp([&bomb1]() { +bomb1; });
+ TestOp([&bomb1]() { -bomb1; });
+ TestOp([&bomb1]() { ++bomb1; });
+ TestOp([&bomb1]() { bomb1++; });
+ TestOp([&bomb1]() { --bomb1; });
+ TestOp([&bomb1]() { bomb1--; });
+
+ TestOp([&]() { bomb1 + bomb2; });
+ TestOp([&]() { bomb1 - bomb2; });
+ TestOp([&]() { bomb1* bomb2; });
+ TestOp([&]() { bomb1 / bomb2; });
+ TestOp([&]() { bomb1 << 1; });
+ TestOp([&]() { bomb1 >> 1; });
+}
+
+TEST(ThrowingValueTest, ThrowingLogicalOps) {
+ ThrowingValue<> bomb1, bomb2;
+
+ TestOp([&bomb1]() { !bomb1; });
+ TestOp([&]() { bomb1&& bomb2; });
+ TestOp([&]() { bomb1 || bomb2; });
+}
+
+TEST(ThrowingValueTest, ThrowingBitwiseOps) {
+ ThrowingValue<> bomb1, bomb2;
+
+ TestOp([&bomb1]() { ~bomb1; });
+ TestOp([&]() { bomb1& bomb2; });
+ TestOp([&]() { bomb1 | bomb2; });
+ TestOp([&]() { bomb1 ^ bomb2; });
+}
+
+TEST(ThrowingValueTest, ThrowingCompoundAssignmentOps) {
+ ThrowingValue<> bomb1(1), bomb2(2);
+
+ TestOp([&]() { bomb1 += bomb2; });
+ TestOp([&]() { bomb1 -= bomb2; });
+ TestOp([&]() { bomb1 *= bomb2; });
+ TestOp([&]() { bomb1 /= bomb2; });
+ TestOp([&]() { bomb1 %= bomb2; });
+ TestOp([&]() { bomb1 &= bomb2; });
+ TestOp([&]() { bomb1 |= bomb2; });
+ TestOp([&]() { bomb1 ^= bomb2; });
+ TestOp([&]() { bomb1 *= bomb2; });
+}
+
+TEST(ThrowingValueTest, ThrowingStreamOps) {
+ ThrowingValue<> bomb;
+
+ TestOp([&]() {
+ std::istringstream stream;
+ stream >> bomb;
+ });
+ TestOp([&]() {
+ std::stringstream stream;
+ stream << bomb;
+ });
+}
+
+// Tests the operator<< of ThrowingValue by forcing ConstructorTracker to emit
+// a nonfatal failure that contains the string representation of the Thrower
+TEST(ThrowingValueTest, StreamOpsOutput) {
+ using ::testing::TypeSpec;
+ exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown);
+
+ // Test default spec list (kEverythingThrows)
+ EXPECT_NONFATAL_FAILURE(
+ {
+ using Thrower = ThrowingValue<TypeSpec{}>;
+ auto thrower = Thrower(123);
+ thrower.~Thrower();
+ },
+ "ThrowingValue<>(123)");
+
+ // Test with one item in spec list (kNoThrowCopy)
+ EXPECT_NONFATAL_FAILURE(
+ {
+ using Thrower = ThrowingValue<TypeSpec::kNoThrowCopy>;
+ auto thrower = Thrower(234);
+ thrower.~Thrower();
+ },
+ "ThrowingValue<kNoThrowCopy>(234)");
+
+ // Test with multiple items in spec list (kNoThrowMove, kNoThrowNew)
+ EXPECT_NONFATAL_FAILURE(
+ {
+ using Thrower =
+ ThrowingValue<TypeSpec::kNoThrowMove | TypeSpec::kNoThrowNew>;
+ auto thrower = Thrower(345);
+ thrower.~Thrower();
+ },
+ "ThrowingValue<kNoThrowMove | kNoThrowNew>(345)");
+
+ // Test with all items in spec list (kNoThrowCopy, kNoThrowMove, kNoThrowNew)
+ EXPECT_NONFATAL_FAILURE(
+ {
+ using Thrower = ThrowingValue<static_cast<TypeSpec>(-1)>;
+ auto thrower = Thrower(456);
+ thrower.~Thrower();
+ },
+ "ThrowingValue<kNoThrowCopy | kNoThrowMove | kNoThrowNew>(456)");
+}
+
+template <typename F>
+void TestAllocatingOp(const F& f) {
+ ExpectNoThrow(f);
+
+ SetCountdown();
+ EXPECT_THROW(f(), exceptions_internal::TestBadAllocException);
+ UnsetCountdown();
+}
+
+TEST(ThrowingValueTest, ThrowingAllocatingOps) {
+ // make_unique calls unqualified operator new, so these exercise the
+ // ThrowingValue overloads.
+ TestAllocatingOp([]() { return absl::make_unique<ThrowingValue<>>(1); });
+ TestAllocatingOp([]() { return absl::make_unique<ThrowingValue<>[]>(2); });
+}
+
+TEST(ThrowingValueTest, NonThrowingMoveCtor) {
+ ThrowingValue<TypeSpec::kNoThrowMove> nothrow_ctor;
+
+ SetCountdown();
+ ExpectNoThrow([&nothrow_ctor]() {
+ ThrowingValue<TypeSpec::kNoThrowMove> nothrow1 = std::move(nothrow_ctor);
+ });
+ UnsetCountdown();
+}
+
+TEST(ThrowingValueTest, NonThrowingMoveAssign) {
+ ThrowingValue<TypeSpec::kNoThrowMove> nothrow_assign1, nothrow_assign2;
+
+ SetCountdown();
+ ExpectNoThrow([&nothrow_assign1, &nothrow_assign2]() {
+ nothrow_assign1 = std::move(nothrow_assign2);
+ });
+ UnsetCountdown();
+}
+
+TEST(ThrowingValueTest, ThrowingCopyCtor) {
+ ThrowingValue<> tv;
+
+ TestOp([&]() { ThrowingValue<> tv_copy(tv); });
+}
+
+TEST(ThrowingValueTest, ThrowingCopyAssign) {
+ ThrowingValue<> tv1, tv2;
+
+ TestOp([&]() { tv1 = tv2; });
+}
+
+TEST(ThrowingValueTest, NonThrowingCopyCtor) {
+ ThrowingValue<TypeSpec::kNoThrowCopy> nothrow_ctor;
+
+ SetCountdown();
+ ExpectNoThrow([&nothrow_ctor]() {
+ ThrowingValue<TypeSpec::kNoThrowCopy> nothrow1(nothrow_ctor);
+ });
+ UnsetCountdown();
+}
+
+TEST(ThrowingValueTest, NonThrowingCopyAssign) {
+ ThrowingValue<TypeSpec::kNoThrowCopy> nothrow_assign1, nothrow_assign2;
+
+ SetCountdown();
+ ExpectNoThrow([&nothrow_assign1, &nothrow_assign2]() {
+ nothrow_assign1 = nothrow_assign2;
+ });
+ UnsetCountdown();
+}
+
+TEST(ThrowingValueTest, ThrowingSwap) {
+ ThrowingValue<> bomb1, bomb2;
+ TestOp([&]() { std::swap(bomb1, bomb2); });
+}
+
+TEST(ThrowingValueTest, NonThrowingSwap) {
+ ThrowingValue<TypeSpec::kNoThrowMove> bomb1, bomb2;
+ ExpectNoThrow([&]() { std::swap(bomb1, bomb2); });
+}
+
+TEST(ThrowingValueTest, NonThrowingAllocation) {
+ ThrowingValue<TypeSpec::kNoThrowNew>* allocated;
+ ThrowingValue<TypeSpec::kNoThrowNew>* array;
+
+ ExpectNoThrow([&allocated]() {
+ allocated = new ThrowingValue<TypeSpec::kNoThrowNew>(1);
+ delete allocated;
+ });
+ ExpectNoThrow([&array]() {
+ array = new ThrowingValue<TypeSpec::kNoThrowNew>[2];
+ delete[] array;
+ });
+}
+
+TEST(ThrowingValueTest, NonThrowingDelete) {
+ auto* allocated = new ThrowingValue<>(1);
+ auto* array = new ThrowingValue<>[2];
+
+ SetCountdown();
+ ExpectNoThrow([allocated]() { delete allocated; });
+ SetCountdown();
+ ExpectNoThrow([array]() { delete[] array; });
+
+ UnsetCountdown();
+}
+
+TEST(ThrowingValueTest, NonThrowingPlacementDelete) {
+ constexpr int kArrayLen = 2;
+ // We intentionally create extra space to store the tag allocated by placement
+ // new[].
+ constexpr int kStorageLen = 4;
+
+ alignas(ThrowingValue<>) unsigned char buf[sizeof(ThrowingValue<>)];
+ alignas(ThrowingValue<>) unsigned char
+ array_buf[sizeof(ThrowingValue<>[kStorageLen])];
+ auto* placed = new (&buf) ThrowingValue<>(1);
+ auto placed_array = new (&array_buf) ThrowingValue<>[kArrayLen];
+
+ SetCountdown();
+ ExpectNoThrow([placed, &buf]() {
+ placed->~ThrowingValue<>();
+ ThrowingValue<>::operator delete(placed, &buf);
+ });
+
+ SetCountdown();
+ ExpectNoThrow([&, placed_array]() {
+ for (int i = 0; i < kArrayLen; ++i) placed_array[i].~ThrowingValue<>();
+ ThrowingValue<>::operator delete[](placed_array, &array_buf);
+ });
+
+ UnsetCountdown();
+}
+
+TEST(ThrowingValueTest, NonThrowingDestructor) {
+ auto* allocated = new ThrowingValue<>();
+
+ SetCountdown();
+ ExpectNoThrow([allocated]() { delete allocated; });
+ UnsetCountdown();
+}
+
+TEST(ThrowingBoolTest, ThrowingBool) {
+ ThrowingBool t = true;
+
+ // Test that it's contextually convertible to bool
+ if (t) { // NOLINT(whitespace/empty_if_body)
+ }
+ EXPECT_TRUE(t);
+
+ TestOp([&]() { (void)!t; });
+}
+
+TEST(ThrowingAllocatorTest, MemoryManagement) {
+ // Just exercise the memory management capabilities under LSan to make sure we
+ // don't leak.
+ ThrowingAllocator<int> int_alloc;
+ int* ip = int_alloc.allocate(1);
+ int_alloc.deallocate(ip, 1);
+ int* i_array = int_alloc.allocate(2);
+ int_alloc.deallocate(i_array, 2);
+
+ ThrowingAllocator<ThrowingValue<>> tv_alloc;
+ ThrowingValue<>* ptr = tv_alloc.allocate(1);
+ tv_alloc.deallocate(ptr, 1);
+ ThrowingValue<>* tv_array = tv_alloc.allocate(2);
+ tv_alloc.deallocate(tv_array, 2);
+}
+
+TEST(ThrowingAllocatorTest, CallsGlobalNew) {
+ ThrowingAllocator<ThrowingValue<>, AllocSpec::kNoThrowAllocate> nothrow_alloc;
+ ThrowingValue<>* ptr;
+
+ SetCountdown();
+ // This will only throw if ThrowingValue::new is called.
+ ExpectNoThrow([&]() { ptr = nothrow_alloc.allocate(1); });
+ nothrow_alloc.deallocate(ptr, 1);
+
+ UnsetCountdown();
+}
+
+TEST(ThrowingAllocatorTest, ThrowingConstructors) {
+ ThrowingAllocator<int> int_alloc;
+ int* ip = nullptr;
+
+ SetCountdown();
+ EXPECT_THROW(ip = int_alloc.allocate(1), TestException);
+ ExpectNoThrow([&]() { ip = int_alloc.allocate(1); });
+
+ *ip = 1;
+ SetCountdown();
+ EXPECT_THROW(int_alloc.construct(ip, 2), TestException);
+ EXPECT_EQ(*ip, 1);
+ int_alloc.deallocate(ip, 1);
+
+ UnsetCountdown();
+}
+
+TEST(ThrowingAllocatorTest, NonThrowingConstruction) {
+ {
+ ThrowingAllocator<int, AllocSpec::kNoThrowAllocate> int_alloc;
+ int* ip = nullptr;
+
+ SetCountdown();
+ ExpectNoThrow([&]() { ip = int_alloc.allocate(1); });
+
+ SetCountdown();
+ ExpectNoThrow([&]() { int_alloc.construct(ip, 2); });
+
+ EXPECT_EQ(*ip, 2);
+ int_alloc.deallocate(ip, 1);
+
+ UnsetCountdown();
+ }
+
+ {
+ ThrowingAllocator<int> int_alloc;
+ int* ip = nullptr;
+ ExpectNoThrow([&]() { ip = int_alloc.allocate(1); });
+ ExpectNoThrow([&]() { int_alloc.construct(ip, 2); });
+ EXPECT_EQ(*ip, 2);
+ int_alloc.deallocate(ip, 1);
+ }
+
+ {
+ ThrowingAllocator<ThrowingValue<>, AllocSpec::kNoThrowAllocate>
+ nothrow_alloc;
+ ThrowingValue<>* ptr;
+
+ SetCountdown();
+ ExpectNoThrow([&]() { ptr = nothrow_alloc.allocate(1); });
+
+ SetCountdown();
+ ExpectNoThrow(
+ [&]() { nothrow_alloc.construct(ptr, 2, testing::nothrow_ctor); });
+
+ EXPECT_EQ(ptr->Get(), 2);
+ nothrow_alloc.destroy(ptr);
+ nothrow_alloc.deallocate(ptr, 1);
+
+ UnsetCountdown();
+ }
+
+ {
+ ThrowingAllocator<int> a;
+
+ SetCountdown();
+ ExpectNoThrow([&]() { ThrowingAllocator<double> a1 = a; });
+
+ SetCountdown();
+ ExpectNoThrow([&]() { ThrowingAllocator<double> a1 = std::move(a); });
+
+ UnsetCountdown();
+ }
+}
+
+TEST(ThrowingAllocatorTest, ThrowingAllocatorConstruction) {
+ ThrowingAllocator<int> a;
+ TestOp([]() { ThrowingAllocator<int> a; });
+ TestOp([&]() { a.select_on_container_copy_construction(); });
+}
+
+TEST(ThrowingAllocatorTest, State) {
+ ThrowingAllocator<int> a1, a2;
+ EXPECT_NE(a1, a2);
+
+ auto a3 = a1;
+ EXPECT_EQ(a3, a1);
+ int* ip = a1.allocate(1);
+ EXPECT_EQ(a3, a1);
+ a3.deallocate(ip, 1);
+ EXPECT_EQ(a3, a1);
+}
+
+TEST(ThrowingAllocatorTest, InVector) {
+ std::vector<ThrowingValue<>, ThrowingAllocator<ThrowingValue<>>> v;
+ for (int i = 0; i < 20; ++i) v.push_back({});
+ for (int i = 0; i < 20; ++i) v.pop_back();
+}
+
+TEST(ThrowingAllocatorTest, InList) {
+ std::list<ThrowingValue<>, ThrowingAllocator<ThrowingValue<>>> l;
+ for (int i = 0; i < 20; ++i) l.push_back({});
+ for (int i = 0; i < 20; ++i) l.pop_back();
+ for (int i = 0; i < 20; ++i) l.push_front({});
+ for (int i = 0; i < 20; ++i) l.pop_front();
+}
+
+template <typename TesterInstance, typename = void>
+struct NullaryTestValidator : public std::false_type {};
+
+template <typename TesterInstance>
+struct NullaryTestValidator<
+ TesterInstance,
+ absl::void_t<decltype(std::declval<TesterInstance>().Test())>>
+ : public std::true_type {};
+
+template <typename TesterInstance>
+bool HasNullaryTest(const TesterInstance&) {
+ return NullaryTestValidator<TesterInstance>::value;
+}
+
+void DummyOp(void*) {}
+
+template <typename TesterInstance, typename = void>
+struct UnaryTestValidator : public std::false_type {};
+
+template <typename TesterInstance>
+struct UnaryTestValidator<
+ TesterInstance,
+ absl::void_t<decltype(std::declval<TesterInstance>().Test(DummyOp))>>
+ : public std::true_type {};
+
+template <typename TesterInstance>
+bool HasUnaryTest(const TesterInstance&) {
+ return UnaryTestValidator<TesterInstance>::value;
+}
+
+TEST(ExceptionSafetyTesterTest, IncompleteTypesAreNotTestable) {
+ using T = exceptions_internal::UninitializedT;
+ auto op = [](T* t) {};
+ auto inv = [](T*) { return testing::AssertionSuccess(); };
+ auto fac = []() { return absl::make_unique<T>(); };
+
+ // Test that providing operation and inveriants still does not allow for the
+ // the invocation of .Test() and .Test(op) because it lacks a factory
+ auto without_fac =
+ testing::MakeExceptionSafetyTester().WithOperation(op).WithContracts(
+ inv, testing::strong_guarantee);
+ EXPECT_FALSE(HasNullaryTest(without_fac));
+ EXPECT_FALSE(HasUnaryTest(without_fac));
+
+ // Test that providing contracts and factory allows the invocation of
+ // .Test(op) but does not allow for .Test() because it lacks an operation
+ auto without_op = testing::MakeExceptionSafetyTester()
+ .WithContracts(inv, testing::strong_guarantee)
+ .WithFactory(fac);
+ EXPECT_FALSE(HasNullaryTest(without_op));
+ EXPECT_TRUE(HasUnaryTest(without_op));
+
+ // Test that providing operation and factory still does not allow for the
+ // the invocation of .Test() and .Test(op) because it lacks contracts
+ auto without_inv =
+ testing::MakeExceptionSafetyTester().WithOperation(op).WithFactory(fac);
+ EXPECT_FALSE(HasNullaryTest(without_inv));
+ EXPECT_FALSE(HasUnaryTest(without_inv));
+}
+
+struct ExampleStruct {};
+
+std::unique_ptr<ExampleStruct> ExampleFunctionFactory() {
+ return absl::make_unique<ExampleStruct>();
+}
+
+void ExampleFunctionOperation(ExampleStruct*) {}
+
+testing::AssertionResult ExampleFunctionContract(ExampleStruct*) {
+ return testing::AssertionSuccess();
+}
+
+struct {
+ std::unique_ptr<ExampleStruct> operator()() const {
+ return ExampleFunctionFactory();
+ }
+} example_struct_factory;
+
+struct {
+ void operator()(ExampleStruct*) const {}
+} example_struct_operation;
+
+struct {
+ testing::AssertionResult operator()(ExampleStruct* example_struct) const {
+ return ExampleFunctionContract(example_struct);
+ }
+} example_struct_contract;
+
+auto example_lambda_factory = []() { return ExampleFunctionFactory(); };
+
+auto example_lambda_operation = [](ExampleStruct*) {};
+
+auto example_lambda_contract = [](ExampleStruct* example_struct) {
+ return ExampleFunctionContract(example_struct);
+};
+
+// Testing that function references, pointers, structs with operator() and
+// lambdas can all be used with ExceptionSafetyTester
+TEST(ExceptionSafetyTesterTest, MixedFunctionTypes) {
+ // function reference
+ EXPECT_TRUE(testing::MakeExceptionSafetyTester()
+ .WithFactory(ExampleFunctionFactory)
+ .WithOperation(ExampleFunctionOperation)
+ .WithContracts(ExampleFunctionContract)
+ .Test());
+
+ // function pointer
+ EXPECT_TRUE(testing::MakeExceptionSafetyTester()
+ .WithFactory(&ExampleFunctionFactory)
+ .WithOperation(&ExampleFunctionOperation)
+ .WithContracts(&ExampleFunctionContract)
+ .Test());
+
+ // struct
+ EXPECT_TRUE(testing::MakeExceptionSafetyTester()
+ .WithFactory(example_struct_factory)
+ .WithOperation(example_struct_operation)
+ .WithContracts(example_struct_contract)
+ .Test());
+
+ // lambda
+ EXPECT_TRUE(testing::MakeExceptionSafetyTester()
+ .WithFactory(example_lambda_factory)
+ .WithOperation(example_lambda_operation)
+ .WithContracts(example_lambda_contract)
+ .Test());
+}
+
+struct NonNegative {
+ bool operator==(const NonNegative& other) const { return i == other.i; }
+ int i;
+};
+
+testing::AssertionResult CheckNonNegativeInvariants(NonNegative* g) {
+ if (g->i >= 0) {
+ return testing::AssertionSuccess();
+ }
+ return testing::AssertionFailure()
+ << "i should be non-negative but is " << g->i;
+}
+
+struct {
+ template <typename T>
+ void operator()(T* t) const {
+ (*t)();
+ }
+} invoker;
+
+auto tester =
+ testing::MakeExceptionSafetyTester().WithOperation(invoker).WithContracts(
+ CheckNonNegativeInvariants);
+auto strong_tester = tester.WithContracts(testing::strong_guarantee);
+
+struct FailsBasicGuarantee : public NonNegative {
+ void operator()() {
+ --i;
+ ThrowingValue<> bomb;
+ ++i;
+ }
+};
+
+TEST(ExceptionCheckTest, BasicGuaranteeFailure) {
+ EXPECT_FALSE(tester.WithInitialValue(FailsBasicGuarantee{}).Test());
+}
+
+struct FollowsBasicGuarantee : public NonNegative {
+ void operator()() {
+ ++i;
+ ThrowingValue<> bomb;
+ }
+};
+
+TEST(ExceptionCheckTest, BasicGuarantee) {
+ EXPECT_TRUE(tester.WithInitialValue(FollowsBasicGuarantee{}).Test());
+}
+
+TEST(ExceptionCheckTest, StrongGuaranteeFailure) {
+ EXPECT_FALSE(strong_tester.WithInitialValue(FailsBasicGuarantee{}).Test());
+ EXPECT_FALSE(strong_tester.WithInitialValue(FollowsBasicGuarantee{}).Test());
+}
+
+struct BasicGuaranteeWithExtraContracts : public NonNegative {
+ // After operator(), i is incremented. If operator() throws, i is set to 9999
+ void operator()() {
+ int old_i = i;
+ i = kExceptionSentinel;
+ ThrowingValue<> bomb;
+ i = ++old_i;
+ }
+
+ static constexpr int kExceptionSentinel = 9999;
+};
+constexpr int BasicGuaranteeWithExtraContracts::kExceptionSentinel;
+
+TEST(ExceptionCheckTest, BasicGuaranteeWithExtraContracts) {
+ auto tester_with_val =
+ tester.WithInitialValue(BasicGuaranteeWithExtraContracts{});
+ EXPECT_TRUE(tester_with_val.Test());
+ EXPECT_TRUE(
+ tester_with_val
+ .WithContracts([](BasicGuaranteeWithExtraContracts* o) {
+ if (o->i == BasicGuaranteeWithExtraContracts::kExceptionSentinel) {
+ return testing::AssertionSuccess();
+ }
+ return testing::AssertionFailure()
+ << "i should be "
+ << BasicGuaranteeWithExtraContracts::kExceptionSentinel
+ << ", but is " << o->i;
+ })
+ .Test());
+}
+
+struct FollowsStrongGuarantee : public NonNegative {
+ void operator()() { ThrowingValue<> bomb; }
+};
+
+TEST(ExceptionCheckTest, StrongGuarantee) {
+ EXPECT_TRUE(tester.WithInitialValue(FollowsStrongGuarantee{}).Test());
+ EXPECT_TRUE(strong_tester.WithInitialValue(FollowsStrongGuarantee{}).Test());
+}
+
+struct HasReset : public NonNegative {
+ void operator()() {
+ i = -1;
+ ThrowingValue<> bomb;
+ i = 1;
+ }
+
+ void reset() { i = 0; }
+};
+
+testing::AssertionResult CheckHasResetContracts(HasReset* h) {
+ h->reset();
+ return testing::AssertionResult(h->i == 0);
+}
+
+TEST(ExceptionCheckTest, ModifyingChecker) {
+ auto set_to_1000 = [](FollowsBasicGuarantee* g) {
+ g->i = 1000;
+ return testing::AssertionSuccess();
+ };
+ auto is_1000 = [](FollowsBasicGuarantee* g) {
+ return testing::AssertionResult(g->i == 1000);
+ };
+ auto increment = [](FollowsStrongGuarantee* g) {
+ ++g->i;
+ return testing::AssertionSuccess();
+ };
+
+ EXPECT_FALSE(tester.WithInitialValue(FollowsBasicGuarantee{})
+ .WithContracts(set_to_1000, is_1000)
+ .Test());
+ EXPECT_TRUE(strong_tester.WithInitialValue(FollowsStrongGuarantee{})
+ .WithContracts(increment)
+ .Test());
+ EXPECT_TRUE(testing::MakeExceptionSafetyTester()
+ .WithInitialValue(HasReset{})
+ .WithContracts(CheckHasResetContracts)
+ .Test(invoker));
+}
+
+TEST(ExceptionSafetyTesterTest, ResetsCountdown) {
+ auto test =
+ testing::MakeExceptionSafetyTester()
+ .WithInitialValue(ThrowingValue<>())
+ .WithContracts([](ThrowingValue<>*) { return AssertionSuccess(); })
+ .WithOperation([](ThrowingValue<>*) {});
+ ASSERT_TRUE(test.Test());
+ // If the countdown isn't reset because there were no exceptions thrown, then
+ // this will fail with a termination from an unhandled exception
+ EXPECT_TRUE(test.Test());
+}
+
+struct NonCopyable : public NonNegative {
+ NonCopyable(const NonCopyable&) = delete;
+ NonCopyable() : NonNegative{0} {}
+
+ void operator()() { ThrowingValue<> bomb; }
+};
+
+TEST(ExceptionCheckTest, NonCopyable) {
+ auto factory = []() { return absl::make_unique<NonCopyable>(); };
+ EXPECT_TRUE(tester.WithFactory(factory).Test());
+ EXPECT_TRUE(strong_tester.WithFactory(factory).Test());
+}
+
+struct NonEqualityComparable : public NonNegative {
+ void operator()() { ThrowingValue<> bomb; }
+
+ void ModifyOnThrow() {
+ ++i;
+ ThrowingValue<> bomb;
+ static_cast<void>(bomb);
+ --i;
+ }
+};
+
+TEST(ExceptionCheckTest, NonEqualityComparable) {
+ auto nec_is_strong = [](NonEqualityComparable* nec) {
+ return testing::AssertionResult(nec->i == NonEqualityComparable().i);
+ };
+ auto strong_nec_tester = tester.WithInitialValue(NonEqualityComparable{})
+ .WithContracts(nec_is_strong);
+
+ EXPECT_TRUE(strong_nec_tester.Test());
+ EXPECT_FALSE(strong_nec_tester.Test(
+ [](NonEqualityComparable* n) { n->ModifyOnThrow(); }));
+}
+
+template <typename T>
+struct ExhaustivenessTester {
+ void operator()() {
+ successes |= 1;
+ T b1;
+ static_cast<void>(b1);
+ successes |= (1 << 1);
+ T b2;
+ static_cast<void>(b2);
+ successes |= (1 << 2);
+ T b3;
+ static_cast<void>(b3);
+ successes |= (1 << 3);
+ }
+
+ bool operator==(const ExhaustivenessTester<ThrowingValue<>>&) const {
+ return true;
+ }
+
+ static unsigned char successes;
+};
+
+struct {
+ template <typename T>
+ testing::AssertionResult operator()(ExhaustivenessTester<T>*) const {
+ return testing::AssertionSuccess();
+ }
+} CheckExhaustivenessTesterContracts;
+
+template <typename T>
+unsigned char ExhaustivenessTester<T>::successes = 0;
+
+TEST(ExceptionCheckTest, Exhaustiveness) {
+ auto exhaust_tester = testing::MakeExceptionSafetyTester()
+ .WithContracts(CheckExhaustivenessTesterContracts)
+ .WithOperation(invoker);
+
+ EXPECT_TRUE(
+ exhaust_tester.WithInitialValue(ExhaustivenessTester<int>{}).Test());
+ EXPECT_EQ(ExhaustivenessTester<int>::successes, 0xF);
+
+ EXPECT_TRUE(
+ exhaust_tester.WithInitialValue(ExhaustivenessTester<ThrowingValue<>>{})
+ .WithContracts(testing::strong_guarantee)
+ .Test());
+ EXPECT_EQ(ExhaustivenessTester<ThrowingValue<>>::successes, 0xF);
+}
+
+struct LeaksIfCtorThrows : private exceptions_internal::TrackedObject {
+ LeaksIfCtorThrows() : TrackedObject(ABSL_PRETTY_FUNCTION) {
+ ++counter;
+ ThrowingValue<> v;
+ static_cast<void>(v);
+ --counter;
+ }
+ LeaksIfCtorThrows(const LeaksIfCtorThrows&) noexcept
+ : TrackedObject(ABSL_PRETTY_FUNCTION) {}
+ static int counter;
+};
+int LeaksIfCtorThrows::counter = 0;
+
+TEST(ExceptionCheckTest, TestLeakyCtor) {
+ testing::TestThrowingCtor<LeaksIfCtorThrows>();
+ EXPECT_EQ(LeaksIfCtorThrows::counter, 1);
+ LeaksIfCtorThrows::counter = 0;
+}
+
+struct Tracked : private exceptions_internal::TrackedObject {
+ Tracked() : TrackedObject(ABSL_PRETTY_FUNCTION) {}
+};
+
+TEST(ConstructorTrackerTest, CreatedBefore) {
+ Tracked a, b, c;
+ exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown);
+}
+
+TEST(ConstructorTrackerTest, CreatedAfter) {
+ exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown);
+ Tracked a, b, c;
+}
+
+TEST(ConstructorTrackerTest, NotDestroyedAfter) {
+ alignas(Tracked) unsigned char storage[sizeof(Tracked)];
+ EXPECT_NONFATAL_FAILURE(
+ {
+ exceptions_internal::ConstructorTracker ct(
+ exceptions_internal::countdown);
+ new (&storage) Tracked();
+ },
+ "not destroyed");
+}
+
+TEST(ConstructorTrackerTest, DestroyedTwice) {
+ exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown);
+ EXPECT_NONFATAL_FAILURE(
+ {
+ Tracked t;
+ t.~Tracked();
+ },
+ "re-destroyed");
+}
+
+TEST(ConstructorTrackerTest, ConstructedTwice) {
+ exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown);
+ alignas(Tracked) unsigned char storage[sizeof(Tracked)];
+ EXPECT_NONFATAL_FAILURE(
+ {
+ new (&storage) Tracked();
+ new (&storage) Tracked();
+ reinterpret_cast<Tracked*>(&storage)->~Tracked();
+ },
+ "re-constructed");
+}
+
+TEST(ThrowingValueTraitsTest, RelationalOperators) {
+ ThrowingValue<> a, b;
+ EXPECT_TRUE((std::is_convertible<decltype(a == b), bool>::value));
+ EXPECT_TRUE((std::is_convertible<decltype(a != b), bool>::value));
+ EXPECT_TRUE((std::is_convertible<decltype(a < b), bool>::value));
+ EXPECT_TRUE((std::is_convertible<decltype(a <= b), bool>::value));
+ EXPECT_TRUE((std::is_convertible<decltype(a > b), bool>::value));
+ EXPECT_TRUE((std::is_convertible<decltype(a >= b), bool>::value));
+}
+
+TEST(ThrowingAllocatorTraitsTest, Assignablility) {
+ EXPECT_TRUE(absl::is_move_assignable<ThrowingAllocator<int>>::value);
+ EXPECT_TRUE(absl::is_copy_assignable<ThrowingAllocator<int>>::value);
+ EXPECT_TRUE(std::is_nothrow_move_assignable<ThrowingAllocator<int>>::value);
+ EXPECT_TRUE(std::is_nothrow_copy_assignable<ThrowingAllocator<int>>::value);
+}
+
+} // namespace
+
+} // namespace testing
+
+#endif // ABSL_HAVE_EXCEPTIONS