// https://github.com/vinniefalco/LuaBridge
 
// Copyright 2020, Dmitry Tarakanov
 
// SPDX-License-Identifier: MIT
 
 
 
#include "TestBase.h"
 
 
 
#include <exception>
 
#include <functional>
 
#include <map>
 
#include <memory>
 
 
 
struct ClassTests : TestBase
 
{
 
    template<class T>
 
    T variable(const std::string& name)
 
    {
 
        runLua("result = " + name);
 
        return result<T>();
 
    }
 
};
 
 
 
namespace {
 
 
 
struct EmptyBase
 
{
 
};
 
 
 
template<class T, class Base>
 
struct Class : Base
 
{
 
    Class() : data() {}
 
 
 
    Class(T data) : data(data) {}
 
 
 
    static Class<T, Base> staticFunction(Class<T, Base> value) { return value; }
 
 
 
    std::string toString() const
 
    {
 
        std::ostringstream stream;
 
        stream << data;
 
        return stream.str();
 
    }
 
 
 
    bool operator==(const Class<T, Base>& rhs) const { return data == rhs.data; }
 
 
 
    bool operator<(const Class<T, Base>& rhs) const { return data < rhs.data; }
 
 
 
    bool operator<=(const Class<T, Base>& rhs) const { return data <= rhs.data; }
 
 
 
    Class<T, Base> operator+(const Class<T, Base>& rhs) const
 
    {
 
        return Class<T, Base>(data + rhs.data);
 
    }
 
 
 
    Class<T, Base> operator-(const Class<T, Base>& rhs) const
 
    {
 
        return Class<T, Base>(data - rhs.data);
 
    }
 
 
 
    Class<T, Base> operator*(const Class<T, Base>& rhs) const
 
    {
 
        return Class<T, Base>(data * rhs.data);
 
    }
 
 
 
    Class<T, Base> operator/(const Class<T, Base>& rhs) const
 
    {
 
        return Class<T, Base>(data / rhs.data);
 
    }
 
 
 
    Class<T, Base> operator%(const Class<T, Base>& rhs) const
 
    {
 
        return Class<T, Base>(data % rhs.data);
 
    }
 
 
 
    Class<T, Base> operator()(T param) { return Class<T, Base>(param); }
 
 
 
    int len() const { return data; }
 
 
 
    Class<T, Base> negate() const { return Class<T, Base>(-data); }
 
 
 
    T method(T value) { return value; }
 
 
 
    T methodState(T value, lua_State*) { return value; }
 
 
 
    T constMethod(T value) const { return value; }
 
 
 
    T getData() const { return data; }
 
 
 
    void setData(T data) { this->data = data; }
 
 
 
    T getDataState(lua_State*) const { return data; }
 
 
 
    void setDataState(T data, lua_State*) { this->data = data; }
 
 
 
    mutable T data;
 
    static T staticData;
 
};
 
 
 
template<class T, class Base>
 
T Class<T, Base>::staticData = {};
 
 
 
} // namespace
 
 
 
TEST_F(ClassTests, IsInstance)
 
{
 
    using BaseClass = Class<int, EmptyBase>;
 
    using OtherClass = Class<float, EmptyBase>;
 
    using DerivedClass = Class<float, BaseClass>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<BaseClass>("BaseClass")
 
        .endClass()
 
        .deriveClass<DerivedClass, BaseClass>("DerivedClass")
 
        .endClass()
 
        .beginClass<OtherClass>("OtherClass")
 
        .endClass();
 
 
 
    BaseClass base;
 
    luabridge::push(L, base);
 
 
 
    DerivedClass derived;
 
    luabridge::push(L, derived);
 
 
 
    OtherClass other;
 
    luabridge::push(L, other);
 
 
 
    ASSERT_TRUE(luabridge::isInstance<BaseClass>(L, -3));
 
    ASSERT_FALSE(luabridge::isInstance<DerivedClass>(L, -3)); // BaseClass is not DerivedClass
 
    ASSERT_FALSE(luabridge::isInstance<OtherClass>(L, -3));
 
 
 
    ASSERT_TRUE(luabridge::isInstance<BaseClass>(L, -2));
 
    ASSERT_TRUE(luabridge::isInstance<DerivedClass>(L, -2)); // DerivedClass is BaseClass
 
    ASSERT_FALSE(luabridge::isInstance<OtherClass>(L, -2));
 
 
 
    ASSERT_FALSE(luabridge::isInstance<BaseClass>(L, -1));
 
    ASSERT_FALSE(luabridge::isInstance<DerivedClass>(L, -1));
 
    ASSERT_TRUE(luabridge::isInstance<OtherClass>(L, -1));
 
}
 
 
 
TEST_F(ClassTests, PassingUnregisteredClassToLuaThrows)
 
{
 
    using Unregistered = Class<int, EmptyBase>;
 
 
 
    runLua("function process_fn (value) end");
 
 
 
    auto process_fn = luabridge::getGlobal(L, "process_fn");
 
    ASSERT_TRUE(process_fn.isFunction());
 
 
 
    Unregistered value(1);
 
    const Unregistered constValue(2);
 
    ASSERT_THROW(process_fn(value), std::exception);
 
    ASSERT_THROW(process_fn(constValue), std::exception);
 
    ASSERT_THROW(process_fn(&value), std::exception);
 
    ASSERT_THROW(process_fn(&constValue), std::exception);
 
}
 
 
 
TEST_F(ClassTests, PassWrongClassFromLuaThrows)
 
{
 
    using Right = Class<int, EmptyBase>;
 
    using WrongBase = Class<float, EmptyBase>;
 
    using Wrong = Class<int, WrongBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Right>("Right")
 
        .endClass()
 
        .beginClass<WrongBase>("WrongBase")
 
        .endClass()
 
        .beginClass<Wrong>("Wrong")
 
        .addConstructor<void (*)(int)>()
 
        .endClass()
 
        .addFunction("processRight", &Right::staticFunction);
 
 
 
    // bad argument #1 to 'processRight' (Right expected, got Wrong)
 
    ASSERT_THROW(runLua("result = processRight (Wrong (5))"), std::exception);
 
    ASSERT_TRUE(result().isNil());
 
}
 
 
 
TEST_F(ClassTests, PassDerivedClassInsteadOfBase)
 
{
 
    using Base = Class<int, EmptyBase>;
 
    using Derived = Class<float, Base>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Base>("Base")
 
        .endClass()
 
        .deriveClass<Derived, Base>("Derived")
 
        .addConstructor<void (*)(float)>()
 
        .endClass()
 
        .addFunction("processBase", &Base::staticFunction);
 
 
 
    runLua("result = processBase (Derived (3.14))");
 
    ASSERT_EQ(0, result<Base>().data);
 
}
 
 
 
