- 81cdce434ff1bd8fa54c832a11dda59af46e79cc Adds a failure signal handler to Abseil. by Derek Mauro <dmauro@google.com>
- 40a973dd1b159e7455dd5fc06ac2d3f494d72c3e Remove test fixture requirement for ExceptionSafetyTester... by Abseil Team <absl-team@google.com> GitOrigin-RevId: 81cdce434ff1bd8fa54c832a11dda59af46e79cc Change-Id: Ia9fca98e38f229b68f7ec45600dee1bbd5dcff33
This commit is contained in:
parent
ea0e750e52
commit
28f5b89070
8 changed files with 792 additions and 105 deletions
|
|
@ -41,15 +41,7 @@ void ExpectNoThrow(const F& f) {
|
|||
}
|
||||
}
|
||||
|
||||
class ThrowingValueTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override { UnsetCountdown(); }
|
||||
|
||||
private:
|
||||
ConstructorTracker clouseau_;
|
||||
};
|
||||
|
||||
TEST_F(ThrowingValueTest, Throws) {
|
||||
TEST(ThrowingValueTest, Throws) {
|
||||
SetCountdown();
|
||||
EXPECT_THROW(ThrowingValue<> bomb, TestException);
|
||||
|
||||
|
|
@ -60,6 +52,8 @@ TEST_F(ThrowingValueTest, Throws) {
|
|||
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
|
||||
|
|
@ -67,7 +61,6 @@ TEST_F(ThrowingValueTest, Throws) {
|
|||
// ThrowingValue if it throws
|
||||
template <typename F>
|
||||
void TestOp(const F& f) {
|
||||
UnsetCountdown();
|
||||
ExpectNoThrow(f);
|
||||
|
||||
SetCountdown();
|
||||
|
|
@ -75,7 +68,7 @@ void TestOp(const F& f) {
|
|||
UnsetCountdown();
|
||||
}
|
||||
|
||||
TEST_F(ThrowingValueTest, ThrowingCtors) {
|
||||
TEST(ThrowingValueTest, ThrowingCtors) {
|
||||
ThrowingValue<> bomb;
|
||||
|
||||
TestOp([]() { ThrowingValue<> bomb(1); });
|
||||
|
|
@ -83,14 +76,14 @@ TEST_F(ThrowingValueTest, ThrowingCtors) {
|
|||
TestOp([&]() { ThrowingValue<> bomb1 = std::move(bomb); });
|
||||
}
|
||||
|
||||
TEST_F(ThrowingValueTest, ThrowingAssignment) {
|
||||
TEST(ThrowingValueTest, ThrowingAssignment) {
|
||||
ThrowingValue<> bomb, bomb1;
|
||||
|
||||
TestOp([&]() { bomb = bomb1; });
|
||||
TestOp([&]() { bomb = std::move(bomb1); });
|
||||
}
|
||||
|
||||
TEST_F(ThrowingValueTest, ThrowingComparisons) {
|
||||
TEST(ThrowingValueTest, ThrowingComparisons) {
|
||||
ThrowingValue<> bomb1, bomb2;
|
||||
TestOp([&]() { return bomb1 == bomb2; });
|
||||
TestOp([&]() { return bomb1 != bomb2; });
|
||||
|
|
@ -100,7 +93,7 @@ TEST_F(ThrowingValueTest, ThrowingComparisons) {
|
|||
TestOp([&]() { return bomb1 >= bomb2; });
|
||||
}
|
||||
|
||||
TEST_F(ThrowingValueTest, ThrowingArithmeticOps) {
|
||||
TEST(ThrowingValueTest, ThrowingArithmeticOps) {
|
||||
ThrowingValue<> bomb1(1), bomb2(2);
|
||||
|
||||
TestOp([&bomb1]() { +bomb1; });
|
||||
|
|
@ -118,7 +111,7 @@ TEST_F(ThrowingValueTest, ThrowingArithmeticOps) {
|
|||
TestOp([&]() { bomb1 >> 1; });
|
||||
}
|
||||
|
||||
TEST_F(ThrowingValueTest, ThrowingLogicalOps) {
|
||||
TEST(ThrowingValueTest, ThrowingLogicalOps) {
|
||||
ThrowingValue<> bomb1, bomb2;
|
||||
|
||||
TestOp([&bomb1]() { !bomb1; });
|
||||
|
|
@ -126,7 +119,7 @@ TEST_F(ThrowingValueTest, ThrowingLogicalOps) {
|
|||
TestOp([&]() { bomb1 || bomb2; });
|
||||
}
|
||||
|
||||
TEST_F(ThrowingValueTest, ThrowingBitwiseOps) {
|
||||
TEST(ThrowingValueTest, ThrowingBitwiseOps) {
|
||||
ThrowingValue<> bomb1, bomb2;
|
||||
|
||||
TestOp([&bomb1]() { ~bomb1; });
|
||||
|
|
@ -135,7 +128,7 @@ TEST_F(ThrowingValueTest, ThrowingBitwiseOps) {
|
|||
TestOp([&]() { bomb1 ^ bomb2; });
|
||||
}
|
||||
|
||||
TEST_F(ThrowingValueTest, ThrowingCompoundAssignmentOps) {
|
||||
TEST(ThrowingValueTest, ThrowingCompoundAssignmentOps) {
|
||||
ThrowingValue<> bomb1(1), bomb2(2);
|
||||
|
||||
TestOp([&]() { bomb1 += bomb2; });
|
||||
|
|
@ -149,7 +142,7 @@ TEST_F(ThrowingValueTest, ThrowingCompoundAssignmentOps) {
|
|||
TestOp([&]() { bomb1 *= bomb2; });
|
||||
}
|
||||
|
||||
TEST_F(ThrowingValueTest, ThrowingStreamOps) {
|
||||
TEST(ThrowingValueTest, ThrowingStreamOps) {
|
||||
ThrowingValue<> bomb;
|
||||
|
||||
TestOp([&]() { std::cin >> bomb; });
|
||||
|
|
@ -158,7 +151,6 @@ TEST_F(ThrowingValueTest, ThrowingStreamOps) {
|
|||
|
||||
template <typename F>
|
||||
void TestAllocatingOp(const F& f) {
|
||||
UnsetCountdown();
|
||||
ExpectNoThrow(f);
|
||||
|
||||
SetCountdown();
|
||||
|
|
@ -166,32 +158,34 @@ void TestAllocatingOp(const F& f) {
|
|||
UnsetCountdown();
|
||||
}
|
||||
|
||||
TEST_F(ThrowingValueTest, ThrowingAllocatingOps) {
|
||||
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_F(ThrowingValueTest, NonThrowingMoveCtor) {
|
||||
TEST(ThrowingValueTest, NonThrowingMoveCtor) {
|
||||
ThrowingValue<NoThrow::kMoveCtor> nothrow_ctor;
|
||||
|
||||
SetCountdown();
|
||||
ExpectNoThrow([¬hrow_ctor]() {
|
||||
ThrowingValue<NoThrow::kMoveCtor> nothrow1 = std::move(nothrow_ctor);
|
||||
});
|
||||
UnsetCountdown();
|
||||
}
|
||||
|
||||
TEST_F(ThrowingValueTest, NonThrowingMoveAssign) {
|
||||
TEST(ThrowingValueTest, NonThrowingMoveAssign) {
|
||||
ThrowingValue<NoThrow::kMoveAssign> nothrow_assign1, nothrow_assign2;
|
||||
|
||||
SetCountdown();
|
||||
ExpectNoThrow([¬hrow_assign1, ¬hrow_assign2]() {
|
||||
nothrow_assign1 = std::move(nothrow_assign2);
|
||||
});
|
||||
UnsetCountdown();
|
||||
}
|
||||
|
||||
TEST_F(ThrowingValueTest, ThrowingSwap) {
|
||||
TEST(ThrowingValueTest, ThrowingSwap) {
|
||||
ThrowingValue<> bomb1, bomb2;
|
||||
TestOp([&]() { std::swap(bomb1, bomb2); });
|
||||
|
||||
|
|
@ -202,12 +196,12 @@ TEST_F(ThrowingValueTest, ThrowingSwap) {
|
|||
TestOp([&]() { std::swap(bomb5, bomb6); });
|
||||
}
|
||||
|
||||
TEST_F(ThrowingValueTest, NonThrowingSwap) {
|
||||
TEST(ThrowingValueTest, NonThrowingSwap) {
|
||||
ThrowingValue<NoThrow::kMoveAssign | NoThrow::kMoveCtor> bomb1, bomb2;
|
||||
ExpectNoThrow([&]() { std::swap(bomb1, bomb2); });
|
||||
}
|
||||
|
||||
TEST_F(ThrowingValueTest, NonThrowingAllocation) {
|
||||
TEST(ThrowingValueTest, NonThrowingAllocation) {
|
||||
ThrowingValue<NoThrow::kAllocation>* allocated;
|
||||
ThrowingValue<NoThrow::kAllocation>* array;
|
||||
|
||||
|
|
@ -221,7 +215,7 @@ TEST_F(ThrowingValueTest, NonThrowingAllocation) {
|
|||
});
|
||||
}
|
||||
|
||||
TEST_F(ThrowingValueTest, NonThrowingDelete) {
|
||||
TEST(ThrowingValueTest, NonThrowingDelete) {
|
||||
auto* allocated = new ThrowingValue<>(1);
|
||||
auto* array = new ThrowingValue<>[2];
|
||||
|
||||
|
|
@ -229,12 +223,14 @@ TEST_F(ThrowingValueTest, NonThrowingDelete) {
|
|||
ExpectNoThrow([allocated]() { delete allocated; });
|
||||
SetCountdown();
|
||||
ExpectNoThrow([array]() { delete[] array; });
|
||||
|
||||
UnsetCountdown();
|
||||
}
|
||||
|
||||
using Storage =
|
||||
absl::aligned_storage_t<sizeof(ThrowingValue<>), alignof(ThrowingValue<>)>;
|
||||
|
||||
TEST_F(ThrowingValueTest, NonThrowingPlacementDelete) {
|
||||
TEST(ThrowingValueTest, NonThrowingPlacementDelete) {
|
||||
constexpr int kArrayLen = 2;
|
||||
// We intentionally create extra space to store the tag allocated by placement
|
||||
// new[].
|
||||
|
|
@ -256,16 +252,19 @@ TEST_F(ThrowingValueTest, NonThrowingPlacementDelete) {
|
|||
for (int i = 0; i < kArrayLen; ++i) placed_array[i].~ThrowingValue<>();
|
||||
ThrowingValue<>::operator delete[](placed_array, &array_buf);
|
||||
});
|
||||
|
||||
UnsetCountdown();
|
||||
}
|
||||
|
||||
TEST_F(ThrowingValueTest, NonThrowingDestructor) {
|
||||
TEST(ThrowingValueTest, NonThrowingDestructor) {
|
||||
auto* allocated = new ThrowingValue<>();
|
||||
|
||||
SetCountdown();
|
||||
ExpectNoThrow([allocated]() { delete allocated; });
|
||||
UnsetCountdown();
|
||||
}
|
||||
|
||||
TEST(ThrowingBoolTest, ThrowingBool) {
|
||||
UnsetCountdown();
|
||||
ThrowingBool t = true;
|
||||
|
||||
// Test that it's contextually convertible to bool
|
||||
|
|
@ -276,15 +275,7 @@ TEST(ThrowingBoolTest, ThrowingBool) {
|
|||
TestOp([&]() { (void)!t; });
|
||||
}
|
||||
|
||||
class ThrowingAllocatorTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override { UnsetCountdown(); }
|
||||
|
||||
private:
|
||||
ConstructorTracker borlu_;
|
||||
};
|
||||
|
||||
TEST_F(ThrowingAllocatorTest, MemoryManagement) {
|
||||
TEST(ThrowingAllocatorTest, MemoryManagement) {
|
||||
// Just exercise the memory management capabilities under LSan to make sure we
|
||||
// don't leak.
|
||||
ThrowingAllocator<int> int_alloc;
|
||||
|
|
@ -300,7 +291,7 @@ TEST_F(ThrowingAllocatorTest, MemoryManagement) {
|
|||
ef_alloc.deallocate(ef_array, 2);
|
||||
}
|
||||
|
||||
TEST_F(ThrowingAllocatorTest, CallsGlobalNew) {
|
||||
TEST(ThrowingAllocatorTest, CallsGlobalNew) {
|
||||
ThrowingAllocator<ThrowingValue<>, NoThrow::kNoThrow> nothrow_alloc;
|
||||
ThrowingValue<>* ptr;
|
||||
|
||||
|
|
@ -308,9 +299,11 @@ TEST_F(ThrowingAllocatorTest, CallsGlobalNew) {
|
|||
// This will only throw if ThrowingValue::new is called.
|
||||
ExpectNoThrow([&]() { ptr = nothrow_alloc.allocate(1); });
|
||||
nothrow_alloc.deallocate(ptr, 1);
|
||||
|
||||
UnsetCountdown();
|
||||
}
|
||||
|
||||
TEST_F(ThrowingAllocatorTest, ThrowingConstructors) {
|
||||
TEST(ThrowingAllocatorTest, ThrowingConstructors) {
|
||||
ThrowingAllocator<int> int_alloc;
|
||||
int* ip = nullptr;
|
||||
|
||||
|
|
@ -323,22 +316,27 @@ TEST_F(ThrowingAllocatorTest, ThrowingConstructors) {
|
|||
EXPECT_THROW(int_alloc.construct(ip, 2), TestException);
|
||||
EXPECT_EQ(*ip, 1);
|
||||
int_alloc.deallocate(ip, 1);
|
||||
|
||||
UnsetCountdown();
|
||||
}
|
||||
|
||||
TEST_F(ThrowingAllocatorTest, NonThrowingConstruction) {
|
||||
TEST(ThrowingAllocatorTest, NonThrowingConstruction) {
|
||||
{
|
||||
ThrowingAllocator<int, NoThrow::kNoThrow> 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();
|
||||
}
|
||||
|
||||
UnsetCountdown();
|
||||
{
|
||||
ThrowingAllocator<int> int_alloc;
|
||||
int* ip = nullptr;
|
||||
|
|
@ -348,37 +346,44 @@ TEST_F(ThrowingAllocatorTest, NonThrowingConstruction) {
|
|||
int_alloc.deallocate(ip, 1);
|
||||
}
|
||||
|
||||
UnsetCountdown();
|
||||
{
|
||||
ThrowingAllocator<ThrowingValue<NoThrow::kIntCtor>, NoThrow::kNoThrow>
|
||||
ef_alloc;
|
||||
ThrowingValue<NoThrow::kIntCtor>* efp;
|
||||
|
||||
SetCountdown();
|
||||
ExpectNoThrow([&]() { efp = ef_alloc.allocate(1); });
|
||||
|
||||
SetCountdown();
|
||||
ExpectNoThrow([&]() { ef_alloc.construct(efp, 2); });
|
||||
|
||||
EXPECT_EQ(efp->Get(), 2);
|
||||
ef_alloc.destroy(efp);
|
||||
ef_alloc.deallocate(efp, 1);
|
||||
|
||||
UnsetCountdown();
|
||||
}
|
||||
|
||||
UnsetCountdown();
|
||||
{
|
||||
ThrowingAllocator<int> a;
|
||||
|
||||
SetCountdown();
|
||||
ExpectNoThrow([&]() { ThrowingAllocator<double> a1 = a; });
|
||||
|
||||
SetCountdown();
|
||||
ExpectNoThrow([&]() { ThrowingAllocator<double> a1 = std::move(a); });
|
||||
|
||||
UnsetCountdown();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ThrowingAllocatorTest, ThrowingAllocatorConstruction) {
|
||||
TEST(ThrowingAllocatorTest, ThrowingAllocatorConstruction) {
|
||||
ThrowingAllocator<int> a;
|
||||
TestOp([]() { ThrowingAllocator<int> a; });
|
||||
TestOp([&]() { a.select_on_container_copy_construction(); });
|
||||
}
|
||||
|
||||
TEST_F(ThrowingAllocatorTest, State) {
|
||||
TEST(ThrowingAllocatorTest, State) {
|
||||
ThrowingAllocator<int> a1, a2;
|
||||
EXPECT_NE(a1, a2);
|
||||
|
||||
|
|
@ -390,13 +395,13 @@ TEST_F(ThrowingAllocatorTest, State) {
|
|||
EXPECT_EQ(a3, a1);
|
||||
}
|
||||
|
||||
TEST_F(ThrowingAllocatorTest, InVector) {
|
||||
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_F(ThrowingAllocatorTest, InList) {
|
||||
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();
|
||||
|
|
@ -772,19 +777,28 @@ struct Tracked : private exceptions_internal::TrackedObject {
|
|||
Tracked() : TrackedObject(ABSL_PRETTY_FUNCTION) {}
|
||||
};
|
||||
|
||||
TEST(ConstructorTrackerTest, Pass) {
|
||||
ConstructorTracker javert;
|
||||
Tracked t;
|
||||
TEST(ConstructorTrackerTest, CreatedBefore) {
|
||||
Tracked a, b, c;
|
||||
exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown);
|
||||
}
|
||||
|
||||
TEST(ConstructorTrackerTest, NotDestroyed) {
|
||||
TEST(ConstructorTrackerTest, CreatedAfter) {
|
||||
exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown);
|
||||
Tracked a, b, c;
|
||||
}
|
||||
|
||||
TEST(ConstructorTrackerTest, NotDestroyedAfter) {
|
||||
absl::aligned_storage_t<sizeof(Tracked), alignof(Tracked)> storage;
|
||||
EXPECT_NONFATAL_FAILURE(
|
||||
{
|
||||
ConstructorTracker gadget;
|
||||
exceptions_internal::ConstructorTracker ct(
|
||||
exceptions_internal::countdown);
|
||||
new (&storage) Tracked;
|
||||
},
|
||||
"not destroyed");
|
||||
|
||||
// Manual destruction of the Tracked instance is not required because
|
||||
// ~ConstructorTracker() handles that automatically when a leak is found
|
||||
}
|
||||
|
||||
TEST(ConstructorTrackerTest, DestroyedTwice) {
|
||||
|
|
|
|||
|
|
@ -37,8 +37,6 @@
|
|||
|
||||
namespace absl {
|
||||
|
||||
struct ConstructorTracker;
|
||||
|
||||
// A configuration enum for Throwing*. Operations whose flags are set will
|
||||
// throw, everything else won't. This isn't meant to be exhaustive, more flags
|
||||
// can always be made in the future.
|
||||
|
|
@ -105,6 +103,8 @@ void MaybeThrow(absl::string_view msg, bool throw_bad_alloc = false);
|
|||
testing::AssertionResult FailureMessage(const TestException& e,
|
||||
int countdown) noexcept;
|
||||
|
||||
class ConstructorTracker;
|
||||
|
||||
class TrackedObject {
|
||||
public:
|
||||
TrackedObject(const TrackedObject&) = delete;
|
||||
|
|
@ -112,26 +112,56 @@ class TrackedObject {
|
|||
|
||||
protected:
|
||||
explicit TrackedObject(const char* child_ctor) {
|
||||
if (!GetAllocs().emplace(this, child_ctor).second) {
|
||||
if (!GetInstanceMap().emplace(this, child_ctor).second) {
|
||||
ADD_FAILURE() << "Object at address " << static_cast<void*>(this)
|
||||
<< " re-constructed in ctor " << child_ctor;
|
||||
}
|
||||
}
|
||||
|
||||
static std::unordered_map<TrackedObject*, absl::string_view>& GetAllocs() {
|
||||
static auto* m =
|
||||
new std::unordered_map<TrackedObject*, absl::string_view>();
|
||||
return *m;
|
||||
}
|
||||
|
||||
~TrackedObject() noexcept {
|
||||
if (GetAllocs().erase(this) == 0) {
|
||||
if (GetInstanceMap().erase(this) == 0) {
|
||||
ADD_FAILURE() << "Object at address " << static_cast<void*>(this)
|
||||
<< " destroyed improperly";
|
||||
}
|
||||
}
|
||||
|
||||
friend struct ::absl::ConstructorTracker;
|
||||
private:
|
||||
using InstanceMap = std::unordered_map<TrackedObject*, absl::string_view>;
|
||||
static InstanceMap& GetInstanceMap() {
|
||||
static auto* instance_map = new InstanceMap();
|
||||
return *instance_map;
|
||||
}
|
||||
|
||||
friend class ConstructorTracker;
|
||||
};
|
||||
|
||||
// Inspects the constructions and destructions of anything inheriting from
|
||||
// TrackedObject. This allows us to safely "leak" TrackedObjects, as
|
||||
// ConstructorTracker will destroy everything left over in its destructor.
|
||||
class ConstructorTracker {
|
||||
public:
|
||||
explicit ConstructorTracker(int c)
|
||||
: init_count_(c), init_instances_(TrackedObject::GetInstanceMap()) {}
|
||||
~ConstructorTracker() {
|
||||
auto& cur_instances = TrackedObject::GetInstanceMap();
|
||||
for (auto it = cur_instances.begin(); it != cur_instances.end();) {
|
||||
if (init_instances_.count(it->first) == 0) {
|
||||
ADD_FAILURE() << "Object at address " << static_cast<void*>(it->first)
|
||||
<< " constructed from " << it->second
|
||||
<< " where the exception countdown was set to "
|
||||
<< init_count_ << " was not destroyed";
|
||||
// Erasing an item inside an unordered_map invalidates the existing
|
||||
// iterator. A new one is returned for iteration to continue.
|
||||
it = cur_instances.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int init_count_;
|
||||
TrackedObject::InstanceMap init_instances_;
|
||||
};
|
||||
|
||||
template <typename Factory, typename Operation, typename Invariant>
|
||||
|
|
@ -707,37 +737,21 @@ class ThrowingAllocator : private exceptions_internal::TrackedObject {
|
|||
template <typename T, NoThrow Throws>
|
||||
int ThrowingAllocator<T, Throws>::next_id_ = 0;
|
||||
|
||||
// Inspects the constructions and destructions of anything inheriting from
|
||||
// TrackedObject. Place this as a member variable in a test fixture to ensure
|
||||
// that every ThrowingValue was constructed and destroyed correctly. This also
|
||||
// allows us to safely "leak" TrackedObjects, as ConstructorTracker will destroy
|
||||
// everything left over in its destructor.
|
||||
struct ConstructorTracker {
|
||||
ConstructorTracker() = default;
|
||||
~ConstructorTracker() {
|
||||
auto& allocs = exceptions_internal::TrackedObject::GetAllocs();
|
||||
for (const auto& kv : allocs) {
|
||||
ADD_FAILURE() << "Object at address " << static_cast<void*>(kv.first)
|
||||
<< " constructed from " << kv.second << " not destroyed";
|
||||
}
|
||||
allocs.clear();
|
||||
}
|
||||
};
|
||||
|
||||
// Tests for resource leaks by attempting to construct a T using args repeatedly
|
||||
// until successful, using the countdown method. Side effects can then be
|
||||
// tested for resource leaks. If a ConstructorTracker is present in the test
|
||||
// fixture, then this will also test that memory resources are not leaked as
|
||||
// long as T allocates TrackedObjects.
|
||||
// tested for resource leaks.
|
||||
template <typename T, typename... Args>
|
||||
T TestThrowingCtor(Args&&... args) {
|
||||
void TestThrowingCtor(Args&&... args) {
|
||||
struct Cleanup {
|
||||
~Cleanup() { exceptions_internal::UnsetCountdown(); }
|
||||
} c;
|
||||
for (int count = 0;; ++count) {
|
||||
exceptions_internal::ConstructorTracker ct(count);
|
||||
exceptions_internal::SetCountdown(count);
|
||||
try {
|
||||
return T(std::forward<Args>(args)...);
|
||||
T temp(std::forward<Args>(args)...);
|
||||
static_cast<void>(temp);
|
||||
break;
|
||||
} catch (const exceptions_internal::TestException&) {
|
||||
}
|
||||
}
|
||||
|
|
@ -934,6 +948,8 @@ class ExceptionSafetyTester {
|
|||
// Starting from 0 and counting upwards until one of the exit conditions is
|
||||
// hit...
|
||||
for (int count = 0;; ++count) {
|
||||
exceptions_internal::ConstructorTracker ct(count);
|
||||
|
||||
// Run the full exception safety test algorithm for the current countdown
|
||||
auto reduced_res =
|
||||
TestAllInvariantsAtCountdown(factory_, selected_operation, count,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue