// Copyright Epic Games, Inc. All Rights Reserved. #include "AutoRTFM.h" #include "AutoRTFMTesting.h" #include "Catch2Includes.h" #include "Templates/RefCounting.h" namespace { struct RefCountedInt : public FRefCountBase { RefCountedInt(int InValue) : Value(InValue) {} int Value; }; struct RefCountedMixinInt : public TRefCountingMixin { RefCountedMixinInt(int InValue) : Value(InValue) {} int Value; }; struct ThreadSafeRefCountedInt : public FThreadSafeRefCountedObject { ThreadSafeRefCountedInt(int InValue) : Value(InValue) {} int Value; }; } TEMPLATE_TEST_CASE("CheckRefCounts", "[RefCountPtr]", RefCountedInt, RefCountedMixinInt, ThreadSafeRefCountedInt) { // Refcounts should be correct outside of a transaction. SECTION("Non-transactional refcounts should be accurate") { TestType* Ptr = new TestType(42); REQUIRE(Ptr->GetRefCount() == 0); (Ptr->AddRef().CheckAtLeast(1)); REQUIRE(Ptr->GetRefCount() == 1); (Ptr->AddRef().CheckAtLeast(2)); REQUIRE(Ptr->GetRefCount() == 2); REQUIRE(Ptr->Release() == 1); REQUIRE(Ptr->GetRefCount() == 1); (Ptr->AddRef().CheckAtLeast(2)); REQUIRE(Ptr->GetRefCount() == 2); REQUIRE(Ptr->Release() == 1); REQUIRE(Ptr->GetRefCount() == 1); REQUIRE(Ptr->Release() == 0); // Ptr is dead } // Refcounts can be inflated inside a transaction, since releases are deferred. SECTION("Transactional refcounts should represent a lower bound") { AutoRTFM::Testing::Commit([] { TestType* Ptr = new TestType(42); REQUIRE(Ptr->GetRefCount() >= 0); (Ptr->AddRef().CheckAtLeast(1)); REQUIRE(Ptr->GetRefCount() >= 1); (Ptr->AddRef().CheckAtLeast(2)); REQUIRE(Ptr->GetRefCount() >= 2); REQUIRE(Ptr->Release() >= 1); REQUIRE(Ptr->GetRefCount() >= 1); (Ptr->AddRef().CheckAtLeast(2)); REQUIRE(Ptr->GetRefCount() >= 2); REQUIRE(Ptr->Release() >= 1); REQUIRE(Ptr->GetRefCount() >= 1); REQUIRE(Ptr->Release() >= 0); // Ptr is dead }); } } TEMPLATE_TEST_CASE("PreviouslyAllocated", "[RefCountPtr]", RefCountedInt, RefCountedMixinInt, ThreadSafeRefCountedInt) { SECTION("Using new T()") { TRefCountPtr Foo(new TestType(42)); AutoRTFM::Testing::Commit([&] { // Make a copy to bump the reference count. TRefCountPtr Copy = Foo; Copy->Value = 13; }); REQUIRE(Foo->Value == 13); REQUIRE(Foo.GetRefCount() == 1); } SECTION("Using MakeRefCount") { TRefCountPtr Foo = MakeRefCount(42); AutoRTFM::Testing::Commit([&] { // Make a copy to bump the reference count. TRefCountPtr Copy = Foo; Copy->Value = 13; }); REQUIRE(Foo->Value == 13); REQUIRE(Foo.GetRefCount() == 1); } } TEMPLATE_TEST_CASE("AbortWithPreviouslyAllocated", "[RefCountPtr]", RefCountedInt, RefCountedMixinInt, ThreadSafeRefCountedInt) { SECTION("Using new T()") { TRefCountPtr Foo(new TestType(42)); AutoRTFM::Testing::Abort([&] { // Make a copy to bump the reference count. TRefCountPtr Copy = Foo; Copy->Value = 13; AutoRTFM::AbortTransaction(); }); REQUIRE(Foo->Value == 42); REQUIRE(Foo.GetRefCount() == 1); } SECTION("Using MakeRefCount") { TRefCountPtr Foo = MakeRefCount(42); AutoRTFM::Testing::Abort([&] { // Make a copy to bump the reference count. TRefCountPtr Copy = Foo; Copy->Value = 13; AutoRTFM::AbortTransaction(); }); REQUIRE(Foo->Value == 42); REQUIRE(Foo.GetRefCount() == 1); } } TEMPLATE_TEST_CASE("AbortWithNewlyAllocated", "[RefCountPtr]", RefCountedInt, RefCountedMixinInt, ThreadSafeRefCountedInt) { int Result = 42; SECTION("Using new T()") { AutoRTFM::Testing::Abort([&] { TRefCountPtr Foo(new TestType(42)); Result = Foo->Value; AutoRTFM::AbortTransaction(); }); } SECTION("Using new T() and a copy") { AutoRTFM::Testing::Abort([&] { TRefCountPtr Foo(new TestType(42)); TRefCountPtr Copy = Foo; Result = Copy->Value; AutoRTFM::AbortTransaction(); }); } SECTION("Using MakeRefCount") { AutoRTFM::Testing::Abort([&] { TRefCountPtr Foo = MakeRefCount(42); Result = Foo->Value; AutoRTFM::AbortTransaction(); }); } SECTION("Using MakeRefCount and a copy") { AutoRTFM::Testing::Abort([&] { TRefCountPtr Foo = MakeRefCount(42); TRefCountPtr Copy = Foo; Result = Copy->Value; AutoRTFM::AbortTransaction(); }); } REQUIRE(Result == 42); } TEMPLATE_TEST_CASE("OnCommitCapturingRefCountPtr", "[RefCountPtr]", RefCountedInt, RefCountedMixinInt, ThreadSafeRefCountedInt) { TRefCountPtr Foo = MakeRefCount(42); SECTION("Committing") { AutoRTFM::Testing::Commit([&] { AutoRTFM::OnCommit([Foo] { Foo->Value = 13; }); }); REQUIRE(Foo->Value == 13); } SECTION("Aborting") { AutoRTFM::Testing::Abort([&] { AutoRTFM::OnCommit([Foo] { Foo->Value = 13; }); AutoRTFM::AbortTransaction(); }); REQUIRE(Foo->Value == 42); } } TEMPLATE_TEST_CASE("OnAbortCapturingRefCountPtr", "[RefCountPtr]", RefCountedInt, RefCountedMixinInt, ThreadSafeRefCountedInt) { TRefCountPtr Foo = MakeRefCount(42); SECTION("Committing") { AutoRTFM::Testing::Commit([&] { AutoRTFM::OnAbort([Foo] { Foo->Value = 13; }); }); // The test harness can decide to abort transactions if `ShouldRetryNonNestedTransactions` is set. REQUIRE((Foo->Value == 42 || AutoRTFM::ForTheRuntime::ShouldRetryNonNestedTransactions())); } SECTION("Aborting") { AutoRTFM::Testing::Abort([&] { AutoRTFM::OnAbort([Foo] { Foo->Value = 13; }); AutoRTFM::AbortTransaction(); }); REQUIRE(Foo->Value == 13); } }