namespace {
 
 
 
template<class T, class Base>
 
T processNonConst(Class<T, Base>* object)
 
{
 
    return object->data;
 
}
 
 
 
} // namespace
 
 
 
TEST_F(ClassTests, PassConstClassInsteadOfNonConstThrows)
 
{
 
    using Base = Class<int, EmptyBase>;
 
    using Derived = Class<float, Base>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Base>("Base")
 
        .endClass()
 
        .deriveClass<Derived, Base>("Derived")
 
        .endClass()
 
        .addFunction("processNonConst", &processNonConst<float, Base>);
 
 
 
    const Derived constObject(1.2f);
 
    luabridge::setGlobal(L, &constObject, "constObject");
 
 
 
    // bad argument #1 to 'processNonConst' (Derived expected, got const Derived)
 
    ASSERT_THROW(runLua("result = processNonConst (constObject)"), std::exception);
 
    ASSERT_TRUE(result().isNil());
 
}
 
 
 
TEST_F(ClassTests, PassOtherTypeInsteadOfNonConstThrows)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>() // Show that it does't matter
 
        .endClass()
 
        .addFunction("processNonConst", &processNonConst<int, EmptyBase>);
 
 
 
    // bad argument #1 to 'processNonConst' (Int expected, got number)
 
    ASSERT_THROW(runLua("result = processNonConst (1)"), std::exception);
 
    ASSERT_TRUE(result().isNil());
 
}
 
 
 
TEST_F(ClassTests, PassRegisteredClassInsteadOfUnregisteredThrows)
 
{
 
    using Int = Class<int, EmptyBase>;
 
    using Float = Class<float, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Float>("Float")
 
        .addConstructor<void (*)(float)>()
 
        .endClass()
 
        .addFunction("processUnregisteredInt", &Int::staticFunction);
 
 
 
    // bad argument #1 to 'processUnregisteredInt' (unregistered class expected, got Float)
 
    ASSERT_THROW(runLua("result = processUnregisteredInt (Float (1.2))"), std::exception);
 
    ASSERT_TRUE(result().isNil());
 
}
 
 
 
namespace {
 
 
 
Class<int, EmptyBase>& returnRef()
 
{
 
    static Class<int, EmptyBase> value(1);
 
    return value;
 
}
 
 
 
const Class<int, EmptyBase>& returnConstRef()
 
{
 
    return returnRef();
 
}
 
 
 
Class<int, EmptyBase>* returnPtr()
 
{
 
    return &returnRef();
 
}
 
 
 
const Class<int, EmptyBase>* returnConstPtr()
 
{
 
    return &returnConstRef();
 
}
 
 
 
Class<int, EmptyBase> returnValue()
 
{
 
    return Class<int, EmptyBase>(2);
 
}
 
 
 
void addHelperFunctions(lua_State* L)
 
{
 
    luabridge::getGlobalNamespace(L)
 
        .addFunction("returnRef", &returnRef)
 
        .addFunction("returnConstRef", &returnConstRef)
 
        .addFunction("returnPtr", &returnPtr)
 
        .addFunction("returnConstPtr", &returnConstPtr)
 
        .addFunction("returnValue", &returnValue);
 
}
 
 
 
} // namespace
 
 
 
TEST_F(ClassTests, PassingUnregisteredClassFromLuaThrows)
 
{
 
    using Unregistered = Class<int, EmptyBase>;
 
 
 
    addHelperFunctions(L);
 
 
 
    ASSERT_THROW(runLua("result = returnRef ()"), std::exception);
 
    ASSERT_THROW(runLua("result = returnConstRef ()"), std::exception);
 
    ASSERT_THROW(runLua("result = returnPtr ()"), std::exception);
 
    ASSERT_THROW(runLua("result = returnConstPtr ()"), std::exception);
 
    ASSERT_THROW(runLua("result = returnValue ()"), std::exception);
 
}
 
 
 
TEST_F(ClassTests, DeriveFromUnregisteredClassThrows)
 
{
 
    using Base = Class<int, EmptyBase>;
 
    using Derived = Class<float, Base>;
 
 
 
    ASSERT_THROW((luabridge::getGlobalNamespace(L).deriveClass<Derived, Base>("Derived")),
 
                 std::exception);
 
 
 
    ASSERT_EQ(1, lua_gettop(L));
 
}
 
 
 
struct ClassFunctions : ClassTests
 
{
 
};
 
 
 
TEST_F(ClassFunctions, MemberFunctions)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addFunction("method", &Int::method)
 
        .endClass();
 
 
 
    addHelperFunctions(L);
 
 
 
    runLua("result = returnRef ():method (1)");
 
    ASSERT_EQ(1, result<int>());
 
 
 
    runLua("result = returnConstRef ().method"); // Don't call, just get
 
    ASSERT_TRUE(result().isNil());
 
 
 
    runLua("result = returnPtr ():method (2)");
 
    ASSERT_EQ(2, result<int>());
 
 
 
    runLua("result = returnConstPtr ().method"); // Don't call, just get
 
    ASSERT_TRUE(result().isNil());
 
 
 
    runLua("result = returnValue ():method (3)");
 
    ASSERT_EQ(3, result<int>());
 
}
 
 
 
TEST_F(ClassFunctions, MemberFunctions_PassState)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addFunction("method", &Int::methodState)
 
        .endClass();
 
 
 
    addHelperFunctions(L);
 
 
 
    runLua("result = returnRef ():method (1)");
 
    ASSERT_EQ(1, result<int>());
 
 
 
    runLua("result = returnConstRef ().method"); // Don't call, just get
 
    ASSERT_TRUE(result().isNil());
 
 
 
    runLua("result = returnPtr ():method (2)");
 
    ASSERT_EQ(2, result<int>());
 
 
 
    runLua("result = returnConstPtr ().method"); // Don't call, just get
 
    ASSERT_TRUE(result().isNil());
 
 
 
    runLua("result = returnValue ():method (3)");
 
    ASSERT_EQ(3, result<int>());
 
}
 
 
 
TEST_F(ClassFunctions, ConstMemberFunctions)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addFunction("constMethod", &Int::constMethod)
 
        .endClass();
 
 
 
    addHelperFunctions(L);
 
 
 
    runLua("result = returnRef ():constMethod (1)");
 
    ASSERT_EQ(1, result<int>());
 
 
 
    runLua("result = returnConstRef ():constMethod (2)");
 
    ASSERT_EQ(2, result<int>());
 
 
 
    runLua("result = returnPtr ():constMethod (3)");
 
    ASSERT_EQ(3, result<int>());
 
 
 
    runLua("result = returnConstPtr ():constMethod (4)");
 
    ASSERT_EQ(4, result<int>());
 
 
 
    runLua("result = returnValue ():constMethod (5)");
 
    ASSERT_EQ(5, result<int>());
 
}
 
 
 
