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 | ||