Files
weaseldb/tests/test_reference.cpp
2025-09-10 22:05:31 -04:00

202 lines
4.8 KiB
C++

#include <doctest/doctest.h>
#include <latch>
#include <thread>
#include <vector>
#include "reference.hpp"
namespace {
struct TestObject {
int value;
explicit TestObject(int v) : value(v) {}
};
struct Node {
int data;
Ref<Node> next;
WeakRef<Node> parent;
explicit Node(int d) : data(d) {}
};
} // anonymous namespace
TEST_CASE("Ref basic functionality") {
SUBCASE("make_ref creates valid Ref") {
auto ref = make_ref<TestObject>(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<TestObject>(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<TestObject>(100);
auto ref2 = make_ref<TestObject>(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<TestObject>(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<TestObject>(789);
auto ref2 = make_ref<TestObject>(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<TestObject>(111);
CHECK(ref);
ref.reset();
CHECK(!ref);
CHECK(ref.get() == nullptr);
}
}
TEST_CASE("WeakRef basic functionality") {
SUBCASE("construction from Ref") {
auto ref = make_ref<TestObject>(333);
WeakRef<TestObject> 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<TestObject> weak_ref;
{
auto ref = make_ref<TestObject>(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<TestObject>(666);
WeakRef<TestObject> weak1 = ref;
WeakRef<TestObject> weak2 = weak1; // copy
WeakRef<TestObject> 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<TestObject>(777);
std::vector<std::thread> 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<TestObject> weak_ref;
// Create object and weak reference
{
auto ref = make_ref<TestObject>(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<Node>(1);
auto child = make_ref<Node>(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());
}
}