namespace {
 
 
 
template<class T, class Base>
 
T proxyFunction(Class<T, Base>* object, T value)
 
{
 
    object->data = value;
 
    return value;
 
}
 
 
 
template<class T, class Base>
 
T proxyFunctionState(Class<T, Base>* object, T value, lua_State*)
 
{
 
    object->data = value;
 
    return value;
 
}
 
 
 
template<class T, class Base>
 
T proxyConstFunction(const Class<T, Base>* object, T value)
 
{
 
    return value;
 
}
 
 
 
} // namespace
 
 
 
TEST_F(ClassFunctions, ProxyFunctions)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addFunction("method", &proxyFunction<int, EmptyBase>)
 
        .endClass();
 
 
 
    addHelperFunctions(L);
 
 
 
    runLua("result = returnRef ():method (1)");
 
    ASSERT_EQ(1, result<int>());
 
 
 
    runLua("result = returnConstRef ().method"); // Don't call, just get
 
    ASSERT_TRUE(result().isNil());
 
 
 
    runLua("result = returnPtr ():method (2)");
 
    ASSERT_EQ(2, result<int>());
 
 
 
    runLua("result = returnConstPtr ().method"); // Don't call, just get
 
    ASSERT_TRUE(result().isNil());
 
 
 
    runLua("result = returnValue ():method (3)");
 
    ASSERT_EQ(3, result<int>());
 
}
 
 
 
TEST_F(ClassFunctions, ProxyFunctions_PassState)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addFunction("method", &proxyFunctionState<int, EmptyBase>)
 
        .endClass();
 
 
 
    addHelperFunctions(L);
 
 
 
    runLua("result = returnRef ():method (1)");
 
    ASSERT_EQ(1, result<int>());
 
 
 
    runLua("result = returnConstRef ().method"); // Don't call, just get
 
    ASSERT_TRUE(result().isNil());
 
 
 
    runLua("result = returnPtr ():method (2)");
 
    ASSERT_EQ(2, result<int>());
 
 
 
    runLua("result = returnConstPtr ().method"); // Don't call, just get
 
    ASSERT_TRUE(result().isNil());
 
 
 
    runLua("result = returnValue ():method (3)");
 
    ASSERT_EQ(3, result<int>());
 
}
 
 
 
TEST_F(ClassFunctions, ConstProxyFunctions)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addFunction("constMethod", &proxyConstFunction<int, EmptyBase>)
 
        .endClass();
 
 
 
    addHelperFunctions(L);
 
 
 
    runLua("result = returnRef ():constMethod (1)");
 
    ASSERT_EQ(1, result<int>());
 
 
 
    runLua("result = returnConstRef ():constMethod (2)");
 
    ASSERT_EQ(2, result<int>());
 
 
 
    runLua("result = returnPtr ():constMethod (3)");
 
    ASSERT_EQ(3, result<int>());
 
 
 
    runLua("result = returnConstPtr ():constMethod (4)");
 
    ASSERT_EQ(4, result<int>());
 
 
 
    runLua("result = returnValue ():constMethod (5)");
 
    ASSERT_EQ(5, result<int>());
 
}
 
 
 
TEST_F(ClassFunctions, StdFunctions)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    auto sharedData = std::make_shared<int>();
 
    std::weak_ptr<int> data = sharedData; // Check __gc meta-method
 
 
 
    std::function<int(Int*, int)> function = [sharedData](Int* object, int value) {
 
        object->data = value;
 
        return value;
 
    };
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addFunction("method", std::move(function))
 
        .endClass();
 
 
 
    sharedData = nullptr;
 
    ASSERT_FALSE(data.expired());
 
 
 
    addHelperFunctions(L);
 
 
 
    runLua("result = returnRef ():method (1)");
 
    ASSERT_EQ(1, result<int>());
 
 
 
    runLua("result = returnConstRef ().method"); // Don't call, just get
 
    ASSERT_TRUE(result().isNil());
 
 
 
    runLua("result = returnPtr ():method (2)");
 
    ASSERT_EQ(2, result<int>());
 
 
 
    runLua("result = returnConstPtr ().method"); // Don't call, just get
 
    ASSERT_TRUE(result().isNil());
 
 
 
    runLua("result = returnValue ():method (3)");
 
    ASSERT_EQ(3, result<int>());
 
 
 
    runLua("result = nil");
 
    lua_close(L); // Force garbage collection
 
    L = nullptr;
 
 
 
    ASSERT_TRUE(data.expired());
 
}
 
 
 
TEST_F(ClassFunctions, StdFunctions_PassState)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    auto sharedData = std::make_shared<int>();
 
    std::weak_ptr<int> data = sharedData; // Check __gc meta-method
 
 
 
    std::function<int(Int*, int, lua_State*)> function =
 
        [sharedData](Int* object, int value, lua_State*) {
 
            object->data = value;
 
            return value;
 
        };
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addFunction("method", std::move(function))
 
        .endClass();
 
 
 
    sharedData = nullptr;
 
    ASSERT_FALSE(data.expired());
 
 
 
    addHelperFunctions(L);
 
 
 
    runLua("result = returnRef ():method (1)");
 
    ASSERT_EQ(1, result<int>());
 
 
 
    runLua("result = returnConstRef ().method"); // Don't call, just get
 
    ASSERT_TRUE(result().isNil());
 
 
 
    runLua("result = returnPtr ():method (2)");
 
    ASSERT_EQ(2, result<int>());
 
 
 
    runLua("result = returnConstPtr ().method"); // Don't call, just get
 
    ASSERT_TRUE(result().isNil());
 
 
 
    runLua("result = returnValue ():method (3)");
 
    ASSERT_EQ(3, result<int>());
 
 
 
    runLua("result = nil");
 
    lua_close(L); // Force garbage collection
 
    L = nullptr;
 
 
 
    ASSERT_TRUE(data.expired());
 
}
 
 
 
TEST_F(ClassFunctions, ConstStdFunctions)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    auto sharedData = std::make_shared<int>();
 
    std::weak_ptr<int> data = sharedData; // Check __gc meta-method
 
 
 
    std::function<int(const Int*, int)> function = [sharedData](const Int* object, int value) {
 
        object->data = value;
 
        return value;
 
    };
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addFunction("constMethod", std::move(function))
 
        .endClass();
 
 
 
    sharedData = nullptr;
 
    ASSERT_FALSE(data.expired());
 
 
 
    addHelperFunctions(L);
 
 
 
    runLua("result = returnRef ():constMethod (1)");
 
    ASSERT_EQ(1, result<int>());
 
 
 
    runLua("result = returnConstRef ():constMethod (2)");
 
    ASSERT_EQ(2, result<int>());
 
 
 
    runLua("result = returnPtr ():constMethod (3)");
 
    ASSERT_EQ(3, result<int>());
 
 
 
    runLua("result = returnConstPtr ():constMethod (4)");
 
    ASSERT_EQ(4, result<int>());
 
 
 
    runLua("result = returnValue ():constMethod (5)");
 
    ASSERT_EQ(5, result<int>());
 
 
 
    runLua("result = nil");
 
    lua_close(L); // Force garbage collection
 
    L = nullptr;
 
 
 
    ASSERT_TRUE(data.expired());
 
}
 
 
 
