// https://github.com/vinniefalco/LuaBridge
// Copyright 2021, Stefan Frings
// SPDX-License-Identifier: MIT
#include <gtest/gtest.h>
#include "LuaBridge/RefCountedPtr.h"
class RefCountedPtrTests : public ::testing::Test, private luabridge::detail::RefCountedPtrBase
{
public:
void SetUp() override { getRefCounts().clear(); }
void TearDown() override
{
for (const RefCountsType::value_type& refCountEntry : getRefCounts())
{
std::cout << "REMAINING ptr=" << refCountEntry.first
<< ", count=" << refCountEntry.second << std::endl;
}
ASSERT_TRUE(getRefCounts().empty());
}
size_t getNumRefCounts() const { return getRefCounts().size(); }
};
TEST_F(RefCountedPtrTests, ConstructorDefault)
{
const luabridge::RefCountedPtr<int> ptr;
ASSERT_EQ(ptr, nullptr);
ASSERT_EQ(getNumRefCounts(), 0);
ASSERT_EQ(ptr.use_count(), 0);
}
TEST_F(RefCountedPtrTests, ConstructorObject)
{
int* const rawPtr = new int(123);
const luabridge::RefCountedPtr<int> ptr(rawPtr);
ASSERT_EQ(ptr, rawPtr);
ASSERT_EQ(getNumRefCounts(), 1);
ASSERT_EQ(ptr.use_count(), 1);
}
TEST_F(RefCountedPtrTests, ConstructorCopy)
{
int* const rawPtr = new int(123);
const luabridge::RefCountedPtr<int> ptr(rawPtr);
const luabridge::RefCountedPtr<int> ptrCopy(ptr);
ASSERT_EQ(ptr, rawPtr);
ASSERT_EQ(ptrCopy, rawPtr);
ASSERT_EQ(getNumRefCounts(), 1);
ASSERT_EQ(ptr.use_count(), 2);
ASSERT_EQ(ptrCopy.use_count(), 2);
}
TEST_F(RefCountedPtrTests, ConstructorCopyPolymorph)
{
struct Base
{
};
struct Specialized : public Base
{
};
Specialized* const rawPtr = new Specialized;
const luabridge::RefCountedPtr<Specialized> ptr(rawPtr);
const luabridge::RefCountedPtr<Base> ptrCopy(ptr);
ASSERT_EQ(ptr, rawPtr);
ASSERT_EQ(ptrCopy, rawPtr);
ASSERT_EQ(getNumRefCounts(), 1);
ASSERT_EQ(ptr.use_count(), 2);
ASSERT_EQ(ptrCopy.use_count(), 2);
}
namespace {
class TestObject
{
public:
explicit TestObject(bool& deleted) : m_deleted(deleted) { m_deleted = false; }
~TestObject() { m_deleted = true; }
private:
bool& m_deleted;
};
} // namespace
TEST_F(RefCountedPtrTests, Destructor)
{
bool deleted = false;
{
const luabridge::RefCountedPtr<TestObject> ptr(new TestObject(deleted));
ASSERT_FALSE(deleted);
}
ASSERT_TRUE(deleted);
}
TEST_F(RefCountedPtrTests, ResetObject)
{
bool deleted = false;
luabridge::RefCountedPtr<TestObject> ptr(new TestObject(deleted));
ASSERT_FALSE(deleted);
ptr.reset();
ASSERT_EQ(ptr, nullptr);
ASSERT_TRUE(deleted);
}
TEST_F(RefCountedPtrTests, ResetNull)
{
luabridge::RefCountedPtr<TestObject> ptr;
ptr.reset();
ASSERT_EQ(ptr, nullptr);
}
TEST_F(RefCountedPtrTests, AssignOperator)
{
bool deletedPrevious = false;
luabridge::RefCountedPtr<TestObject> ptr(new TestObject(deletedPrevious));
bool deletedNew = false;
TestObject* const rawPtr = new TestObject(deletedNew);
const luabridge::RefCountedPtr<TestObject>& returnValue = (ptr = rawPtr);
ASSERT_EQ(&returnValue, &ptr);
ASSERT_EQ(ptr, rawPtr);
ASSERT_EQ(getNumRefCounts(), 1);
ASSERT_EQ(ptr.use_count(), 1);
ASSERT_TRUE(deletedPrevious);
ASSERT_FALSE(deletedNew);
}
TEST_F(RefCountedPtrTests, AssignOperatorSameObject)
{
bool deleted = false;
TestObject* const rawPtr = new TestObject(deleted);
luabridge::RefCountedPtr<TestObject> ptr(rawPtr);
const luabridge::RefCountedPtr<TestObject>& returnValue = (ptr = rawPtr);
ASSERT_EQ(&returnValue, &ptr);
ASSERT_EQ(ptr, rawPtr);
ASSERT_EQ(getNumRefCounts(), 1);
ASSERT_EQ(ptr.use_count(), 1);
ASSERT_FALSE(deleted);
}
TEST_F(RefCountedPtrTests, AssignOperatorRef)
{
bool deletedPrevious = false;
luabridge::RefCountedPtr<TestObject> ptr(new TestObject(deletedPrevious));
bool deletedNew = false;
TestObject* const rawPtrNew = new TestObject(deletedNew);
const luabridge::RefCountedPtr<TestObject> ptrNew(rawPtrNew);
const luabridge::RefCountedPtr<TestObject>& returnValue = (ptr = ptrNew);
ASSERT_EQ(&returnValue, &ptr);
ASSERT_EQ(ptr, rawPtrNew);
ASSERT_EQ(getNumRefCounts(), 1);
ASSERT_EQ(ptr.use_count(), 2);
ASSERT_EQ(ptrNew.use_count(), 2);
ASSERT_TRUE(deletedPrevious);
ASSERT_FALSE(deletedNew);
}
TEST_F(RefCountedPtrTests, AssignOperatorRefPolymorph)
{
struct TestObjectSpecialized : public TestObject
{
explicit TestObjectSpecialized(bool& deleted) : TestObject(deleted) {}
};
bool deletedPrevious = false;
luabridge::RefCountedPtr<TestObject> ptr(new TestObject(deletedPrevious));
bool deletedNew = false;
TestObjectSpecialized* const rawPtrNew = new TestObjectSpecialized(deletedNew);
const luabridge::RefCountedPtr<TestObjectSpecialized> ptrNew(rawPtrNew);
const luabridge::RefCountedPtr<TestObject>& returnValue = (ptr = ptrNew);
ASSERT_EQ(&returnValue, &ptr);
ASSERT_EQ(ptr, rawPtrNew);
ASSERT_EQ(getNumRefCounts(), 1);
ASSERT_EQ(ptr.use_count(), 2);
ASSERT_EQ(ptrNew.use_count(), 2);
ASSERT_TRUE(deletedPrevious);
ASSERT_FALSE(deletedNew);
}
TEST_F(RefCountedPtrTests, AssignOperatorRefSelfAssignment)
{
bool deleted = false;
TestObject* const rawPtr = new TestObject(deleted);
luabridge::RefCountedPtr<TestObject> ptr(rawPtr);
const luabridge::RefCountedPtr<TestObject>& returnValue = (ptr = ptr);
ASSERT_EQ(&returnValue, &ptr);
ASSERT_EQ(ptr, rawPtr);
ASSERT_EQ(getNumRefCounts(), 1);
ASSERT_EQ(ptr.use_count(), 1);
ASSERT_FALSE(deleted);
}
TEST_F(RefCountedPtrTests, AssignOperatorRefSameObject)
{
bool deleted = false;
TestObject* const rawPtr = new TestObject(deleted);
const luabridge::RefCountedPtr<TestObject> ptr1(rawPtr);
luabridge::RefCountedPtr<TestObject> ptr2(rawPtr);
const luabridge::RefCountedPtr<TestObject>& returnValue = (ptr2 = ptr1);
ASSERT_EQ(&returnValue, &ptr2);
ASSERT_EQ(ptr1, rawPtr);
ASSERT_EQ(ptr2, rawPtr);
ASSERT_EQ(getNumRefCounts(), 1);
ASSERT_EQ(ptr1.use_count(), 2);
ASSERT_EQ(ptr2.use_count(), 2);
ASSERT_FALSE(deleted);
}
namespace {
class TestObjectNested
{
public:
explicit TestObjectNested(const uint64_t value) : m_value(value) {}
uint64_t getValue() const { return m_value; }
luabridge::RefCountedPtr<TestObjectNested>& getChild() { return m_child; }
private:
const uint64_t m_value;
luabridge::RefCountedPtr<TestObjectNested> m_child;
};
} // namespace
TEST_F(RefCountedPtrTests, AssignOperatorRefNestedObjects)
{
// Test assignment operator in the case that the previous referenced object is
// part of the new referenced object. This nested situation can only be handled
// if the reference count of the new object is FIRST increased and after that
// the count of the old object is decreased. If this happens vice versa the
// stored pointer is invalid after the assignment because it points to an already
// deleted object.
const uint64_t parentValue = 123, childValue = 456;
luabridge::RefCountedPtr<TestObjectNested> ref = new TestObjectNested(parentValue);
ref->getChild() = new TestObjectNested(childValue);
ASSERT_EQ(ref->getValue(), parentValue);
ASSERT_EQ(ref->getChild()->getValue(), childValue);
const luabridge::RefCountedPtr<TestObjectNested>& returnValue = (ref = ref->getChild());
ASSERT_EQ(&returnValue, &ref);
ASSERT_EQ(ref->getValue(), childValue);
}
TEST_F(RefCountedPtrTests, CompareOperators)
{
int* const rawPtr1 = new int;
luabridge::RefCountedPtr<int> ptr1(rawPtr1);
int* const rawPtr2 = new int;
luabridge::RefCountedPtr<int> ptr2(rawPtr2);
ASSERT_TRUE(ptr1 == ptr1);
ASSERT_TRUE(ptr1 != ptr2);
ASSERT_TRUE(rawPtr1 == ptr1);
ASSERT_TRUE(ptr1 == rawPtr1);
ASSERT_TRUE(rawPtr2 != ptr1);
ASSERT_TRUE(ptr1 != rawPtr2);
}