#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(); CHECK(ptr1 != nullptr); CHECK_EQ(ptr1->getValue(), 0); // Test single argument constructor auto ptr2 = fl::make_intrusive(42); CHECK(ptr2 != nullptr); CHECK_EQ(ptr2->getValue(), 42); // Test multiple argument constructor auto ptr3 = fl::make_intrusive(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(100); auto ptr2 = fl::NewPtr(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 ptr1; fl::Ptr ptr2; { auto ptr = fl::make_intrusive(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(42, true); CHECK_EQ(ptr->getValue(), 42); CHECK(ptr->isCopy()); } TEST_CASE("fl::intrusive_ptr alias functionality") { // Test that fl::intrusive_ptr works as an alias for fl::Ptr fl::intrusive_ptr ptr1 = fl::make_intrusive(42); CHECK(ptr1 != nullptr); CHECK_EQ(ptr1->getValue(), 42); // Test assignment between intrusive_ptr and Ptr fl::Ptr 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 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 next) { next_ = next; } fl::intrusive_ptr getNext() const { return next_; } private: int value_; fl::intrusive_ptr next_; }; FASTLED_SMART_PTR(IntrusiveNode); TEST_CASE("fl::intrusive_ptr self-assignment safety - a = b scenario") { auto nodeA = fl::make_intrusive(1); auto nodeB = fl::make_intrusive(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()); CHECK_EQ(nodeB->ref_count(), 2); // nodeB + nodeA }