// https://github.com/vinniefalco/LuaBridge
 
// Copyright 2019, Dmitry Tarakanov
 
// SPDX-License-Identifier: MIT
 
 
 
#include "TestBase.h"
 
 
 
#include "LuaBridge/RefCountedObject.h"
 
 
 
struct RefCountedObjectTests : TestBase
 
{
 
    template<class T>
 
    T variable(const std::string& name)
 
    {
 
        runLua("result = " + name);
 
        return result().cast<T>();
 
    }
 
};
 
 
 
namespace {
 
 
 
class RefCounted : public luabridge::RefCountedObject
 
{
 
public:
 
    explicit RefCounted(bool& deleted) : m_deleted(deleted) { m_deleted = false; }
 
 
 
    ~RefCounted() { m_deleted = true; }
 
 
 
    bool isDeleted() const { return m_deleted; }
 
 
 
private:
 
    bool& m_deleted;
 
};
 
 
 
} // namespace
 
 
 
TEST_F(RefCountedObjectTests, ConstructorDefault)
 
{
 
    const luabridge::RefCountedObjectPtr<RefCounted> ptr;
 
 
 
    ASSERT_EQ(ptr, nullptr);
 
}
 
 
 
TEST_F(RefCountedObjectTests, ConstructorObject)
 
{
 
    bool deleted = false;
 
    RefCounted* const rawPtr = new RefCounted(deleted);
 
    const luabridge::RefCountedObjectPtr<RefCounted> ptr(rawPtr);
 
 
 
    ASSERT_EQ(ptr, rawPtr);
 
    ASSERT_EQ(rawPtr->getReferenceCount(), 1);
 
    ASSERT_FALSE(deleted);
 
}
 
 
 
TEST_F(RefCountedObjectTests, ConstructorCopy)
 
{
 
    bool deleted = false;
 
    RefCounted* const rawPtr = new RefCounted(deleted);
 
    const luabridge::RefCountedObjectPtr<RefCounted> ptr(rawPtr);
 
    const luabridge::RefCountedObjectPtr<RefCounted> ptrCopy(ptr);
 
 
 
    ASSERT_EQ(ptr, rawPtr);
 
    ASSERT_EQ(ptrCopy, rawPtr);
 
    ASSERT_EQ(rawPtr->getReferenceCount(), 2);
 
    ASSERT_FALSE(deleted);
 
}
 
 
 
TEST_F(RefCountedObjectTests, ConstructorCopyPolymorph)
 
{
 
    struct RefCountedSpecialized : public RefCounted
 
    {
 
        explicit RefCountedSpecialized(bool& deleted) : RefCounted(deleted) {}
 
    };
 
 
 
    bool deleted = false;
 
    RefCountedSpecialized* const rawPtr = new RefCountedSpecialized(deleted);
 
 
 
    const luabridge::RefCountedObjectPtr<RefCountedSpecialized> ptr(rawPtr);
 
    const luabridge::RefCountedObjectPtr<RefCounted> ptrCopy(ptr);
 
 
 
    ASSERT_EQ(ptr, rawPtr);
 
    ASSERT_EQ(ptrCopy, rawPtr);
 
    ASSERT_EQ(rawPtr->getReferenceCount(), 2);
 
    ASSERT_FALSE(deleted);
 
}
 
 
 
TEST_F(RefCountedObjectTests, Destructor)
 
{
 
    bool deleted = false;
 
    {
 
        luabridge::RefCountedObjectPtr<RefCounted> ptr(new RefCounted(deleted));
 
        ASSERT_FALSE(deleted);
 
    }
 
 
 
    ASSERT_TRUE(deleted);
 
}
 
 
 
TEST_F(RefCountedObjectTests, AssignOperator)
 
{
 
    bool deletedPrevious = false;
 
    luabridge::RefCountedObjectPtr<RefCounted> ptr(new RefCounted(deletedPrevious));
 
 
 
    bool deletedNew = false;
 
    RefCounted* const rawPtr = new RefCounted(deletedNew);
 
 
 
    const luabridge::RefCountedObjectPtr<RefCounted>& returnValue = (ptr = rawPtr);
 
 
 
    ASSERT_EQ(&returnValue, &ptr);
 
    ASSERT_EQ(ptr, rawPtr);
 
    ASSERT_EQ(rawPtr->getReferenceCount(), 1);
 
    ASSERT_TRUE(deletedPrevious);
 
    ASSERT_FALSE(deletedNew);
 
}
 
 
 
