sol2/tests/runtime_tests/source/lua_value.cpp
ThePhD 053a7fdcb1
Fix #949, #935, #948, #945, and #933.
We will need a new feature to fix #941, which is related to #872.
2020-03-31 00:24:23 -04:00

402 lines
16 KiB
C++

// sol3
// The MIT License (MIT)
// Copyright (c) 2013-2019 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 "sol_test.hpp"
#include <catch.hpp>
#include <vector>
#include <map>
#include <thread>
#include <mutex>
#include <string_view>
#include <variant>
struct int_entry {
int value;
int_entry() : value(0) {
}
int_entry(int v) : value(v) {
}
bool operator==(const int_entry& e) const {
return value == e.value;
}
};
std::mutex lua_value_construct_require_mutex;
void lua_value_construct_race() {
sol::state lua;
try {
lua.open_libraries();
lua["a"] = sol::lua_value(lua, 24);
int a = lua["a"];
{
std::lock_guard<std::mutex> lg(lua_value_construct_require_mutex);
REQUIRE(a == 24);
}
}
catch (const sol::error& e) {
std::lock_guard<std::mutex> lg(lua_value_construct_require_mutex);
INFO(e.what());
REQUIRE(false);
}
catch (...) {
std::lock_guard<std::mutex> lg(lua_value_construct_require_mutex);
REQUIRE(false);
}
}
TEST_CASE("lua_value/nested", "make nested values can be put in lua_value properly") {
#if defined(SOL_STD_VARIANT) && SOL_STD_VARIANT
using mixed_table_entry = std::variant<int, int_entry, std::string>;
using nested_entry = std::variant<int, int_entry, std::string, std::vector<mixed_table_entry>>;
const std::vector<std::variant<int, int_entry>> mixed_table_truth = { 1, int_entry(2), 3, int_entry(4), 5 };
const std::vector<nested_entry> mixed_nested_table_truth
= { 1, int_entry(2), 3, int_entry(4), std::vector<mixed_table_entry> { 5, 6, int_entry(7), "8" } };
sol::state lua;
sol::lua_value lv_mixed_table(lua, sol::array_value { 1, int_entry(2), 3, int_entry(4), 5 });
sol::lua_value lv_mixed_nested_table(lua, sol::array_value { 1, int_entry(2), 3, int_entry(4), sol::array_value { 5, 6, int_entry(7), "8" } });
REQUIRE(lv_mixed_table.is<sol::table>());
REQUIRE(lv_mixed_nested_table.is<sol::table>());
std::vector<std::variant<int, int_entry>> mixed_table_value_lv = lv_mixed_table.as<std::vector<std::variant<int, int_entry>>>();
std::vector<nested_entry> mixed_nested_table_value_lv = lv_mixed_nested_table.as<std::vector<nested_entry>>();
REQUIRE(mixed_table_truth == mixed_table_value_lv);
REQUIRE(mixed_nested_table_truth == mixed_nested_table_value_lv);
SECTION("type check (object)") {
sol::object obj_mixed_table(lv_mixed_table.value());
sol::object obj_mixed_nested_table(lv_mixed_nested_table.value());
REQUIRE(obj_mixed_table.is<sol::table>());
REQUIRE(obj_mixed_nested_table.is<sol::table>());
std::vector<std::variant<int, int_entry>> mixed_table_value = obj_mixed_table.as<std::vector<std::variant<int, int_entry>>>();
std::vector<nested_entry> mixed_nested_table_value = obj_mixed_nested_table.as<std::vector<nested_entry>>();
REQUIRE(mixed_table_truth == mixed_table_value);
REQUIRE(mixed_nested_table_truth == mixed_nested_table_value);
}
SECTION("pushing/popping") {
lua["obj_mixed_table"] = lv_mixed_table;
lua["obj_mixed_nested_table"] = lv_mixed_nested_table;
sol::lua_value obj_mixed_table = lua["obj_mixed_table"];
sol::lua_value obj_mixed_nested_table = lua["obj_mixed_nested_table"];
REQUIRE(obj_mixed_table.is<sol::table>());
REQUIRE(obj_mixed_nested_table.is<sol::table>());
std::vector<std::variant<int, int_entry>> mixed_table_value = obj_mixed_table.as<std::vector<std::variant<int, int_entry>>>();
std::vector<nested_entry> mixed_nested_table_value = obj_mixed_nested_table.as<std::vector<nested_entry>>();
REQUIRE(mixed_table_truth == mixed_table_value);
REQUIRE(mixed_nested_table_truth == mixed_nested_table_value);
}
SECTION("pushing/popping (object)") {
lua["obj_mixed_table"] = lv_mixed_table;
lua["obj_mixed_nested_table"] = lv_mixed_nested_table;
sol::object obj_mixed_table = lua["obj_mixed_table"];
sol::object obj_mixed_nested_table = lua["obj_mixed_nested_table"];
REQUIRE(obj_mixed_table.is<sol::table>());
REQUIRE(obj_mixed_nested_table.is<sol::table>());
std::vector<std::variant<int, int_entry>> mixed_table_value = obj_mixed_table.as<std::vector<std::variant<int, int_entry>>>();
std::vector<nested_entry> mixed_nested_table_value = obj_mixed_nested_table.as<std::vector<nested_entry>>();
REQUIRE(mixed_table_truth == mixed_table_value);
REQUIRE(mixed_nested_table_truth == mixed_nested_table_value);
}
#else
REQUIRE(true);
#endif // C++17
}
TEST_CASE("lua_value/nested key value", "make nested values (key value) can be put in lua_value properly") {
#if defined(SOL_STD_VARIANT) && SOL_STD_VARIANT
using mixed_table_entry = std::variant<int, int_entry, std::string>;
using nested_entry = std::variant<int, int_entry, std::string, std::vector<mixed_table_entry>>;
const std::vector<std::variant<int, int_entry>> mixed_table_truth = { 1, int_entry(2), 3, int_entry(4), 5 };
const std::vector<nested_entry> mixed_nested_table_truth
= { 1, int_entry(2), 3, int_entry(4), std::vector<mixed_table_entry> { 5, 6, int_entry(7), "8" } };
sol::state lua;
sol::lua_value lv_mixed_table(lua, sol::array_value { 1, int_entry(2), 3, int_entry(4), 5 });
sol::lua_value lv_mixed_nested_table(lua, sol::array_value { 1, int_entry(2), 3, int_entry(4), sol::array_value { 5, 6, int_entry(7), "8" } });
REQUIRE(lv_mixed_table.is<sol::table>());
REQUIRE(lv_mixed_nested_table.is<sol::table>());
std::vector<std::variant<int, int_entry>> mixed_table_value_lv = lv_mixed_table.as<std::vector<std::variant<int, int_entry>>>();
std::vector<nested_entry> mixed_nested_table_value_lv = lv_mixed_nested_table.as<std::vector<nested_entry>>();
REQUIRE(mixed_table_truth == mixed_table_value_lv);
REQUIRE(mixed_nested_table_truth == mixed_nested_table_value_lv);
SECTION("type check (object)") {
sol::object obj_mixed_table(lv_mixed_table.value());
sol::object obj_mixed_nested_table(lv_mixed_nested_table.value());
REQUIRE(obj_mixed_table.is<sol::table>());
REQUIRE(obj_mixed_nested_table.is<sol::table>());
std::vector<std::variant<int, int_entry>> mixed_table_value = obj_mixed_table.as<std::vector<std::variant<int, int_entry>>>();
std::vector<nested_entry> mixed_nested_table_value = obj_mixed_nested_table.as<std::vector<nested_entry>>();
REQUIRE(mixed_table_truth == mixed_table_value);
REQUIRE(mixed_nested_table_truth == mixed_nested_table_value);
}
SECTION("pushing/popping") {
lua["obj_mixed_table"] = lv_mixed_table;
lua["obj_mixed_nested_table"] = lv_mixed_nested_table;
sol::lua_value obj_mixed_table = lua["obj_mixed_table"];
sol::lua_value obj_mixed_nested_table = lua["obj_mixed_nested_table"];
REQUIRE(obj_mixed_table.is<sol::table>());
REQUIRE(obj_mixed_nested_table.is<sol::table>());
std::vector<std::variant<int, int_entry>> mixed_table_value = obj_mixed_table.as<std::vector<std::variant<int, int_entry>>>();
std::vector<nested_entry> mixed_nested_table_value = obj_mixed_nested_table.as<std::vector<nested_entry>>();
REQUIRE(mixed_table_truth == mixed_table_value);
REQUIRE(mixed_nested_table_truth == mixed_nested_table_value);
}
SECTION("pushing/popping (object)") {
lua["obj_mixed_table"] = lv_mixed_table;
lua["obj_mixed_nested_table"] = lv_mixed_nested_table;
sol::object obj_mixed_table = lua["obj_mixed_table"];
sol::object obj_mixed_nested_table = lua["obj_mixed_nested_table"];
REQUIRE(obj_mixed_table.is<sol::table>());
REQUIRE(obj_mixed_nested_table.is<sol::table>());
std::vector<std::variant<int, int_entry>> mixed_table_value = obj_mixed_table.as<std::vector<std::variant<int, int_entry>>>();
std::vector<nested_entry> mixed_nested_table_value = obj_mixed_nested_table.as<std::vector<nested_entry>>();
REQUIRE(mixed_table_truth == mixed_table_value);
REQUIRE(mixed_nested_table_truth == mixed_nested_table_value);
}
#else
REQUIRE(true);
#endif // C++17
}
TEST_CASE("lua_value/basic types", "make sure we can stick values and nested values in a lua_value and retrieve them") {
sol::state lua;
const int_entry userdata_truth = int_entry(3);
const std::vector<int> int_table_truth = { 1, 2, 3, 4, 5 };
const std::map<int, int> int_map_truth = { { 1, 2 }, { 3, 4 }, { 5, 6 } };
sol::lua_value lv_int(lua, 1);
sol::lua_value lv_double(lua, 2.0);
sol::lua_value lv_string(lua, "heyo");
sol::lua_value lv_lstring(lua, L"hiyo");
sol::lua_value lv_bool(lua, true);
sol::lua_value lv_nil(lua, sol::lua_nil);
sol::lua_value lv_userdata(lua, int_entry(3));
sol::lua_value lv_int_table(lua, { 1, 2, 3, 4, 5 });
sol::lua_value lv_int_map(lua, { { 1, 2 }, { 3, 4 }, { 5, 6 } });
REQUIRE(lv_int.is<int>());
REQUIRE(lv_double.is<double>());
REQUIRE(lv_string.is<std::string>());
REQUIRE(lv_lstring.is<std::string>());
REQUIRE(lv_bool.is<bool>());
REQUIRE(lv_nil.is<sol::lua_nil_t>());
REQUIRE(lv_userdata.is<sol::userdata>());
REQUIRE(lv_userdata.is<int_entry>());
REQUIRE(lv_int_table.is<sol::table>());
REQUIRE(lv_int_map.is<sol::table>());
REQUIRE(lv_int.as<int>() == 1);
REQUIRE(lv_double.as<double>() == 2.0);
REQUIRE(lv_string.as<std::string>() == "heyo");
REQUIRE(lv_lstring.as<std::string>() == "hiyo");
REQUIRE(lv_lstring.as<std::wstring>() == L"hiyo");
REQUIRE(lv_bool.as<bool>());
REQUIRE(lv_nil.as<sol::lua_nil_t>() == sol::lua_nil);
REQUIRE(lv_userdata.as<int_entry>() == userdata_truth);
std::vector<int> int_table_value_lv = lv_int_table.as<std::vector<int>>();
REQUIRE(int_table_truth == int_table_value_lv);
std::map<int, int> int_map_value_lv = lv_int_map.as<std::map<int, int>>();
REQUIRE(int_map_truth == int_map_value_lv);
SECTION("type check (object)") {
sol::object obj_int(lv_int.value());
sol::object obj_double(lv_double.value());
sol::object obj_string(lv_string.value());
sol::object obj_lstring(lv_lstring.value());
sol::object obj_bool(lv_bool.value());
sol::object obj_nil(lv_nil.value());
sol::object obj_userdata(lv_userdata.value());
sol::object obj_int_table(lv_int_table.value());
sol::object obj_int_map(lv_int_map.value());
REQUIRE(obj_int.is<int>());
REQUIRE(obj_double.is<double>());
REQUIRE(obj_string.is<std::string>());
REQUIRE(obj_lstring.is<std::string>());
REQUIRE(obj_bool.is<bool>());
REQUIRE(obj_nil.is<sol::lua_nil_t>());
REQUIRE(obj_userdata.is<sol::userdata>());
REQUIRE(obj_userdata.is<int_entry>());
REQUIRE(obj_int_table.is<sol::table>());
REQUIRE(obj_int_map.is<sol::table>());
REQUIRE(obj_int.as<int>() == 1);
REQUIRE(obj_double.as<double>() == 2.0);
REQUIRE(obj_string.as<std::string>() == "heyo");
REQUIRE(obj_lstring.as<std::string>() == "hiyo");
REQUIRE(obj_lstring.as<std::wstring>() == L"hiyo");
REQUIRE(obj_bool.as<bool>());
REQUIRE(obj_userdata.as<int_entry>() == userdata_truth);
REQUIRE(obj_nil.as<sol::lua_nil_t>() == sol::lua_nil);
std::vector<int> int_table_value = obj_int_table.as<std::vector<int>>();
REQUIRE(int_table_truth == int_table_value);
std::map<int, int> int_map_value = obj_int_map.as<std::map<int, int>>();
REQUIRE(int_map_truth == int_map_value);
}
SECTION("push/popping") {
lua["obj_int"] = lv_int;
lua["obj_double"] = lv_double;
lua["obj_string"] = lv_string;
lua["obj_lstring"] = lv_lstring;
lua["obj_bool"] = lv_bool;
lua["obj_nil"] = lv_nil;
lua["obj_userdata"] = lv_userdata;
lua["obj_int_table"] = lv_int_table;
lua["obj_int_map"] = lv_int_map;
// these all actually invoke the constructor
// so do one .get<> explicitly to ensure it's
// working correctl for a few cases...
// but it's nice to make sure it's all there now
sol::lua_value obj_int = lua["obj_int"].get<sol::lua_value>();
sol::lua_value obj_double = lua["obj_double"].get<sol::lua_value>();
sol::lua_value obj_string = lua["obj_string"].get<sol::lua_value>();
sol::lua_value obj_lstring = lua["obj_lstring"].get<sol::lua_value>();
sol::lua_value obj_bool = lua["obj_bool"].get<sol::lua_value>();
sol::lua_value obj_nil = lua["obj_nil"];
sol::lua_value obj_userdata = lua["obj_userdata"];
sol::lua_value obj_int_table = lua["obj_int_table"];
sol::lua_value obj_int_map = lua["obj_int_map"];
REQUIRE(obj_int.is<int>());
REQUIRE(obj_double.is<double>());
REQUIRE(obj_string.is<std::string>());
REQUIRE(obj_lstring.is<std::string>());
REQUIRE(obj_bool.is<bool>());
REQUIRE(obj_nil.is<sol::lua_nil_t>());
REQUIRE(obj_int_table.is<sol::table>());
REQUIRE(obj_int_map.is<sol::table>());
REQUIRE(obj_int.as<int>() == 1);
REQUIRE(obj_double.as<double>() == 2.0);
REQUIRE(obj_string.as<std::string>() == "heyo");
REQUIRE(obj_lstring.as<std::string>() == "hiyo");
REQUIRE(obj_lstring.as<std::wstring>() == L"hiyo");
REQUIRE(obj_bool.as<bool>());
REQUIRE(obj_nil.as<sol::lua_nil_t>() == sol::lua_nil);
std::vector<int> int_table_value = obj_int_table.as<std::vector<int>>();
REQUIRE(int_table_truth == int_table_value);
std::map<int, int> int_map_value = obj_int_map.as<std::map<int, int>>();
REQUIRE(int_map_truth == int_map_value);
}
SECTION("push/popping (object)") {
lua["obj_int"] = lv_int;
lua["obj_double"] = lv_double;
lua["obj_string"] = lv_string;
lua["obj_lstring"] = lv_lstring;
lua["obj_bool"] = lv_bool;
lua["obj_nil"] = lv_nil;
lua["obj_userdata"] = lv_userdata;
lua["obj_int_table"] = lv_int_table;
lua["obj_int_map"] = lv_int_map;
sol::object obj_int = lua["obj_int"];
sol::object obj_double = lua["obj_double"];
sol::object obj_string = lua["obj_string"];
sol::object obj_lstring = lua["obj_lstring"];
sol::object obj_bool = lua["obj_bool"];
sol::object obj_nil = lua["obj_nil"];
sol::object obj_userdata = lua["obj_userdata"];
sol::object obj_int_table = lua["obj_int_table"];
sol::object obj_int_map = lua["obj_int_map"];
REQUIRE(obj_int.is<int>());
REQUIRE(obj_double.is<double>());
REQUIRE(obj_string.is<std::string>());
REQUIRE(obj_lstring.is<std::string>());
REQUIRE(obj_bool.is<bool>());
REQUIRE(obj_nil.is<sol::lua_nil_t>());
REQUIRE(obj_userdata.is<sol::userdata>());
REQUIRE(obj_userdata.is<int_entry>());
REQUIRE(obj_int_table.is<sol::table>());
REQUIRE(obj_int.as<int>() == 1);
REQUIRE(obj_double.as<double>() == 2.0);
REQUIRE(obj_string.as<std::string>() == "heyo");
REQUIRE(obj_lstring.as<std::string>() == "hiyo");
REQUIRE(obj_lstring.as<std::wstring>() == L"hiyo");
REQUIRE(obj_bool.as<bool>());
REQUIRE(obj_nil.as<sol::lua_nil_t>() == sol::lua_nil);
REQUIRE(obj_userdata.as<int_entry>() == userdata_truth);
std::vector<int> int_table_value = obj_int_table.as<std::vector<int>>();
REQUIRE(int_table_truth == int_table_value);
std::map<int, int> int_map_value = obj_int_map.as<std::map<int, int>>();
REQUIRE(int_map_truth == int_map_value);
}
}
TEST_CASE("lua_value/threading", "test that thread_local in lua_value constructors do not race or clobber") {
REQUIRE_NOTHROW([]() {
std::thread thrds[24];
for (int i = 0; i < 24; i++) {
thrds[i] = std::thread(&lua_value_construct_race);
}
for (int i = 0; i < 24; i++) {
thrds[i].join();
}
}());
}