// sol3 // The MIT License (MIT) // Copyright (c) 2013-2018 Rapptz, ThePhD and contributors // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "test_sol.hpp" #include #include #include #include #include #include #include #include #include #include #include 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: typedef std::input_iterator_tag iterator_category; typedef int_shim value_type; typedef value_type& reference; typedef const value_type& const_reference; typedef value_type* pointer; typedef std::ptrdiff_t difference_type; 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 gcc_warning_block() { return int_shim(); } std::size_t size() const { return 100; } }; struct my_vec : public std::vector { typedef std::vector base_t; using base_t::base_t; }; namespace sol { template <> struct is_container : std::true_type {}; template <> struct container_traits { static auto begin(lua_State*, my_vec& self) { return self.begin(); } static auto end(lua_State*, my_vec& self) { return self.end(); } static std::ptrdiff_t index_adjustment(lua_State*, my_vec&) { return 0; } }; } // namespace sol auto test_table_return_one() { return sol::as_table(std::vector{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); } auto test_table_return_two() { return sol::as_table(std::vector>{ { "one", 1 }, { "two", 2 }, { "three", 3 } }); } auto test_table_return_three() { return sol::as_table(std::map{ { "name", "Rapptz" }, { "friend", "ThePhD" }, { "project", "sol" } }); } auto test_table_return_four() { return sol::as_table(std::array, 4>{ { { "one", 1 }, { "two", 2 }, { "three", 3 }, { "four", 4 } } }); } template void check_ordered_values(S& src, T& target) { std::size_t idx = 0; auto b = std::begin(target); auto e = std::end(target); for (; b != e; ++b, ++idx) { const auto& v = src[idx]; REQUIRE((*b == v)); } } template void check_unordered_values(S& src, T& target) { std::size_t idx = 0; auto b = std::begin(target); auto e = std::end(target); for (; b != e; ++b, ++idx) { auto sb = std::begin(src); auto se = std::end(src); auto it = std::find(sb, se, *b); REQUIRE((it != se)); } } 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; }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); 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/table conversions (lvalue)", "test table conversions with as_table and nested, when not directly serializing a temporary / new value") { sol::state lua; auto f = []() { std::vector response_words; response_words.push_back("a"); response_words.push_back("b"); response_words.push_back("c"); return sol::as_table(response_words); }; auto g = []() { std::vector response_words; response_words.push_back("a"); response_words.push_back("b"); response_words.push_back("c"); return sol::as_nested(response_words); }; lua["f"] = std::ref(f); lua["g"] = std::ref(g); sol::safe_function sff = lua["f"]; sol::safe_function sfg = lua["g"]; sol::table tf = sff(); sol::table tg = sfg(); std::string af = tf[1]; std::string bf = tf[2]; std::string cf = tf[3]; std::string ag = tf[1]; std::string bg = tf[2]; std::string cg = tf[3]; REQUIRE(tf.size() == 3); REQUIRE(af == "a"); REQUIRE(bf == "b"); REQUIRE(cf == "c"); REQUIRE(tg.size() == 3); REQUIRE(ag == "a"); REQUIRE(bg == "b"); REQUIRE(cg == "c"); } TEST_CASE("containers/table conversions (std::ref)", "test table conversions with as_table and nested, when not directly serializing a temporary / new value") { sol::state lua; std::vector response_words; response_words.push_back("a"); response_words.push_back("b"); response_words.push_back("c"); auto f = [&response_words]() { return sol::as_table(std::ref(response_words)); }; auto g = [&response_words]() { return sol::as_nested(std::ref(response_words)); }; lua["f"] = std::ref(f); lua["g"] = std::ref(g); sol::safe_function sff = lua["f"]; sol::safe_function sfg = lua["g"]; sol::table tf = sff(); sol::table tg = sfg(); std::string af = tf[1]; std::string bf = tf[2]; std::string cf = tf[3]; std::string ag = tf[1]; std::string bg = tf[2]; std::string cg = tf[3]; REQUIRE(tf.size() == 3); REQUIRE(af == "a"); REQUIRE(bf == "b"); REQUIRE(cf == "c"); REQUIRE(tg.size() == 3); REQUIRE(ag == "a"); REQUIRE(bg == "b"); REQUIRE(cg == "c"); } TEST_CASE("containers/table conversion", "test table conversions with as_table and nested") { sol::state lua; lua.open_libraries(sol::lib::base); lua.set_function("bark", []() { return sol::as_nested(std::vector{ "bark", "woof" }); }); lua.set_function("woof", []() { return sol::as_nested(std::vector{ "bark", "woof" }); }); auto result1 = lua.safe_script("v1 = bark()", sol::script_pass_on_error); REQUIRE(result1.valid()); auto result2 = lua.safe_script("v2 = woof()", sol::script_pass_on_error); REQUIRE(result2.valid()); sol::as_table_t> as_table_strings = lua["v1"]; sol::nested> nested_strings = lua["v2"]; std::vector expected_values{ "bark", "woof" }; REQUIRE(as_table_strings.source == expected_values); REQUIRE(nested_strings.source == expected_values); } TEST_CASE("containers/from table argument conversions", "test table conversions without as_table and nested for function args") { const std::vector expected_values{ "bark", "woof" }; sol::state lua; lua.open_libraries(sol::lib::base); lua.set_function("f", [&](std::vector t) { return t == expected_values; }); auto result0 = lua.safe_script("t = { \"bark\", \"woof\" }"); REQUIRE(result0.valid()); auto result1 = lua.safe_script("assert(f(t))", sol::script_pass_on_error); REQUIRE(result1.valid()); sol::function f = lua["f"]; sol::table t = lua["t"]; bool passed = f(t); REQUIRE(passed); } TEST_CASE("containers/deeply nested", "make sure nested works for deeply-nested C++ containers and works as advertised") { typedef std::map info_t; typedef std::vector info_vector; class ModList { public: info_vector list; ModList() { list.push_back(info_t{ {"a", "b"} }); } sol::nested getList () { return sol::nested(list); }; }; sol::state lua; lua.open_libraries(sol::lib::base); lua.new_usertype("ModList", "getList", &ModList::getList ); sol::string_view code = R"( mods = ModList.new() local modlist = mods:getList() print(modlist[1]) assert(type(modlist) == "table") assert(type(modlist[1]) == "table") )"; auto result1 = lua.safe_script(code, sol::script_pass_on_error); REQUIRE(result1.valid()); } 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; }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); std::vector x = lua["x"]; bool areequal = x == v; REQUIRE(areequal); } TEST_CASE("containers/deque roundtrip", "make sure deques can be round-tripped") { sol::state lua; std::deque v{ 1, 2, 3 }; lua.set_function("f", [&]() -> std::deque& { return v; }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); std::deque x = lua["x"]; bool areequal = x == v; REQUIRE(areequal); } TEST_CASE("containers/array roundtrip", "make sure arrays can be round-tripped") { sol::state lua; std::array v{ { 1, 2, 3 } }; lua.set_function("f", [&]() -> std::array& { return v; }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); std::array 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; }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); std::list x = lua["x"]; bool areequal = x == v; REQUIRE(areequal); } TEST_CASE("containers/forward_list roundtrip", "make sure forward_lists can be round-tripped") { sol::state lua; std::forward_list v{ 1, 2, 3 }; lua.set_function("f", [&]() -> std::forward_list& { return v; }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); std::forward_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; }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); 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; }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); 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; }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); 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; }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); std::set x = lua["x"]; bool areequal = x == v; REQUIRE(areequal); } TEST_CASE("containers/vector table roundtrip", "make sure vectors can be round-tripped") { sol::state lua; std::vector v{ 1, 2, 3 }; lua.set_function("f", [&]() { return sol::as_table(v); }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); sol::as_table_t> x = lua["x"]; bool areequal = x.source == v; REQUIRE(areequal); } TEST_CASE("containers/deque table roundtrip", "make sure deques can be round-tripped") { sol::state lua; std::deque v{ 1, 2, 3 }; lua.set_function("f", [&]() { return sol::as_table(v); }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); sol::as_table_t> x = lua["x"]; bool areequal = x.source == v; REQUIRE(areequal); } TEST_CASE("containers/array table roundtrip", "make sure arrays can be round-tripped") { sol::state lua; std::array v{ { 1, 2, 3 } }; lua.set_function("f", [&]() { return sol::as_table(v); }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); sol::as_table_t> x = lua["x"]; bool areequal = x.source == v; REQUIRE(areequal); } TEST_CASE("containers/list table roundtrip", "make sure lists can be round-tripped") { sol::state lua; std::list v{ 1, 2, 3 }; lua.set_function("f", [&]() { return sol::as_table(v); }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); sol::as_table_t> x = lua["x"]; bool areequal = x.source == v; REQUIRE(areequal); } TEST_CASE("containers/forward_list table roundtrip", "make sure forward_lists can be round-tripped") { sol::state lua; std::forward_list v{ 1, 2, 3 }; lua.set_function("f", [&]() { return sol::as_table(v); }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); sol::as_table_t> x = lua["x"]; bool areequal = x.source == v; REQUIRE(areequal); } TEST_CASE("containers/map table roundtrip", "make sure maps can be round-tripped") { sol::state lua; std::map v{ { "a", 1 }, { "b", 2 }, { "c", 3 } }; lua.set_function("f", [&]() { return sol::as_table(v); }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); sol::as_table_t> x = lua["x"]; bool areequal = x.source == v; REQUIRE(areequal); } TEST_CASE("containers/unordered_map table 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", [&]() { return sol::as_table(v); }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); sol::as_table_t> x = lua["x"]; bool areequal = x.source == v; REQUIRE(areequal); } TEST_CASE("containers/unordered_set table roundtrip", "make sure unordered_sets can be round-tripped") { sol::state lua; std::unordered_set v{ 1, 2, 3 }; lua.set_function("f", [&]() { return sol::as_table(v); }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); sol::as_table_t> x = lua["x"]; bool areequal = x.source == v; REQUIRE(areequal); } TEST_CASE("containers/set table roundtrip", "make sure sets can be round-tripped") { sol::state lua; std::set v{ 1, 2, 3 }; lua.set_function("f", [&]() { return sol::as_table(v); }); auto result1 = lua.safe_script("x = f()", sol::script_pass_on_error); REQUIRE(result1.valid()); sol::as_table_t> x = lua["x"]; bool areequal = x.source == 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); { auto result0 = lua.safe_script("assert(a:at(24) == 50)", sol::script_pass_on_error); REQUIRE(result0.valid()); auto result1 = lua.safe_script("a:something()", sol::script_pass_on_error); REQUIRE(result1.valid()); } lua.set("a", obj); { auto result = lua.safe_script("assert(a:at(24) == 50)", sol::script_pass_on_error); REQUIRE(result.valid()); } { auto result = lua.safe_script("a:something()", sol::script_pass_on_error); REQUIRE(result.valid()); } } 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", std::ref(obj)); auto result0 = lua.safe_script("assert(a[24] == 50)", sol::script_pass_on_error); REQUIRE(result0.valid()); auto result1 = lua.safe_script("a[24] = 51", sol::script_pass_on_error); REQUIRE_FALSE(result1.valid()); auto result2 = lua.safe_script("assert(a[24] == 50)", sol::script_pass_on_error); REQUIRE(result2.valid()); } } 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 }); { auto result = lua.safe_script("for k = 1, #b do assert(k == b[k]) end", sol::script_pass_on_error); REQUIRE(result.valid()); } 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); { auto result = lua.safe_script("for k = 1, #b do assert(k == b[k]) end", sol::script_pass_on_error); REQUIRE(result.valid()); } lua.set("b", &w); { auto result = lua.safe_script("for k = 1, #b do assert(k == b[k]) end", sol::script_pass_on_error); REQUIRE(result.valid()); } lua.set("b", std::ref(w)); { auto result = lua.safe_script("for k = 1, #b do assert(k == b[k]) end", sol::script_pass_on_error); REQUIRE(result.valid()); } } #if 0 // LUL const int holders 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 }); { auto result = lua.safe_script("for k, v in pairs(b) do assert(k == v) end", sol::script_pass_on_error); REQUIRE(result.valid()); } { auto result = lua.safe_script("b[1] = 20", sol::script_pass_on_error); REQUIRE_FALSE(result.valid()); } } #endif 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 })); { auto result = lua.safe_script("for k, v in ipairs(b) do assert(k == v) end", sol::script_pass_on_error); REQUIRE(result.valid()); } 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)); { auto result = lua.safe_script("for k, v in ipairs(b) do assert(k == v) end", sol::script_pass_on_error); REQUIRE(result.valid()); } lua.set("b", sol::as_table(&w)); { auto result = lua.safe_script("for k, v in ipairs(b) do assert(k == v) end", sol::script_pass_on_error); REQUIRE(result.valid()); } lua.set("b", sol::as_table(std::ref(w))); { auto result = lua.safe_script("for k, v in ipairs(b) do assert(k == v) end", sol::script_pass_on_error); REQUIRE(result.valid()); } } 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); auto result0 = lua.safe_script(R"( func = function(vecs) for i = 1, #vecs do vec = vecs[i] print(i, ":", vec.x, vec.y, vec.z) end end )", sol::script_pass_on_error); REQUIRE(result0.valid()); sol::protected_function f(lua["func"]); auto pfr1 = f(foo); REQUIRE(pfr1.valid()); auto pfr2 = f(bar); REQUIRE(pfr2.valid()); } 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); lua.set_function("test_four", test_table_return_four); { auto result = lua.safe_script("a = test_one()", sol::script_pass_on_error); REQUIRE(result.valid()); } { auto result = lua.safe_script("b = test_two()", sol::script_pass_on_error); REQUIRE(result.valid()); } { auto result = lua.safe_script("c = test_three()", sol::script_pass_on_error); REQUIRE(result.valid()); } { auto result = lua.safe_script("d = test_four()", sol::script_pass_on_error); REQUIRE(result.valid()); } { auto result = lua.safe_script("assert(#a == 10, 'error')", sol::script_pass_on_error); REQUIRE(result.valid()); } { auto result = lua.safe_script("assert(a[3] == 3, 'error')", sol::script_pass_on_error); REQUIRE(result.valid()); } { auto result = lua.safe_script("assert(b.one == 1, 'error')", sol::script_pass_on_error); REQUIRE(result.valid()); } { auto result = lua.safe_script("assert(b.three == 3, 'error')", sol::script_pass_on_error); REQUIRE(result.valid()); } { auto result = lua.safe_script("assert(c.name == 'Rapptz', 'error')", sol::script_pass_on_error); REQUIRE(result.valid()); } { auto result = lua.safe_script("assert(c.project == 'sol', 'error')", sol::script_pass_on_error); REQUIRE(result.valid()); } { auto result = lua.safe_script("assert(d.one == 1, 'error')", sol::script_pass_on_error); REQUIRE(result.valid()); } { auto result = lua.safe_script("assert(d.three == 3, 'error')", sol::script_pass_on_error); REQUIRE(result.valid()); } { auto result = lua.safe_script("assert(d.four == 4, 'error')", sol::script_pass_on_error); REQUIRE(result.valid()); } sol::table a = lua.get("a"); sol::table b = lua.get("b"); sol::table c = lua.get("c"); sol::table d = lua["d"]; 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"); REQUIRE(d.get("one") == 1); REQUIRE(d.get("three") == 3); REQUIRE(d.get("four") == 4); } 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); auto result = lua.safe_script(R"( b = B.new() a_ref = b.a_list[2] )", sol::script_pass_on_error); REQUIRE(result.valid()); 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 {}; } // namespace sol 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; auto result0 = lua.safe_script(R"( machine:opt():output_help() )", sol::script_pass_on_error); REQUIRE(result0.valid()); 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{ {}, {}, {} }; auto result0 = lua.safe_script(R"( a = foo.new() x = a.seq a.seq = value y = a.readonly_seq )", sol::script_pass_on_error); REQUIRE(result0.valid()); std::list& seqrefx = lua["x"]; std::list& seqrefy = lua["y"]; REQUIRE(&seqrefx == &seqrefy); REQUIRE(seqrefx.size() == 3); auto result = lua.safe_script("a.readonly_seq = value", sol::script_pass_on_error); REQUIRE_FALSE(result.valid()); } TEST_CASE("containers/to_args", "Test that the to_args abstractions works") { sol::state lua; lua.open_libraries(); auto result1 = lua.safe_script("function f (a, b, c, d) print(a, b, c, d) return a, b, c, d end", sol::script_pass_on_error); REQUIRE(result1.valid()); 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") { struct thing { int x = 20; }; thing t{}; sol::state lua; lua.open_libraries(); lua.set_function("f", [&t]() { return std::vector(5, &t); }); auto result1 = lua.safe_script(R"( c = f() )", sol::script_pass_on_error); REQUIRE(result1.valid()); auto result2 = lua.safe_script(R"( check = {} local i = 1 while c[i] do check[i] = c[i] i = i + 1 end )", sol::script_pass_on_error); REQUIRE(result2.valid()); 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); auto result1 = lua.safe_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 )"); REQUIRE(result1.valid()); 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{}; auto pfr = lua.safe_script("t = test.new() t.b = v", sol::script_pass_on_error); REQUIRE_FALSE(pfr.valid()); } } TEST_CASE("containers/input iterators", "test shitty input iterators that are all kinds of B L E H") { 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 > 502 auto result0 = lua.safe_script(R"lua( for k, v in pairs(c) do assert((k - 1) == v:val()) end )lua", sol::script_pass_on_error); REQUIRE(result0.valid()); #endif auto result1 = lua.safe_script(R"lua( for k=1,#c do v = c[k] assert((k - 1) == v:val()) end )lua", sol::script_pass_on_error); REQUIRE(result1.valid()); } TEST_CASE("containers/pairs", "test how well pairs work with the underlying system") { typedef std::pair pair_arr_t[5]; typedef int arr_t[5]; sol::state lua; lua.open_libraries(sol::lib::base); std::vector> a{ { "one", 1 }, { "two", 2 }, { "three", 3 }, { "four", 4 }, { "five", 5 } }; std::array, 5> b{ { { "one", 1 }, { "two", 2 }, { "three", 3 }, { "four", 4 }, { "five", 5 } } }; pair_arr_t c{ { "one", 1 }, { "two", 2 }, { "three", 3 }, { "four", 4 }, { "five", 5 } }; arr_t d = { 1, 2, 3, 4, 5 }; lua["a"] = std::ref(a); lua["b"] = &b; lua["c"] = std::ref(c); lua["d"] = &d; auto result1 = lua.safe_script("av1, av2 = a:get(1)", sol::script_pass_on_error); REQUIRE(result1.valid()); auto result2 = lua.safe_script("bv1, bv2 = b:get(1)", sol::script_pass_on_error); REQUIRE(result2.valid()); auto result3 = lua.safe_script("cv1, cv2 = c:get(1)", sol::script_pass_on_error); REQUIRE(result3.valid()); auto result4 = lua.safe_script("dv1, dv2 = d:get(1)", sol::script_pass_on_error); REQUIRE(result4.valid()); std::vector>& la = lua["a"]; std::array, 5>& lb = lua["b"]; pair_arr_t* plc = lua["c"]; pair_arr_t& lc = *plc; arr_t* pld = lua["d"]; arr_t& ld = *pld; std::pair& va = la[0]; std::pair& vb = lb[0]; std::pair& vc = lc[0]; int& vd = ld[0]; std::string av1 = lua["av1"]; int av2 = lua["av2"]; std::string bv1 = lua["bv1"]; int bv2 = lua["bv2"]; std::string cv1 = lua["cv1"]; int cv2 = lua["cv2"]; int dv1 = lua["dv1"]; sol::lua_nil_t dv2 = lua["dv2"]; REQUIRE(va.first == "one"); REQUIRE(va.second == 1); REQUIRE(vb.first == "one"); REQUIRE(vb.second == 1); REQUIRE(vc.first == "one"); REQUIRE(vc.second == 1); REQUIRE(vd == 1); REQUIRE(av1 == "one"); REQUIRE(av2 == 1); REQUIRE(bv1 == "one"); REQUIRE(bv2 == 1); REQUIRE(cv1 == "one"); REQUIRE(cv2 == 1); REQUIRE(dv1 == 1); REQUIRE(dv2 == sol::lua_nil); } TEST_CASE("containers/pointer types", "check that containers with unique usertypes and pointers or something") { struct base_t { virtual int get() const = 0; }; struct derived_1_t : base_t { virtual int get() const override { return 250; } }; struct derived_2_t : base_t { virtual int get() const override { return 500; } }; sol::state lua; lua.open_libraries(sol::lib::base); derived_1_t d1; derived_2_t d2; std::vector> v1; v1.push_back(std::make_unique()); v1.push_back(std::make_unique()); std::vector v2; v2.push_back(&d1); v2.push_back(&d2); lua["c1"] = std::move(v1); lua["c2"] = &v2; auto result1 = lua.safe_script("b1 = c1[1]", sol::script_pass_on_error); REQUIRE(result1.valid()); base_t* b1 = lua["b1"]; int val1 = b1->get(); REQUIRE(val1 == 250); auto result2 = lua.safe_script("b2 = c2[2]", sol::script_pass_on_error); REQUIRE(result2.valid()); base_t* b2 = lua["b2"]; int val2 = b2->get(); REQUIRE(val2 == 500); } TEST_CASE("containers/initializer-list", "test initializer lists get pushed as tables directly rather than userdata") { SECTION("array-like") { sol::state lua; lua.open_libraries(sol::lib::base, sol::lib::table); lua["c"] = { 1, 2, 3, 4, 5 }; auto result1 = lua.safe_script(R"lua( for k, v in pairs(c) do assert(k == v) end )lua", sol::script_pass_on_error); sol::as_table_t> t1vector = lua["c"]; sol::as_table_t> t1deque = lua["c"]; sol::as_table_t> t1list = lua["c"]; sol::as_table_t> t1flist = lua["c"]; sol::as_table_t> t1set = lua["c"]; const int src[5] = { 1, 2, 3, 4, 5 }; check_ordered_values(src, t1vector.source); check_ordered_values(src, t1deque.source); check_ordered_values(src, t1list.source); check_ordered_values(src, t1flist.source); check_ordered_values(src, t1set.source); } SECTION("map-like") { sol::state lua; lua.open_libraries(sol::lib::base, sol::lib::table); std::pair src[5]{ { "a", 21 }, { "b", 22 }, { "c", 23 }, { "d", 24 }, { "e", 25 } }; lua["c"] = std::initializer_list>{ { "a", 21 }, { "b", 22 }, { "c", 23 }, { "d", 24 }, { "e", 25 } }; sol::as_table_t> t1umap = lua["c"]; sol::as_table_t> t1ummap = lua["c"]; check_unordered_values(src, t1umap.source); check_unordered_values(src, t1ummap.source); } } TEST_CASE("containers/custom indexing", "allow containers to set a custom indexing offset") { sol::state lua; lua.open_libraries(sol::lib::base); lua["c"] = my_vec{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; auto result1 = lua.safe_script("for i=0,9 do assert(i == c[i]) end", sol::script_pass_on_error); REQUIRE(result1.valid()); auto result2 = lua.safe_script("assert(c[10] == nil)", sol::script_pass_on_error); REQUIRE(result2.valid()); auto result3 = lua.safe_script("assert(c[-1] == nil)", sol::script_pass_on_error); REQUIRE(result3.valid()); } TEST_CASE("containers/containers of pointers", "containers of pointers shouldn't have their value_type's overly stripped") { sol::state lua; class MyContainer { public: typedef int** iterator; typedef int* value_type; std::vector m_vec; inline iterator begin() { return m_vec.data(); } inline iterator end() { return m_vec.data() + m_vec.size(); } inline void push_back(value_type v) { m_vec.push_back(v); } }; int a = 500; int b = 600; MyContainer ctr; ctr.push_back(&a); ctr.push_back(&b); lua["c"] = ctr; { auto result1 = lua.safe_script("ap = c[1]", sol::script_pass_on_error); REQUIRE(result1.valid()); auto result2 = lua.safe_script("bp = c[2]", sol::script_pass_on_error); REQUIRE(result2.valid()); int* ap = lua["ap"]; int* bp = lua["bp"]; REQUIRE(ap == &a); REQUIRE(bp == &b); REQUIRE(*ap == 500); REQUIRE(*bp == 600); } std::unordered_map ptrs; ptrs[5] = &a; ptrs[6] = &b; lua["c2"] = ptrs; { auto result1 = lua.safe_script("ap = c2[5]", sol::script_pass_on_error); REQUIRE(result1.valid()); auto result2 = lua.safe_script("bp = c2[6]", sol::script_pass_on_error); REQUIRE(result2.valid()); int* ap = lua["ap"]; int* bp = lua["bp"]; REQUIRE(ap == &a); REQUIRE(bp == &b); REQUIRE(*ap == 500); REQUIRE(*bp == 600); } }