struct ClassProperties : ClassTests
 
{
 
};
 
 
 
TEST_F(ClassProperties, FieldPointers)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addProperty("data", &Int::data, true)
 
        .endClass();
 
 
 
    runLua("result = Int (501)");
 
    ASSERT_TRUE(result()["data"].isNumber());
 
    ASSERT_EQ(501, result()["data"].cast<int>());
 
 
 
    runLua("result.data = 2");
 
    ASSERT_TRUE(result()["data"].isNumber());
 
    ASSERT_EQ(2, result()["data"].cast<int>());
 
 
 
    runLua("result = Int (42).data");
 
    ASSERT_TRUE(result().isNumber());
 
    ASSERT_EQ(42, result<int>());
 
}
 
 
 
TEST_F(ClassProperties, FieldPointers_ReadOnly)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addProperty("data", &Int::data, false)
 
        .endClass();
 
 
 
    runLua("result = Int (501)");
 
    ASSERT_TRUE(result()["data"].isNumber());
 
    ASSERT_EQ(501, result()["data"].cast<int>());
 
 
 
    ASSERT_THROW(runLua("result.data = 2"), std::exception);
 
 
 
    runLua("result = Int (42).data");
 
    ASSERT_TRUE(result().isNumber());
 
    ASSERT_EQ(42, result<int>());
 
}
 
 
 
TEST_F(ClassProperties, MemberFunctions)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addProperty("data", &Int::getData, &Int::setData)
 
        .endClass();
 
 
 
    runLua("result = Int (501)");
 
    ASSERT_TRUE(result()["data"].isNumber());
 
    ASSERT_EQ(501, result()["data"].cast<int>());
 
 
 
    runLua("result.data = -2");
 
    ASSERT_TRUE(result()["data"].isNumber());
 
    ASSERT_EQ(-2, result()["data"].cast<int>());
 
}
 
 
 
TEST_F(ClassProperties, MemberFunctions_PassState)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addProperty("data", &Int::getDataState, &Int::setDataState)
 
        .endClass();
 
 
 
    runLua("result = Int (501)");
 
    ASSERT_TRUE(result()["data"].isNumber());
 
    ASSERT_EQ(501, result()["data"].cast<int>());
 
 
 
    runLua("result.data = -2");
 
    ASSERT_TRUE(result()["data"].isNumber());
 
    ASSERT_EQ(-2, result()["data"].cast<int>());
 
}
 
 
 
TEST_F(ClassProperties, MemberFunctions_ReadOnly)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addProperty("data", &Int::getData)
 
        .endClass();
 
 
 
    runLua("result = Int (501)");
 
    ASSERT_TRUE(result()["data"].isNumber());
 
    ASSERT_EQ(501, result()["data"].cast<int>());
 
 
 
    ASSERT_THROW(runLua("result.data = -2"), std::exception);
 
    ASSERT_EQ(501, result()["data"].cast<int>());
 
}
 
 
 
TEST_F(ClassProperties, MemberFunctions_Derived)
 
{
 
    using Base = Class<std::string, EmptyBase>;
 
    using Derived = Class<int, Base>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Base>("Base")
 
        .addProperty("data", &Base::getData, &Base::setData)
 
        .endClass()
 
        .deriveClass<Derived, Base>("Derived")
 
        .endClass();
 
 
 
    Derived derived(12);
 
    derived.Base::data = "abc";
 
    luabridge::setGlobal(L, &derived, "derived");
 
 
 
    runLua("result = derived.data");
 
    ASSERT_TRUE(result().isString());
 
    ASSERT_EQ("abc", result<std::string>());
 
 
 
    runLua("derived.data = 5"); // Lua just casts integer to string
 
    ASSERT_EQ("5", derived.Base::data);
 
    ASSERT_EQ(12, derived.data);
 
 
 
    runLua("derived.data = '123'");
 
    ASSERT_EQ("123", derived.Base::data);
 
    ASSERT_EQ(12, derived.data);
 
}
 
 
 
TEST_F(ClassProperties, MemberFunctions_Overridden)
 
{
 
    using Base = Class<float, EmptyBase>;
 
    using Derived = Class<int, Base>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Base>("Base")
 
        .addProperty("data", &Base::getData, &Base::setData)
 
        .endClass()
 
        .deriveClass<Derived, Base>("Derived")
 
        .addProperty("data", &Derived::getData, &Derived::setData)
 
        .endClass();
 
 
 
    Derived derived(50);
 
    derived.Base::data = 1.23f;
 
    luabridge::setGlobal(L, static_cast<Base*>(&derived), "base");
 
    luabridge::setGlobal(L, &derived, "derived");
 
 
 
    runLua("result = base.data");
 
    ASSERT_TRUE(result().isNumber());
 
    ASSERT_EQ(1.23f, result<float>());
 
 
 
    runLua("result = derived.data");
 
    ASSERT_TRUE(result().isNumber());
 
    ASSERT_EQ(50, result<int>());
 
 
 
    runLua("base.data = -3.14");
 
    ASSERT_EQ(-3.14f, derived.Base::data);
 
    ASSERT_EQ(50, derived.data);
 
 
 
    runLua("derived.data = 7");
 
    ASSERT_EQ(-3.14f, derived.Base::data);
 
    ASSERT_EQ(7, derived.data);
 
}
 
 
 
namespace {
 
 
 
template<class T, class BaseClass>
 
T getData(const Class<T, BaseClass>* object)
 
{
 
    return object->data;
 
}
 
 
 
template<class T, class BaseClass>
 
void setData(Class<T, BaseClass>* object, T data)
 
{
 
    object->data = data;
 
}
 
 
 
} // namespace
 
 
 
TEST_F(ClassProperties, ProxyFunctions)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addProperty("data", &getData<int, EmptyBase>, &setData<int, EmptyBase>)
 
        .endClass();
 
 
 
    runLua("result = Int (501)");
 
    ASSERT_TRUE(result()["data"].isNumber());
 
    ASSERT_EQ(501, result()["data"].cast<int>());
 
 
 
    runLua("result.data = -2");
 
    ASSERT_TRUE(result()["data"].isNumber());
 
    ASSERT_EQ(-2, result()["data"].cast<int>());
 
}
 
 
 