TEST_F(RefCountedObjectTests, AssignOperatorSameObject)
 
{
 
    bool deleted = false;
 
    RefCounted* const rawPtr = new RefCounted(deleted);
 
 
 
    luabridge::RefCountedObjectPtr<RefCounted> ptr(rawPtr);
 
 
 
    const luabridge::RefCountedObjectPtr<RefCounted>& returnValue = (ptr = rawPtr);
 
 
 
    ASSERT_EQ(&returnValue, &ptr);
 
    ASSERT_EQ(ptr, rawPtr);
 
    ASSERT_EQ(rawPtr->getReferenceCount(), 1);
 
    ASSERT_FALSE(deleted);
 
}
 
 
 
TEST_F(RefCountedObjectTests, AssignOperatorRef)
 
{
 
    bool deletedPrevious = false;
 
    luabridge::RefCountedObjectPtr<RefCounted> ptr(new RefCounted(deletedPrevious));
 
 
 
    bool deletedNew = false;
 
    RefCounted* const rawPtrNew = new RefCounted(deletedNew);
 
    const luabridge::RefCountedObjectPtr<RefCounted> ptrNew(rawPtrNew);
 
 
 
    const luabridge::RefCountedObjectPtr<RefCounted>& returnValue = (ptr = ptrNew);
 
 
 
    ASSERT_EQ(&returnValue, &ptr);
 
    ASSERT_EQ(ptr, rawPtrNew);
 
    ASSERT_EQ(rawPtrNew->getReferenceCount(), 2);
 
    ASSERT_TRUE(deletedPrevious);
 
    ASSERT_FALSE(deletedNew);
 
}
 
 
 
TEST_F(RefCountedObjectTests, AssignOperatorRefPolymorph)
 
{
 
    struct RefCountedSpecialized : public RefCounted
 
    {
 
        explicit RefCountedSpecialized(bool& deleted) : RefCounted(deleted) {}
 
    };
 
 
 
    bool deletedPrevious = false;
 
    luabridge::RefCountedObjectPtr<RefCounted> ptr(new RefCounted(deletedPrevious));
 
 
 
    bool deletedNew = false;
 
    RefCountedSpecialized* const rawPtrNew = new RefCountedSpecialized(deletedNew);
 
    const luabridge::RefCountedObjectPtr<RefCountedSpecialized> ptrNew(rawPtrNew);
 
 
 
    const luabridge::RefCountedObjectPtr<RefCounted>& returnValue = (ptr = ptrNew);
 
 
 
    ASSERT_EQ(&returnValue, &ptr);
 
    ASSERT_EQ(ptr, rawPtrNew);
 
    ASSERT_EQ(rawPtrNew->getReferenceCount(), 2);
 
    ASSERT_TRUE(deletedPrevious);
 
    ASSERT_FALSE(deletedNew);
 
}
 
 
 
TEST_F(RefCountedObjectTests, AssignOperatorRefSelfAssignment)
 
{
 
    bool deleted = false;
 
    RefCounted* const rawPtr = new RefCounted(deleted);
 
 
 
    luabridge::RefCountedObjectPtr<RefCounted> ptr(rawPtr);
 
 
 
    const luabridge::RefCountedObjectPtr<RefCounted>& returnValue = (ptr = ptr);
 
 
 
    ASSERT_EQ(&returnValue, &ptr);
 
    ASSERT_EQ(ptr, rawPtr);
 
    ASSERT_EQ(rawPtr->getReferenceCount(), 1);
 
    ASSERT_FALSE(deleted);
 
}
 
 
 
TEST_F(RefCountedObjectTests, AssignOperatorRefSameObject)
 
