aboutsummaryrefslogtreecommitdiff
path: root/unit_tests/shared.cpp
diff options
context:
space:
mode:
authorBenoit Germain <benoit.germain@ubisoft.com>2024-12-13 17:22:17 +0100
committerBenoit Germain <benoit.germain@ubisoft.com>2024-12-13 17:22:17 +0100
commitdc7a2bc3a9c8316e17902493b832ca117805d29f (patch)
tree3c1a03edf586869119ef0de03631f6f603650720 /unit_tests/shared.cpp
parent7500a80fc06c5311c46df8f1761f25ae67277fc4 (diff)
downloadlanes-dc7a2bc3a9c8316e17902493b832ca117805d29f.tar.gz
lanes-dc7a2bc3a9c8316e17902493b832ca117805d29f.tar.bz2
lanes-dc7a2bc3a9c8316e17902493b832ca117805d29f.zip
Append all unit tests to depot
Diffstat (limited to 'unit_tests/shared.cpp')
-rw-r--r--unit_tests/shared.cpp360
1 files changed, 360 insertions, 0 deletions
diff --git a/unit_tests/shared.cpp b/unit_tests/shared.cpp
new file mode 100644
index 0000000..14b2b13
--- /dev/null
+++ b/unit_tests/shared.cpp
@@ -0,0 +1,360 @@
1#include "_pch.hpp"
2
3#include "shared.h"
4
5// #################################################################################################
6// #################################################################################################
7// internal fixture module
8// #################################################################################################
9// #################################################################################################
10
11LANES_API int luaopen_deep_test(lua_State* L_);
12
13namespace
14{
15 extern int luaopen_fixture(lua_State*);
16 namespace local {
17 void PreloadModule(lua_State* const L_, std::string_view const& name_, lua_CFunction const openf_)
18 {
19 STACK_CHECK_START_REL(L_, 0);
20 lua_getglobal(L_, "package"); // L_: package
21 luaG_getfield(L_, kIdxTop, "preload"); // L_: package package.preload
22 lua_pushcfunction(L_, openf_); // L_: package package.preload openf_
23 luaG_setfield(L_, StackIndex{ -2 }, name_); // L_: package package.preload
24 lua_pop(L_, 2);
25 STACK_CHECK(L_, 0);
26 }
27
28
29 static std::map<lua_State*, std::atomic_flag> sFinalizerHits;
30 static std::mutex sCallCountsLock;
31
32 // a finalizer that we can detect even after closing the state
33 lua_CFunction sThrowingFinalizer = +[](lua_State* L_) {
34 std::lock_guard _guard{ sCallCountsLock };
35 sFinalizerHits[L_].test_and_set();
36 luaG_pushstring(L_, "throw");
37 return 1;
38 };
39
40 // a finalizer that we can detect even after closing the state
41 lua_CFunction sYieldingFinalizer = +[](lua_State* L_) {
42 std::lock_guard _guard{ sCallCountsLock };
43 sFinalizerHits[L_].test_and_set();
44 return 0;
45 };
46
47 // a function that runs forever
48 lua_CFunction sForever = +[](lua_State* L_) {
49 while (true) {
50 std::this_thread::yield();
51 }
52 return 0;
53 };
54
55 lua_CFunction sNewLightUserData = +[](lua_State* const L_) {
56 lua_pushlightuserdata(L_, std::bit_cast<void*>(static_cast<uintptr_t>(42)));
57 return 1;
58 };
59
60 lua_CFunction sNewUserData = +[](lua_State* const L_) {
61 std::ignore = luaG_newuserdatauv<int>(L_, UserValueCount{ 0 });
62 return 1;
63 };
64
65 // a function that enables any lane to require "fixture"
66 lua_CFunction sOnStateCreate = +[](lua_State* const L_) {
67 PreloadModule(L_, "fixture", luaopen_fixture);
68 PreloadModule(L_, "deep_test", luaopen_deep_test);
69 return 0;
70 };
71
72 static luaL_Reg const sFixture[] = {
73 { "forever", sForever },
74 { "newlightuserdata", sNewLightUserData },
75 { "newuserdata", sNewUserData },
76 { "on_state_create", sOnStateCreate },
77 { "throwing_finalizer", sThrowingFinalizer },
78 { "yielding_finalizer", sYieldingFinalizer },
79 { nullptr, nullptr }
80 };
81 } // namespace local
82
83 // ############################################################################################
84
85 int luaopen_fixture(lua_State* L_)
86 {
87 STACK_CHECK_START_REL(L_, 0);
88 luaG_newlib<std::size(local::sFixture)>(L_, local::sFixture); // M
89 STACK_CHECK(L_, 1);
90 return 1;
91 }
92
93} // namespace
94
95// #################################################################################################
96// #################################################################################################
97// Internals
98// #################################################################################################
99// #################################################################################################
100
101TEST(Internals, StackChecker)
102{
103 LuaState _L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } };
104 StackChecker::CallsCassert = false;
105
106 auto _doStackCheckerTest = [&_L](lua_CFunction const _f, LuaError const _expected) {
107 lua_pushcfunction(_L, _f);
108 ASSERT_EQ(ToLuaError(lua_pcall(_L, 0, 0, 0)), _expected);
109 };
110
111 // function where the StackChecker detects something wrong with the stack
112 lua_CFunction _unbalancedStack1 = +[](lua_State* const _L) {
113 // record current position
114 STACK_CHECK_START_REL(_L, 0);
115 // push something
116 lua_newtable(_L);
117 // check if we are at the same position as before (no)
118 STACK_CHECK(_L, 0);
119 return 1;
120 };
121
122 // function where the StackChecker detects no issue
123 lua_CFunction _balancedStack1 = +[](lua_State* const _L) {
124 // record current position
125 STACK_CHECK_START_REL(_L, 0);
126 // check if we are at the same position as before (yes)
127 STACK_CHECK(_L, 0);
128 return 0;
129 };
130
131 lua_CFunction _goodStart = +[](lua_State* const _L) {
132 // check that the stack ends at the specified position, and record that as our reference point
133 STACK_CHECK_START_ABS(_L, 0);
134 // check if we are at the same position as before (yes)
135 STACK_CHECK(_L, 0);
136 return 0;
137 };
138
139 lua_CFunction _badStart = +[](lua_State* const _L) {
140 // check that the stack ends at the specified position (no), and record that as our reference point
141 STACK_CHECK_START_ABS(_L, 1);
142 // check if we are at the same position as before (yes)
143 STACK_CHECK(_L, 0);
144 return 0;
145 };
146
147 _doStackCheckerTest(_unbalancedStack1, LuaError::ERRRUN);
148 _doStackCheckerTest(_balancedStack1, LuaError::OK);
149 _doStackCheckerTest(_goodStart, LuaError::OK);
150 _doStackCheckerTest(_badStart, LuaError::ERRRUN);
151}
152
153// #################################################################################################
154// #################################################################################################
155// LuaState
156// #################################################################################################
157// #################################################################################################
158
159LuaState::LuaState(WithBaseLibs const withBaseLibs_, WithFixture const withFixture_)
160{
161 STACK_CHECK_START_REL(L, 0);
162 if (withBaseLibs_) {
163 luaL_openlibs(L);
164 }
165 if (withFixture_) {
166 // make require "fixture" call luaopen_fixture
167 local::PreloadModule(L, "fixture", luaopen_fixture);
168 local::PreloadModule(L, "deep_test", luaopen_deep_test);
169 }
170 STACK_CHECK(L, 0);
171}
172
173// #################################################################################################
174
175void LuaState::close()
176{
177 if (L) {
178 lua_close(L);
179
180 {
181 std::lock_guard _guard{ local::sCallCountsLock };
182 finalizerWasCalled = local::sFinalizerHits[L].test();
183 local::sFinalizerHits.erase(L);
184 }
185
186 L = nullptr;
187 }
188}
189
190// #################################################################################################
191
192LuaError LuaState::doString(std::string_view const& str_) const
193{
194 lua_settop(L, 0);
195 if (str_.empty()) {
196 lua_pushnil(L);
197 return LuaError::OK;
198 }
199 STACK_CHECK_START_REL(L, 0);
200 LuaError const _loadErr{ luaL_loadstring(L, str_.data()) }; // L: chunk()
201 if (_loadErr != LuaError::OK) {
202 STACK_CHECK(L, 1); // the error message is on the stack
203 return _loadErr;
204 }
205 LuaError const _callErr{ lua_pcall(L, 0, 1, 0) }; // L: "<msg>"?
206 [[maybe_unused]] std::string_view const _out{ luaG_tostring(L, kIdxTop) };
207 STACK_CHECK(L, 1);
208 return _callErr;
209}
210
211// #################################################################################################
212
213std::string_view LuaState::doStringAndRet(std::string_view const& str_) const
214{
215 lua_settop(L, 0);
216 if (str_.empty()) {
217 luaG_pushstring(L, "");
218 return luaG_tostring(L, kIdxTop);
219 }
220 STACK_CHECK_START_REL(L, 0);
221 LuaError const _loadErr{ luaL_loadstring(L, str_.data()) }; // L: chunk()
222 if (_loadErr != LuaError::OK) {
223 STACK_CHECK(L, 1); // the error message is on the stack
224 return "";
225 }
226 LuaError const _callErr{ lua_pcall(L, 0, 1, 0) }; // L: "<msg>"?|retstring
227 STACK_CHECK(L, 1);
228 return luaG_tostring(L, kIdxTop);
229}
230
231// #################################################################################################
232
233LuaError LuaState::doFile(std::filesystem::path const& root_, std::string_view const& str_) const
234{
235 lua_settop(L, 0);
236 if (str_.empty()) {
237 lua_pushnil(L);
238 return LuaError::OK;
239 }
240 STACK_CHECK_START_REL(L, 0);
241 std::filesystem::path _combined{ root_ };
242 _combined.append(str_);
243 _combined.replace_extension(".lua");
244 LuaError const _loadErr{ luaL_loadfile(L, _combined.generic_string().c_str()) }; // L: chunk()
245 if (_loadErr != LuaError::OK) {
246 STACK_CHECK(L, 1);
247 return _loadErr;
248 }
249 LuaError const _callErr{ lua_pcall(L, 0, 1, 0) }; // L: "<msg>"?
250 STACK_CHECK(L, 1); // either nil, a return value, or an error string
251 return _callErr;
252}
253
254// #################################################################################################
255
256LuaError LuaState::loadString(std::string_view const& str_) const
257{
258 lua_settop(L, 0);
259 if (str_.empty()) {
260 // this particular test is disabled: just create a dummy function that will run without error
261 lua_pushcfunction(L, +[](lua_State*){return 0;});
262 return LuaError::OK;
263 }
264 STACK_CHECK_START_REL(L, 0);
265 LuaError const _loadErr{ luaL_loadstring(L, str_.data()) }; // L: chunk()
266 STACK_CHECK(L, 1); // function on success, error string on failure
267 return _loadErr;
268}
269
270// #################################################################################################
271
272LuaError LuaState::loadFile(std::filesystem::path const& root_, std::string_view const& str_) const
273{
274 lua_settop(L, 0);
275 STACK_CHECK_START_REL(L, 0);
276 if (str_.empty()) {
277 // this particular test is disabled: just create a dummy function that will run without error
278 lua_pushcfunction(L, +[](lua_State*){return 0;});
279 return LuaError::OK;
280 }
281
282 std::filesystem::path _combined{ root_ };
283 _combined.append(str_);
284 _combined.replace_extension(".lua");
285 LuaError const _loadErr{ luaL_loadfile(L, _combined.generic_string().c_str()) }; // L: chunk()
286 STACK_CHECK(L, 1); // function on success, error string on failure
287 return _loadErr;
288}
289
290// #################################################################################################
291
292LuaError LuaState::runChunk() const
293{
294 STACK_CHECK_START_ABS(L, 1); // we must start with the chunk on the stack (or an error string if it failed to load)
295 LuaError const _callErr{ lua_pcall(L, 0, 1, 0) }; // L: "<msg>"?
296 STACK_CHECK(L, 1);
297 return _callErr;
298}
299
300// #################################################################################################
301// #################################################################################################
302
303TEST(LuaState, DoString)
304{
305 LuaState _L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } };
306 // if the script fails to load, we should find the error message at the top of the stack
307 ASSERT_TRUE([&L = _L]() { std::ignore = L.doString("function end"); return lua_gettop(L) == 1 && luaG_type(L, StackIndex{1}) == LuaType::STRING; }());
308
309 // if the script runs, the stack should contain its return value
310 ASSERT_TRUE([&L = _L]() { std::ignore = L.doString("return true"); return lua_gettop(L) == 1 && luaG_type(L, StackIndex{1}) == LuaType::BOOLEAN; }());
311 ASSERT_TRUE([&L = _L]() { std::ignore = L.doString("return 'hello'"); return lua_gettop(L) == 1 && luaG_tostring(L, StackIndex{1}) == "hello"; }());
312 // or nil if it didn't return anything
313 ASSERT_TRUE([&L = _L]() { std::ignore = L.doString("return"); return lua_gettop(L) == 1 && luaG_type(L, StackIndex{1}) == LuaType::NIL; }());
314
315 // on failure, doStringAndRet returns "", and the error message is on the stack
316 ASSERT_TRUE([&L = _L]() { return L.doStringAndRet("function end") == "" && lua_gettop(L) == 1 && luaG_type(L, StackIndex{1}) == LuaType::STRING && luaG_tostring(L, StackIndex{1}) != ""; }());
317 // on success doStringAndRet returns the string returned by the script, that is also at the top of the stack
318 ASSERT_TRUE([&L = _L]() { return L.doStringAndRet("return 'hello'") == "hello" && lua_gettop(L) == 1 && luaG_type(L, StackIndex{1}) == LuaType::STRING && luaG_tostring(L, StackIndex{1}) == "hello"; }());
319 // if the returned value is not (convertible to) a string, we should get an empty string out of doStringAndRet
320 ASSERT_TRUE([&L = _L]() { return L.doStringAndRet("return function() end") == "" && lua_gettop(L) == 1 && luaG_type(L, StackIndex{1}) == LuaType::FUNCTION && luaG_tostring(L, StackIndex{1}) == ""; }());
321}
322
323// #################################################################################################
324// #################################################################################################
325// UnitTestRunner
326// #################################################################################################
327// #################################################################################################
328
329UnitTestRunner::UnitTestRunner()
330{
331 [[maybe_unused]] std::filesystem::path const _current{ std::filesystem::current_path() };
332 std::filesystem::path _assertPath{ R"(.\lanes\unit_tests\scripts)" };
333 // I need to append that path to the list of locations where modules can be required
334 // so that the scripts can require "_assert" and find _assert.lua (same with "_utils.lua")
335 std::string _script{ "package.path = package.path.." };
336 _script += "';";
337 _script += std::filesystem::canonical(_assertPath).generic_string();
338 _script += "/?.lua'";
339 std::ignore = L.doString(_script.c_str());
340
341 root = std::filesystem::canonical(R"(.\lanes\unit_tests\scripts)").generic_string();
342}
343
344// #################################################################################################
345
346TEST_P(UnitTestRunner, ScriptedTest)
347{
348 FileRunnerParam const& _param = GetParam();
349 switch (_param.test) {
350 case TestType::AssertNoLuaError:
351 ASSERT_EQ(L.doFile(root, _param.script), LuaError::OK) << L;
352 break;
353 case TestType::AssertNoThrow:
354 ASSERT_NO_THROW((std::ignore = L.doFile(root, _param.script), L.close())) << L;
355 break;
356 case TestType::AssertThrows:
357 ASSERT_THROW((std::ignore = L.doFile(root, _param.script), L.close()), std::logic_error) << L;
358 break;
359 }
360}