TEST_F(ClassProperties, ProxyFunctions_ReadOnly)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addProperty("data", &getData<int, EmptyBase>)
 
        .endClass();
 
 
 
    runLua("result = Int (501)");
 
    ASSERT_TRUE(result()["data"].isNumber());
 
    ASSERT_EQ(501, result()["data"].cast<int>());
 
 
 
    ASSERT_THROW(runLua("result.data = -2"), std::exception);
 
    ASSERT_EQ(501, result()["data"].cast<int>());
 
}
 
 
 
TEST_F(ClassProperties, ProxyFunctions_Derived)
 
{
 
    using Base = Class<std::string, EmptyBase>;
 
    using Derived = Class<int, Base>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Base>("Base")
 
        .addProperty("data", &getData<std::string, EmptyBase>, &setData<std::string, EmptyBase>)
 
        .endClass()
 
        .deriveClass<Derived, Base>("Derived")
 
        .endClass();
 
 
 
    Derived derived(12);
 
    derived.Base::data = "abc";
 
    luabridge::setGlobal(L, &derived, "derived");
 
 
 
    runLua("result = derived.data");
 
    ASSERT_TRUE(result().isString());
 
    ASSERT_EQ("abc", result<std::string>());
 
 
 
    runLua("derived.data = 5"); // Lua just casts integer to string
 
    ASSERT_EQ("5", derived.Base::data);
 
    ASSERT_EQ(12, derived.data);
 
 
 
    runLua("derived.data = '123'");
 
    ASSERT_EQ("123", derived.Base::data);
 
    ASSERT_EQ(12, derived.data);
 
}
 
 
 
TEST_F(ClassProperties, ProxyFunctions_Overridden)
 
{
 
    using Base = Class<float, EmptyBase>;
 
    using Derived = Class<int, Base>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Base>("Base")
 
        .addProperty("data", &getData<float, EmptyBase>, &setData<float, EmptyBase>)
 
        .endClass()
 
        .deriveClass<Derived, Base>("Derived")
 
        .addProperty("data", &getData<int, Base>, &setData<int, Base>)
 
        .endClass();
 
 
 
    Derived derived(50);
 
    derived.Base::data = 1.23f;
 
    luabridge::setGlobal(L, static_cast<Base*>(&derived), "base");
 
    luabridge::setGlobal(L, &derived, "derived");
 
 
 
    runLua("result = base.data");
 
    ASSERT_TRUE(result().isNumber());
 
    ASSERT_EQ(1.23f, result<float>());
 
 
 
    runLua("result = derived.data");
 
    ASSERT_TRUE(result().isNumber());
 
    ASSERT_EQ(50, result<int>());
 
 
 
    runLua("base.data = -3.14");
 
    ASSERT_EQ(-3.14f, derived.Base::data);
 
    ASSERT_EQ(50, derived.data);
 
 
 
    runLua("derived.data = 7");
 
    ASSERT_EQ(-3.14f, derived.Base::data);
 
    ASSERT_EQ(7, derived.data);
 
}
 
 
 
namespace {
 
 
 
template<class T, class BaseClass>
 
int getDataC(lua_State* L)
 
{
 
    auto objectRef = luabridge::LuaRef::fromStack(L, 1);
 
    auto* object = objectRef.cast<const Class<T, BaseClass>*>();
 
    luabridge::Stack<T>::push(L, object->data);
 
    return 1;
 
}
 
 
 
template<class T, class BaseClass>
 
int setDataC(lua_State* L)
 
{
 
    auto objectRef = luabridge::LuaRef::fromStack(L, 1);
 
    auto* object = objectRef.cast<const Class<T, BaseClass>*>();
 
    auto valueRef = luabridge::LuaRef::fromStack(L, 2);
 
    T value = valueRef.cast<T>();
 
    object->data = value;
 
    return 0;
 
}
 
 
 
} // namespace
 
 
 
TEST_F(ClassProperties, ProxyCFunctions)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addProperty("data", &getDataC<int, EmptyBase>, &setDataC<int, EmptyBase>)
 
        .endClass();
 
 
 
    runLua("result = Int (501)");
 
    ASSERT_TRUE(result()["data"].isNumber());
 
    ASSERT_EQ(501, result()["data"].cast<int>());
 
 
 
    runLua("result.data = -2");
 
    ASSERT_TRUE(result()["data"].isNumber());
 
    ASSERT_EQ(-2, result()["data"].cast<int>());
 
}
 
 
 
TEST_F(ClassProperties, ProxyCFunctions_ReadOnly)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addProperty("data", &getDataC<int, EmptyBase>)
 
        .endClass();
 
 
 
    runLua("result = Int (501)");
 
    ASSERT_TRUE(result()["data"].isNumber());
 
    ASSERT_EQ(501, result()["data"].cast<int>());
 
 
 
    ASSERT_THROW(runLua("result.data = -2"), std::exception);
 
    ASSERT_EQ(501, result()["data"].cast<int>());
 
}
 
 
 
TEST_F(ClassProperties, ProxyCFunctions_Derived)
 
{
 
    using Base = Class<std::string, EmptyBase>;
 
    using Derived = Class<int, Base>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Base>("Base")
 
        .addProperty("data", &getDataC<std::string, EmptyBase>, &setDataC<std::string, EmptyBase>)
 
        .endClass()
 
        .deriveClass<Derived, Base>("Derived")
 
        .endClass();
 
 
 
    Derived derived(12);
 
    derived.Base::data = "abc";
 
    luabridge::setGlobal(L, &derived, "derived");
 
 
 
    runLua("result = derived.data");
 
    ASSERT_TRUE(result().isString());
 
    ASSERT_EQ("abc", result<std::string>());
 
 
 
    runLua("derived.data = 5"); // Lua just casts integer to string
 
    ASSERT_EQ("5", derived.Base::data);
 
    ASSERT_EQ(12, derived.data);
 
 
 
    runLua("derived.data = '123'");
 
    ASSERT_EQ("123", derived.Base::data);
 
    ASSERT_EQ(12, derived.data);
 
}
 
 
 
TEST_F(ClassProperties, ProxyCFunctions_Overridden)
 
{
 
    using Base = Class<float, EmptyBase>;
 
    using Derived = Class<int, Base>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Base>("Base")
 
        .addProperty("data", &getDataC<float, EmptyBase>, &setDataC<float, EmptyBase>)
 
        .endClass()
 
        .deriveClass<Derived, Base>("Derived")
 
        .addProperty("data", &getData<int, Base>, &setData<int, Base>)
 
        .endClass();
 
 
 
    Derived derived(50);
 
    derived.Base::data = 1.23f;
 
    luabridge::setGlobal(L, static_cast<Base*>(&derived), "base");
 
    luabridge::setGlobal(L, &derived, "derived");
 
 
 
    runLua("result = base.data");
 
    ASSERT_TRUE(result().isNumber());
 
    ASSERT_EQ(1.23f, result<float>());
 
 
 
    runLua("result = derived.data");
 
    ASSERT_TRUE(result().isNumber());
 
    ASSERT_EQ(50, result<int>());
 
 
 
    runLua("base.data = -3.14");
 
    ASSERT_EQ(-3.14f, derived.Base::data);
 
    ASSERT_EQ(50, derived.data);
 
 
 
    runLua("derived.data = 7");
 
    ASSERT_EQ(-3.14f, derived.Base::data);
 
    ASSERT_EQ(7, derived.data);
 
}
 
 
 
