Files
klubhaus-doorbell/libraries/FastLED/tests/test_weak_ptr.cpp
2026-02-12 00:45:31 -08:00

572 lines
19 KiB
C++

#include "test.h"
#include "fl/shared_ptr.h"
#include "fl/weak_ptr.h"
#include "fl/memory.h"
#include "fl/compiler_control.h"
#include "fl/vector.h"
// Test class that does NOT inherit from fl::Referent (non-intrusive)
class TestClass {
public:
TestClass() : value_(0), destructor_called_(nullptr) {}
TestClass(int value) : value_(value), destructor_called_(nullptr) {}
// Constructor that allows tracking destructor calls
TestClass(int value, bool* destructor_flag) : value_(value), destructor_called_(destructor_flag) {}
~TestClass() {
if (destructor_called_) {
*destructor_called_ = true;
}
}
int getValue() const { return value_; }
void setValue(int value) { value_ = value; }
private:
int value_;
bool* destructor_called_;
};
TEST_CASE("fl::weak_ptr default construction") {
fl::weak_ptr<TestClass> weak;
CHECK_EQ(weak.use_count(), 0);
CHECK(weak.expired());
auto shared = weak.lock();
CHECK(!shared);
CHECK_EQ(shared.get(), nullptr);
}
TEST_CASE("fl::weak_ptr construction from shared_ptr") {
fl::shared_ptr<TestClass> shared = fl::make_shared<TestClass>(42);
CHECK_EQ(shared.use_count(), 1);
fl::weak_ptr<TestClass> weak(shared);
CHECK_EQ(weak.use_count(), 1);
CHECK_EQ(shared.use_count(), 1); // weak_ptr doesn't increase shared count
CHECK(!weak.expired());
auto locked = weak.lock();
CHECK(locked);
CHECK_EQ(locked.use_count(), 2); // lock() creates a shared_ptr
CHECK_EQ(locked->getValue(), 42);
}
TEST_CASE("fl::weak_ptr copy construction") {
fl::shared_ptr<TestClass> shared = fl::make_shared<TestClass>(42);
fl::weak_ptr<TestClass> weak1(shared);
fl::weak_ptr<TestClass> weak2(weak1);
CHECK_EQ(weak1.use_count(), 1);
CHECK_EQ(weak2.use_count(), 1);
CHECK(!weak1.expired());
CHECK(!weak2.expired());
auto locked1 = weak1.lock();
auto locked2 = weak2.lock();
CHECK_EQ(locked1.get(), locked2.get());
CHECK_EQ(locked1->getValue(), 42);
}
TEST_CASE("fl::weak_ptr move construction") {
fl::shared_ptr<TestClass> shared = fl::make_shared<TestClass>(42);
fl::weak_ptr<TestClass> weak1(shared);
fl::weak_ptr<TestClass> weak2(fl::move(weak1));
CHECK_EQ(weak1.use_count(), 0);
CHECK(weak1.expired());
CHECK_EQ(weak2.use_count(), 1);
CHECK(!weak2.expired());
auto locked = weak2.lock();
CHECK(locked);
CHECK_EQ(locked->getValue(), 42);
}
TEST_CASE("fl::weak_ptr assignment from shared_ptr") {
fl::shared_ptr<TestClass> shared1 = fl::make_shared<TestClass>(42);
fl::shared_ptr<TestClass> shared2 = fl::make_shared<TestClass>(100);
fl::weak_ptr<TestClass> weak(shared1);
CHECK_EQ(weak.lock()->getValue(), 42);
weak = shared2;
CHECK_EQ(weak.lock()->getValue(), 100);
}
TEST_CASE("fl::weak_ptr assignment from weak_ptr") {
fl::shared_ptr<TestClass> shared1 = fl::make_shared<TestClass>(42);
fl::shared_ptr<TestClass> shared2 = fl::make_shared<TestClass>(100);
fl::weak_ptr<TestClass> weak1(shared1);
fl::weak_ptr<TestClass> weak2(shared2);
CHECK_EQ(weak1.lock()->getValue(), 42);
CHECK_EQ(weak2.lock()->getValue(), 100);
weak1 = weak2;
CHECK_EQ(weak1.lock()->getValue(), 100);
CHECK_EQ(weak2.lock()->getValue(), 100);
}
TEST_CASE("fl::weak_ptr move assignment") {
fl::shared_ptr<TestClass> shared1 = fl::make_shared<TestClass>(42);
fl::shared_ptr<TestClass> shared2 = fl::make_shared<TestClass>(100);
fl::weak_ptr<TestClass> weak1(shared1);
fl::weak_ptr<TestClass> weak2(shared2);
weak1 = fl::move(weak2);
CHECK_EQ(weak1.lock()->getValue(), 100);
CHECK(weak2.expired());
}
TEST_CASE("fl::weak_ptr expiration when shared_ptr destroyed") {
bool destructor_called = false;
fl::weak_ptr<TestClass> weak;
{
fl::shared_ptr<TestClass> shared = fl::make_shared<TestClass>(42, &destructor_called);
weak = shared;
CHECK(!weak.expired());
CHECK_EQ(weak.use_count(), 1);
CHECK(!destructor_called);
auto locked = weak.lock();
CHECK(locked);
CHECK_EQ(locked->getValue(), 42);
}
// shared_ptr is destroyed, object should be destroyed
CHECK(destructor_called);
CHECK(weak.expired());
CHECK_EQ(weak.use_count(), 0);
auto locked = weak.lock();
CHECK(!locked);
CHECK_EQ(locked.get(), nullptr);
}
TEST_CASE("fl::weak_ptr with multiple shared_ptr references") {
fl::shared_ptr<TestClass> shared1 = fl::make_shared<TestClass>(42);
fl::shared_ptr<TestClass> shared2 = shared1;
fl::weak_ptr<TestClass> weak(shared1);
CHECK_EQ(shared1.use_count(), 2);
CHECK_EQ(weak.use_count(), 2);
CHECK(!weak.expired());
shared1.reset();
CHECK_EQ(shared2.use_count(), 1);
CHECK_EQ(weak.use_count(), 1);
CHECK(!weak.expired());
shared2.reset();
CHECK(weak.expired());
CHECK_EQ(weak.use_count(), 0);
}
TEST_CASE("fl::weak_ptr reset functionality") {
fl::shared_ptr<TestClass> shared = fl::make_shared<TestClass>(42);
fl::weak_ptr<TestClass> weak(shared);
CHECK(!weak.expired());
CHECK_EQ(weak.use_count(), 1);
weak.reset();
CHECK(weak.expired());
CHECK_EQ(weak.use_count(), 0);
auto locked = weak.lock();
CHECK(!locked);
}
TEST_CASE("fl::weak_ptr swap functionality") {
fl::shared_ptr<TestClass> shared1 = fl::make_shared<TestClass>(42);
fl::shared_ptr<TestClass> shared2 = fl::make_shared<TestClass>(100);
fl::weak_ptr<TestClass> weak1(shared1);
fl::weak_ptr<TestClass> weak2(shared2);
CHECK_EQ(weak1.lock()->getValue(), 42);
CHECK_EQ(weak2.lock()->getValue(), 100);
weak1.swap(weak2);
CHECK_EQ(weak1.lock()->getValue(), 100);
CHECK_EQ(weak2.lock()->getValue(), 42);
}
TEST_CASE("fl::weak_ptr owner_before functionality") {
fl::shared_ptr<TestClass> shared1 = fl::make_shared<TestClass>(42);
fl::shared_ptr<TestClass> shared2 = fl::make_shared<TestClass>(100);
fl::weak_ptr<TestClass> weak1(shared1);
fl::weak_ptr<TestClass> weak2(shared2);
// owner_before should provide a strict weak ordering
bool order1 = weak1.owner_before(weak2);
bool order2 = weak2.owner_before(weak1);
// One should be true and the other false (strict ordering)
CHECK(order1 != order2);
// Test owner_before with shared_ptr
bool order3 = weak1.owner_before(shared2);
bool order4 = weak2.owner_before(shared1);
CHECK(order3 != order4);
}
TEST_CASE("fl::weak_ptr conversion to shared_ptr") {
fl::shared_ptr<TestClass> shared = fl::make_shared<TestClass>(42);
fl::weak_ptr<TestClass> weak(shared);
// Test construction from weak_ptr
fl::shared_ptr<TestClass> converted(weak);
CHECK(converted);
CHECK_EQ(converted.use_count(), 2);
CHECK_EQ(shared.use_count(), 2);
CHECK_EQ(converted->getValue(), 42);
CHECK_EQ(converted.get(), shared.get());
}
TEST_CASE("fl::weak_ptr conversion from expired weak_ptr") {
fl::weak_ptr<TestClass> weak;
{
fl::shared_ptr<TestClass> shared = fl::make_shared<TestClass>(42);
weak = shared;
CHECK(!weak.expired());
}
CHECK(weak.expired());
// Converting expired weak_ptr should result in empty shared_ptr
fl::shared_ptr<TestClass> converted(weak);
CHECK(!converted);
CHECK_EQ(converted.get(), nullptr);
CHECK_EQ(converted.use_count(), 0);
}
TEST_CASE("fl::weak_ptr multiple weak references") {
fl::shared_ptr<TestClass> shared = fl::make_shared<TestClass>(42);
fl::weak_ptr<TestClass> weak1(shared);
fl::weak_ptr<TestClass> weak2(shared);
fl::weak_ptr<TestClass> weak3(weak1);
CHECK_EQ(shared.use_count(), 1);
CHECK_EQ(weak1.use_count(), 1);
CHECK_EQ(weak2.use_count(), 1);
CHECK_EQ(weak3.use_count(), 1);
shared.reset();
CHECK(weak1.expired());
CHECK(weak2.expired());
CHECK(weak3.expired());
CHECK_EQ(weak1.use_count(), 0);
CHECK_EQ(weak2.use_count(), 0);
CHECK_EQ(weak3.use_count(), 0);
}
TEST_CASE("fl::weak_ptr self-assignment safety") {
fl::shared_ptr<TestClass> shared = fl::make_shared<TestClass>(42);
fl::weak_ptr<TestClass> weak(shared);
CHECK_EQ(weak.use_count(), 1);
CHECK(!weak.expired());
// Self-assignment should not change anything
FL_DISABLE_WARNING_PUSH
FL_DISABLE_WARNING_SELF_ASSIGN_OVERLOADED
weak = weak; // Test self-assignment
FL_DISABLE_WARNING_POP
CHECK_EQ(weak.use_count(), 1);
CHECK(!weak.expired());
CHECK_EQ(weak.lock()->getValue(), 42);
// Self-move assignment should not break anything
weak = fl::move(weak);
CHECK_EQ(weak.use_count(), 1);
CHECK(!weak.expired());
CHECK_EQ(weak.lock()->getValue(), 42);
}
// Node class for testing circular references and self-assignment scenarios
class Node {
public:
Node(int value) : value_(value), destructor_called_(nullptr) {}
Node(int value, bool* destructor_flag) : value_(value), destructor_called_(destructor_flag) {}
~Node() {
if (destructor_called_) {
*destructor_called_ = true;
}
}
int getValue() const { return value_; }
void setValue(int value) { value_ = value; }
void setNext(fl::shared_ptr<Node> next) { next_ = next; }
fl::shared_ptr<Node> getNext() const { return next_; }
void setWeakNext(fl::weak_ptr<Node> next) { weak_next_ = next; }
fl::weak_ptr<Node> getWeakNext() const { return weak_next_; }
private:
int value_;
bool* destructor_called_;
fl::shared_ptr<Node> next_;
fl::weak_ptr<Node> weak_next_;
};
TEST_CASE("fl::weak_ptr dead memory safety - basic scenario") {
bool destructor_called = false;
fl::weak_ptr<TestClass> weak;
// Create shared_ptr and weak_ptr in a scope
{
auto shared = fl::make_shared<TestClass>(42);
//shared = fl::shared_ptr<TestClass>(new TestClass(100, &destructor_called));
shared = fl::make_shared<TestClass>(100, &destructor_called);
weak = shared;
CHECK(!weak.expired());
CHECK_EQ(weak.use_count(), 1);
CHECK(!destructor_called);
// Verify we can still access the object
auto locked = weak.lock();
CHECK(locked);
CHECK_EQ(locked->getValue(), 100);
} // shared_ptr goes out of scope here
// Object should be destroyed, weak_ptr should be expired
CHECK(destructor_called);
CHECK(weak.expired());
CHECK_EQ(weak.use_count(), 0);
// Attempting to lock should return empty shared_ptr - no segfault!
auto locked = weak.lock();
CHECK(!locked);
CHECK_EQ(locked.get(), nullptr);
}
TEST_CASE("fl::weak_ptr dead memory safety - multiple weak_ptrs") {
bool destructor_called = false;
fl::weak_ptr<TestClass> weak1, weak2, weak3;
{
//auto shared = fl::shared_ptr<TestClass>(new TestClass(42, &destructor_called));
auto shared = fl::make_shared<TestClass>(42, &destructor_called);
weak1 = shared;
weak2 = weak1;
weak3 = shared;
CHECK_EQ(weak1.use_count(), 1);
CHECK_EQ(weak2.use_count(), 1);
CHECK_EQ(weak3.use_count(), 1);
CHECK(!destructor_called);
} // shared_ptr destroyed
// All weak_ptrs should be expired
CHECK(destructor_called);
CHECK(weak1.expired());
CHECK(weak2.expired());
CHECK(weak3.expired());
// None should be able to lock
CHECK(!weak1.lock());
CHECK(!weak2.lock());
CHECK(!weak3.lock());
}
TEST_CASE("fl::weak_ptr dead memory safety - repeated lock attempts") {
bool destructor_called = false;
fl::weak_ptr<TestClass> weak;
{
//auto shared = fl::shared_ptr<TestClass>(new TestClass(42, &destructor_called));
auto shared = fl::make_shared<TestClass>(42, &destructor_called);
weak = shared;
}
CHECK(destructor_called);
CHECK(weak.expired());
// Try locking multiple times - should never segfault
for (int i = 0; i < 10; ++i) {
auto locked = weak.lock();
CHECK(!locked);
}
}
TEST_CASE("fl::weak_ptr circular reference - basic linked list") {
bool nodeA_destroyed = false;
bool nodeB_destroyed = false;
{
//auto nodeA = fl::shared_ptr<Node>(new Node(1, &nodeA_destroyed));
auto nodeA = fl::make_shared<Node>(1, &nodeA_destroyed);
//auto nodeB = fl::shared_ptr<Node>(new Node(2, &nodeB_destroyed));
auto nodeB = fl::make_shared<Node>(2, &nodeB_destroyed);
// Create circular reference: A -> B -> A
nodeA->setNext(nodeB);
nodeB->setNext(nodeA);
CHECK_EQ(nodeA.use_count(), 2); // nodeA and nodeB->next_
CHECK_EQ(nodeB.use_count(), 2); // nodeB and nodeA->next_
CHECK(!nodeA_destroyed);
CHECK(!nodeB_destroyed);
} // nodeA and nodeB local variables destroyed
// Due to circular reference, objects are NOT destroyed yet
// This is expected behavior - circular references prevent cleanup
// In real code, you'd use weak_ptr to break cycles
}
TEST_CASE("fl::weak_ptr circular reference - broken with weak_ptr") {
bool nodeA_destroyed = false;
bool nodeB_destroyed = false;
{
//auto nodeA = fl::shared_ptr<Node>(new Node(1, &nodeA_destroyed));
auto nodeA = fl::make_shared<Node>(1, &nodeA_destroyed);
//auto nodeB = fl::shared_ptr<Node>(new Node(2, &nodeB_destroyed));
auto nodeB = fl::make_shared<Node>(2, &nodeB_destroyed);
// Create non-circular reference: A -> B, A <- weak B
nodeA->setNext(nodeB);
nodeB->setWeakNext(nodeA); // Use weak_ptr to break cycle
CHECK_EQ(nodeA.use_count(), 1); // Only nodeA variable
CHECK_EQ(nodeB.use_count(), 2); // nodeB variable and nodeA->next_
CHECK(!nodeA_destroyed);
CHECK(!nodeB_destroyed);
} // nodeA and nodeB local variables destroyed
// Objects should be properly destroyed since cycle is broken
CHECK(nodeA_destroyed);
CHECK(nodeB_destroyed);
}
TEST_CASE("fl::weak_ptr self-assignment safety - a = b scenario") {
bool nodeA_destroyed = false;
bool nodeB_destroyed = false;
//auto nodeA = fl::shared_ptr<Node>(new Node(1, &nodeA_destroyed));
auto nodeA = fl::make_shared<Node>(1, &nodeA_destroyed);
//auto nodeB = fl::shared_ptr<Node>(new Node(2, &nodeB_destroyed));
auto nodeB = fl::make_shared<Node>(2, &nodeB_destroyed);
// Test the scenario: a -> b, and we have a, and a = b
nodeA->setNext(nodeB);
// Verify initial state
CHECK_EQ(nodeA->getValue(), 1);
CHECK_EQ(nodeB->getValue(), 2);
CHECK_EQ(nodeA->getNext().get(), nodeB.get());
CHECK_EQ(nodeA.use_count(), 1); // Only nodeA variable
CHECK_EQ(nodeB.use_count(), 2); // nodeB variable + nodeA->next_
CHECK(!nodeA_destroyed);
CHECK(!nodeB_destroyed);
// Get a reference to A before the dangerous assignment
auto aRef = nodeA;
CHECK_EQ(aRef.get(), nodeA.get());
CHECK_EQ(nodeA.use_count(), 2); // nodeA + aRef
CHECK_EQ(nodeB.use_count(), 2); // nodeB + nodeA->next_
// Now do the dangerous assignment: a = b (while a is referenced through aRef)
// This could cause issues if a gets destroyed while setting itself to b
nodeA = nodeB; // a = b (dangerous assignment)
// Verify no segfault occurred and state is consistent
CHECK_EQ(nodeA.get(), nodeB.get()); // nodeA should now point to nodeB
CHECK_EQ(nodeA->getValue(), 2); // Should have nodeB's value
CHECK_EQ(nodeB->getValue(), 2); // nodeB unchanged
CHECK(!nodeA_destroyed); // Original nodeA object should still exist
CHECK(!nodeB_destroyed);
// aRef should still be valid (original nodeA should still exist)
CHECK(aRef);
CHECK_EQ(aRef->getValue(), 1); // Original nodeA value
CHECK_EQ(aRef.use_count(), 1); // Only aRef now points to original nodeA
// nodeB should now have increased reference count
CHECK_EQ(nodeB.use_count(), 3); // nodeB + nodeA + nodeA->next_ (which points to nodeB)
// Clean up - clear the circular reference in the original node
aRef->setNext(nullptr);
CHECK_EQ(nodeB.use_count(), 2); // nodeB + nodeA
CHECK(!nodeA_destroyed); // Original nodeA still referenced by aRef
CHECK(!nodeB_destroyed);
// Clear the reference to original nodeA
aRef.reset();
CHECK(nodeA_destroyed); // Now original nodeA should be destroyed
CHECK(!nodeB_destroyed); // nodeB still referenced by nodeA
// Clear final reference
nodeA.reset();
nodeB.reset();
CHECK(nodeB_destroyed); // Now nodeB should be destroyed
}
TEST_CASE("fl::weak_ptr complex circular scenario with weak references") {
bool nodeA_destroyed = false;
bool nodeB_destroyed = false;
bool nodeC_destroyed = false;
fl::weak_ptr<Node> weakA, weakB, weakC;
{
//auto nodeA = fl::shared_ptr<Node>(new Node(1, &nodeA_destroyed));
auto nodeA = fl::make_shared<Node>(1, &nodeA_destroyed);
//auto nodeB = fl::shared_ptr<Node>(new Node(2, &nodeB_destroyed));
auto nodeB = fl::make_shared<Node>(2, &nodeB_destroyed);
//auto nodeC = fl::shared_ptr<Node>(new Node(3, &nodeC_destroyed));
auto nodeC = fl::make_shared<Node>(3, &nodeC_destroyed);
// Create complex references: A -> B -> C, with weak back-references
nodeA->setNext(nodeB);
nodeB->setNext(nodeC);
nodeC->setWeakNext(nodeA); // Weak reference back to A
weakA = nodeA;
weakB = nodeB;
weakC = nodeC;
CHECK(!weakA.expired());
CHECK(!weakB.expired());
CHECK(!weakC.expired());
CHECK(!nodeA_destroyed);
CHECK(!nodeB_destroyed);
CHECK(!nodeC_destroyed);
} // All shared_ptr variables destroyed
// All objects should be destroyed since no circular strong references exist
CHECK(nodeA_destroyed);
CHECK(nodeB_destroyed);
CHECK(nodeC_destroyed);
CHECK(weakA.expired());
CHECK(weakB.expired());
CHECK(weakC.expired());
}
TEST_CASE("fl::weak_ptr stress test - rapid creation and destruction") {
fl::vector<fl::weak_ptr<TestClass>> weak_ptrs;
weak_ptrs.reserve(100);
// Create many shared_ptrs and weak_ptrs rapidly
for (int i = 0; i < 100; ++i) {
auto shared = fl::make_shared<TestClass>(i);
weak_ptrs.push_back(fl::weak_ptr<TestClass>(shared));
// Shared goes out of scope immediately - weak_ptr should handle this
}
// All weak_ptrs should be expired
for (auto& weak : weak_ptrs) {
CHECK(weak.expired());
CHECK(!weak.lock());
}
}