#include #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("Control block cleanup race condition test") { // This test specifically targets the race condition where both // the last strong reference and last weak reference are destroyed // simultaneously, potentially causing double-free of control block const int test_iterations = 10000; // Shared state for passing references between threads Ref ptr1; WeakRef ptr2; auto setup = [&]() { ptr1 = make_ref(0); ptr2 = ptr1; }; // Barrier for synchronization - 2 participants (main thread + worker thread) std::barrier sync_barrier{2}; std::thread worker_thread([&]() { for (int iter = 0; iter < test_iterations; ++iter) { // Wait for main thread to create the references sync_barrier.arrive_and_wait(); // Worker thread destroys the weak reference simultaneously with main // thread ptr2.reset(); // Wait for next iteration sync_barrier.arrive_and_wait(); } }); for (int iter = 0; iter < test_iterations; ++iter) { // Create references setup(); // Both threads are ready - synchronize for simultaneous destruction sync_barrier.arrive_and_wait(); // Main thread destroys the strong reference at the same time // as worker thread destroys the weak reference ptr1.reset(); // Wait for both destructions to complete sync_barrier.arrive_and_wait(); // Clean up for next iteration ptr1.reset(); ptr2.reset(); } worker_thread.join(); // If we reach here without segfault/double-free, the test passes // The bug would manifest as a crash or memory corruption } 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()); } }