TEST_F(ClassProperties, StdFunctions)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    auto sharedGetterData = std::make_shared<int>();
 
    std::weak_ptr<int> getterData = sharedGetterData; // Check __gc meta-method
 
 
 
    auto sharedSetterData = std::make_shared<int>();
 
    std::weak_ptr<int> setterData = sharedGetterData; // Check __gc meta-method
 
 
 
    std::function<int(const Int*)> getter = [sharedGetterData](const Int* object) {
 
        return object->data;
 
    };
 
 
 
    std::function<void(Int*, int)> setter = [sharedSetterData](Int* object, int value) {
 
        object->data = value;
 
    };
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addProperty("data", std::move(getter), std::move(setter))
 
        .endClass();
 
 
 
    sharedGetterData = nullptr;
 
    ASSERT_FALSE(getterData.expired());
 
 
 
    sharedSetterData = nullptr;
 
    ASSERT_FALSE(setterData.expired());
 
 
 
    runLua("result = Int (501)");
 
    ASSERT_EQ(501, result()["data"].cast<int>());
 
 
 
    runLua("result.data = -2");
 
    ASSERT_TRUE(result()["data"].isNumber());
 
    ASSERT_EQ(-2, result()["data"].cast<int>());
 
 
 
    runLua("result = nil");
 
    lua_close(L); // Force garbage collection
 
    L = nullptr;
 
 
 
    ASSERT_TRUE(getterData.expired());
 
    ASSERT_TRUE(setterData.expired());
 
}
 
 
 
TEST_F(ClassProperties, StdFunctions_ReadOnly)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    auto sharedGetterData = std::make_shared<int>();
 
    std::weak_ptr<int> getterData = sharedGetterData; // Check __gc meta-method
 
 
 
    std::function<int(const Int*)> getter = [sharedGetterData](const Int* object) {
 
        return object->data;
 
    };
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addProperty("data", std::move(getter))
 
        .endClass();
 
 
 
    sharedGetterData = nullptr;
 
    ASSERT_FALSE(getterData.expired());
 
 
 
    runLua("result = Int (501)");
 
    ASSERT_TRUE(result()["data"].isNumber());
 
    ASSERT_EQ(501, result()["data"].cast<int>());
 
 
 
    ASSERT_THROW(runLua("result.data = -2"), std::exception);
 
    ASSERT_EQ(501, result()["data"].cast<int>());
 
 
 
    runLua("result = nil");
 
    lua_close(L); // Force garbage collection
 
    L = nullptr;
 
 
 
    ASSERT_TRUE(getterData.expired());
 
}
 
 
 
struct ClassStaticFunctions : ClassTests
 
{
 
};
 
 
 
TEST_F(ClassStaticFunctions, Functions)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addStaticFunction("static", &Int::staticFunction)
 
        .endClass();
 
 
 
    runLua("result = Int.static (Int (35))");
 
    ASSERT_EQ(35, result<Int>().data);
 
}
 
 
 
TEST_F(ClassStaticFunctions, Functions_Derived)
 
{
 
    using Base = Class<std::string, EmptyBase>;
 
    using Derived = Class<int, Base>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Base>("Base")
 
        .addConstructor<void (*)(std::string)>()
 
        .addStaticFunction("static", &Base::staticFunction)
 
        .endClass()
 
        .deriveClass<Derived, Base>("Derived")
 
        .endClass();
 
 
 
    runLua("result = Derived.static (Base ('abc'))");
 
    ASSERT_EQ("abc", result<Base>().data);
 
}
 
 
 
TEST_F(ClassStaticFunctions, Functions_Overridden)
 
{
 
    using Base = Class<std::string, EmptyBase>;
 
    using Derived = Class<int, Base>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Base>("Base")
 
        .addConstructor<void (*)(std::string)>()
 
        .addStaticFunction("staticFunction", &Base::staticFunction)
 
        .endClass()
 
        .deriveClass<Derived, Base>("Derived")
 
        .addConstructor<void (*)(int)>()
 
        .addStaticFunction("staticFunction", &Derived::staticFunction)
 
        .endClass();
 
 
 
    runLua("result = Base.staticFunction (Base ('abc'))");
 
    ASSERT_EQ("abc", result<Base>().data);
 
 
 
    runLua("result = Derived.staticFunction (Derived (123))");
 
    ASSERT_EQ(123, result<Derived>().data);
 
}
 
 
 
TEST_F(ClassStaticFunctions, StdFunctions)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addStaticFunction("static", std::function<Int(Int)>(&Int::staticFunction))
 
        .endClass();
 
 
 
    runLua("result = Int.static (Int (35))");
 
    ASSERT_EQ(35, result<Int>().data);
 
}
 
 
 
struct ClassStaticProperties : ClassTests
 
{
 
};
 
 
 
TEST_F(ClassStaticProperties, FieldPointers)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addStaticProperty("staticData", &Int::staticData, true)
 
        .endClass();
 
 
 
    Int::staticData = 10;
 
 
 
    runLua("result = Int.staticData");
 
    ASSERT_TRUE(result().isNumber());
 
    ASSERT_EQ(10, result<int>());
 
 
 
    runLua("Int.staticData = 20");
 
    ASSERT_EQ(20, Int::staticData);
 
}
 
 
 
TEST_F(ClassStaticProperties, FieldPointers_ReadOnly)
 
{
 
    using Int = Class<int, EmptyBase>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addStaticProperty("staticData", &Int::staticData, false)
 
        .endClass();
 
 
 
    Int::staticData = 10;
 
 
 
    runLua("result = Int.staticData");
 
    ASSERT_TRUE(result().isNumber());
 
    ASSERT_EQ(10, result<int>());
 
 
 
    ASSERT_THROW(runLua("Int.staticData = 20"), std::exception);
 
    ASSERT_EQ(10, Int::staticData);
 
}
 
 
 
TEST_F(ClassStaticProperties, FieldPointers_Derived)
 
