#define SOL_CHECK_ARGUMENTS #include #include #include #include #include #include #include #include #include std::vector test_table_return_one() { return{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; } std::vector> test_table_return_two() { return{ { "one", 1 },{ "two", 2 },{ "three", 3 } }; } std::map test_table_return_three() { return{ { "name", "Rapptz" },{ "friend", "ThePhD" },{ "project", "sol" } }; } TEST_CASE("containers/returns", "make sure that even references to vectors are being serialized as tables") { sol::state lua; std::vector v{ 1, 2, 3 }; lua.set_function("f", [&]() -> std::vector& { return v; }); lua.script("x = f()"); sol::object x = lua["x"]; sol::type xt = x.get_type(); REQUIRE(xt == sol::type::userdata); sol::table t = x; bool matching; matching = t[1] == 1; REQUIRE(matching); matching = t[2] == 2; REQUIRE(matching); matching = t[3] == 3; REQUIRE(matching); } TEST_CASE("containers/vector_roundtrip", "make sure vectors can be round-tripped") { sol::state lua; std::vector v{ 1, 2, 3 }; lua.set_function("f", [&]() -> std::vector& { return v; }); lua.script("x = f()"); std::vector x = lua["x"]; bool areequal = x == v; REQUIRE(areequal); } TEST_CASE("containers/list_roundtrip", "make sure lists can be round-tripped") { sol::state lua; std::list v{ 1, 2, 3 }; lua.set_function("f", [&]() -> std::list& { return v; }); lua.script("x = f()"); std::list x = lua["x"]; bool areequal = x == v; REQUIRE(areequal); } TEST_CASE("containers/map_roundtrip", "make sure maps can be round-tripped") { sol::state lua; std::map v{ { "a", 1 },{ "b", 2 },{ "c", 3 } }; lua.set_function("f", [&]() -> std::map& { return v; }); lua.script("x = f()"); std::map x = lua["x"]; bool areequal = x == v; REQUIRE(areequal); } TEST_CASE("containers/unordered_map_roundtrip", "make sure unordered_maps can be round-tripped") { sol::state lua; std::unordered_map v{ { "a", 1 },{ "b", 2 },{ "c", 3 } }; lua.set_function("f", [&]() -> std::unordered_map& { return v; }); lua.script("x = f()"); std::unordered_map x = lua["x"]; bool areequal = x == v; REQUIRE(areequal); } TEST_CASE("containers/unordered_set_roundtrip", "make sure unordered_sets can be round-tripped") { sol::state lua; std::unordered_set v{ 1, 2, 3 }; lua.set_function("f", [&]() -> std::unordered_set& { return v; }); lua.script("x = f()"); std::unordered_set x = lua["x"]; bool areequal = x == v; REQUIRE(areequal); } TEST_CASE("containers/set_roundtrip", "make sure sets can be round-tripped") { sol::state lua; std::set v{ 1, 2, 3 }; lua.set_function("f", [&]() -> std::set& { return v; }); lua.script("x = f()"); std::set x = lua["x"]; bool areequal = x == v; REQUIRE(areequal); } TEST_CASE("containers/custom-usertype", "make sure container usertype metatables can be overridden") { typedef std::unordered_map bark; sol::state lua; lua.open_libraries(); lua.new_usertype("bark", "something", [](const bark& b) { INFO("It works: " << b.at(24)); }, "size", &bark::size, "at", sol::resolve(&bark::at), "clear", &bark::clear ); bark obj{ { 24, 50 } }; lua.set("a", &obj); REQUIRE_NOTHROW(lua.script("assert(a:at(24) == 50)")); REQUIRE_NOTHROW(lua.script("a:something()")); lua.set("a", obj); REQUIRE_NOTHROW(lua.script("assert(a:at(24) == 50)")); REQUIRE_NOTHROW(lua.script("a:something()")); } TEST_CASE("containers/const-serialization-kvp", "make sure const keys / values are respected") { typedef std::map bark; sol::state lua; lua.open_libraries(); bark obj{ { 24, 50 } }; lua.set("a", &obj); REQUIRE_NOTHROW(lua.script("assert(a[24] == 50)")); REQUIRE_THROWS(lua.script("a[24] = 51")); REQUIRE_NOTHROW(lua.script("assert(a[24] == 50)")); } TEST_CASE("containers/basic-serialization", "make sure containers are turned into proper userdata and have basic hooks established") { typedef std::vector woof; sol::state lua; lua.open_libraries(); lua.set("b", woof{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 }); REQUIRE_NOTHROW( lua.script("for k = 1, #b do assert(k == b[k]) end") ); woof w{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 }; lua.set("b", w); REQUIRE_NOTHROW( lua.script("for k = 1, #b do assert(k == b[k]) end") ); lua.set("b", &w); REQUIRE_NOTHROW( lua.script("for k = 1, #b do assert(k == b[k]) end") ); lua.set("b", std::ref(w)); REQUIRE_NOTHROW( lua.script("for k = 1, #b do assert(k == b[k]) end") ); } #if 0 // glibc is a fuccboi TEST_CASE("containers/const-serialization", "make sure containers are turned into proper userdata and the basic hooks respect const-ness") { typedef std::vector woof; sol::state lua; lua.open_libraries(); lua.set("b", woof{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 }); REQUIRE_NOTHROW( lua.script("for k, v in pairs(b) do assert(k == v) end"); ); REQUIRE_THROWS(lua.script("b[1] = 20")); } #endif // Fuck you, glibc TEST_CASE("containers/table-serialization", "ensure types can be serialized as tables still") { typedef std::vector woof; sol::state lua; lua.open_libraries(); lua.set("b", sol::as_table(woof{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 })); REQUIRE_NOTHROW( lua.script("for k, v in ipairs(b) do assert(k == v) end") ); woof w{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 }; lua.set("b", sol::as_table(w)); REQUIRE_NOTHROW( lua.script("for k, v in ipairs(b) do assert(k == v) end") ); lua.set("b", sol::as_table(&w)); REQUIRE_NOTHROW( lua.script("for k, v in ipairs(b) do assert(k == v) end") ); lua.set("b", sol::as_table(std::ref(w))); REQUIRE_NOTHROW( lua.script("for k, v in ipairs(b) do assert(k == v) end") ); } TEST_CASE("containers/const-correctness", "usertype metatable names should reasonably ignore const attributes") { struct Vec { int x, y, z; }; sol::state lua; lua.open_libraries(sol::lib::base); lua.new_usertype("Vec", "x", &Vec::x, "y", &Vec::y, "z", &Vec::z); Vec vec; vec.x = 1; vec.y = 2; vec.z = -3; std::vector foo; foo.push_back(vec); std::vector bar; bar.push_back(&vec); lua.script(R"( func = function(vecs) for i = 1, #vecs do vec = vecs[i] print(i, ":", vec.x, vec.y, vec.z) end end )"); REQUIRE_NOTHROW([&]{ lua["func"](foo); lua["func"](bar); }()); } TEST_CASE("containers/arbitrary-creation", "userdata and tables should be usable from standard containers") { sol::state lua; lua.open_libraries(sol::lib::base); lua.set_function("test_one", test_table_return_one); lua.set_function("test_two", test_table_return_two); lua.set_function("test_three", test_table_return_three); REQUIRE_NOTHROW(lua.script("a = test_one()")); REQUIRE_NOTHROW(lua.script("b = test_two()")); REQUIRE_NOTHROW(lua.script("c = test_three()")); REQUIRE_NOTHROW(lua.script("assert(#a == 10, 'error')")); REQUIRE_NOTHROW(lua.script("assert(a[3] == 3, 'error')")); REQUIRE_NOTHROW(lua.script("assert(b.one == 1, 'error')")); REQUIRE_NOTHROW(lua.script("assert(b.three == 3, 'error')")); REQUIRE_NOTHROW(lua.script("assert(c.name == 'Rapptz', 'error')")); REQUIRE_NOTHROW(lua.script("assert(c.project == 'sol', 'error')")); sol::table a = lua.get("a"); sol::table b = lua.get("b"); sol::table c = lua.get("c"); REQUIRE(a.size() == 10ULL); REQUIRE(a.get(3) == 3); REQUIRE(b.get("one") == 1); REQUIRE(b.get("three") == 3); REQUIRE(c.get("name") == "Rapptz"); REQUIRE(c.get("project") == "sol"); } TEST_CASE("containers/extra-functions", "make sure the manipulation functions are present and usable and working across various container types") { sol::state lua; lua.open_libraries(); lua.script(R"( function g (x) x:add(20) end function h (x) x:add(20, 40) end function i (x) x:clear() end function sf (x,v) return x:find(v) end )"); // Have the function we // just defined in Lua sol::function g = lua["g"]; sol::function h = lua["h"]; sol::function i = lua["i"]; sol::function sf = lua["sf"]; // Set a global variable called // "arr" to be a vector of 5 lements lua["c_arr"] = std::array{ { 2, 4, 6, 8, 10 } }; lua["arr"] = std::vector{ 2, 4, 6, 8, 10 }; lua["map"] = std::map{ { 1 , 2 },{ 2, 4 },{ 3, 6 },{ 4, 8 },{ 5, 10 } }; lua["set"] = std::set{ 2, 4, 6, 8, 10 }; std::array& c_arr = lua["c_arr"]; std::vector& arr = lua["arr"]; std::map& map = lua["map"]; std::set& set = lua["set"]; REQUIRE(c_arr.size() == 5); REQUIRE(arr.size() == 5); REQUIRE(map.size() == 5); REQUIRE(set.size() == 5); g(lua["set"]); g(lua["arr"]); h(lua["map"]); REQUIRE(arr.size() == 6); REQUIRE(map.size() == 6); REQUIRE(set.size() == 6); { int r = sf(set, 8); REQUIRE(r == 8); sol::object rn = sf(set, 9); REQUIRE(rn == sol::nil); } { int r = sf(map, 3); REQUIRE(r == 6); sol::object rn = sf(map, 9); REQUIRE(rn == sol::nil); } i(lua["arr"]); i(lua["map"]); i(lua["set"]); REQUIRE(arr.empty()); REQUIRE(map.empty()); REQUIRE(set.empty()); REQUIRE_NOTHROW([&]() { lua.script(R"( c_arr[1] = 7 c_arr[2] = 7 c_arr[3] = 7 )"); }()); SECTION("throw test") { sol::state tlua; tlua["c_arr"] = std::array{ { 2, 4, 6, 8, 10 } }; REQUIRE_THROWS([&]() { tlua.script(R"( c_arr[0] = 7 )"); }()); } SECTION("throw test 2") { sol::state tlua; tlua["c_arr"] = std::array{ { 2, 4, 6, 8, 10 } }; REQUIRE_THROWS([&]() { tlua.script(R"( c_arr[-1] = 7 )"); }()); } } TEST_CASE("containers/usertype-transparency", "Make sure containers pass their arguments through transparently and push the results as references, not new values") { class A { public: int a; A(int b = 2) : a(b) {}; void func() { } }; struct B { B() { for (std::size_t i = 0; i < 20; ++i) { a_list.emplace_back(static_cast(i)); } } std::vector a_list; }; sol::state lua; lua.new_usertype("B", "a_list", &B::a_list ); lua.script(R"( b = B.new() a_ref = b.a_list[2] )"); B& b = lua["b"]; A& a_ref = lua["a_ref"]; REQUIRE(&b.a_list[1] == &a_ref); REQUIRE(b.a_list[1].a == a_ref.a); } struct options { static int livingcount; static options* last; options() { ++livingcount; last = this; INFO("constructor: " << this); } std::string output_help() { last = this; INFO("func: " << this); return ""; } void begin() {} void end() {} ~options() { last = this; --livingcount; } }; options* options::last = nullptr; int options::livingcount = 0; struct machine { options opt; }; namespace sol { template <> struct is_container : std::false_type {}; } TEST_CASE("containers/is-container", "make sure the is_container trait behaves properly") { sol::state lua; lua.open_libraries(); lua.new_usertype("options_type", "output_help", &options::output_help ); lua.new_usertype("machine_type", "new", sol::no_constructor, "opt", [](machine& m) { return &m.opt; }, "copy_opt", [](machine& m) { return m.opt; } ); { machine m; lua["machine"] = &m; lua.script(R"( machine:opt():output_help() )"); REQUIRE(options::last == &m.opt); REQUIRE(options::livingcount == 1); } REQUIRE(options::livingcount == 0); } TEST_CASE("containers/readonly", "make sure readonly members are stored appropriately") { sol::state lua; lua.open_libraries(); struct bar { int x = 24; }; struct foo { std::list seq; }; lua.new_usertype( "foo", "seq", &foo::seq, // this one works "readonly_seq", sol::readonly(&foo::seq) // this one does not work ); lua["value"] = std::list{ {},{},{} }; lua.script(R"( a = foo.new() x = a.seq a.seq = value y = a.readonly_seq )"); std::list& seqrefx = lua["x"]; std::list& seqrefy = lua["y"]; REQUIRE(&seqrefx == &seqrefy); REQUIRE(seqrefx.size() == 3); auto result = lua.do_string(R"( a.readonly_seq = value; )"); REQUIRE_FALSE(result.valid()); } TEST_CASE("containers/to_args", "Test that the to_args abstractions works") { sol::state lua; lua.open_libraries(); lua.script("function f (a, b, c, d) print(a, b, c, d) return a, b, c, d end"); sol::function f = lua["f"]; int a, b, c, d; std::vector v2{ 3, 4 }; sol::tie(a, b, c, d) = f(1, 2, sol::as_args(v2)); REQUIRE(a == 1); REQUIRE(b == 2); REQUIRE(c == 3); REQUIRE(d == 4); std::set v4{ 7, 6, 8, 5 }; sol::tie(a, b, c, d) = f(sol::as_args(v4)); REQUIRE(a == 5); REQUIRE(b == 6); REQUIRE(c == 7); REQUIRE(d == 8); int v3[] = { 10, 11, 12 }; sol::tie(a, b, c, d) = f(9, sol::as_args(v3)); REQUIRE(a == 9); REQUIRE(b == 10); REQUIRE(c == 11); REQUIRE(d == 12); } TEST_CASE("containers/ipairs-test", "ensure that abstractions roundtrip properly and push nils to stop pairs / ipairs") { struct thing { int x = 20; }; thing t{}; sol::state lua; lua.open_libraries(); lua.set_function("f", [&t]() { return std::vector(5, &t); }); lua.script(R"( c = f() )"); lua.script(R"( check = {} local i = 1 while c[i] do check[i] = c[i] i = i + 1 end )"); sol::table c = lua["check"]; for (std::size_t i = 1; i < 6; ++i) { thing& ct = c[i]; REQUIRE(&t == &ct); REQUIRE(ct.x == 20); } } TEST_CASE("containers/append-idiom", "ensure the append-idiom works as intended") { sol::state lua; lua.open_libraries(sol::lib::base); lua.script( R"( function f_fill(vec) print("#vec in lua: " .. #vec) for k = 1, #vec do vec[k] = k end print("#vec in lua: " .. #vec) end function f_append(vec) print("#vec in lua: " .. #vec) vec[#vec] = -10456407 vec[#vec + 1] = -54 print("#vec in lua: " .. #vec) end )"); std::vector fill_cmp{ 1, 2, 3 }; std::vector append_cmp{ -1, -1, -10456407, -54 }; std::vector vec1{ -1, -1, -1 }; std::vector vec2{ -1, -1, -1 }; REQUIRE(vec1.size() == 3); lua["f_fill"](vec1); REQUIRE(vec1.size() == 3); REQUIRE(vec1 == fill_cmp); REQUIRE(vec2.size() == 3); lua["f_append"](vec2); REQUIRE(vec2.size() == 4); REQUIRE(vec2 == append_cmp); } TEST_CASE("containers/non_copyable", "make sure non-copyable types in containers behave properly when stored as a member variable in a bound usertype") { struct non_copyable { non_copyable(non_copyable&& other) noexcept = default; non_copyable& operator=(non_copyable&& other) noexcept = default; non_copyable(const non_copyable& other) noexcept = delete; non_copyable& operator=(const non_copyable& other) noexcept = delete; }; struct test { std::vector b; test() : b() {} test(test&&) = default; test& operator=(test&&) = default; test(const test&) = delete; test& operator=(const test&) = delete; }; SECTION("normal") { sol::state lua; lua.new_usertype("test", "b", sol::readonly(&test::b) ); lua["v"] = std::vector{}; REQUIRE_THROWS([&lua]() { lua.script("t = test.new()\nt.b = v"); }()); } SECTION("simple") { sol::state lua; lua.new_simple_usertype("test", "b", sol::readonly(&test::b) ); lua["v"] = std::vector{}; REQUIRE_THROWS([&lua]() { lua.script("t = test.new()\nt.b = v"); }()); } } TEST_CASE("containers/input_iterators", "test shitty input iterators that are all kinds of B L E H") { class int_shim { public: int_shim() = default; int_shim(int x) : x_(x) {} int val() const { return x_; } private: int x_ = -1; }; class input_it : public std::iterator { public: input_it() = default; input_it(int n, int m) : n_(n), m_(m), value_(n_) { assert(n_ >= 0); assert(m_ >= 0); assert(n_ <= m_); if (!n_ && !m_) { n_ = -1; m_ = -1; value_ = -1; } } const int_shim &operator*() const { return value_; } const int_shim *operator->() const { return &value_; } input_it &operator++() { assert(n_ >= 0); assert(m_ >= 0); if (n_ == m_ - 1) { n_ = m_ = -1; } else { ++n_; } value_ = n_; return *this; } bool operator==(const input_it &i) const { return n_ == i.n_ && m_ == i.m_; } bool operator!=(const input_it &i) const { return !(*this == i); } private: int n_ = -1; int m_ = -1; int_shim value_; }; class not_really_a_container { public: using value_type = int_shim; using iterator = input_it; using const_iterator = input_it; const_iterator begin() const { return iterator(0, 100); } const_iterator end() const { return iterator(); } value_type fuck_off_gcc_warning() { // "typedef not used locally" // but it's used elsewhere in the code GCC // so maybe your shitty warning should go // fuck itself??? return int_shim(); } std::size_t size() const { return 100; } }; sol::state lua; lua.open_libraries(sol::lib::base, sol::lib::package); lua.new_usertype("int_shim", "new", sol::no_constructor, "val", &int_shim::val); not_really_a_container c; lua["c"] = &c; #if SOL_LUA_VERSION > 503 lua.script(R"lua( for k, v in pairs(c) do assert((k - 1) == v:val()) end )lua"); #else lua.script(R"lua( for k=1,#c do v = c[k] assert((k - 1) == v:val()) end )lua"); #endif }