{
 
    bool deleted = false;
 
    RefCounted* const rawPtr = new RefCounted(deleted);
 
 
 
    const luabridge::RefCountedObjectPtr<RefCounted> ptr1(rawPtr);
 
    luabridge::RefCountedObjectPtr<RefCounted> ptr2(rawPtr);
 
 
 
    const luabridge::RefCountedObjectPtr<RefCounted>& returnValue = (ptr2 = ptr1);
 
 
 
    ASSERT_EQ(&returnValue, &ptr2);
 
    ASSERT_EQ(ptr1, rawPtr);
 
    ASSERT_EQ(ptr2, rawPtr);
 
    ASSERT_EQ(rawPtr->getReferenceCount(), 2);
 
    ASSERT_FALSE(deleted);
 
}
 
 
 
namespace {
 
 
 
class TestObjectNested final : public luabridge::RefCountedObject
 
{
 
public:
 
    explicit TestObjectNested(const uint64_t value) : m_value(value) {}
 
 
 
    uint64_t getValue() const { return m_value; }
 
 
 
    luabridge::RefCountedObjectPtr<TestObjectNested>& getChild() { return m_child; }
 
 
 
private:
 
    const uint64_t m_value;
 
 
 
    luabridge::RefCountedObjectPtr<TestObjectNested> m_child;
 
};
 
 
 
} // namespace
 
 
 
TEST_F(RefCountedObjectTests, 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::RefCountedObjectPtr<TestObjectNested> ref = new TestObjectNested(parentValue);
 
    ref->getChild() = new TestObjectNested(childValue);
 
 
 
    ASSERT_EQ(ref->getValue(), parentValue);
 
    ASSERT_EQ(ref->getChild()->getValue(), childValue);
 
 
 
    const luabridge::RefCountedObjectPtr<TestObjectNested>& returnValue = (ref = ref->getChild());
 
    ASSERT_EQ(&returnValue, &ref);
 
    ASSERT_EQ(ref->getValue(), childValue);
 
}
 
 
 
TEST_F(RefCountedObjectTests, CompareOperators)
 
{
 
    bool deleted = false;
 
 
 
    RefCounted* const rawPtr1 = new RefCounted(deleted);
 
    luabridge::RefCountedObjectPtr<RefCounted> ptr1(rawPtr1);
 
 
 
    RefCounted* const rawPtr2 = new RefCounted(deleted);
 
    luabridge::RefCountedObjectPtr<RefCounted> 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);
 
}
 
 
 
TEST_F(RefCountedObjectTests, LastReferenceInLua)
 
{
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<RefCounted>("Class")
 
        .addProperty("deleted", &RefCounted::isDeleted)
 
        .endClass();
 
 
 
    bool deleted = false;
 
    luabridge::RefCountedObjectPtr<RefCounted> object(new RefCounted(deleted));
 
 
 
    luabridge::setGlobal(L, object, "object");
 
    runLua("result = object.deleted");
 
    ASSERT_EQ(true, result().isBool());
 
    ASSERT_EQ(false, result<bool>());
 
 
 
    object = nullptr;
 
    runLua("result = object.deleted");
 
    ASSERT_EQ(true, result().isBool());
 
    ASSERT_EQ(false, result<bool>());
 
    ASSERT_EQ(false, deleted);
 
 
 
    runLua("object = nil");
 
    lua_gc(L, LUA_GCCOLLECT, 1);
 
 
 
    ASSERT_EQ(true, deleted);
 
}
 
 
 
TEST_F(RefCountedObjectTests, LastReferenceInCpp)
 
{
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<RefCounted>("Class")
 
        .addProperty("deleted", &RefCounted::isDeleted)
 
        .endClass();
 
 
 
    bool deleted = false;
 
    luabridge::RefCountedObjectPtr<RefCounted> object(new RefCounted(deleted));
 
 
 
    luabridge::setGlobal(L, object, "object");
 
    runLua("result = object.deleted");
 
    ASSERT_EQ(true, result().isBool());
 
    ASSERT_EQ(false, result<bool>());
 
 
 
    runLua("object = nil");
 
    lua_gc(L, LUA_GCCOLLECT, 1);
 
    ASSERT_EQ(false, deleted);
 
 
 
    object = nullptr;
 
    ASSERT_EQ(true, deleted);
 
}