#define SOL_CHECK_ARGUMENTS 1 #define SOL_ENABLE_INTEROP 1 #include #include #include #include #include #include #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(); REQUIRE(isdir); auto dir = obj.as(); 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(); REQUIRE(isdir); auto dir = obj.as(); 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.safe_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 }; SECTION("variadics") { 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); } SECTION("initializer_list") { 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.safe_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()) { case 0: REQUIRE((value.as() == "Hi")); break; case 1: REQUIRE((value.as() == 123.45)); break; case 2: REQUIRE((value.as() == "String value")); break; case 3: REQUIRE((value.is())); break; } break; case sol::type::string: if (key.as() == "WOOF") { REQUIRE((value.as() == 123)); } break; case sol::type::lua_nil: REQUIRE((value.as() == 3)); break; default: break; } }; auto fxpair = [&fx](std::pair 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.safe_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()) { case 0: REQUIRE((value.as() == "Hi")); break; case 1: REQUIRE((value.as() == 123.45)); break; case 2: REQUIRE((value.as() == "String value")); break; case 3: REQUIRE((value.is())); break; } break; case sol::type::string: if (key.as() == "WOOF") { REQUIRE((value.as() == 123)); } break; case sol::type::lua_nil: REQUIRE((value.as() == 3)); break; default: break; } }; auto fxpair = [&fx](std::pair 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.safe_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()) { case 0: REQUIRE((value.as() == "Hi")); break; case 1: REQUIRE((value.as() == 123.45)); break; case 2: REQUIRE((value.as() == "String value")); break; case 3: REQUIRE((value.is())); break; } break; case sol::type::string: if (key.as() == "WOOF") { REQUIRE((value.as() == 123)); } break; case sol::type::lua_nil: REQUIRE((value.as() == 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("os").set("name", "windows"); REQUIRE_NOTHROW(lua.safe_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 testtable = testobj.as(); 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 testtable = testobj.as(); 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()); 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 testtable = testobj.as(); 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.safe_script("assert(os.fun() == \"test\")"); }; lua.get("os").set_function("fun", []() { INFO("stateless lambda()"); return "test"; }); REQUIRE_NOTHROW(run_script(lua)); lua.get("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("os").set_function("fun", &object::operator(), lval); } REQUIRE_NOTHROW(run_script(lua)); auto reflval = object(); lua.get("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("os").set_function("fun", [&breakit]() { INFO("stateful lambda()"); return "test"; }); REQUIRE_NOTHROW(run_script(lua)); // r-value, cannot optimise lua.get("os").set_function("fun", &object::operator(), object()); REQUIRE_NOTHROW(run_script(lua)); // r-value, cannot optimise auto rval = object(); lua.get("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.safe_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.safe_script("assert(test(10, 11, \"hello\") == 11)")); // function retrieval sol::function test = lua["test"]; REQUIRE(test.call(10, 11, "hello") == 11); // setting a lambda lua["lamb"] = [](int x) { return x * 2; }; REQUIRE_NOTHROW(lua.safe_script("assert(lamb(220) == 440)")); // function retrieval of a lambda sol::function lamb = lua["lamb"]; REQUIRE(lamb.call(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 test1 = lua["no_exist_yet"]; bool present = (bool)test1; REQUIRE_FALSE(present); lua["no_exist_yet"] = 262; sol::optional test2 = lua["no_exist_yet"]; present = (bool)test2; REQUIRE(present); REQUIRE(test2.value() == 262); sol::optional nope = lua["nope"]["kek"]["hah"]; auto nope2 = lua.get>(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 non_nope = lua["nope"]["kek"]["hah"].get>(); sol::optional non_nope2 = lua.get>(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 non_nope3 = lua["nope"]["kek"]["hah"].get>(); sol::optional non_nope4 = lua.get>(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 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/raw set and raw get", "ensure raw setting and getting works through metatables") { sol::state lua; sol::table t = lua.create_table(); t[sol::metatable_key] = lua.create_table_with( sol::meta_function::new_index, [](lua_State* L) { return luaL_error(L, "nay"); }, sol::meta_function::index, [](lua_State* L) { return luaL_error(L, "nay"); }); t.raw_set("a", 2.5); double la = t.raw_get("a"); REQUIRE(la == 2.5); } 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.safe_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 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{ 0x4D } }; REQUIRE(sol_state["requires_move"].get().secret_code == 0x4D); }