diff options
Diffstat (limited to 'unit_tests/shared.cpp')
-rw-r--r-- | unit_tests/shared.cpp | 127 |
1 files changed, 79 insertions, 48 deletions
diff --git a/unit_tests/shared.cpp b/unit_tests/shared.cpp index d139579..9f3b08e 100644 --- a/unit_tests/shared.cpp +++ b/unit_tests/shared.cpp | |||
@@ -18,42 +18,26 @@ namespace | |||
18 | { | 18 | { |
19 | STACK_CHECK_START_REL(L_, 0); | 19 | STACK_CHECK_START_REL(L_, 0); |
20 | lua_getglobal(L_, "package"); // L_: package | 20 | lua_getglobal(L_, "package"); // L_: package |
21 | std::ignore = luaG_getfield(L_, kIdxTop, "preload"); // L_: package package.preload | 21 | std::ignore = luaW_getfield(L_, kIdxTop, "preload"); // L_: package package.preload |
22 | lua_pushcfunction(L_, openf_); // L_: package package.preload openf_ | 22 | lua_pushcfunction(L_, openf_); // L_: package package.preload openf_ |
23 | luaG_setfield(L_, StackIndex{ -2 }, name_); // L_: package package.preload | 23 | luaW_setfield(L_, StackIndex{ -2 }, name_); // L_: package package.preload |
24 | lua_pop(L_, 2); | 24 | lua_pop(L_, 2); |
25 | STACK_CHECK(L_, 0); | 25 | STACK_CHECK(L_, 0); |
26 | } | 26 | } |
27 | 27 | ||
28 | |||
29 | static std::map<lua_State*, std::atomic_flag> sFinalizerHits; | 28 | static std::map<lua_State*, std::atomic_flag> sFinalizerHits; |
30 | static std::mutex sCallCountsLock; | 29 | static std::mutex sCallCountsLock; |
31 | 30 | ||
32 | // a finalizer that we can detect even after closing the state | 31 | // a finalizer that we can detect even after closing the state |
33 | lua_CFunction sThrowingFinalizer = +[](lua_State* L_) { | 32 | lua_CFunction sFreezingFinalizer = +[](lua_State* const L_) { |
34 | std::lock_guard _guard{ sCallCountsLock }; | 33 | std::lock_guard _guard{ sCallCountsLock }; |
35 | sFinalizerHits[L_].test_and_set(); | 34 | sFinalizerHits[L_].test_and_set(); |
36 | luaG_pushstring(L_, "throw"); | 35 | luaW_pushstring(L_, "freeze"); // just freeze the thread in place so that it can be debugged |
37 | return 1; | 36 | return 1; |
38 | }; | 37 | }; |
39 | 38 | ||
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 | // a function that returns immediately (so that LuaJIT issues a function call for it) | 39 | // a function that returns immediately (so that LuaJIT issues a function call for it) |
56 | lua_CFunction sGiveMeBack = +[](lua_State* L_) { | 40 | lua_CFunction sGiveMeBack = +[](lua_State* const L_) { |
57 | return lua_gettop(L_); | 41 | return lua_gettop(L_); |
58 | }; | 42 | }; |
59 | 43 | ||
@@ -63,25 +47,53 @@ namespace | |||
63 | }; | 47 | }; |
64 | 48 | ||
65 | lua_CFunction sNewUserData = +[](lua_State* const L_) { | 49 | lua_CFunction sNewUserData = +[](lua_State* const L_) { |
66 | std::ignore = luaG_newuserdatauv<int>(L_, UserValueCount{ 0 }); | 50 | std::ignore = luaW_newuserdatauv<int>(L_, UserValueCount{ 0 }); |
67 | return 1; | 51 | return 1; |
68 | }; | 52 | }; |
69 | 53 | ||
70 | // a function that enables any lane to require "fixture" | 54 | // a function that enables any lane to require "fixture" and "deep_userdata_example" |
71 | lua_CFunction sOnStateCreate = +[](lua_State* const L_) { | 55 | lua_CFunction sOnStateCreate = +[](lua_State* const L_) { |
72 | PreloadModule(L_, "fixture", luaopen_fixture); | 56 | PreloadModule(L_, "fixture", luaopen_fixture); |
73 | PreloadModule(L_, "deep_userdata_example", luaopen_deep_userdata_example); | 57 | PreloadModule(L_, "deep_userdata_example", luaopen_deep_userdata_example); |
74 | return 0; | 58 | return 0; |
75 | }; | 59 | }; |
76 | 60 | ||
61 | // a function that blocks for the specified duration (in seconds) by putting the current thread to sleep | ||
62 | lua_CFunction sBlockFor = +[](lua_State* const L_) { | ||
63 | std::chrono::time_point<std::chrono::steady_clock> _until{ std::chrono::time_point<std::chrono::steady_clock>::max() }; | ||
64 | lua_settop(L_, 1); | ||
65 | if (luaW_type(L_, kIdxTop) == LuaType::NUMBER) { // we don't want to use lua_isnumber() because of autocoercion | ||
66 | lua_Duration const _duration{ lua_tonumber(L_, kIdxTop) }; | ||
67 | if (_duration.count() >= 0.0) { | ||
68 | _until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(_duration); | ||
69 | } else { | ||
70 | raise_luaL_argerror(L_, kIdxTop, "duration cannot be < 0"); | ||
71 | } | ||
72 | |||
73 | } else if (!lua_isnoneornil(L_, 2)) { | ||
74 | raise_luaL_argerror(L_, StackIndex{ 2 }, "incorrect duration type"); | ||
75 | } | ||
76 | std::this_thread::sleep_until(_until); | ||
77 | return 0; | ||
78 | }; | ||
79 | |||
80 | // a finalizer that we can detect even after closing the state | ||
81 | lua_CFunction sThrowingFinalizer = +[](lua_State* const L_) { | ||
82 | std::lock_guard _guard{ sCallCountsLock }; | ||
83 | sFinalizerHits[L_].test_and_set(); | ||
84 | bool const _allLanesTerminated = lua_toboolean(L_, kIdxTop); | ||
85 | luaW_pushstring(L_, "Finalizer%s", _allLanesTerminated ? "" : ": Uncooperative lanes detected"); | ||
86 | return 1; | ||
87 | }; | ||
88 | |||
77 | static luaL_Reg const sFixture[] = { | 89 | static luaL_Reg const sFixture[] = { |
78 | { "forever", sForever }, | 90 | { "freezing_finalizer", sFreezingFinalizer }, |
79 | { "give_me_back()", sGiveMeBack }, | 91 | { "give_me_back", sGiveMeBack }, |
80 | { "newlightuserdata", sNewLightUserData }, | 92 | { "newlightuserdata", sNewLightUserData }, |
81 | { "newuserdata", sNewUserData }, | 93 | { "newuserdata", sNewUserData }, |
82 | { "on_state_create", sOnStateCreate }, | 94 | { "on_state_create", sOnStateCreate }, |
95 | { "block_for", sBlockFor }, | ||
83 | { "throwing_finalizer", sThrowingFinalizer }, | 96 | { "throwing_finalizer", sThrowingFinalizer }, |
84 | { "yielding_finalizer", sYieldingFinalizer }, | ||
85 | { nullptr, nullptr } | 97 | { nullptr, nullptr } |
86 | }; | 98 | }; |
87 | } // namespace local | 99 | } // namespace local |
@@ -91,7 +103,7 @@ namespace | |||
91 | int luaopen_fixture(lua_State* L_) | 103 | int luaopen_fixture(lua_State* L_) |
92 | { | 104 | { |
93 | STACK_CHECK_START_REL(L_, 0); | 105 | STACK_CHECK_START_REL(L_, 0); |
94 | luaG_newlib<std::size(local::sFixture)>(L_, local::sFixture); // M | 106 | luaW_newlib<std::size(local::sFixture)>(L_, local::sFixture); // M |
95 | STACK_CHECK(L_, 1); | 107 | STACK_CHECK(L_, 1); |
96 | return 1; | 108 | return 1; |
97 | } | 109 | } |
@@ -234,7 +246,7 @@ LuaError LuaState::doString(std::string_view const& str_) const | |||
234 | return _loadErr; | 246 | return _loadErr; |
235 | } | 247 | } |
236 | LuaError const _callErr{ lua_pcall(L, 0, 1, 0) }; // L: "<msg>"? | 248 | LuaError const _callErr{ lua_pcall(L, 0, 1, 0) }; // L: "<msg>"? |
237 | [[maybe_unused]] std::string_view const _out{ luaG_tostring(L, kIdxTop) }; | 249 | [[maybe_unused]] std::string_view const _out{ luaW_tostring(L, kIdxTop) }; |
238 | STACK_CHECK(L, 1); | 250 | STACK_CHECK(L, 1); |
239 | return _callErr; | 251 | return _callErr; |
240 | } | 252 | } |
@@ -245,8 +257,8 @@ std::string_view LuaState::doStringAndRet(std::string_view const& str_) const | |||
245 | { | 257 | { |
246 | lua_settop(L, 0); | 258 | lua_settop(L, 0); |
247 | if (str_.empty()) { | 259 | if (str_.empty()) { |
248 | luaG_pushstring(L, ""); | 260 | luaW_pushstring(L, ""); |
249 | return luaG_tostring(L, kIdxTop); | 261 | return luaW_tostring(L, kIdxTop); |
250 | } | 262 | } |
251 | STACK_CHECK_START_REL(L, 0); | 263 | STACK_CHECK_START_REL(L, 0); |
252 | LuaError const _loadErr{ luaL_loadstring(L, str_.data()) }; // L: chunk() | 264 | LuaError const _loadErr{ luaL_loadstring(L, str_.data()) }; // L: chunk() |
@@ -256,7 +268,7 @@ std::string_view LuaState::doStringAndRet(std::string_view const& str_) const | |||
256 | } | 268 | } |
257 | [[maybe_unused]] LuaError const _callErr{ lua_pcall(L, 0, 1, 0) }; // L: "<msg>"?|retstring | 269 | [[maybe_unused]] LuaError const _callErr{ lua_pcall(L, 0, 1, 0) }; // L: "<msg>"?|retstring |
258 | STACK_CHECK(L, 1); | 270 | STACK_CHECK(L, 1); |
259 | return luaG_tostring(L, kIdxTop); | 271 | return luaW_tostring(L, kIdxTop); |
260 | } | 272 | } |
261 | 273 | ||
262 | // ################################################################################################# | 274 | // ################################################################################################# |
@@ -324,7 +336,7 @@ void LuaState::requireFailure(std::string_view const& script_) | |||
324 | { | 336 | { |
325 | auto const _result{ doString(script_) }; | 337 | auto const _result{ doString(script_) }; |
326 | if (_result == LuaError::OK) { | 338 | if (_result == LuaError::OK) { |
327 | WARN(luaG_tostring(L, kIdxTop)); | 339 | WARN(luaW_tostring(L, kIdxTop)); |
328 | } | 340 | } |
329 | REQUIRE(_result != LuaError::OK); | 341 | REQUIRE(_result != LuaError::OK); |
330 | lua_settop(L, 0); | 342 | lua_settop(L, 0); |
@@ -360,7 +372,7 @@ void LuaState::requireSuccess(std::string_view const& script_) | |||
360 | { | 372 | { |
361 | auto const _result{ doString(script_) }; | 373 | auto const _result{ doString(script_) }; |
362 | if (_result != LuaError::OK) { | 374 | if (_result != LuaError::OK) { |
363 | WARN(luaG_tostring(L, kIdxTop)); | 375 | WARN(luaW_tostring(L, kIdxTop)); |
364 | } | 376 | } |
365 | REQUIRE(_result == LuaError::OK); | 377 | REQUIRE(_result == LuaError::OK); |
366 | lua_settop(L, 0); | 378 | lua_settop(L, 0); |
@@ -372,7 +384,7 @@ void LuaState::requireSuccess(std::filesystem::path const& root_, std::string_vi | |||
372 | { | 384 | { |
373 | auto const _result{ doFile(root_, path_) }; | 385 | auto const _result{ doFile(root_, path_) }; |
374 | if (_result != LuaError::OK) { | 386 | if (_result != LuaError::OK) { |
375 | WARN(luaG_tostring(L, kIdxTop)); | 387 | WARN(luaW_tostring(L, kIdxTop)); |
376 | } | 388 | } |
377 | REQUIRE(_result == LuaError::OK); | 389 | REQUIRE(_result == LuaError::OK); |
378 | lua_settop(L, 0); | 390 | lua_settop(L, 0); |
@@ -395,20 +407,20 @@ TEST_CASE("LuaState.doString") | |||
395 | { | 407 | { |
396 | LuaState _L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; | 408 | LuaState _L{ LuaState::WithBaseLibs{ true }, LuaState::WithFixture{ false } }; |
397 | // if the script fails to load, we should find the error message at the top of the stack | 409 | // if the script fails to load, we should find the error message at the top of the stack |
398 | REQUIRE([&L = _L]() { std::ignore = L.doString("function end"); return lua_gettop(L) == 1 && luaG_type(L, StackIndex{1}) == LuaType::STRING; }()); | 410 | REQUIRE([&L = _L]() { std::ignore = L.doString("function end"); return lua_gettop(L) == 1 && luaW_type(L, StackIndex{1}) == LuaType::STRING; }()); |
399 | 411 | ||
400 | // if the script runs, the stack should contain its return value | 412 | // if the script runs, the stack should contain its return value |
401 | REQUIRE([&L = _L]() { std::ignore = L.doString("return true"); return lua_gettop(L) == 1 && luaG_type(L, StackIndex{1}) == LuaType::BOOLEAN; }()); | 413 | REQUIRE([&L = _L]() { std::ignore = L.doString("return true"); return lua_gettop(L) == 1 && luaW_type(L, StackIndex{1}) == LuaType::BOOLEAN; }()); |
402 | REQUIRE([&L = _L]() { std::ignore = L.doString("return 'hello'"); return lua_gettop(L) == 1 && luaG_tostring(L, StackIndex{1}) == "hello"; }()); | 414 | REQUIRE([&L = _L]() { std::ignore = L.doString("return 'hello'"); return lua_gettop(L) == 1 && luaW_tostring(L, StackIndex{1}) == "hello"; }()); |
403 | // or nil if it didn't return anything | 415 | // or nil if it didn't return anything |
404 | REQUIRE([&L = _L]() { std::ignore = L.doString("return"); return lua_gettop(L) == 1 && luaG_type(L, StackIndex{1}) == LuaType::NIL; }()); | 416 | REQUIRE([&L = _L]() { std::ignore = L.doString("return"); return lua_gettop(L) == 1 && luaW_type(L, StackIndex{1}) == LuaType::NIL; }()); |
405 | 417 | ||
406 | // on failure, doStringAndRet returns "", and the error message is on the stack | 418 | // on failure, doStringAndRet returns "", and the error message is on the stack |
407 | REQUIRE([&L = _L]() { return L.doStringAndRet("function end") == "" && lua_gettop(L) == 1 && luaG_type(L, StackIndex{ 1 }) == LuaType::STRING && luaG_tostring(L, StackIndex{ 1 }) != ""; }()); | 419 | REQUIRE([&L = _L]() { return L.doStringAndRet("function end") == "" && lua_gettop(L) == 1 && luaW_type(L, StackIndex{ 1 }) == LuaType::STRING && luaW_tostring(L, StackIndex{ 1 }) != ""; }()); |
408 | // on success doStringAndRet returns the string returned by the script, that is also at the top of the stack | 420 | // on success doStringAndRet returns the string returned by the script, that is also at the top of the stack |
409 | REQUIRE([&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"; }()); | 421 | REQUIRE([&L = _L]() { return L.doStringAndRet("return 'hello'") == "hello" && lua_gettop(L) == 1 && luaW_type(L, StackIndex{ 1 }) == LuaType::STRING && luaW_tostring(L, StackIndex{ 1 }) == "hello"; }()); |
410 | // if the returned value is not (convertible to) a string, we should get an empty string out of doStringAndRet | 422 | // if the returned value is not (convertible to) a string, we should get an empty string out of doStringAndRet |
411 | REQUIRE([&L = _L]() { return L.doStringAndRet("return function() end") == "" && lua_gettop(L) == 1 && luaG_type(L, StackIndex{ 1 }) == LuaType::FUNCTION && luaG_tostring(L, StackIndex{ 1 }) == ""; }()); | 423 | REQUIRE([&L = _L]() { return L.doStringAndRet("return function() end") == "" && lua_gettop(L) == 1 && luaW_type(L, StackIndex{ 1 }) == LuaType::FUNCTION && luaW_tostring(L, StackIndex{ 1 }) == ""; }()); |
412 | } | 424 | } |
413 | 425 | ||
414 | // ################################################################################################# | 426 | // ################################################################################################# |
@@ -424,9 +436,9 @@ FileRunner::FileRunner(std::string_view const& where_) | |||
424 | // because the VS Test Explorer doesn't appreciate the text output of some scripts, so absorb them | 436 | // because the VS Test Explorer doesn't appreciate the text output of some scripts, so absorb them |
425 | if constexpr (1) { | 437 | if constexpr (1) { |
426 | auto const _nullprint = +[](lua_State* const L_) { return 0; }; | 438 | auto const _nullprint = +[](lua_State* const L_) { return 0; }; |
427 | luaG_pushglobaltable(L); | 439 | luaW_pushglobaltable(L); |
428 | lua_pushcfunction(L, _nullprint); | 440 | lua_pushcfunction(L, _nullprint); |
429 | luaG_setfield(L, StackIndex{ -2 }, std::string_view{ "print" }); | 441 | luaW_setfield(L, StackIndex{ -2 }, std::string_view{ "print" }); |
430 | lua_pop(L, 1); | 442 | lua_pop(L, 1); |
431 | stackCheck(0); | 443 | stackCheck(0); |
432 | } | 444 | } |
@@ -448,16 +460,35 @@ FileRunner::FileRunner(std::string_view const& where_) | |||
448 | 460 | ||
449 | void FileRunner::performTest(FileRunnerParam const& testParam_) | 461 | void FileRunner::performTest(FileRunnerParam const& testParam_) |
450 | { | 462 | { |
463 | static constexpr auto _atPanic = [](lua_State* const L_) { | ||
464 | throw std::logic_error("panic!"); | ||
465 | return 0; | ||
466 | }; | ||
467 | |||
468 | #if LUA_VERSION_NUM >= 504 // // warnings are a Lua 5.4 feature | ||
469 | std::string _warnMessage; | ||
470 | static constexpr auto _onWarn = [](void* const opaque_, char const* const msg_, int const tocont_) { | ||
471 | std::string& _warnMessage = *static_cast<std::string*>(opaque_); | ||
472 | _warnMessage += msg_; | ||
473 | }; | ||
474 | #endif // LUA_VERSION_NUM | ||
475 | |||
451 | INFO(testParam_.script); | 476 | INFO(testParam_.script); |
452 | switch (testParam_.test) { | 477 | switch (testParam_.test) { |
453 | case TestType::AssertNoLuaError: | 478 | case TestType::AssertNoLuaError: |
479 | lua_atpanic(L, _atPanic); | ||
454 | requireSuccess(root, testParam_.script); | 480 | requireSuccess(root, testParam_.script); |
455 | break; | 481 | break; |
456 | case TestType::AssertNoThrow: | 482 | |
457 | REQUIRE_NOTHROW((std::ignore = doFile(root, testParam_.script), close())); | 483 | #if LUA_VERSION_NUM >= 504 // // warnings are a Lua 5.4 feature |
458 | break; | 484 | case TestType::AssertWarns: |
459 | case TestType::AssertThrows: | 485 | lua_atpanic(L, _atPanic); |
460 | REQUIRE_THROWS_AS((std::ignore = doFile(root, testParam_.script), close()), std::logic_error); | 486 | lua_setwarnf(L, _onWarn, &_warnMessage); |
487 | std::ignore = doFile(root, testParam_.script); | ||
488 | close(); | ||
489 | WARN(_warnMessage); | ||
490 | REQUIRE(_warnMessage != std::string_view{}); | ||
461 | break; | 491 | break; |
492 | #endif // LUA_VERSION_NUM | ||
462 | } | 493 | } |
463 | } | 494 | } |