osb/source/test/lua_test.cpp

839 lines
24 KiB
C++
Raw Normal View History

2023-06-20 04:33:09 +00:00
#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"));
}