aboutsummaryrefslogtreecommitdiffstats
path: root/lib/LuaBridge/Tests/Source/ClassTests.cpp
diff options
context:
space:
mode:
authorAndy Belle-Isle <drumsetmonkey@gmail.com>2019-08-28 00:57:57 -0400
committerAndy Belle-Isle <drumsetmonkey@gmail.com>2019-08-28 00:57:57 -0400
commit85fb2bff38b2ef6cb17e86c5f602ee09a365b117 (patch)
treea8066d33233ec9b6a2b9bb281a1de040ab96be7b /lib/LuaBridge/Tests/Source/ClassTests.cpp
parent787393dd86d6c37b5680847dd4eef14406a86687 (diff)
Added LuaBridge support
Diffstat (limited to 'lib/LuaBridge/Tests/Source/ClassTests.cpp')
-rw-r--r--lib/LuaBridge/Tests/Source/ClassTests.cpp1665
1 files changed, 1665 insertions, 0 deletions
diff --git a/lib/LuaBridge/Tests/Source/ClassTests.cpp b/lib/LuaBridge/Tests/Source/ClassTests.cpp
new file mode 100644
index 0000000..ab789f4
--- /dev/null
+++ b/lib/LuaBridge/Tests/Source/ClassTests.cpp
@@ -0,0 +1,1665 @@
+// https://github.com/vinniefalco/LuaBridge
+//
+// Copyright 2019, Dmitry Tarakanov
+// SPDX-License-Identifier: MIT
+
+#include "TestBase.h"
+
+#include <exception>
+#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, 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>;
+
+ 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 <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> ());
+}
+
+#ifdef LUABRIDGE_CXX11
+
+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());
+}
+
+#endif // LUABRIDGE_CXX11
+
+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);
+}
+
+#ifdef LUABRIDGE_CXX11
+
+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 ());
+}
+
+#endif // LUABRIDGE_CXX11
+
+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);
+}
+
+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> ());
+}