mirror of
https://github.com/ThePhD/sol2.git
synced 2024-03-22 13:10:44 +08:00
eb1560d12a
improve exception documentation improve state_view default handlers add SAFE_PROPAGATION defines for compiling C++ as Lua add examples for automatic operator registrations and as_container fix tutorial code change tests to not throw unless absolutely necessary provide synchronization for file writing in tests provide thread safety around thread tests for REQUIRE add ostream automatic support change 5.1 compat to only kick in luaL_loadbufferx and luaL_loadfilex when LuaJIT is version 2.0.1 and lower
587 lines
15 KiB
C++
587 lines
15 KiB
C++
#define SOL_CHECK_ARGUMENTS
|
|
|
|
#include <catch.hpp>
|
|
#include <sol.hpp>
|
|
|
|
#include <iostream>
|
|
#include <algorithm>
|
|
#include <numeric>
|
|
#include <vector>
|
|
|
|
#include "test_stack_guard.hpp"
|
|
|
|
std::string free_function() {
|
|
INFO("free_function()");
|
|
return "test";
|
|
}
|
|
|
|
struct object {
|
|
std::string operator() () {
|
|
INFO("member_test()");
|
|
return "test";
|
|
}
|
|
};
|
|
|
|
int plop_xyz(int x, int y, std::string z) {
|
|
INFO(x << " " << y << " " << z);
|
|
return 11;
|
|
}
|
|
|
|
TEST_CASE("tables/as enums", "Making sure enums can be put in and gotten out as values") {
|
|
enum direction {
|
|
up,
|
|
down,
|
|
left,
|
|
right
|
|
};
|
|
|
|
sol::state lua;
|
|
lua.open_libraries(sol::lib::base);
|
|
|
|
lua["direction"] = lua.create_table_with(
|
|
"up", direction::up,
|
|
"down", direction::down,
|
|
"left", direction::left,
|
|
"right", direction::right
|
|
);
|
|
|
|
sol::object obj = lua["direction"]["up"];
|
|
bool isdir = obj.is<direction>();
|
|
REQUIRE(isdir);
|
|
auto dir = obj.as<direction>();
|
|
REQUIRE(dir == direction::up);
|
|
}
|
|
|
|
TEST_CASE("tables/as enum classes", "Making sure enums can be put in and gotten out as values") {
|
|
enum class direction {
|
|
up,
|
|
down,
|
|
left,
|
|
right
|
|
};
|
|
|
|
sol::state lua;
|
|
lua.open_libraries(sol::lib::base);
|
|
|
|
lua["direction"] = lua.create_table_with(
|
|
"up", direction::up,
|
|
"down", direction::down,
|
|
"left", direction::left,
|
|
"right", direction::right
|
|
);
|
|
|
|
sol::object obj = lua["direction"]["up"];
|
|
bool isdir = obj.is<direction>();
|
|
REQUIRE(isdir);
|
|
auto dir = obj.as<direction>();
|
|
REQUIRE(dir == direction::up);
|
|
}
|
|
|
|
TEST_CASE("tables/cleanup", "make sure tables leave the stack balanced") {
|
|
sol::state lua;
|
|
lua.open_libraries();
|
|
|
|
auto f = [] { return 5; };
|
|
for (int i = 0; i < 30; i++) {
|
|
std::string name = std::string("init") + std::to_string(i);
|
|
int top = lua_gettop(lua);
|
|
lua[name] = f;
|
|
int aftertop = lua_gettop(lua);
|
|
REQUIRE(aftertop == top);
|
|
int val = lua[name]();
|
|
REQUIRE(val == 5);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("tables/nested cleanup", "make sure tables leave the stack balanced") {
|
|
sol::state lua;
|
|
lua.open_libraries();
|
|
|
|
lua.script("A={}");
|
|
auto f = [] { return 5; };
|
|
for (int i = 0; i < 30; i++) {
|
|
std::string name = std::string("init") + std::to_string(i);
|
|
int top = lua_gettop(lua);
|
|
auto A = lua["A"];
|
|
int beforetop = lua_gettop(lua);
|
|
REQUIRE(beforetop == top);
|
|
A[name] = f;
|
|
int aftertop = lua_gettop(lua);
|
|
REQUIRE(aftertop == top);
|
|
int val = A[name]();
|
|
REQUIRE(val == 5);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("tables/new_enum", "Making sure enums can be put in and gotten out as values") {
|
|
enum class direction {
|
|
up,
|
|
down,
|
|
left,
|
|
right
|
|
};
|
|
|
|
sol::state lua;
|
|
lua.open_libraries(sol::lib::base);
|
|
|
|
lua.new_enum( "direction",
|
|
"up", direction::up,
|
|
"down", direction::down,
|
|
"left", direction::left,
|
|
"right", direction::right
|
|
);
|
|
|
|
direction d = lua["direction"]["left"];
|
|
REQUIRE(d == direction::left);
|
|
auto result = lua.safe_script("direction.left = 50", sol::script_pass_on_error);
|
|
REQUIRE_FALSE(result.valid());
|
|
d = lua["direction"]["left"];
|
|
REQUIRE(d == direction::left);
|
|
}
|
|
|
|
TEST_CASE("tables/for_each", "Testing the use of for_each to get values from a lua table") {
|
|
sol::state lua;
|
|
lua.open_libraries(sol::lib::base);
|
|
|
|
lua.script("arr = {\n"
|
|
"[0] = \"Hi\",\n"
|
|
"[1] = 123.45,\n"
|
|
"[2] = \"String value\",\n"
|
|
// Does nothing
|
|
//"[3] = nil,\n"
|
|
//"[nil] = 3,\n"
|
|
"[\"WOOF\"] = 123,\n"
|
|
"}");
|
|
sol::table tbl = lua["arr"];
|
|
std::size_t tablesize = 4;
|
|
std::size_t iterations = 0;
|
|
auto fx = [&iterations](sol::object key, sol::object value) {
|
|
++iterations;
|
|
sol::type keytype = key.get_type();
|
|
switch (keytype) {
|
|
case sol::type::number:
|
|
switch (key.as<int>()) {
|
|
case 0:
|
|
REQUIRE((value.as<std::string>() == "Hi"));
|
|
break;
|
|
case 1:
|
|
REQUIRE((value.as<double>() == 123.45));
|
|
break;
|
|
case 2:
|
|
REQUIRE((value.as<std::string>() == "String value"));
|
|
break;
|
|
case 3:
|
|
REQUIRE((value.is<sol::nil_t>()));
|
|
break;
|
|
}
|
|
break;
|
|
case sol::type::string:
|
|
if (key.as<std::string>() == "WOOF") {
|
|
REQUIRE((value.as<double>() == 123));
|
|
}
|
|
break;
|
|
case sol::type::nil:
|
|
REQUIRE((value.as<double>() == 3));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
auto fxpair = [&fx](std::pair<sol::object, sol::object> kvp) { fx(kvp.first, kvp.second); };
|
|
tbl.for_each(fx);
|
|
REQUIRE(iterations == tablesize);
|
|
|
|
iterations = 0;
|
|
tbl.for_each(fxpair);
|
|
REQUIRE(iterations == tablesize);
|
|
}
|
|
|
|
TEST_CASE("tables/for_each empty", "empty tables should not crash") {
|
|
sol::state lua;
|
|
lua.open_libraries(sol::lib::base);
|
|
|
|
lua.script("arr = {}");
|
|
sol::table tbl = lua["arr"];
|
|
REQUIRE(tbl.empty());
|
|
std::size_t tablesize = 0;
|
|
std::size_t iterations = 0;
|
|
auto fx = [&iterations](sol::object key, sol::object value) {
|
|
++iterations;
|
|
sol::type keytype = key.get_type();
|
|
switch (keytype) {
|
|
case sol::type::number:
|
|
switch (key.as<int>()) {
|
|
case 0:
|
|
REQUIRE((value.as<std::string>() == "Hi"));
|
|
break;
|
|
case 1:
|
|
REQUIRE((value.as<double>() == 123.45));
|
|
break;
|
|
case 2:
|
|
REQUIRE((value.as<std::string>() == "String value"));
|
|
break;
|
|
case 3:
|
|
REQUIRE((value.is<sol::nil_t>()));
|
|
break;
|
|
}
|
|
break;
|
|
case sol::type::string:
|
|
if (key.as<std::string>() == "WOOF") {
|
|
REQUIRE((value.as<double>() == 123));
|
|
}
|
|
break;
|
|
case sol::type::nil:
|
|
REQUIRE((value.as<double>() == 3));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
auto fxpair = [&fx](std::pair<sol::object, sol::object> kvp) { fx(kvp.first, kvp.second); };
|
|
tbl.for_each(fx);
|
|
REQUIRE(iterations == tablesize);
|
|
|
|
iterations = 0;
|
|
tbl.for_each(fxpair);
|
|
REQUIRE(iterations == tablesize);
|
|
|
|
iterations = 0;
|
|
for (const auto& kvp : tbl) {
|
|
fxpair(kvp);
|
|
++iterations;
|
|
}
|
|
REQUIRE(iterations == tablesize);
|
|
}
|
|
|
|
TEST_CASE("tables/iterators", "Testing the use of iteratrs to get values from a lua table") {
|
|
sol::state lua;
|
|
lua.open_libraries(sol::lib::base);
|
|
|
|
lua.script("arr = {\n"
|
|
"[0] = \"Hi\",\n"
|
|
"[1] = 123.45,\n"
|
|
"[2] = \"String value\",\n"
|
|
// Does nothing
|
|
//"[3] = nil,\n"
|
|
//"[nil] = 3,\n"
|
|
"[\"WOOF\"] = 123,\n"
|
|
"}");
|
|
sol::table tbl = lua["arr"];
|
|
std::size_t tablesize = 4;
|
|
std::size_t iterations = 0;
|
|
|
|
int begintop = 0;
|
|
int endtop = 0;
|
|
{
|
|
test_stack_guard s(lua.lua_state(), begintop, endtop);
|
|
for (auto& kvp : tbl) {
|
|
[&iterations](sol::object key, sol::object value) {
|
|
++iterations;
|
|
sol::type keytype = key.get_type();
|
|
switch (keytype) {
|
|
case sol::type::number:
|
|
switch (key.as<int>()) {
|
|
case 0:
|
|
REQUIRE((value.as<std::string>() == "Hi"));
|
|
break;
|
|
case 1:
|
|
REQUIRE((value.as<double>() == 123.45));
|
|
break;
|
|
case 2:
|
|
REQUIRE((value.as<std::string>() == "String value"));
|
|
break;
|
|
case 3:
|
|
REQUIRE((value.is<sol::nil_t>()));
|
|
break;
|
|
}
|
|
break;
|
|
case sol::type::string:
|
|
if (key.as<std::string>() == "WOOF") {
|
|
REQUIRE((value.as<double>() == 123));
|
|
}
|
|
break;
|
|
case sol::type::nil:
|
|
REQUIRE((value.as<double>() == 3));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}(kvp.first, kvp.second);
|
|
}
|
|
} REQUIRE(begintop == endtop);
|
|
REQUIRE(iterations == tablesize);
|
|
}
|
|
|
|
TEST_CASE("tables/variables", "Check if tables and variables work as intended") {
|
|
sol::state lua;
|
|
lua.open_libraries(sol::lib::base, sol::lib::os);
|
|
lua.get<sol::table>("os").set("name", "windows");
|
|
REQUIRE_NOTHROW(lua.script("assert(os.name == \"windows\")"));
|
|
}
|
|
|
|
TEST_CASE("tables/create", "Check if creating a table is kosher") {
|
|
sol::state lua;
|
|
lua["testtable"] = sol::table::create(lua.lua_state(), 0, 0, "Woof", "Bark", 1, 2, 3, 4);
|
|
sol::object testobj = lua["testtable"];
|
|
REQUIRE(testobj.is<sol::table>());
|
|
sol::table testtable = testobj.as<sol::table>();
|
|
REQUIRE((testtable["Woof"] == std::string("Bark")));
|
|
REQUIRE((testtable[1] == 2));
|
|
REQUIRE((testtable[3] == 4));
|
|
}
|
|
|
|
TEST_CASE("tables/create local", "Check if creating a table is kosher") {
|
|
sol::state lua;
|
|
lua["testtable"] = lua.create_table(0, 0, "Woof", "Bark", 1, 2, 3, 4);
|
|
sol::object testobj = lua["testtable"];
|
|
REQUIRE(testobj.is<sol::table>());
|
|
sol::table testtable = testobj.as<sol::table>();
|
|
REQUIRE((testtable["Woof"] == std::string("Bark")));
|
|
REQUIRE((testtable[1] == 2));
|
|
REQUIRE((testtable[3] == 4));
|
|
}
|
|
|
|
TEST_CASE("tables/create local named", "Check if creating a table is kosher") {
|
|
sol::state lua;
|
|
sol::table testtable = lua.create_table("testtable", 0, 0, "Woof", "Bark", 1, 2, 3, 4);
|
|
sol::object testobj = lua["testtable"];
|
|
REQUIRE(testobj.is<sol::table>());
|
|
REQUIRE((testobj == testtable));
|
|
REQUIRE_FALSE((testobj != testtable));
|
|
REQUIRE((testtable["Woof"] == std::string("Bark")));
|
|
REQUIRE((testtable[1] == 2));
|
|
REQUIRE((testtable[3] == 4));
|
|
}
|
|
|
|
TEST_CASE("tables/create-with-local", "Check if creating a table is kosher") {
|
|
sol::state lua;
|
|
lua["testtable"] = lua.create_table_with("Woof", "Bark", 1, 2, 3, 4);
|
|
sol::object testobj = lua["testtable"];
|
|
REQUIRE(testobj.is<sol::table>());
|
|
sol::table testtable = testobj.as<sol::table>();
|
|
REQUIRE((testtable["Woof"] == std::string("Bark")));
|
|
REQUIRE((testtable[1] == 2));
|
|
REQUIRE((testtable[3] == 4));
|
|
}
|
|
|
|
TEST_CASE("tables/function variables", "Check if tables and function calls work as intended") {
|
|
sol::state lua;
|
|
lua.open_libraries(sol::lib::base, sol::lib::os);
|
|
auto run_script = [](sol::state& lua) -> void {
|
|
lua.script("assert(os.fun() == \"test\")");
|
|
};
|
|
|
|
lua.get<sol::table>("os").set_function("fun",
|
|
[]() {
|
|
INFO("stateless lambda()");
|
|
return "test";
|
|
}
|
|
);
|
|
REQUIRE_NOTHROW(run_script(lua));
|
|
|
|
lua.get<sol::table>("os").set_function("fun", &free_function);
|
|
REQUIRE_NOTHROW(run_script(lua));
|
|
|
|
// l-value, canNOT optimise
|
|
// prefer value semantics unless wrapped with std::reference_wrapper
|
|
{
|
|
auto lval = object();
|
|
lua.get<sol::table>("os").set_function("fun", &object::operator(), lval);
|
|
}
|
|
REQUIRE_NOTHROW(run_script(lua));
|
|
|
|
auto reflval = object();
|
|
lua.get<sol::table>("os").set_function("fun", &object::operator(), std::ref(reflval));
|
|
REQUIRE_NOTHROW(run_script(lua));
|
|
|
|
|
|
// stateful lambda: non-convertible, cannot be optimised
|
|
int breakit = 50;
|
|
lua.get<sol::table>("os").set_function("fun",
|
|
[&breakit]() {
|
|
INFO("stateful lambda()");
|
|
return "test";
|
|
}
|
|
);
|
|
REQUIRE_NOTHROW(run_script(lua));
|
|
|
|
// r-value, cannot optimise
|
|
lua.get<sol::table>("os").set_function("fun", &object::operator(), object());
|
|
REQUIRE_NOTHROW(run_script(lua));
|
|
|
|
// r-value, cannot optimise
|
|
auto rval = object();
|
|
lua.get<sol::table>("os").set_function("fun", &object::operator(), std::move(rval));
|
|
REQUIRE_NOTHROW(run_script(lua));
|
|
}
|
|
|
|
TEST_CASE("tables/operator[]", "Check if operator[] retrieval and setting works properly") {
|
|
sol::state lua;
|
|
lua.open_libraries(sol::lib::base);
|
|
|
|
lua.script("foo = 20\nbar = \"hello world\"");
|
|
// basic retrieval
|
|
std::string bar = lua["bar"];
|
|
int foo = lua["foo"];
|
|
REQUIRE(bar == "hello world");
|
|
REQUIRE(foo == 20);
|
|
// test operator= for stringification
|
|
// errors due to ambiguous operators
|
|
bar = lua["bar"];
|
|
|
|
// basic setting
|
|
lua["bar"] = 20.4;
|
|
lua["foo"] = "goodbye";
|
|
|
|
// doesn't modify the actual values obviously.
|
|
REQUIRE(bar == "hello world");
|
|
REQUIRE(foo == 20);
|
|
|
|
// function setting
|
|
lua["test"] = plop_xyz;
|
|
REQUIRE_NOTHROW(lua.script("assert(test(10, 11, \"hello\") == 11)"));
|
|
|
|
// function retrieval
|
|
sol::function test = lua["test"];
|
|
REQUIRE(test.call<int>(10, 11, "hello") == 11);
|
|
|
|
// setting a lambda
|
|
lua["lamb"] = [](int x) {
|
|
return x * 2;
|
|
};
|
|
|
|
REQUIRE_NOTHROW(lua.script("assert(lamb(220) == 440)"));
|
|
|
|
// function retrieval of a lambda
|
|
sol::function lamb = lua["lamb"];
|
|
REQUIRE(lamb.call<int>(220) == 440);
|
|
|
|
// test const table retrieval
|
|
auto assert1 = [](const sol::table& t) {
|
|
std::string a = t["foo"];
|
|
double b = t["bar"];
|
|
REQUIRE(a == "goodbye");
|
|
REQUIRE(b == 20.4);
|
|
};
|
|
|
|
REQUIRE_NOTHROW(assert1(lua.globals()));
|
|
}
|
|
|
|
TEST_CASE("tables/operator[] valid", "Test if proxies on tables can lazily evaluate validity") {
|
|
sol::state lua;
|
|
bool isFullScreen = false;
|
|
auto fullscreennopers = lua["fullscreen"]["nopers"];
|
|
auto fullscreen = lua["fullscreen"];
|
|
REQUIRE_FALSE(fullscreennopers.valid());
|
|
REQUIRE_FALSE(fullscreen.valid());
|
|
|
|
lua["fullscreen"] = true;
|
|
|
|
REQUIRE_FALSE(fullscreennopers.valid());
|
|
REQUIRE(fullscreen.valid());
|
|
isFullScreen = lua["fullscreen"];
|
|
REQUIRE(isFullScreen);
|
|
|
|
lua["fullscreen"] = false;
|
|
REQUIRE_FALSE(fullscreennopers.valid());
|
|
REQUIRE(fullscreen.valid());
|
|
isFullScreen = lua["fullscreen"];
|
|
REQUIRE_FALSE(isFullScreen);
|
|
}
|
|
|
|
TEST_CASE("tables/operator[] optional", "Test if proxies on tables can lazily evaluate validity") {
|
|
sol::state lua;
|
|
|
|
sol::optional<int> test1 = lua["no_exist_yet"];
|
|
bool present = (bool)test1;
|
|
REQUIRE_FALSE(present);
|
|
|
|
lua["no_exist_yet"] = 262;
|
|
sol::optional<int> test2 = lua["no_exist_yet"];
|
|
present = (bool)test2;
|
|
REQUIRE(present);
|
|
REQUIRE(test2.value() == 262);
|
|
|
|
sol::optional<int> nope = lua["nope"]["kek"]["hah"];
|
|
auto nope2 = lua.get<sol::optional<int>>(std::make_tuple("nope", "kek", "hah"));
|
|
present = (bool)nope;
|
|
REQUIRE_FALSE(present);
|
|
present = (bool)nope2;
|
|
REQUIRE_FALSE(present);
|
|
lua.create_named_table("nope", "kek", lua.create_table_with("hah", 1));
|
|
sol::optional<int> non_nope = lua["nope"]["kek"]["hah"].get<sol::optional<int>>();
|
|
sol::optional<int> non_nope2 = lua.get<sol::optional<int>>(std::make_tuple("nope", "kek", "hah"));
|
|
present = (bool)non_nope;
|
|
REQUIRE(present);
|
|
present = (bool)non_nope2;
|
|
REQUIRE(present);
|
|
REQUIRE(non_nope.value() == 1);
|
|
REQUIRE(non_nope2.value() == 1);
|
|
|
|
INFO("Keys: nope, kek, hah");
|
|
lua.set(std::make_tuple("nope", "kek", "hah"), 35);
|
|
sol::optional<int> non_nope3 = lua["nope"]["kek"]["hah"].get<sol::optional<int>>();
|
|
sol::optional<int> non_nope4 = lua.get<sol::optional<int>>(std::make_tuple("nope", "kek", "hah"));
|
|
present = (bool)non_nope3;
|
|
REQUIRE(present);
|
|
present = (bool)non_nope4;
|
|
REQUIRE(present);
|
|
REQUIRE(non_nope3.value() == 35);
|
|
REQUIRE(non_nope4.value() == 35);
|
|
}
|
|
|
|
TEST_CASE("tables/add", "Basic test to make sure the 'add' feature works") {
|
|
static const int sz = 120;
|
|
|
|
sol::state lua;
|
|
sol::table t = lua.create_table(sz, 0);
|
|
|
|
std::vector<int> bigvec( sz );
|
|
std::iota(bigvec.begin(), bigvec.end(), 1);
|
|
|
|
for (std::size_t i = 0; i < bigvec.size(); ++i) {
|
|
t.add(bigvec[i]);
|
|
}
|
|
for (std::size_t i = 0; i < bigvec.size(); ++i) {
|
|
int val = t[i + 1];
|
|
REQUIRE(val == bigvec[i]);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("tables/boolean keys", "make sure boolean keys don't get caught up in `is_integral` specializations") {
|
|
sol::state lua;
|
|
lua.open_libraries(sol::lib::base);
|
|
|
|
lua.script(R"(
|
|
tbl = {}
|
|
tbl[true] = 10
|
|
tbl[1] = 20
|
|
|
|
print(tbl[true])
|
|
print(tbl[1])
|
|
)");
|
|
sol::table tbl = lua["tbl"];
|
|
int v1 = tbl[true];
|
|
int v2 = tbl[1];
|
|
REQUIRE(v1 == 10);
|
|
REQUIRE(v2 == 20);
|
|
|
|
tbl[true] = 30;
|
|
tbl[1] = 40;
|
|
v1 = tbl[true];
|
|
v2 = tbl[1];
|
|
REQUIRE(v1 == 30);
|
|
REQUIRE(v2 == 40);
|
|
}
|
|
|
|
TEST_CASE("tables/optional move", "ensure pushing a sol::optional<T> rvalue correctly moves the contained object into tables") {
|
|
sol::state sol_state;
|
|
struct move_only {
|
|
int secret_code;
|
|
move_only(const move_only&) = delete;
|
|
move_only(move_only&&) = default;
|
|
};
|
|
sol_state["requires_move"] = sol::optional<move_only>{ move_only{ 0x4D } };
|
|
REQUIRE(sol_state["requires_move"].get<move_only>().secret_code == 0x4D);
|
|
}
|