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

173 lines
5.5 KiB
C++

#include "test.h"
#include "fl/memory.h"
#include "fl/referent.h"
// Test class that inherits from fl::Referent
class TestClass : public fl::Referent {
public:
TestClass() : value_(0) {}
TestClass(int value) : value_(value) {}
TestClass(int a, int b) : value_(a + b) {}
int getValue() const { return value_; }
private:
int value_;
};
FASTLED_SMART_PTR(TestClass);
TEST_CASE("fl::make_intrusive basic functionality") {
// Test default constructor
auto ptr1 = fl::make_intrusive<TestClass>();
CHECK(ptr1 != nullptr);
CHECK_EQ(ptr1->getValue(), 0);
// Test single argument constructor
auto ptr2 = fl::make_intrusive<TestClass>(42);
CHECK(ptr2 != nullptr);
CHECK_EQ(ptr2->getValue(), 42);
// Test multiple argument constructor
auto ptr3 = fl::make_intrusive<TestClass>(10, 20);
CHECK(ptr3 != nullptr);
CHECK_EQ(ptr3->getValue(), 30);
}
TEST_CASE("fl::make_intrusive equivalence with NewPtr") {
// Test that make_intrusive behaves the same as NewPtr
auto ptr1 = fl::make_intrusive<TestClass>(100);
auto ptr2 = fl::NewPtr<TestClass>(100);
CHECK(ptr1 != nullptr);
CHECK(ptr2 != nullptr);
CHECK_EQ(ptr1->getValue(), ptr2->getValue());
CHECK_EQ(ptr1->getValue(), 100);
}
TEST_CASE("fl::make_intrusive reference counting") {
fl::Ptr<TestClass> ptr1;
fl::Ptr<TestClass> ptr2;
{
auto ptr = fl::make_intrusive<TestClass>(50);
CHECK_EQ(ptr->ref_count(), 1);
ptr1 = ptr;
CHECK_EQ(ptr->ref_count(), 2);
CHECK_EQ(ptr1->ref_count(), 2);
ptr2 = ptr1;
CHECK_EQ(ptr->ref_count(), 3);
CHECK_EQ(ptr1->ref_count(), 3);
CHECK_EQ(ptr2->ref_count(), 3);
}
// Original ptr goes out of scope
CHECK_EQ(ptr1->ref_count(), 2);
CHECK_EQ(ptr2->ref_count(), 2);
ptr1.reset();
CHECK_EQ(ptr2->ref_count(), 1);
CHECK_EQ(ptr2->getValue(), 50);
}
TEST_CASE("fl::make_intrusive perfect forwarding") {
// Test that arguments are properly forwarded
class ForwardingTest : public fl::Referent {
public:
ForwardingTest(int value, bool is_copy) : value_(value), is_copy_(is_copy) {}
int getValue() const { return value_; }
bool isCopy() const { return is_copy_; }
private:
int value_;
bool is_copy_;
};
// Test argument forwarding with multiple parameters
auto ptr = fl::make_intrusive<ForwardingTest>(42, true);
CHECK_EQ(ptr->getValue(), 42);
CHECK(ptr->isCopy());
}
TEST_CASE("fl::intrusive_ptr alias functionality") {
// Test that fl::intrusive_ptr<T> works as an alias for fl::Ptr<T>
fl::intrusive_ptr<TestClass> ptr1 = fl::make_intrusive<TestClass>(42);
CHECK(ptr1 != nullptr);
CHECK_EQ(ptr1->getValue(), 42);
// Test assignment between intrusive_ptr and Ptr
fl::Ptr<TestClass> ptr2 = ptr1;
CHECK_EQ(ptr1.get(), ptr2.get());
CHECK_EQ(ptr1->ref_count(), 2);
CHECK_EQ(ptr2->ref_count(), 2);
// Test that they are the same type
fl::intrusive_ptr<TestClass> ptr3;
ptr3 = ptr2; // Should work seamlessly
CHECK_EQ(ptr3->getValue(), 42);
CHECK_EQ(ptr3->ref_count(), 3);
}
// Node class for testing circular references and self-assignment scenarios
class IntrusiveNode : public fl::Referent {
public:
IntrusiveNode(int value) : value_(value) {}
int getValue() const { return value_; }
void setValue(int value) { value_ = value; }
void setNext(fl::intrusive_ptr<IntrusiveNode> next) { next_ = next; }
fl::intrusive_ptr<IntrusiveNode> getNext() const { return next_; }
private:
int value_;
fl::intrusive_ptr<IntrusiveNode> next_;
};
FASTLED_SMART_PTR(IntrusiveNode);
TEST_CASE("fl::intrusive_ptr self-assignment safety - a = b scenario") {
auto nodeA = fl::make_intrusive<IntrusiveNode>(1);
auto nodeB = fl::make_intrusive<IntrusiveNode>(2);
// 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->ref_count(), 1); // Only nodeA variable
CHECK_EQ(nodeB->ref_count(), 2); // nodeB variable + nodeA->next_
// Get a reference to A before the dangerous assignment
auto aRef = nodeA;
CHECK_EQ(aRef.get(), nodeA.get());
CHECK_EQ(nodeA->ref_count(), 2); // nodeA + aRef
CHECK_EQ(nodeB->ref_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
// aRef should still be valid (original nodeA should still exist)
CHECK(aRef);
CHECK_EQ(aRef->getValue(), 1); // Original nodeA value
CHECK_EQ(aRef->ref_count(), 1); // Only aRef now points to original nodeA
// nodeB should now have increased reference count
CHECK_EQ(nodeB->ref_count(), 3); // nodeB + nodeA + nodeA->next_ (which points to nodeB)
// Clean up - clear the circular reference in the original node
aRef->setNext(fl::intrusive_ptr<IntrusiveNode>());
CHECK_EQ(nodeB->ref_count(), 2); // nodeB + nodeA
}