#include #include #include #include #include "reference.hpp" namespace { struct TestObject { int value; explicit TestObject(int v) : value(v) {} }; struct Node { int data; Ref next; WeakRef parent; explicit Node(int d) : data(d) {} }; } // anonymous namespace TEST_CASE("Ref basic functionality") { SUBCASE("make_ref creates valid Ref") { auto ref = make_ref(42); CHECK(ref); CHECK(ref.get() != nullptr); CHECK(ref->value == 42); CHECK((*ref).value == 42); } SUBCASE("copy construction increments reference count") { auto ref1 = make_ref(123); auto ref2 = ref1; CHECK(ref1); CHECK(ref2); CHECK(ref1.get() == ref2.get()); CHECK(ref1->value == 123); CHECK(ref2->value == 123); } SUBCASE("copy assignment works correctly") { auto ref1 = make_ref(100); auto ref2 = make_ref(200); ref2 = ref1; CHECK(ref1.get() == ref2.get()); CHECK(ref1->value == 100); CHECK(ref2->value == 100); } SUBCASE("move construction transfers ownership") { auto ref1 = make_ref(456); auto *ptr = ref1.get(); auto ref2 = std::move(ref1); CHECK(!ref1); CHECK(ref2); CHECK(ref2.get() == ptr); CHECK(ref2->value == 456); } SUBCASE("move assignment transfers ownership") { auto ref1 = make_ref(789); auto ref2 = make_ref(999); auto *ptr = ref1.get(); ref2 = std::move(ref1); CHECK(!ref1); CHECK(ref2); CHECK(ref2.get() == ptr); CHECK(ref2->value == 789); } SUBCASE("reset clears reference") { auto ref = make_ref(111); CHECK(ref); ref.reset(); CHECK(!ref); CHECK(ref.get() == nullptr); } } TEST_CASE("WeakRef basic functionality") { SUBCASE("construction from Ref") { auto ref = make_ref(333); WeakRef weak_ref = ref; auto locked = weak_ref.lock(); CHECK(locked); CHECK(locked.get() == ref.get()); CHECK(locked->value == 333); } SUBCASE("lock() returns empty when object destroyed") { WeakRef weak_ref; { auto ref = make_ref(444); weak_ref = ref; } // ref goes out of scope, object should be destroyed auto locked = weak_ref.lock(); CHECK(!locked); } SUBCASE("copy and move semantics") { auto ref = make_ref(666); WeakRef weak1 = ref; WeakRef weak2 = weak1; // copy WeakRef weak3 = std::move(weak1); // move auto locked2 = weak2.lock(); auto locked3 = weak3.lock(); CHECK(locked2); CHECK(locked3); CHECK(locked2->value == 666); CHECK(locked3->value == 666); } } TEST_CASE("Ref thread safety") { SUBCASE("concurrent copying") { const int num_threads = 4; const int copies_per_thread = 100; const int test_iterations = 1000; for (int iter = 0; iter < test_iterations; ++iter) { auto ref = make_ref(777); std::vector threads; std::latch start_latch{num_threads + 1}; for (int i = 0; i < num_threads; ++i) { threads.emplace_back([&]() { start_latch.arrive_and_wait(); for (int j = 0; j < copies_per_thread; ++j) { auto copy = ref; CHECK(copy); CHECK(copy->value == 777); } }); } start_latch.arrive_and_wait(); for (auto &t : threads) { t.join(); } CHECK(ref); CHECK(ref->value == 777); } } } TEST_CASE("WeakRef prevents circular references") { SUBCASE("simple weak reference lifecycle") { WeakRef weak_ref; // Create object and weak reference { auto ref = make_ref(123); weak_ref = ref; // Should be able to lock while object exists auto locked = weak_ref.lock(); CHECK(locked); CHECK(locked->value == 123); } // Object destroyed when ref goes out of scope // Should not be able to lock after object destroyed auto locked = weak_ref.lock(); CHECK(!locked); } SUBCASE("parent-child cycle with WeakRef breaks cycle") { auto parent = make_ref(1); auto child = make_ref(2); // Create potential cycle parent->next = child; // Strong reference: parent → child child->parent = parent; // WeakRef: child ⇝ parent (breaks cycle) CHECK(parent->data == 1); CHECK(child->data == 2); CHECK(parent->next == child); // Verify weak reference works while parent exists CHECK(child->parent.lock() == parent); // Clear the only strong reference to parent parent.reset(); // This should destroy the parent object // Now child's weak reference should fail to lock since parent is destroyed CHECK(!child->parent.lock()); } }