// https://github.com/vinniefalco/LuaBridge // // Copyright 2019, Dmitry Tarakanov // SPDX-License-Identifier: MIT #include "TestBase.h" #include #include #include struct ClassTests : TestBase { template T variable (const std::string& name) { runLua ("result = " + name); return result (); } }; namespace { struct EmptyBase { }; template struct Class : Base { Class () : data () { } Class (T data) : data (data) { } static Class staticFunction (Class value) { return value; } std::string toString () const { std::ostringstream stream; stream << data; return stream.str (); } bool operator== (const Class & rhs) const { return data == rhs.data; } bool operator< (const Class & rhs) const { return data < rhs.data; } bool operator<= (const Class & rhs) const { return data <= rhs.data; } Class operator+ (const Class & rhs) const { return Class (data + rhs.data); } Class operator- (const Class & rhs) const { return Class (data - rhs.data); } Class operator* (const Class & rhs) const { return Class (data * rhs.data); } Class operator/ (const Class & rhs) const { return Class (data / rhs.data); } Class operator% (const Class & rhs) const { return Class (data % rhs.data); } Class operator() (T param) { return Class (param); } int len () const { return data; } Class negate () const { return Class (-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 T Class ::staticData = {}; } // namespace TEST_F (ClassTests, PassingUnregisteredClassToLuaThrows) { using Unregistered = Class ; 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 ; using WrongBase = Class ; using Wrong = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Right") .endClass () .beginClass ("WrongBase") .endClass () .beginClass ("Wrong") .addConstructor () .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 ; using Derived = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Base") .endClass () .deriveClass ("Derived") .addConstructor () .endClass () .addFunction ("processBase", &Base::staticFunction); runLua ("result = processBase (Derived (3.14))"); ASSERT_EQ (0, result ().data); } namespace { template T processNonConst (Class * object) { return object->data; } } // namespace TEST_F (ClassTests, PassConstClassInsteadOfNonConstThrows) { using Base = Class ; using Derived = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Base") .endClass () .deriveClass ("Derived") .endClass () .addFunction ("processNonConst", &processNonConst ); 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 ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () // Show that it does't matter .endClass () .addFunction ("processNonConst", &processNonConst ); // 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 ; using Float = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Float") .addConstructor () .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 & returnRef () { static Class value (1); return value; } const Class & returnConstRef () { return returnRef (); } Class * returnPtr () { return &returnRef (); } const Class * returnConstPtr () { return &returnConstRef (); } Class returnValue () { return Class (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 ; luabridge::getGlobalNamespace (L) .addFunction ("returnRef", &returnRef) .addFunction ("returnConstRef", &returnConstRef) .addFunction ("returnPtr", &returnPtr) .addFunction ("returnConstPtr", &returnConstPtr) .addFunction ("returnValue", &returnValue); 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 ; using Derived = Class ; ASSERT_THROW ( (luabridge::getGlobalNamespace (L).deriveClass ("Derived")), std::exception); ASSERT_EQ (1, lua_gettop (L)); } struct ClassFunctions : ClassTests { }; TEST_F (ClassFunctions, MemberFunctions) { using Int = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addFunction ("method", &Int::method) .endClass (); addHelperFunctions (L); runLua ("result = returnRef ():method (1)"); ASSERT_EQ (1, result ()); runLua ("result = returnConstRef ().method"); // Don't call, just get ASSERT_TRUE (result ().isNil ()); runLua ("result = returnPtr ():method (2)"); ASSERT_EQ (2, result ()); runLua("result = returnConstPtr ().method"); // Don't call, just get ASSERT_TRUE (result ().isNil ()); runLua ("result = returnValue ():method (3)"); ASSERT_EQ (3, result ()); } TEST_F (ClassFunctions, MemberFunctions_PassState) { using Int = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addFunction ("method", &Int::methodState) .endClass (); addHelperFunctions (L); runLua ("result = returnRef ():method (1)"); ASSERT_EQ (1, result ()); runLua ("result = returnConstRef ().method"); // Don't call, just get ASSERT_TRUE (result ().isNil ()); runLua ("result = returnPtr ():method (2)"); ASSERT_EQ (2, result ()); runLua("result = returnConstPtr ().method"); // Don't call, just get ASSERT_TRUE (result ().isNil ()); runLua ("result = returnValue ():method (3)"); ASSERT_EQ (3, result ()); } TEST_F (ClassFunctions, ConstMemberFunctions) { using Int = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addFunction ("constMethod", &Int::constMethod) .endClass (); addHelperFunctions (L); runLua ("result = returnRef ():constMethod (1)"); ASSERT_EQ (1, result ()); runLua ("result = returnConstRef ():constMethod (2)"); ASSERT_EQ (2, result ()); runLua ("result = returnPtr ():constMethod (3)"); ASSERT_EQ (3, result ()); runLua ("result = returnConstPtr ():constMethod (4)"); ASSERT_EQ (4, result ()); runLua ("result = returnValue ():constMethod (5)"); ASSERT_EQ (5, result ()); } #ifdef LUABRIDGE_CXX11 namespace { template T proxyFunction (Class * object, T value) { object->data = value; return value; } template T proxyFunctionState (Class * object, T value, lua_State*) { object->data = value; return value; } template T proxyConstFunction (const Class * object, T value) { return value; } } // namespace TEST_F (ClassFunctions, ProxyFunctions) { using Int = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addFunction ("method", &proxyFunction ) .endClass (); addHelperFunctions (L); runLua ("result = returnRef ():method (1)"); ASSERT_EQ (1, result ()); runLua ("result = returnConstRef ().method"); // Don't call, just get ASSERT_TRUE (result ().isNil ()); runLua ("result = returnPtr ():method (2)"); ASSERT_EQ (2, result ()); runLua("result = returnConstPtr ().method"); // Don't call, just get ASSERT_TRUE (result ().isNil ()); runLua ("result = returnValue ():method (3)"); ASSERT_EQ (3, result ()); } TEST_F (ClassFunctions, ProxyFunctions_PassState) { using Int = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addFunction ("method", &proxyFunctionState ) .endClass (); addHelperFunctions (L); runLua ("result = returnRef ():method (1)"); ASSERT_EQ (1, result ()); runLua ("result = returnConstRef ().method"); // Don't call, just get ASSERT_TRUE (result ().isNil ()); runLua ("result = returnPtr ():method (2)"); ASSERT_EQ (2, result ()); runLua("result = returnConstPtr ().method"); // Don't call, just get ASSERT_TRUE (result ().isNil ()); runLua ("result = returnValue ():method (3)"); ASSERT_EQ (3, result ()); } TEST_F (ClassFunctions, ConstProxyFunctions) { using Int = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addFunction ("constMethod", &proxyConstFunction ) .endClass (); addHelperFunctions (L); runLua ("result = returnRef ():constMethod (1)"); ASSERT_EQ (1, result ()); runLua ("result = returnConstRef ():constMethod (2)"); ASSERT_EQ (2, result ()); runLua ("result = returnPtr ():constMethod (3)"); ASSERT_EQ (3, result ()); runLua ("result = returnConstPtr ():constMethod (4)"); ASSERT_EQ (4, result ()); runLua ("result = returnValue ():constMethod (5)"); ASSERT_EQ (5, result ()); } TEST_F (ClassFunctions, StdFunctions) { using Int = Class ; auto sharedData = std::make_shared (); std::weak_ptr data = sharedData; // Check __gc meta-method std::function function = [sharedData] (Int* object, int value) { object->data = value; return value; }; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addFunction ("method", std::move(function)) .endClass (); sharedData = nullptr; ASSERT_FALSE (data.expired ()); addHelperFunctions (L); runLua ("result = returnRef ():method (1)"); ASSERT_EQ (1, result ()); runLua ("result = returnConstRef ().method"); // Don't call, just get ASSERT_TRUE (result ().isNil ()); runLua ("result = returnPtr ():method (2)"); ASSERT_EQ (2, result ()); runLua("result = returnConstPtr ().method"); // Don't call, just get ASSERT_TRUE (result ().isNil ()); runLua ("result = returnValue ():method (3)"); ASSERT_EQ (3, result ()); runLua ("result = nil"); lua_close (L); // Force garbage collection L = nullptr; ASSERT_TRUE (data.expired()); } TEST_F (ClassFunctions, StdFunctions_PassState) { using Int = Class ; auto sharedData = std::make_shared (); std::weak_ptr data = sharedData; // Check __gc meta-method std::function function = [sharedData] (Int* object, int value, lua_State*) { object->data = value; return value; }; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addFunction ("method", std::move(function)) .endClass (); sharedData = nullptr; ASSERT_FALSE (data.expired ()); addHelperFunctions (L); runLua ("result = returnRef ():method (1)"); ASSERT_EQ (1, result ()); runLua ("result = returnConstRef ().method"); // Don't call, just get ASSERT_TRUE (result ().isNil ()); runLua ("result = returnPtr ():method (2)"); ASSERT_EQ (2, result ()); runLua("result = returnConstPtr ().method"); // Don't call, just get ASSERT_TRUE (result ().isNil ()); runLua ("result = returnValue ():method (3)"); ASSERT_EQ (3, result ()); runLua ("result = nil"); lua_close (L); // Force garbage collection L = nullptr; ASSERT_TRUE (data.expired()); } TEST_F (ClassFunctions, ConstStdFunctions) { using Int = Class ; auto sharedData = std::make_shared (); std::weak_ptr data = sharedData; // Check __gc meta-method std::function function = [sharedData] (const Int* object, int value) { object->data = value; return value; }; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addFunction ("constMethod", std::move(function)) .endClass (); sharedData = nullptr; ASSERT_FALSE (data.expired ()); addHelperFunctions (L); runLua ("result = returnRef ():constMethod (1)"); ASSERT_EQ (1, result ()); runLua ("result = returnConstRef ():constMethod (2)"); ASSERT_EQ (2, result ()); runLua ("result = returnPtr ():constMethod (3)"); ASSERT_EQ (3, result ()); runLua ("result = returnConstPtr ():constMethod (4)"); ASSERT_EQ (4, result ()); runLua ("result = returnValue ():constMethod (5)"); ASSERT_EQ (5, result ()); runLua ("result = nil"); lua_close (L); // Force garbage collection L = nullptr; ASSERT_TRUE (data.expired()); } #endif // LUABRIDGE_CXX11 struct ClassProperties : ClassTests { }; TEST_F (ClassProperties, FieldPointers) { using Int = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addProperty ("data", &Int::data, true) .endClass (); runLua ("result = Int (501)"); ASSERT_TRUE (result () ["data"].isNumber ()); ASSERT_EQ (501, result () ["data"].cast ()); runLua ("result.data = 2"); ASSERT_TRUE (result () ["data"].isNumber ()); ASSERT_EQ (2, result () ["data"].cast ()); runLua ("result = Int (42).data"); ASSERT_TRUE (result ().isNumber ()); ASSERT_EQ (42, result ()); } TEST_F (ClassProperties, FieldPointers_ReadOnly) { using Int = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addProperty ("data", &Int::data, false) .endClass (); runLua ("result = Int (501)"); ASSERT_TRUE (result () ["data"].isNumber ()); ASSERT_EQ (501, result () ["data"].cast ()); ASSERT_THROW (runLua ("result.data = 2"), std::exception); runLua ("result = Int (42).data"); ASSERT_TRUE (result ().isNumber ()); ASSERT_EQ (42, result ()); } TEST_F (ClassProperties, MemberFunctions) { using Int = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addProperty ("data", &Int::getData, &Int::setData) .endClass (); runLua ("result = Int (501)"); ASSERT_TRUE (result () ["data"].isNumber ()); ASSERT_EQ (501, result () ["data"].cast ()); runLua ("result.data = -2"); ASSERT_TRUE (result () ["data"].isNumber ()); ASSERT_EQ (-2, result () ["data"].cast ()); } TEST_F (ClassProperties, MemberFunctions_PassState) { using Int = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addProperty ("data", &Int::getDataState, &Int::setDataState) .endClass (); runLua ("result = Int (501)"); ASSERT_TRUE (result () ["data"].isNumber ()); ASSERT_EQ (501, result () ["data"].cast ()); runLua ("result.data = -2"); ASSERT_TRUE (result () ["data"].isNumber ()); ASSERT_EQ (-2, result () ["data"].cast ()); } TEST_F (ClassProperties, MemberFunctions_ReadOnly) { using Int = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addProperty ("data", &Int::getData) .endClass (); runLua ("result = Int (501)"); ASSERT_TRUE (result () ["data"].isNumber ()); ASSERT_EQ (501, result () ["data"].cast ()); ASSERT_THROW (runLua ("result.data = -2"), std::exception); ASSERT_EQ (501, result () ["data"].cast ()); } TEST_F (ClassProperties, MemberFunctions_Derived) { using Base = Class ; using Derived = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Base") .addProperty ("data", &Base::getData, &Base::setData) .endClass () .deriveClass ("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 ()); 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 ; using Derived = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Base") .addProperty ("data", &Base::getData, &Base::setData) .endClass () .deriveClass ("Derived") .addProperty ("data", &Derived::getData, &Derived::setData) .endClass (); Derived derived (50); derived.Base::data = 1.23f; luabridge::setGlobal (L, static_cast (&derived), "base"); luabridge::setGlobal (L, &derived, "derived"); runLua ("result = base.data"); ASSERT_TRUE (result ().isNumber ()); ASSERT_EQ (1.23f, result ()); runLua ("result = derived.data"); ASSERT_TRUE (result ().isNumber ()); ASSERT_EQ (50, result ()); 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 T getData (const Class * object) { return object->data; } template void setData (Class * object, T data) { object->data = data; } } // namespace TEST_F (ClassProperties, ProxyFunctions) { using Int = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addProperty ("data", &getData , &setData ) .endClass (); runLua ("result = Int (501)"); ASSERT_TRUE (result () ["data"].isNumber ()); ASSERT_EQ (501, result () ["data"].cast ()); runLua ("result.data = -2"); ASSERT_TRUE (result () ["data"].isNumber ()); ASSERT_EQ (-2, result () ["data"].cast ()); } TEST_F (ClassProperties, ProxyFunctions_ReadOnly) { using Int = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addProperty ("data", &getData ) .endClass (); runLua ("result = Int (501)"); ASSERT_TRUE (result () ["data"].isNumber ()); ASSERT_EQ (501, result () ["data"].cast ()); ASSERT_THROW (runLua ("result.data = -2"), std::exception); ASSERT_EQ (501, result () ["data"].cast ()); } TEST_F (ClassProperties, ProxyFunctions_Derived) { using Base = Class ; using Derived = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Base") .addProperty ("data", &getData , &setData ) .endClass () .deriveClass ("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 ()); 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 ; using Derived = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Base") .addProperty("data", &getData , &setData ) .endClass () .deriveClass ("Derived") .addProperty ("data", &getData , &setData ) .endClass (); Derived derived (50); derived.Base::data = 1.23f; luabridge::setGlobal (L, static_cast (&derived), "base"); luabridge::setGlobal (L, &derived, "derived"); runLua ("result = base.data"); ASSERT_TRUE (result ().isNumber ()); ASSERT_EQ (1.23f, result ()); runLua ("result = derived.data"); ASSERT_TRUE (result ().isNumber ()); ASSERT_EQ (50, result ()); 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 int getDataC (lua_State* L) { auto objectRef = luabridge::LuaRef::fromStack (L, 1); auto* object = objectRef.cast *> (); luabridge::Stack ::push (L, object->data); return 1; } template int setDataC (lua_State* L) { auto objectRef = luabridge::LuaRef::fromStack (L, 1); auto* object = objectRef.cast *>(); auto valueRef = luabridge::LuaRef::fromStack (L, 2); T value = valueRef.cast (); object->data = value; return 0; } } // namespace TEST_F (ClassProperties, ProxyCFunctions) { using Int = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addProperty ("data", &getDataC , &setDataC ) .endClass (); runLua ("result = Int (501)"); ASSERT_TRUE (result () ["data"].isNumber ()); ASSERT_EQ (501, result () ["data"].cast ()); runLua ("result.data = -2"); ASSERT_TRUE (result () ["data"].isNumber ()); ASSERT_EQ (-2, result () ["data"].cast ()); } TEST_F (ClassProperties, ProxyCFunctions_ReadOnly) { using Int = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addProperty ("data", &getDataC ) .endClass (); runLua ("result = Int (501)"); ASSERT_TRUE (result () ["data"].isNumber ()); ASSERT_EQ (501, result () ["data"].cast ()); ASSERT_THROW (runLua ("result.data = -2"), std::exception); ASSERT_EQ (501, result () ["data"].cast ()); } TEST_F (ClassProperties, ProxyCFunctions_Derived) { using Base = Class ; using Derived = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Base") .addProperty ("data", &getDataC , &setDataC ) .endClass () .deriveClass ("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 ()); 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 ; using Derived = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Base") .addProperty ("data", &getDataC , &setDataC ) .endClass () .deriveClass ("Derived") .addProperty ("data", &getData , &setData ) .endClass (); Derived derived (50); derived.Base::data = 1.23f; luabridge::setGlobal (L, static_cast (&derived), "base"); luabridge::setGlobal (L, &derived, "derived"); runLua ("result = base.data"); ASSERT_TRUE (result ().isNumber ()); ASSERT_EQ (1.23f, result ()); runLua ("result = derived.data"); ASSERT_TRUE (result ().isNumber ()); ASSERT_EQ (50, result ()); 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); } #ifdef LUABRIDGE_CXX11 TEST_F (ClassProperties, StdFunctions) { using Int = Class ; auto sharedGetterData = std::make_shared (); std::weak_ptr getterData = sharedGetterData; // Check __gc meta-method auto sharedSetterData = std::make_shared (); std::weak_ptr setterData = sharedGetterData; // Check __gc meta-method std::function getter = [sharedGetterData] (const Int* object) { return object->data; }; std::function setter = [sharedSetterData] (Int* object, int value) { object->data = value; }; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .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 ()); runLua ("result.data = -2"); ASSERT_TRUE (result () ["data"].isNumber ()); ASSERT_EQ (-2, result () ["data"].cast ()); 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 ; auto sharedGetterData = std::make_shared (); std::weak_ptr getterData = sharedGetterData; // Check __gc meta-method std::function getter = [sharedGetterData] (const Int* object) { return object->data; }; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .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 ()); ASSERT_THROW (runLua ("result.data = -2"), std::exception); ASSERT_EQ (501, result () ["data"].cast ()); runLua ("result = nil"); lua_close (L); // Force garbage collection L = nullptr; ASSERT_TRUE (getterData.expired ()); } #endif // LUABRIDGE_CXX11 struct ClassStaticFunctions : ClassTests { }; TEST_F (ClassStaticFunctions, Functions) { using Int = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addStaticFunction ("static", &Int::staticFunction) .endClass (); runLua ("result = Int.static (Int (35))"); ASSERT_EQ (35, result ().data); } TEST_F (ClassStaticFunctions, Functions_Derived) { using Base = Class ; using Derived = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Base") .addConstructor () .addStaticFunction ("static", &Base::staticFunction) .endClass () .deriveClass ("Derived") .endClass (); runLua ("result = Derived.static (Base ('abc'))"); ASSERT_EQ ("abc", result ().data); } TEST_F (ClassStaticFunctions, Functions_Overridden) { using Base = Class ; using Derived = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Base") .addConstructor () .addStaticFunction ("staticFunction", &Base::staticFunction) .endClass () .deriveClass ("Derived") .addConstructor () .addStaticFunction ("staticFunction", &Derived::staticFunction) .endClass (); runLua ("result = Base.staticFunction (Base ('abc'))"); ASSERT_EQ ("abc", result ().data); runLua ("result = Derived.staticFunction (Derived (123))"); ASSERT_EQ (123, result ().data); } struct ClassStaticProperties : ClassTests { }; TEST_F (ClassStaticProperties, FieldPointers) { using Int = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addStaticProperty ("staticData", &Int::staticData, true) .endClass (); Int::staticData = 10; runLua ("result = Int.staticData"); ASSERT_TRUE (result ().isNumber ()); ASSERT_EQ (10, result ()); runLua ("Int.staticData = 20"); ASSERT_EQ (20, Int::staticData); } TEST_F (ClassStaticProperties, FieldPointers_ReadOnly) { using Int = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addStaticProperty ("staticData", &Int::staticData, false) .endClass (); Int::staticData = 10; runLua ("result = Int.staticData"); ASSERT_TRUE (result ().isNumber ()); ASSERT_EQ (10, result ()); ASSERT_THROW (runLua ("Int.staticData = 20"), std::exception); ASSERT_EQ (10, Int::staticData); } TEST_F (ClassStaticProperties, FieldPointers_Derived) { using Base = Class ; using Derived = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Base") .addStaticProperty ("staticData", &Base::staticData, true) .endClass () .deriveClass ("Derived") .endClass (); Base::staticData = 1.23f; Derived::staticData = 50; runLua ("result = Derived.staticData"); ASSERT_TRUE (result ().isNumber ()); ASSERT_EQ (1.23f, result ()); runLua ("Derived.staticData = -3.14"); ASSERT_EQ (-3.14f, Base::staticData); ASSERT_EQ (50, Derived::staticData); } TEST_F (ClassStaticProperties, FieldPointers_Overridden) { using Base = Class ; using Derived = Class ; luabridge::getGlobalNamespace (L) .beginClass ("Base") .addStaticProperty ("staticData", &Base::staticData, true) .endClass () .deriveClass ("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 ()); runLua ("result = Derived.staticData"); ASSERT_TRUE (result ().isNumber ()); ASSERT_EQ (50, result ()); 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; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addFunction ("__call", &Int::operator()) .endClass (); runLua ("result = Int (1) (-1)"); ASSERT_TRUE (result ().isUserdata ()); ASSERT_EQ (-1, result ().data); runLua ("result = Int (2) (5)"); ASSERT_TRUE (result ().isUserdata ()); ASSERT_EQ (5, result ().data); } TEST_F (ClassMetaMethods, __tostring) { typedef Class Int; typedef Class StringClass; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addFunction ("__tostring", &Int::toString) .endClass () .beginClass ("String") .addConstructor () .addFunction ("__tostring", &StringClass::toString) .endClass (); runLua ("result = tostring (Int (-123))"); ASSERT_EQ ("-123", result ()); #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 ()); #endif } TEST_F (ClassMetaMethods, __eq) { typedef Class Int; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addFunction ("__eq", &Int::operator==) .endClass (); runLua ("result = Int (1) == Int (1)"); ASSERT_EQ (true, result ()); runLua ("result = Int (1) ~= Int (1)"); ASSERT_EQ (false, result ()); runLua ("result = Int (1) == Int (2)"); ASSERT_EQ (false, result ()); runLua ("result = Int (1) ~= Int (2)"); ASSERT_EQ (true, result ()); } TEST_F (ClassMetaMethods, __lt) { typedef Class Int; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addFunction ("__lt", &Int::operator<) .endClass (); runLua ("result = Int (1) < Int (1)"); ASSERT_EQ (false, result ()); runLua ("result = Int (1) < Int (2)"); ASSERT_EQ (true, result ()); runLua ("result = Int (2) < Int (1)"); ASSERT_EQ (false, result ()); } TEST_F (ClassMetaMethods, __le) { typedef Class Int; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addFunction ("__le", &Int::operator<=) .endClass (); runLua ("result = Int (1) <= Int (1)"); ASSERT_EQ (true, result ()); runLua ("result = Int (1) <= Int (2)"); ASSERT_EQ (true, result ()); runLua ("result = Int (2) <= Int (1)"); ASSERT_EQ (false, result ()); } TEST_F (ClassMetaMethods, __add) { typedef Class Int; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addFunction ("__add", &Int::operator+) .endClass (); runLua ("result = Int (1) + Int (2)"); ASSERT_TRUE (result ().isUserdata ()); ASSERT_EQ (3, result ().data); } TEST_F (ClassMetaMethods, __sub) { typedef Class Int; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addFunction ("__sub", &Int::operator-) .endClass (); runLua ("result = Int (1) - Int (2)"); ASSERT_TRUE (result ().isUserdata ()); ASSERT_EQ (-1, result ().data); } TEST_F (ClassMetaMethods, __mul) { typedef Class Int; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addFunction ("__mul", &Int::operator*) .endClass (); runLua ("result = Int (-2) * Int (-5)"); ASSERT_TRUE (result ().isUserdata ()); ASSERT_EQ (10, result ().data); } TEST_F (ClassMetaMethods, __div) { typedef Class Int; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addFunction ("__div", &Int::operator/) .endClass (); runLua ("result = Int (10) / Int (2)"); ASSERT_TRUE (result ().isUserdata ()); ASSERT_EQ (5, result ().data); } TEST_F (ClassMetaMethods, __mod) { typedef Class Int; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addFunction ("__mod", &Int::operator%) .endClass (); runLua ("result = Int (7) % Int (2)"); ASSERT_TRUE (result ().isUserdata ()); ASSERT_EQ (1, result ().data); } TEST_F (ClassMetaMethods, __pow) { typedef Class Int; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addFunction ("__pow", &Int::operator-) .endClass (); runLua ("result = Int (5) ^ Int (2)"); ASSERT_TRUE (result ().isUserdata ()); ASSERT_EQ (3, result ().data); } TEST_F (ClassMetaMethods, __unm) { typedef Class Int; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addFunction ("__unm", &Int::negate) .endClass (); runLua ("result = -Int (-3)"); ASSERT_TRUE (result ().isUserdata ()); ASSERT_EQ (3, result ().data); } TEST_F (ClassMetaMethods, __concat) { typedef Class String; luabridge::getGlobalNamespace (L) .beginClass ("String") .addConstructor () .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 ().data); } TEST_F (ClassMetaMethods, __len) { typedef Class Int; luabridge::getGlobalNamespace (L) .beginClass ("Int") .addConstructor () .addFunction ("__len", &Int::len) .endClass (); runLua ("result = #Int (1)"); ASSERT_TRUE (result ().isNumber ()); ASSERT_EQ (1, result ()); runLua ("result = #Int (5)"); ASSERT_TRUE (result ().isNumber ()); ASSERT_EQ (5, result ()); } 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 map; }; } // namespace TEST_F (ClassMetaMethods, __index) { luabridge::getGlobalNamespace (L) .beginClass ("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 ()); runLua ("result = t.b"); ASSERT_TRUE (result ().isNumber ()); ASSERT_EQ (2, result ()); ASSERT_THROW (runLua ("result = t.c"), std::exception); // at ("c") throws } TEST_F (ClassMetaMethods, __newindex) { typedef Class Int; luabridge::getGlobalNamespace (L) .beginClass
("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 {{"a", 1}, {"b", 2}}), t.map); } TEST_F (ClassMetaMethods, __gcForbidden) { typedef Class Int; ASSERT_THROW ( luabridge::getGlobalNamespace (L) .beginClass ("Int") .addFunction ("__gc", &Int::method) .endClass (), std::exception); } TEST_F (ClassTests, EnclosedClassProperties) { typedef Class Inner; typedef Class Outer; luabridge::getGlobalNamespace (L) .beginClass ("Inner") .addProperty ("data", &Inner::data) .endClass () .beginClass ("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 ()); }