diff options
author | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
---|---|---|
committer | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
commit | 6352e8e3196f78388b6c771073f9e03eaa612673 (patch) | |
tree | e23772f79a7fbc41bc9108951e9e136857484bf4 /source/test/lua_test.cpp | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/test/lua_test.cpp')
-rw-r--r-- | source/test/lua_test.cpp | 838 |
1 files changed, 838 insertions, 0 deletions
diff --git a/source/test/lua_test.cpp b/source/test/lua_test.cpp new file mode 100644 index 0000000..68b95f1 --- /dev/null +++ b/source/test/lua_test.cpp @@ -0,0 +1,838 @@ +#include "StarLua.hpp" +#include "StarLuaConverters.hpp" +#include "StarLexicalCast.hpp" + +#include "gtest/gtest.h" + +using namespace Star; + +TEST(LuaTest, BasicGetSet) { + auto luaEngine = LuaEngine::create(); + LuaContext luaContext = luaEngine->createContext(); + luaContext.load(R"SCRIPT( + data1 = 1.0 + data2 = 3.0 > 2.0 + data3 = "hello" + )SCRIPT"); + + luaContext.set("data4", 4.0); + + EXPECT_EQ(luaContext.get<double>("data1"), 1.0); + EXPECT_EQ(luaContext.get<bool>("data2"), true); + EXPECT_EQ(luaContext.get<String>("data3"), "hello"); + EXPECT_EQ(luaContext.get<double>("data4"), 4.0); +} + +TEST(LuaTest, TableReferences) { + auto luaEngine = LuaEngine::create(); + LuaContext luaContext = luaEngine->createContext(); + luaContext.load(R"SCRIPT( + table = {foo=1, bar=2} + tableRef = table + )SCRIPT"); + + auto table = luaContext.get<LuaTable>("table"); + auto tableRef1 = luaContext.get<LuaTable>("tableRef"); + auto tableRef2 = table; + + EXPECT_EQ(table.get<double>("foo"), 1.0); + EXPECT_EQ(table.get<double>("bar"), 2.0); + + table.set("baz", 3.0); + EXPECT_EQ(tableRef1.get<double>("baz"), 3.0); + + tableRef1.set("baf", 4.0); + EXPECT_EQ(table.get<double>("baf"), 4.0); + EXPECT_EQ(tableRef2.get<double>("baf"), 4.0); +} + +TEST(LuaTest, FunctionCallTest) { + weak_ptr<int> destructionObserver; + + { + auto luaEngine = LuaEngine::create(); + LuaContext luaContext = luaEngine->createContext(); + luaContext.load(R"SCRIPT( + function testFunc(arg1, arg2) + return callback(3) + arg1 + arg2 + end + + function testEmpty() + return emptyCallback() + end + )SCRIPT"); + + auto toDestruct = make_shared<int>(); + destructionObserver = toDestruct; + luaContext.set("callback", luaEngine->createFunction([toDestruct](double n) { return n * 2; })); + + luaContext.set("emptyCallback", luaEngine->createFunction([]() { return "heyooo"; })); + + EXPECT_EQ(luaContext.invokePath<double>("testFunc", 5.0, 10.0), 21.0); + EXPECT_EQ(luaContext.invokePath<String>("emptyCallback"), "heyooo"); + } + + EXPECT_TRUE(destructionObserver.expired()); +} + +TEST(LuaTest, CoroutineTest) { + auto luaEngine = LuaEngine::create(); + LuaContext luaContext = luaEngine->createContext(); + + luaContext.load(R"SCRIPT( + function accumulate(sum) + return sum + callback(coroutine.yield(sum)) + end + + function run() + local sum = 0 + for i=1,4 do + sum = accumulate(sum) + end + return sum + end + + co = coroutine.create(run) + )SCRIPT"); + + luaContext.set("callback", luaEngine->createFunction([](double num) { return num * 2; })); + + LuaThread thread = luaEngine->createThread(); + EXPECT_EQ(thread.status(), LuaThread::Status::Dead); + LuaFunction func = luaContext.get<LuaFunction>("run"); + thread.pushFunction(func); + EXPECT_EQ(thread.status(), LuaThread::Status::Active); + EXPECT_EQ(thread.resume<double>(), 0.0); + EXPECT_EQ(thread.resume<double>(1.0), 2.0); + EXPECT_EQ(thread.resume<double>(3.0), 8.0); + EXPECT_EQ(thread.resume<double>(5.0), 18.0); + EXPECT_EQ(thread.resume<double>(7.0), 32.0); + // manually created threads are empty after execution is finished + EXPECT_EQ(thread.status(), LuaThread::Status::Dead); + + thread.pushFunction(func); + EXPECT_EQ(thread.resume<double>(), 0.0); + EXPECT_EQ(thread.resume<double>(1.0), 2.0); + EXPECT_THROW(thread.pushFunction(func), LuaException); // pushing function to suspended or errored thread + + auto coroutine = luaContext.get<LuaThread>("co"); + EXPECT_EQ(coroutine.status(), LuaThread::Status::Active); + EXPECT_EQ(coroutine.resume<double>(), 0.0); + EXPECT_EQ(coroutine.resume<double>(1.0), 2.0); + EXPECT_EQ(coroutine.resume<double>(3.0), 8.0); + EXPECT_EQ(coroutine.resume<double>(5.0), 18.0); + EXPECT_EQ(coroutine.resume<double>(7.0), 32.0); + EXPECT_EQ(coroutine.status(), LuaThread::Status::Dead); + EXPECT_THROW(coroutine.resume(), LuaException); + EXPECT_EQ(coroutine.status(), LuaThread::Status::Dead); + + LuaThread thread2 = luaEngine->createThread(); + EXPECT_EQ(thread2.status(), LuaThread::Status::Dead); + thread2.pushFunction(func); + EXPECT_EQ(thread2.status(), LuaThread::Status::Active); + EXPECT_EQ(thread2.resume<double>(), 0.0); + EXPECT_EQ(thread2.resume<double>(1.0), 2.0); + EXPECT_THROW(thread2.resume<String>("not_a_number"), LuaException); + EXPECT_EQ(thread2.status(), LuaThread::Status::Error); + EXPECT_THROW(thread2.resume(), LuaException); + EXPECT_EQ(thread2.status(), LuaThread::Status::Error); +} + +template <typename T> +bool roundTripEqual(LuaContext const& context, T t) { + return context.invokePath<T>("roundTrip", t) == t; +} + +TEST(LuaTest, Converters) { + auto luaEngine = LuaEngine::create(); + LuaContext luaContext = luaEngine->createContext(); + + luaContext.load( R"SCRIPT( + function makeVec() + return {1, 2} + end + + function makePoly() + return {{1, 2}, {3, 4}, {5, 6}} + end + + function roundTrip(ret) + return ret + end + )SCRIPT"); + + auto vecCompare = Vec2F(1.0f, 2.0f); + auto polyCompare = PolyF({Vec2F(1.0f, 2.0f), Vec2F(3.0f, 4.0f), Vec2F(5.0f, 6.0f)}); + + EXPECT_TRUE(luaContext.invokePath<Vec2F>("makeVec") == vecCompare); + EXPECT_TRUE(luaContext.invokePath<PolyF>("makePoly") == polyCompare); + EXPECT_TRUE(luaContext.invokePath<Vec2F>("roundTrip", vecCompare) == vecCompare); + EXPECT_TRUE(luaContext.invokePath<PolyF>("roundTrip", polyCompare) == polyCompare); + + EXPECT_TRUE(roundTripEqual(luaContext, PolyF())); + EXPECT_TRUE(roundTripEqual(luaContext, List<int>{1, 2, 3, 4})); + EXPECT_TRUE(roundTripEqual(luaContext, List<PolyF>{PolyF(), PolyF()})); + EXPECT_TRUE(roundTripEqual(luaContext, Maybe<int>(1))); + EXPECT_TRUE(roundTripEqual(luaContext, Maybe<int>())); + + auto listCompare = List<int>{1, 2, 3, 4}; + EXPECT_TRUE(luaContext.invokePath<List<int>>("roundTrip", JsonArray{1, 2, 3, 4}) == listCompare); + auto mapCompare = StringMap<String>{{"one", "two"}, {"three", "four"}}; + EXPECT_TRUE(luaContext.invokePath<StringMap<String>>("roundTrip", JsonObject{{"one", "two"}, {"three", "four"}}) == mapCompare); +} + +struct TestUserData1 { + int field; +}; + +struct TestUserData2 { + int field; +}; + +namespace Star { +template <> +struct LuaConverter<TestUserData1> : LuaUserDataConverter<TestUserData1> {}; + +template <> +struct LuaConverter<TestUserData2> : LuaUserDataConverter<TestUserData2> {}; +} + +TEST(LuaTest, UserDataTest) { + auto luaEngine = LuaEngine::create(); + + LuaContext luaContext = luaEngine->createContext(); + luaContext.load( + R"SCRIPT( + function doit(ref) + global = ref + end + )SCRIPT"); + + auto userdata1 = luaEngine->createUserData(TestUserData1{1}); + auto userdata2 = luaEngine->createUserData(TestUserData2{2}); + + luaContext.invokePath("doit", userdata1); + auto userdata3 = luaContext.get<LuaUserData>("global"); + + EXPECT_TRUE(userdata2.is<TestUserData2>()); + EXPECT_FALSE(userdata2.is<TestUserData1>()); + + EXPECT_TRUE(userdata1.is<TestUserData1>()); + EXPECT_FALSE(userdata1.is<TestUserData2>()); + + EXPECT_TRUE(userdata3.is<TestUserData1>()); + EXPECT_FALSE(userdata3.is<TestUserData2>()); + + EXPECT_EQ(userdata1.get<TestUserData1>().field, 1); + EXPECT_EQ(userdata2.get<TestUserData2>().field, 2); + EXPECT_EQ(userdata3.get<TestUserData1>().field, 1); + + userdata1.get<TestUserData1>().field = 3; + EXPECT_EQ(userdata1.get<TestUserData1>().field, 3); + EXPECT_EQ(userdata3.get<TestUserData1>().field, 3); + + luaContext.invokePath("doit", TestUserData1()); + auto userdata4 = luaContext.get("global"); + EXPECT_TRUE(luaEngine->luaMaybeTo<TestUserData1>(userdata4).isValid()); + + luaContext.invokePath("doit", "notuserdata"); + auto notuserdata = luaContext.get("global"); + EXPECT_FALSE(luaEngine->luaMaybeTo<TestUserData1>(notuserdata).isValid()); +} + +namespace Star { +template <> +struct LuaConverter<Vec3F> : LuaUserDataConverter<Vec3F> {}; + +template <> +struct LuaUserDataMethods<Vec3F> { + static LuaMethods<Vec3F> make() { + LuaMethods<Vec3F> methods; + methods.registerMethodWithSignature<float, Vec3F const&>("magnitude", mem_fn(&Vec3F::magnitude)); + return methods; + } +}; +} + +TEST(LuaTest, UserMethodTest) { + auto luaEngine = LuaEngine::create(); + luaEngine->setGlobal("vec3", luaEngine->createFunctionWithSignature<Vec3F, float, float, float>(construct<Vec3F>())); + + LuaContext luaContext = luaEngine->createContext(); + luaContext.load(R"SCRIPT( + v = vec3(3, 2, 1) + function testMagnitude(v2) + return v:magnitude() + v2:magnitude() + end + )SCRIPT"); + + float magnitude = luaContext.invokePath<float>("testMagnitude", Vec3F(5, 5, 5)); + EXPECT_TRUE(fabs(magnitude - (Vec3F(3, 2, 1).magnitude() + Vec3F(5, 5, 5).magnitude())) < 0.00001f); +} + +TEST(LuaTest, GlobalTest) { + auto luaEngine = LuaEngine::create(); + luaEngine->setGlobal("globalfoo", LuaInt(42)); + EXPECT_EQ(luaEngine->getGlobal("globalfoo"), LuaInt(42)); + + LuaContext luaContext = luaEngine->createContext(); + luaContext.load(R"SCRIPT( + function test() + return globalfoo + end + )SCRIPT"); + + EXPECT_EQ(luaContext.invokePath("test"), LuaInt(42)); +} + +TEST(LuaTest, ArgTest) { + auto luaEngine = LuaEngine::create(); + + LuaContext luaContext = luaEngine->createContext(); + luaContext.load(R"SCRIPT( + function test() + callback("2", 3, nil) + end + )SCRIPT"); + + luaContext.set("callback", + luaEngine->createFunction([](LuaFloat n, LuaString s, LuaBoolean b, LuaValue o) { + EXPECT_EQ(n, 2.0); + EXPECT_EQ(s, String("3")); + EXPECT_EQ(b, false); + EXPECT_EQ(o, LuaNil); + })); + + luaContext.invokePath("test"); +} + +TEST(LuaTest, ArrayTest) { + auto luaEngine = LuaEngine::create(); + LuaContext luaContext = luaEngine->createContext(); + luaContext.load(R"SCRIPT( + function test() + return {2, 4, 6, 8, 10} + end + )SCRIPT"); + + auto arrayTable = luaContext.invokePath("test").get<LuaTable>(); + + EXPECT_EQ(arrayTable.length(), 5); + EXPECT_EQ(arrayTable.get(2), LuaInt(4)); + EXPECT_EQ(arrayTable.get(5), LuaInt(10)); + + List<pair<int, int>> values; + arrayTable.iterate([&values](LuaValue const& key, LuaValue const& value) { + auto ikey = key.get<LuaInt>(); + auto ivalue = value.get<LuaInt>(); + values.append({ikey, ivalue}); + }); + List<pair<int, int>> compare = {{1, 2}, {2, 4}, {3, 6}, {4, 8}, {5, 10}}; + EXPECT_EQ(values, compare); +} + +TEST(LuaTest, PathTest) { + auto luaEngine = LuaEngine::create(); + LuaContext luaContext = luaEngine->createContext(); + luaContext.load(R"SCRIPT( + foo = { + bar = { + baz = 1 + } + } + + function test() + return foo.bar.baf + end + )SCRIPT"); + + EXPECT_EQ(luaContext.containsPath("foo.bar.baz"), true); + EXPECT_EQ(luaContext.getPath("foo.bar.baz"), LuaInt(1)); + EXPECT_EQ(luaContext.containsPath("foo.nothing.at.all"), false); + + luaContext.setPath("foo.bar.baf", LuaInt(5)); + EXPECT_EQ(luaContext.invokePath("test"), LuaInt(5)); + + luaContext.setPath("new.table.value", LuaInt(5)); + EXPECT_EQ(luaContext.getPath("new.table.value"), LuaInt(5)); +} + +TEST(LuaTest, CallbackTest) { + auto luaEngine = LuaEngine::create(); + + LuaCallbacks callbacks; + callbacks.registerCallback("add", [](LuaInt a, LuaInt b) { return a + b; }); + callbacks.registerCallbackWithSignature<LuaInt, LuaInt, LuaInt>("subtract", [](int a, int b) { return a - b; }); + callbacks.registerCallbackWithSignature<LuaInt, LuaInt>("multiply2", bind([](int a, int b) { return a * b; }, 2, _1)); + callbacks.registerCallbackWithSignature<void, LuaValue>("nothing", [](LuaValue v) { return v; }); + + LuaContext luaContext = luaEngine->createContext(); + luaContext.setCallbacks("callbacks", callbacks); + luaContext.load(R"SCRIPT( + function test1() + return callbacks.multiply2(callbacks.add(5, 10) + callbacks.subtract(3, 10)) + end + + function test2() + return callbacks.nothing(1) + end + )SCRIPT"); + + EXPECT_EQ(luaContext.invokePath("test1").get<LuaInt>(), 16); + EXPECT_EQ(luaContext.invokePath("test2"), LuaNil); +} + +TEST(LuaTest, VariableParameters) { + auto luaEngine = LuaEngine::create(); + auto context1 = luaEngine->createContext(); + + context1.load(R"SCRIPT( + function variableArgsCount(...) + local arg = {...} + return #arg + end + )SCRIPT"); + + auto res1 = context1.invokePath<int>("variableArgsCount"); + auto res2 = context1.invokePath<int>("variableArgsCount", 1); + auto res3 = context1.invokePath<int>("variableArgsCount", 1, 1); + auto res4 = context1.invokePath<int>("variableArgsCount", 1, 1, 1); + + EXPECT_EQ(res1, 0); + EXPECT_EQ(res2, 1); + EXPECT_EQ(res3, 2); + EXPECT_EQ(res4, 3); +} + +TEST(LuaTest, Scope) { + auto luaEngine = LuaEngine::create(); + auto script1 = luaEngine->compile(R"SCRIPT( + function create(param) + local self = {} + local foo = param + + local getValue = function() + return foo + end + + function self.get() + return getValue() + end + + return self + end + )SCRIPT"); + + auto script2 = luaEngine->compile(R"SCRIPT( + function init() + obj = create(param) + end + + function produce() + return obj.get() + end + )SCRIPT"); + + auto context1 = luaEngine->createContext(); + context1.load(script1); + context1.load(script2); + + auto context2 = luaEngine->createContext(); + context2.load(script1); + context2.load(script2); + + context1.setPath("param", 1); + context1.invokePath("init"); + + context2.setPath("param", 2); + context2.invokePath("init"); + + EXPECT_EQ(context1.invokePath<int>("produce"), 1); + EXPECT_EQ(context2.invokePath<int>("produce"), 2); + + context1.setPath("param", 2); + context1.invokePath("init"); + + context2.setPath("param", 1); + context2.invokePath("init"); + + EXPECT_EQ(context1.invokePath<int>("produce"), 2); + EXPECT_EQ(context2.invokePath<int>("produce"), 1); +} + +TEST(LuaTest, Scope2) { + auto luaEngine = LuaEngine::create(); + + auto context1 = luaEngine->createContext(); + context1.load(R"SCRIPT( + function init1() + global = {} + global.val = 10 + end + )SCRIPT"); + + auto context2 = luaEngine->createContext(); + context2.load(R"SCRIPT( + function init2() + global = {} + global.val = 20 + end + )SCRIPT"); + + EXPECT_TRUE(context1.contains("init1")); + EXPECT_TRUE(context2.contains("init2")); + EXPECT_FALSE(context1.contains("init2")); + EXPECT_FALSE(context2.contains("init1")); + + context1.invokePath("init1"); + EXPECT_EQ(context1.getPath<int>("global.val"), 10); + + EXPECT_TRUE(context2.getPath("global") == LuaNil); + + context2.invokePath("init2"); + EXPECT_EQ(context2.getPath<int>("global.val"), 20); + + EXPECT_EQ(context1.getPath<int>("global.val"), 10); +} + +TEST(LuaTest, MetaTable) { + auto luaEngine = LuaEngine::create(); + + auto context = luaEngine->createContext(); + context.load(R"SCRIPT( + function add(a, b) + return a + b + end + )SCRIPT"); + + auto mt = luaEngine->createTable(); + mt.set("__add", + luaEngine->createFunction([](LuaEngine& engine, LuaTable const& a, LuaTable const& b) { + return engine.createArrayTable( + initializer_list<double>{a.get<double>(1) + b.get<double>(1), a.get<double>(2) + b.get<double>(2)}); + })); + mt.set("test", "hello"); + + auto t1 = luaEngine->createArrayTable(initializer_list<double>{1, 2}); + t1.setMetatable(mt); + + auto t2 = luaEngine->createArrayTable(initializer_list<double>{5, 6}); + t2.setMetatable(mt); + + auto tr = context.invokePath<LuaTable>("add", t1, t2); + EXPECT_EQ(tr.get<double>(1), 6); + EXPECT_EQ(tr.get<double>(2), 8); + EXPECT_EQ(t1.getMetatable()->get<String>("test"), "hello"); + EXPECT_EQ(t2.getMetatable()->get<String>("test"), "hello"); +} + +TEST(LuaTest, Integers) { + auto luaEngine = LuaEngine::create(); + auto context = luaEngine->createContext(); + context.load(R"SCRIPT( + n1 = 0 + n2 = 1 + n3 = 1.0 + n4 = 1.1 + n5 = 5.0 + n6 = 5 + )SCRIPT"); + + EXPECT_EQ(context.get("n1"), LuaInt(0)); + EXPECT_EQ(context.get("n2"), LuaInt(1)); + EXPECT_EQ(context.get("n3"), LuaFloat(1.0)); + EXPECT_EQ(context.get("n4"), LuaFloat(1.1)); + EXPECT_EQ(context.get("n5"), LuaFloat(5.0)); + EXPECT_EQ(context.get("n6"), LuaInt(5)); +} + +TEST(LuaTest, Require) { + auto luaEngine = LuaEngine::create(); + auto context = luaEngine->createContext(); + context.setRequireFunction([](LuaContext& context, LuaString const& arg) { + context.set(arg, context.createFunction([arg]() { + return arg; + })); + }); + + context.load(R"SCRIPT( + require "a" + require "b" + require "c" + + function res() + return a() .. b() .. c() + end + )SCRIPT"); + + EXPECT_EQ(context.invokePath<LuaString>("res"), String("abc")); +} + +TEST(LuaTest, Eval) { + auto luaEngine = LuaEngine::create(); + auto context = luaEngine->createContext(); + + context.eval("i = 3"); + // Make sure statements and expressions both work in eval. + EXPECT_EQ(context.eval<int>("i + 1"), 4); + EXPECT_EQ(context.eval<int>("return i + 1"), 4); +} + +TEST(LuaTest, Multi) { + auto luaEngine = LuaEngine::create(); + auto script = luaEngine->compile(R"SCRIPT( + function entry() + return callbacks.func(2, 4) + end + + function sum(...) + local sum = 0 + for i,v in ipairs(arg) do + sum = sum + v + end + return sum + end + )SCRIPT"); + + auto context1 = luaEngine->createContext(); + auto context2 = luaEngine->createContext(); + auto context3 = luaEngine->createContext(); + + context1.load(script); + context2.load(script); + context3.load(script); + + LuaCallbacks addCallbacks; + addCallbacks.registerCallback("func", + [](LuaVariadic<int> const& args) -> int { + int sum = 0.0; + for (auto arg : args) + sum += arg; + return sum; + }); + + LuaCallbacks multCallbacks; + multCallbacks.registerCallback("func", + [](LuaVariadic<int> const& args) -> int { + int mult = 1.0; + for (auto arg : args) + mult *= arg; + return mult; + }); + + context1.setCallbacks("callbacks", addCallbacks); + context2.setCallbacks("callbacks", multCallbacks); + context3.setCallbacks("callbacks", addCallbacks); + + EXPECT_EQ(context1.invokePath<int>("entry"), 6); + EXPECT_EQ(context2.invokePath<int>("entry"), 8); + EXPECT_EQ(context3.invokePath<int>("entry"), 6); + EXPECT_EQ(context1.invokePath<int>("entry"), 6); + EXPECT_EQ(context2.invokePath<int>("entry"), 8); + EXPECT_EQ(context3.invokePath<int>("entry"), 6); + EXPECT_EQ(context1.invokePath<int>("entry"), 6); + + auto context4 = luaEngine->createContext(); + context4.load(R"SCRIPT( + function sum(...) + local args = {...} + local sum = 0 + for i = 1, #args do + sum = sum + args[i] + end + return sum + end + + function mreturn(...) + return ... + end + + function callbacktest(...) + local x, y = callback() + return x, y + end + + function emptycallbacktest(...) + return emptycallback() + end + )SCRIPT"); + EXPECT_EQ(context4.invokePath<int>("sum", LuaVariadic<int>{1, 2, 3}), 6); + EXPECT_EQ(context4.invokePath<int>("sum", 5, LuaVariadic<int>{1, 2, 3}, 10), 21); + EXPECT_EQ(context4.invokePath<LuaVariadic<int>>("mreturn", 1, 2, 3), LuaVariadic<int>({1, 2, 3})); + + int a; + float b; + String c; + luaTie(a, b, c) = context4.invokePath<LuaTupleReturn<int, float, String>>("mreturn", 1, 2.0f, "foo"); + EXPECT_EQ(a, 1); + EXPECT_EQ(b, 2.0f); + EXPECT_EQ(c, "foo"); + + context4.set("callback", context4.createFunction([]() { return luaTupleReturn(5, 10); })); + + context4.set("emptycallback", context4.createFunction([]() { return luaTupleReturn(); })); + + int d; + int e; + luaTie(d, e) = context4.invokePath<LuaTupleReturn<int, int>>("callbacktest"); + EXPECT_EQ(d, 5); + EXPECT_EQ(e, 10); + + EXPECT_EQ(context4.invokePath("emptycallbacktest"), LuaNil); +} + +TEST(LuaTest, Limits) { + auto luaEngine = LuaEngine::create(); + luaEngine->setInstructionLimit(500000); + luaEngine->setRecursionLimit(64); + auto context = luaEngine->createContext(); + context.load(R"SCRIPT( + function toinfinityandbeyond() + while true do + end + end + + function toabignumberandthenstop() + for i = 0, 50000 do + end + end + )SCRIPT"); + + // Make sure infinite loops trigger the instruction limit. + EXPECT_THROW(context.invokePath("toinfinityandbeyond"), LuaInstructionLimitReached); + + // Make sure the instruction count is reset after each call. + context.invokePath("toabignumberandthenstop"); + + auto infLoop = R"SCRIPT( + while true do + end + )SCRIPT"; + + // Make sure loading code into context with infinite loops in their + // evaluation triggers instruction limit. + EXPECT_THROW(context.load(infLoop), LuaInstructionLimitReached); + + // And the same for eval + EXPECT_THROW(context.eval(infLoop), LuaInstructionLimitReached); + + auto call1 = [&context]() { context.invokePath("call2"); }; + + auto call2 = [&context]() { context.invokePath("call1"); }; + + context.set("call1", context.createFunction(call1)); + context.set("call2", context.createFunction(call2)); + + EXPECT_THROW(context.invokePath("call1"), LuaRecursionLimitReached); + + // Make sure the context still functions properly after these previous + // errors. + EXPECT_EQ(context.eval<int>("1 + 1"), 2); +} + +TEST(LuaTest, Errors) { + auto luaEngine = LuaEngine::create(); + auto context = luaEngine->createContext(); + + EXPECT_THROW(context.eval("while true do"), LuaIncompleteStatementException); + context.setPath("val", 1.0); + EXPECT_THROW(context.getPath<Vec2D>("val"), LuaConversionException); + EXPECT_EQ(luaEngine->luaMaybeTo<RectF>(context.get("val")), Maybe<RectF>()); + + context.set("throwException", luaEngine->createFunction([]() { + throw StarException("lua caught the exception!"); + })); + + context.load(R"SCRIPT( + function throwError() + return throwException() + end + function catchError() + return pcall(throwException) + end + )SCRIPT"); + + EXPECT_THROW(context.invokePath("throwError"), LuaException); + + bool status; + LuaValue error; + luaTie(status, error) = context.invokePath<LuaTupleReturn<bool, LuaValue>>("catchError"); + EXPECT_EQ(status, false); + EXPECT_TRUE(String(toString(error)).contains("lua caught the exception")); +} + +TEST(LuaTest, GarbageCollection) { + auto engine = LuaEngine::create(); + auto context = engine->createContext(); + + auto ptr = make_shared<int>(5); + engine->setAutoGarbageCollection(false); + context.setPath("ref", context.createUserData(ptr)); + EXPECT_EQ(ptr.use_count(), 2); + context.setPath("ref", LuaNil); + EXPECT_EQ(ptr.use_count(), 2); + engine->collectGarbage(); + EXPECT_EQ(ptr.use_count(), 1); +} + +TEST(LuaTest, IntTest) { + auto engine = LuaEngine::create(); + auto context = engine->createContext(); + context.setPath("test", (uint64_t)-1); + EXPECT_EQ(context.getPath<uint64_t>("test"), (uint64_t)-1); +} + +TEST(LuaTest, VariantTest) { + auto engine = LuaEngine::create(); + auto context = engine->createContext(); + + typedef Variant<int, String> IntOrString; + + EXPECT_EQ(context.eval<IntOrString>("'foo'"), IntOrString(String("foo"))); + EXPECT_EQ(context.eval<IntOrString>("'1'"), IntOrString(1)); + + typedef MVariant<Maybe<int>, String> MIntOrString; + + EXPECT_EQ(context.eval<MIntOrString>("'foo'"), MIntOrString(String("foo"))); + EXPECT_EQ(context.eval<MIntOrString>("'1'"), MIntOrString(Maybe<int>(1))); + EXPECT_EQ(context.eval<MIntOrString>("nil"), MIntOrString()); +} + +TEST(LuaTest, ProfilingTest) { + auto luaEngine = LuaEngine::create(); + luaEngine->setProfilingEnabled(true); + luaEngine->setInstructionMeasureInterval(1000); + + auto context = luaEngine->createContext(); + context.eval(R"SCRIPT( + function function1() + for i = 1, 1000 do + end + end + + function function2() + for i = 1, 1000 do + end + end + + function function3() + for i = 1, 1000 do + end + end + + for i = 1, 10000 do + function1() + function2() + function3() + end + )SCRIPT"); + + StringSet names; + List<LuaProfileEntry> profile = luaEngine->getProfile(); + for (auto const& p : profile[0].calls) + names.add(p.second->name.value()); + + EXPECT_TRUE(names.contains("function1")); + EXPECT_TRUE(names.contains("function2")); + EXPECT_TRUE(names.contains("function3")); +} |