{
 
    using Base = Class<float, EmptyBase>;
 
    using Derived = Class<int, Base>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Base>("Base")
 
        .addStaticProperty("staticData", &Base::staticData, true)
 
        .endClass()
 
        .deriveClass<Derived, Base>("Derived")
 
        .endClass();
 
 
 
    Base::staticData = 1.23f;
 
    Derived::staticData = 50;
 
 
 
    runLua("result = Derived.staticData");
 
    ASSERT_TRUE(result().isNumber());
 
    ASSERT_EQ(1.23f, result<float>());
 
 
 
    runLua("Derived.staticData = -3.14");
 
    ASSERT_EQ(-3.14f, Base::staticData);
 
    ASSERT_EQ(50, Derived::staticData);
 
}
 
 
 
TEST_F(ClassStaticProperties, FieldPointers_Overridden)
 
{
 
    using Base = Class<float, EmptyBase>;
 
    using Derived = Class<int, Base>;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Base>("Base")
 
        .addStaticProperty("staticData", &Base::staticData, true)
 
        .endClass()
 
        .deriveClass<Derived, Base>("Derived")
 
        .addStaticProperty("staticData", &Derived::staticData, true)
 
        .endClass();
 
 
 
    Base::staticData = 1.23f;
 
    Derived::staticData = 50;
 
 
 
    runLua("result = Base.staticData");
 
    ASSERT_TRUE(result().isNumber());
 
    ASSERT_EQ(1.23f, result<float>());
 
 
 
    runLua("result = Derived.staticData");
 
    ASSERT_TRUE(result().isNumber());
 
    ASSERT_EQ(50, result<int>());
 
 
 
    runLua("Base.staticData = -3.14");
 
    ASSERT_EQ(-3.14f, Base::staticData);
 
    ASSERT_EQ(50, Derived::staticData);
 
 
 
    runLua("Derived.staticData = 7");
 
    ASSERT_EQ(-3.14f, Base::staticData);
 
    ASSERT_EQ(7, Derived::staticData);
 
}
 
 
 
struct ClassMetaMethods : ClassTests
 
{
 
};
 
 
 
TEST_F(ClassMetaMethods, __call)
 
{
 
    typedef Class<int, EmptyBase> Int;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addFunction("__call", &Int::operator())
 
        .endClass();
 
 
 
    runLua("result = Int (1) (-1)");
 
    ASSERT_TRUE(result().isUserdata());
 
    ASSERT_EQ(-1, result<Int>().data);
 
 
 
    runLua("result = Int (2) (5)");
 
    ASSERT_TRUE(result().isUserdata());
 
    ASSERT_EQ(5, result<Int>().data);
 
}
 
 
 
TEST_F(ClassMetaMethods, __tostring)
 
{
 
    typedef Class<int, EmptyBase> Int;
 
    typedef Class<std::string, EmptyBase> StringClass;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addFunction("__tostring", &Int::toString)
 
        .endClass()
 
        .beginClass<StringClass>("String")
 
        .addConstructor<void (*)(std::string)>()
 
        .addFunction("__tostring", &StringClass::toString)
 
        .endClass();
 
 
 
    runLua("result = tostring (Int (-123))");
 
    ASSERT_EQ("-123", result<std::string>());
 
 
 
#if LUA_VERSION_NUM >= 502
 
    // Lua 5.1 string.format doesn't use __tostring
 
    runLua("result = string.format ('%s%s', String ('abc'), Int (-123))");
 
    ASSERT_EQ("abc-123", result<std::string>());
 
#endif
 
}
 
 
 
TEST_F(ClassMetaMethods, __eq)
 
{
 
    typedef Class<int, EmptyBase> Int;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addFunction("__eq", &Int::operator==)
 
        .endClass();
 
 
 
    runLua("result = Int (1) == Int (1)");
 
    ASSERT_EQ(true, result<bool>());
 
 
 
    runLua("result = Int (1) ~= Int (1)");
 
    ASSERT_EQ(false, result<bool>());
 
 
 
    runLua("result = Int (1) == Int (2)");
 
    ASSERT_EQ(false, result<bool>());
 
 
 
    runLua("result = Int (1) ~= Int (2)");
 
    ASSERT_EQ(true, result<bool>());
 
}
 
 
 
TEST_F(ClassMetaMethods, __lt)
 
{
 
    typedef Class<int, EmptyBase> Int;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addFunction("__lt", &Int::operator<)
 
        .endClass();
 
 
 
    runLua("result = Int (1) < Int (1)");
 
    ASSERT_EQ(false, result<bool>());
 
 
 
    runLua("result = Int (1) < Int (2)");
 
    ASSERT_EQ(true, result<bool>());
 
 
 
    runLua("result = Int (2) < Int (1)");
 
    ASSERT_EQ(false, result<bool>());
 
}
 
 
 
TEST_F(ClassMetaMethods, __le)
 
{
 
    typedef Class<int, EmptyBase> Int;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addFunction("__le", &Int::operator<=)
 
        .endClass();
 
 
 
    runLua("result = Int (1) <= Int (1)");
 
    ASSERT_EQ(true, result<bool>());
 
 
 
    runLua("result = Int (1) <= Int (2)");
 
    ASSERT_EQ(true, result<bool>());
 
 
 
    runLua("result = Int (2) <= Int (1)");
 
    ASSERT_EQ(false, result<bool>());
 
}
 
 
 
TEST_F(ClassMetaMethods, __add)
 
{
 
    typedef Class<int, EmptyBase> Int;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addFunction("__add", &Int::operator+)
 
        .endClass();
 
 
 
    runLua("result = Int (1) + Int (2)");
 
    ASSERT_TRUE(result().isUserdata());
 
    ASSERT_EQ(3, result<Int>().data);
 
}
 
 
 
TEST_F(ClassMetaMethods, __sub)
 
{
 
    typedef Class<int, EmptyBase> Int;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addFunction("__sub", &Int::operator-)
 
        .endClass();
 
 
 
    runLua("result = Int (1) - Int (2)");
 
    ASSERT_TRUE(result().isUserdata());
 
    ASSERT_EQ(-1, result<Int>().data);
 
}
 
 
 
TEST_F(ClassMetaMethods, __mul)
 
{
 
    typedef Class<int, EmptyBase> Int;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addFunction("__mul", &Int::operator*)
 
        .endClass();
 
 
 
    runLua("result = Int (-2) * Int (-5)");
 
    ASSERT_TRUE(result().isUserdata());
 
    ASSERT_EQ(10, result<Int>().data);
 
}
 
 
 
TEST_F(ClassMetaMethods, __div)
 
{
 
    typedef Class<int, EmptyBase> Int;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addFunction("__div", &Int::operator/)
 
        .endClass();
 
 
 
    runLua("result = Int (10) / Int (2)");
 
    ASSERT_TRUE(result().isUserdata());
 
    ASSERT_EQ(5, result<Int>().data);
 
}
 
 
 
TEST_F(ClassMetaMethods, __mod)
 
