diff options
| author | Benoit Germain <benoit.germain@ubisoft.com> | 2025-03-11 11:05:22 +0100 |
|---|---|---|
| committer | Benoit Germain <benoit.germain@ubisoft.com> | 2025-03-11 11:05:22 +0100 |
| commit | b1822d8d07f8ee34cef2e7e74391695dd4e1ea81 (patch) | |
| tree | 961692802cfc90f7217baf2744712820b3d00939 /unit_tests | |
| parent | cd6169cf3ecaa41a0aec1cfddd98284fa831285c (diff) | |
| download | lanes-b1822d8d07f8ee34cef2e7e74391695dd4e1ea81.tar.gz lanes-b1822d8d07f8ee34cef2e7e74391695dd4e1ea81.tar.bz2 lanes-b1822d8d07f8ee34cef2e7e74391695dd4e1ea81.zip | |
More unit tests
Diffstat (limited to 'unit_tests')
| -rw-r--r-- | unit_tests/embedded_tests.cpp | 231 | ||||
| -rw-r--r-- | unit_tests/shared.h | 10 |
2 files changed, 194 insertions, 47 deletions
diff --git a/unit_tests/embedded_tests.cpp b/unit_tests/embedded_tests.cpp index 1a63721..470deab 100644 --- a/unit_tests/embedded_tests.cpp +++ b/unit_tests/embedded_tests.cpp | |||
| @@ -18,51 +18,129 @@ namespace | |||
| 18 | return 0; | 18 | return 0; |
| 19 | } | 19 | } |
| 20 | } | 20 | } |
| 21 | |||
| 22 | // ######################################################################################### | ||
| 23 | |||
| 24 | // count the number of live allocations (not the actual byte count, because we can't be sure osize is correct | ||
| 25 | std::mutex sAllocStatsLock; | ||
| 26 | size_t sAllocCount = 0; | ||
| 27 | size_t sAllocBytes = 0; | ||
| 28 | std::map<void*, size_t> sAllocs; | ||
| 29 | |||
| 30 | // ######################################################################################### | ||
| 31 | |||
| 32 | static void* allocf([[maybe_unused]] void* ud, void* ptr, size_t osize, size_t nsize) | ||
| 33 | { | ||
| 34 | std::lock_guard guard{ sAllocStatsLock }; | ||
| 35 | |||
| 36 | if (!nsize) // free | ||
| 37 | { | ||
| 38 | if (ptr) { | ||
| 39 | --sAllocCount; | ||
| 40 | sAllocBytes -= osize; | ||
| 41 | free(ptr); | ||
| 42 | sAllocs.erase(ptr); | ||
| 43 | } | ||
| 44 | return nullptr; | ||
| 45 | } else if (!ptr) // malloc | ||
| 46 | { | ||
| 47 | ++sAllocCount; | ||
| 48 | sAllocBytes += nsize; | ||
| 49 | void* new_ptr = realloc(ptr, nsize); | ||
| 50 | if (new_ptr) | ||
| 51 | sAllocs[new_ptr] = nsize; | ||
| 52 | return new_ptr; | ||
| 53 | } else // realloc | ||
| 54 | { | ||
| 55 | int64_t delta = static_cast<int64_t>(nsize) - static_cast<int64_t>(osize); | ||
| 56 | sAllocBytes += static_cast<size_t>(delta); | ||
| 57 | if (!delta) { | ||
| 58 | return ptr; | ||
| 59 | } else { | ||
| 60 | sAllocs.erase(ptr); | ||
| 61 | void* new_ptr = realloc(ptr, nsize); | ||
| 62 | sAllocs[new_ptr] = nsize; | ||
| 63 | return new_ptr; | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | // same as lauxlib l_alloc | ||
| 68 | //(void)osize; | ||
| 69 | // if (nsize == 0) | ||
| 70 | //{ | ||
| 71 | // free(ptr); | ||
| 72 | // return nullptr; | ||
| 73 | //} | ||
| 74 | // else | ||
| 75 | // return realloc(ptr, nsize); | ||
| 76 | } | ||
| 77 | |||
| 78 | // ######################################################################################### | ||
| 79 | |||
| 80 | class EmbeddedLuaState : public LuaState | ||
| 81 | { | ||
| 82 | private: | ||
| 83 | HMODULE hCore{ LoadLibraryW(L"lanes\\core") }; | ||
| 84 | lua_CFunction lanes_register{}; | ||
| 85 | |||
| 86 | public: | ||
| 87 | ~EmbeddedLuaState() { | ||
| 88 | close(); | ||
| 89 | FreeLibrary(hCore); | ||
| 90 | } | ||
| 91 | EmbeddedLuaState() | ||
| 92 | : LuaState { LuaState::WithBaseLibs{ false }, LuaState::WithFixture{ false } } | ||
| 93 | { | ||
| 94 | |||
| 95 | if (!hCore) { | ||
| 96 | throw std::logic_error("Could not load lanes.core"); | ||
| 97 | } | ||
| 98 | luaopen_lanes_embedded_t const _p_luaopen_lanes_embedded{ reinterpret_cast<luaopen_lanes_embedded_t>(GetProcAddress(hCore, "luaopen_lanes_embedded")) }; | ||
| 99 | if (!_p_luaopen_lanes_embedded) { | ||
| 100 | throw std::logic_error("Could not bind luaopen_lanes_embedded"); | ||
| 101 | } | ||
| 102 | |||
| 103 | lanes_register = reinterpret_cast<lua_CFunction>(GetProcAddress(hCore, "lanes_register")); | ||
| 104 | if (!lanes_register) { | ||
| 105 | throw std::logic_error("Could not bind lanes_register"); | ||
| 106 | } | ||
| 107 | // need base to make lanes happy | ||
| 108 | luaL_requiref(L, LUA_GNAME, luaopen_base, 1); | ||
| 109 | lua_pop(L, 1); | ||
| 110 | stackCheck(0); | ||
| 111 | |||
| 112 | // need package to be able to require lanes | ||
| 113 | luaL_requiref(L, LUA_LOADLIBNAME, luaopen_package, 1); | ||
| 114 | lua_pop(L, 1); | ||
| 115 | stackCheck(0); | ||
| 116 | |||
| 117 | // need table to make lanes happy | ||
| 118 | luaL_requiref(L, LUA_TABLIBNAME, luaopen_table, 1); | ||
| 119 | lua_pop(L, 1); | ||
| 120 | stackCheck(0); | ||
| 121 | |||
| 122 | // need string to make lanes happy | ||
| 123 | luaL_requiref(L, LUA_STRLIBNAME, luaopen_string, 1); | ||
| 124 | lua_pop(L, 1); | ||
| 125 | stackCheck(0); | ||
| 126 | |||
| 127 | _p_luaopen_lanes_embedded(L, local::load_lanes_lua); // S: lanes | ||
| 128 | lua_pop(L, 1); | ||
| 129 | stackCheck(0); | ||
| 130 | } | ||
| 131 | |||
| 132 | [[nodiscard]] | ||
| 133 | auto get_lanes_register() const noexcept { return lanes_register; } | ||
| 134 | }; | ||
| 135 | |||
| 21 | } // namespace local | 136 | } // namespace local |
| 22 | } | 137 | } |
| 23 | 138 | ||
| 24 | // ################################################################################################# | 139 | // ################################################################################################# |
| 25 | 140 | ||
| 26 | TEST_CASE("lanes.embedding") | 141 | TEST_CASE("lanes.embedding.with default allocator") |
| 27 | { | 142 | { |
| 28 | LuaState S{ LuaState::WithBaseLibs{ false }, LuaState::WithFixture{ false } }; | 143 | local::EmbeddedLuaState S; |
| 29 | |||
| 30 | HMODULE hCore = LoadLibraryW(L"lanes\\core"); | ||
| 31 | if (!hCore) { | ||
| 32 | throw std::logic_error("Could not load lanes.core"); | ||
| 33 | } | ||
| 34 | luaopen_lanes_embedded_t const _p_luaopen_lanes_embedded{ reinterpret_cast<luaopen_lanes_embedded_t>(GetProcAddress(hCore, "luaopen_lanes_embedded")) }; | ||
| 35 | if (!_p_luaopen_lanes_embedded) { | ||
| 36 | throw std::logic_error("Could not bind luaopen_lanes_embedded"); | ||
| 37 | } | ||
| 38 | |||
| 39 | lua_CFunction lanes_register = reinterpret_cast<lua_CFunction>(GetProcAddress(hCore, "lanes_register")); | ||
| 40 | if (!lanes_register) { | ||
| 41 | throw std::logic_error("Could not bind lanes_register"); | ||
| 42 | } | ||
| 43 | // need base to make lanes happy | ||
| 44 | luaL_requiref(S, LUA_GNAME, luaopen_base, 1); | ||
| 45 | lua_pop(S, 1); | ||
| 46 | S.stackCheck(0); | ||
| 47 | |||
| 48 | // need package to be able to require lanes | ||
| 49 | luaL_requiref(S, LUA_LOADLIBNAME, luaopen_package, 1); | ||
| 50 | lua_pop(S, 1); | ||
| 51 | S.stackCheck(0); | ||
| 52 | |||
| 53 | // need table to make lanes happy | ||
| 54 | luaL_requiref(S, LUA_TABLIBNAME, luaopen_table, 1); | ||
| 55 | lua_pop(S, 1); | ||
| 56 | S.stackCheck(0); | ||
| 57 | |||
| 58 | // need string to make lanes happy | ||
| 59 | luaL_requiref(S, LUA_STRLIBNAME, luaopen_string, 1); | ||
| 60 | lua_pop(S, 1); | ||
| 61 | S.stackCheck(0); | ||
| 62 | |||
| 63 | _p_luaopen_lanes_embedded(S, local::load_lanes_lua); // S: lanes | ||
| 64 | lua_pop(S, 1); | ||
| 65 | S.stackCheck(0); | ||
| 66 | 144 | ||
| 67 | // --------------------------------------------------------------------------------------------- | 145 | // --------------------------------------------------------------------------------------------- |
| 68 | 146 | ||
| @@ -102,9 +180,9 @@ TEST_CASE("lanes.embedding") | |||
| 102 | lua_pop(S, 1); | 180 | lua_pop(S, 1); |
| 103 | S.stackCheck(0); | 181 | S.stackCheck(0); |
| 104 | 182 | ||
| 105 | // try to send io.open into a linda | 183 | // try to send io.open into a linda, which fails if io base library is not loaded |
| 106 | std::string_view const _script{ | 184 | std::string_view const _script{ |
| 107 | " local lanes = require 'lanes'.configure{with_timers = false}" | 185 | " local lanes = require 'lanes'" |
| 108 | " local l = lanes.linda'gleh'" | 186 | " local l = lanes.linda'gleh'" |
| 109 | " l:set('yo', io.open)" | 187 | " l:set('yo', io.open)" |
| 110 | " return 'SUCCESS'" | 188 | " return 'SUCCESS'" |
| @@ -112,17 +190,78 @@ TEST_CASE("lanes.embedding") | |||
| 112 | S.requireNotReturnedString(_script, "SUCCESS"); | 190 | S.requireNotReturnedString(_script, "SUCCESS"); |
| 113 | 191 | ||
| 114 | // try again after manual registration | 192 | // try again after manual registration |
| 115 | lua_pushcfunction(S, lanes_register); // S: lanes_register | 193 | lua_pushcfunction(S, S.get_lanes_register()); // S: lanes_register |
| 116 | luaG_pushstring(S, LUA_IOLIBNAME); // S: lanes_register "io" | 194 | luaG_pushstring(S, LUA_IOLIBNAME); // S: lanes_register "io" |
| 117 | luaL_requiref(S, LUA_IOLIBNAME, luaopen_io, 1); // S: lanes_register "io" io | 195 | luaL_requiref(S, LUA_IOLIBNAME, luaopen_io, 1); // S: lanes_register "io" io |
| 118 | lua_call(S, 2, 0); // S: | 196 | lua_call(S, 2, 0); // S: |
| 119 | S.stackCheck(0); | 197 | S.stackCheck(0); |
| 120 | S.requireReturnedString(_script, "SUCCESS"); | 198 | S.requireReturnedString(_script, "SUCCESS"); |
| 121 | } | 199 | } |
| 200 | } | ||
| 201 | |||
| 202 | // ################################################################################################# | ||
| 203 | |||
| 204 | // this is not really a test yet, just something sitting here until it is converted properly | ||
| 205 | TEST_CASE("lanes.embedding.with custom allocator") | ||
| 206 | { | ||
| 207 | static constexpr auto logPrint = +[](lua_State* L) { | ||
| 208 | lua_getglobal(L, "ID"); // ID | ||
| 209 | printf("[L%d] %s\n", (int) lua_tointeger(L, 2), lua_tostring(L, 1)); | ||
| 210 | return 0; | ||
| 211 | }; | ||
| 212 | |||
| 213 | static constexpr auto on_state_create = +[](lua_State* L) { | ||
| 214 | lua_pushcfunction(L, logPrint); // logPrint | ||
| 215 | lua_setglobal(L, "logPrint"); // | ||
| 216 | return 0; | ||
| 217 | }; | ||
| 218 | |||
| 219 | static constexpr auto launch_lane = +[](lua_CFunction on_state_create_, int id_, int n_) { | ||
| 220 | char script[500]; | ||
| 221 | lua_State* L = lua_newstate(local::allocf, nullptr); | ||
| 222 | // _G.ID = id_ | ||
| 223 | luaL_openlibs(L); | ||
| 224 | luaL_dostring(L, "lanes = require 'lanes'"); | ||
| 225 | lua_getglobal(L, "lanes"); // lanes | ||
| 226 | lua_getfield(L, -1, "configure"); // lanes configure | ||
| 227 | lua_replace(L, 1); // configure | ||
| 228 | lua_newtable(L); // configure {} | ||
| 229 | lua_pushcclosure(L, on_state_create_, 0); // configure {} on_state_create_ | ||
| 230 | lua_setfield(L, -2, "on_state_create"); // configure {on_state_create = on_state_create_} | ||
| 231 | lua_pushboolean(L, 0); // configure {on_state_create = on_state_create_} false | ||
| 232 | lua_setfield(L, -2, "with_timers"); // configure {on_state_create = on_state_create_, with_timers = false} | ||
| 233 | // lua_pushliteral(L, "protected"); // configure {on_state_create = on_state_create_} "protected" | ||
| 234 | // lua_setfield(L, -2, "allocator"); // configure {on_state_create = on_state_create_, allocater = "protected"} | ||
| 235 | lua_pcall(L, 1, 0, 0); | ||
| 236 | sprintf_s(script, | ||
| 237 | "g = lanes.gen('*', {globals = {ID = %d}}, function(id_) lane_threadname('Lane %d.'..id_) logPrint('This is L%d.'..id_) end)" // lane generator | ||
| 238 | "for i = 1,%d do _G['a'..i] = g(i) end" // launch a few lanes, handle stored in global a<i> | ||
| 239 | , | ||
| 240 | id_, | ||
| 241 | id_, | ||
| 242 | id_, | ||
| 243 | n_); | ||
| 244 | luaL_dostring(L, script); // when script ends, globals are collected, lanes are terminated gracefully | ||
| 245 | return L; | ||
| 246 | }; | ||
| 247 | |||
| 248 | local::EmbeddedLuaState S; | ||
| 122 | 249 | ||
| 123 | // --------------------------------------------------------------------------------------------- | 250 | // --------------------------------------------------------------------------------------------- |
| 124 | 251 | ||
| 125 | // close state manually before we unload lanes.core | 252 | // L1: require 'lanes'.configure{on_state_create = L1_on_state_create, with_timers = false} |
| 126 | S.close(); | 253 | lua_State* const L1 = launch_lane(on_state_create, 1, 5); |
| 127 | FreeLibrary(hCore); | 254 | |
| 128 | } | 255 | // L2: require 'lanes'.configure{on_state_create = L2_on_state_create, with_timers = false} |
| 256 | lua_State* const L2 = launch_lane(on_state_create, 2, 5); | ||
| 257 | |||
| 258 | // L3: require 'lanes'.configure{on_state_create = L3_on_state_create, with_timers = false} | ||
| 259 | lua_State* const L3 = launch_lane(on_state_create, 3, 5); | ||
| 260 | |||
| 261 | // give some time to the lanes to execute | ||
| 262 | std::this_thread::sleep_for(1000ms); | ||
| 263 | |||
| 264 | lua_close(L3); | ||
| 265 | lua_close(L2); | ||
| 266 | lua_close(L1); | ||
| 267 | } \ No newline at end of file | ||
diff --git a/unit_tests/shared.h b/unit_tests/shared.h index de7938e..c7cdba5 100644 --- a/unit_tests/shared.h +++ b/unit_tests/shared.h | |||
| @@ -18,7 +18,15 @@ class LuaState | |||
| 18 | } | 18 | } |
| 19 | LuaState(WithBaseLibs withBaseLibs_, WithFixture withFixture_); | 19 | LuaState(WithBaseLibs withBaseLibs_, WithFixture withFixture_); |
| 20 | 20 | ||
| 21 | LuaState(LuaState&& rhs_) = default; | 21 | LuaState(LuaState const&) = delete; |
| 22 | LuaState(LuaState&& rhs_) noexcept | ||
| 23 | : L{ std::exchange(rhs_.L, nullptr) } | ||
| 24 | { | ||
| 25 | } | ||
| 26 | LuaState& operator=(LuaState const&) = delete; | ||
| 27 | LuaState& operator=(LuaState&& rhs_) noexcept { | ||
| 28 | L = std::exchange(rhs_.L, nullptr); | ||
| 29 | } | ||
| 22 | 30 | ||
| 23 | public: | 31 | public: |
| 24 | 32 | ||