{
 
    typedef Class<int, EmptyBase> Int;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addFunction("__mod", &Int::operator%)
 
        .endClass();
 
 
 
    runLua("result = Int (7) % Int (2)");
 
    ASSERT_TRUE(result().isUserdata());
 
    ASSERT_EQ(1, result<Int>().data);
 
}
 
 
 
TEST_F(ClassMetaMethods, __pow)
 
{
 
    typedef Class<int, EmptyBase> Int;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addFunction("__pow", &Int::operator-)
 
        .endClass();
 
 
 
    runLua("result = Int (5) ^ Int (2)");
 
    ASSERT_TRUE(result().isUserdata());
 
    ASSERT_EQ(3, result<Int>().data);
 
}
 
 
 
TEST_F(ClassMetaMethods, __unm)
 
{
 
    typedef Class<int, EmptyBase> Int;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addFunction("__unm", &Int::negate)
 
        .endClass();
 
 
 
    runLua("result = -Int (-3)");
 
    ASSERT_TRUE(result().isUserdata());
 
    ASSERT_EQ(3, result<Int>().data);
 
}
 
 
 
TEST_F(ClassMetaMethods, __concat)
 
{
 
    typedef Class<std::string, EmptyBase> String;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<String>("String")
 
        .addConstructor<void (*)(std::string)>()
 
        .addFunction("__concat", &String::operator+)
 
        .endClass();
 
 
 
    ASSERT_THROW(runLua("result = String ('a') + String ('b')"), std::exception);
 
 
 
    runLua("result = String ('ab') .. String ('cd')");
 
    ASSERT_TRUE(result().isUserdata());
 
    ASSERT_EQ("abcd", result<String>().data);
 
}
 
 
 
TEST_F(ClassMetaMethods, __len)
 
{
 
    typedef Class<int, EmptyBase> Int;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Int>("Int")
 
        .addConstructor<void (*)(int)>()
 
        .addFunction("__len", &Int::len)
 
        .endClass();
 
 
 
    runLua("result = #Int (1)");
 
    ASSERT_TRUE(result().isNumber());
 
    ASSERT_EQ(1, result<int>());
 
 
 
    runLua("result = #Int (5)");
 
    ASSERT_TRUE(result().isNumber());
 
    ASSERT_EQ(5, result<int>());
 
}
 
 
 
namespace {
 
 
 
struct Table
 
{
 
    int index(const std::string& key) { return map.at(key); }
 
 
 
    void newIndex(const std::string& key, int value) { map.emplace(key, value); }
 
 
 
    std::map<std::string, int> map;
 
};
 
 
 
} // namespace
 
 
 
TEST_F(ClassMetaMethods, __index)
 
{
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Table>("Table")
 
        .addFunction("__index", &Table::index)
 
        .endClass();
 
 
 
    Table t{{{"a", 1}, {"b", 2}}};
 
 
 
    luabridge::setGlobal(L, &t, "t");
 
 
 
    runLua("result = t.a");
 
    ASSERT_TRUE(result().isNumber());
 
    ASSERT_EQ(1, result<int>());
 
 
 
    runLua("result = t.b");
 
    ASSERT_TRUE(result().isNumber());
 
    ASSERT_EQ(2, result<int>());
 
 
 
    ASSERT_THROW(runLua("result = t.c"), std::exception); // at ("c") throws
 
}
 
 
 
TEST_F(ClassMetaMethods, __newindex)
 
{
 
    typedef Class<int, EmptyBase> Int;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Table>("Table")
 
        .addFunction("__newindex", &Table::newIndex)
 
        .endClass();
 
 
 
    Table t;
 
 
 
    luabridge::setGlobal(L, &t, "t");
 
 
 
    runLua("t.a = 1\n"
 
           "t ['b'] = 2");
 
 
 
    ASSERT_EQ((std::map<std::string, int>{{"a", 1}, {"b", 2}}), t.map);
 
}
 
 
 
TEST_F(ClassMetaMethods, __gcForbidden)
 
{
 
    typedef Class<int, EmptyBase> Int;
 
 
 
    ASSERT_THROW(luabridge::getGlobalNamespace(L)
 
                     .beginClass<Int>("Int")
 
                     .addFunction("__gc", &Int::method)
 
                     .endClass(),
 
                 std::exception);
 
}
 
 
 
TEST_F(ClassTests, EnclosedClassProperties)
 
{
 
    typedef Class<int, EmptyBase> Inner;
 
    typedef Class<Inner, EmptyBase> Outer;
 
 
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<Inner>("Inner")
 
        .addProperty("data", &Inner::data)
 
        .endClass()
 
        .beginClass<Outer>("Outer")
 
        .addProperty("data", &Outer::data)
 
        .endClass();
 
 
 
    Outer outer(Inner(0));
 
    luabridge::setGlobal(L, &outer, "outer");
 
 
 
    outer.data.data = 1;
 
    runLua("outer.data.data = 10");
 
    ASSERT_EQ(10, outer.data.data);
 
 
 
    runLua("result = outer.data.data");
 
    ASSERT_EQ(10, result<int>());
 
}
 
 
 
// namespace {
 
 
 
struct InnerClass
 
{
 
    ~InnerClass() { ++destructorCallCount; }
 
 
 
    static unsigned destructorCallCount;
 
};
 
 
 
unsigned InnerClass::destructorCallCount;
 
 
 
struct OuterClass
 
{
 
    OuterClass() { throw std::runtime_error("Exception"); }
 
 
 
    ~OuterClass() { ++destructorCallCount; }
 
 
 
    static unsigned destructorCallCount;
 
    InnerClass inner;
 
};
 
 
 
unsigned OuterClass::destructorCallCount;
 
 
 
//} // namespace
 
 
 
TEST_F(ClassTests, DestructorIsNotCalledIfConstructorThrows)
 
{
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<OuterClass>("OuterClass")
 
        .addConstructor<void (*)()>()
 
        .endClass();
 
 
 
    InnerClass::destructorCallCount = 0;
 
    OuterClass::destructorCallCount = 0;
 
    ASSERT_THROW(runLua("result = OuterClass ()"), std::exception);
 
    ASSERT_EQ(1, InnerClass::destructorCallCount);
 
 
 
    lua_close(L);
 
    L = nullptr;
 
    ASSERT_EQ(1, InnerClass::destructorCallCount);
 
    ASSERT_EQ(0, OuterClass::destructorCallCount);
 
}
 
 
 
TEST_F(ClassTests, DestructorIsCalledOnce)
 
{
 
    luabridge::getGlobalNamespace(L)
 
        .beginClass<InnerClass>("InnerClass")
 
        .addConstructor<void (*)()>()
 
        .endClass();
 
 
 
    InnerClass::destructorCallCount = 0;
 
    runLua("result = InnerClass ()");
 
 
 
    lua_close(L);
 
    L = nullptr;
 
    ASSERT_EQ(1, InnerClass::destructorCallCount);
 
}