From a57690123ae3ce5bdd7e970690f1380e88e4eaf6 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Mon, 17 Mar 2025 12:34:08 +0100 Subject: Raise a regular Lua error instead of throwing a C++ std::logic_error exception in Universe::UniverseGC --- unit_tests/_pch.cpp | 14 ---- unit_tests/lane_tests.cpp | 10 +-- unit_tests/scripts/lane/uncooperative_shutdown.lua | 4 +- unit_tests/shared.cpp | 82 +++++++++++++++------- unit_tests/shared.h | 5 +- 5 files changed, 66 insertions(+), 49 deletions(-) (limited to 'unit_tests') diff --git a/unit_tests/_pch.cpp b/unit_tests/_pch.cpp index 189089a..0d37ba4 100644 --- a/unit_tests/_pch.cpp +++ b/unit_tests/_pch.cpp @@ -1,15 +1 @@ #include "_pch.hpp" - -// IMPORTANT INFORMATION: some relative paths coded in the test implementations suppose that the cwd when debugging is $(SolutionDir)Lanes -// Therefore that's what needs to be set in Google Test Adapter 'Working Directory' global setting... - -//int main(int argc, char* argv[]) -//{ -// // your setup ... -// -// int result = Catch::Session().run(argc, argv); -// -// // your clean-up... -// -// return result; -//} \ No newline at end of file diff --git a/unit_tests/lane_tests.cpp b/unit_tests/lane_tests.cpp index 0c4feba..d6ef2e0 100644 --- a/unit_tests/lane_tests.cpp +++ b/unit_tests/lane_tests.cpp @@ -327,10 +327,10 @@ TEST_CASE("scripted tests." #DIR "." #FILE) \ } MAKE_TEST_CASE(lane, cooperative_shutdown, AssertNoLuaError) -#if LUAJIT_FLAVOR() == 0 -// TODO: for some reason, even though we throw as expected, the test fails with LuaJIT. To be investigated -MAKE_TEST_CASE(lane, uncooperative_shutdown, AssertThrows) -#endif // LUAJIT_FLAVOR() +#if LUA_VERSION_NUM >= 504 // // warnings are a Lua 5.4 feature +// NOTE: when this test ends, there are resource leaks and a dangling thread +MAKE_TEST_CASE(lane, uncooperative_shutdown, AssertWarns) +#endif // LUA_VERSION_NUM MAKE_TEST_CASE(lane, tasking_basic, AssertNoLuaError) MAKE_TEST_CASE(lane, tasking_cancelling, AssertNoLuaError) MAKE_TEST_CASE(lane, tasking_comms_criss_cross, AssertNoLuaError) @@ -350,7 +350,7 @@ TEST_CASE("lanes.scripted tests") { auto const& _testParam = GENERATE( FileRunnerParam{ PUC_LUA_ONLY("lane/cooperative_shutdown"), TestType::AssertNoLuaError }, // 0 - FileRunnerParam{ "lane/uncooperative_shutdown", TestType::AssertThrows }, + FileRunnerParam{ "lane/uncooperative_shutdown", TestType::AssertWarns }, FileRunnerParam{ "lane/tasking_basic", TestType::AssertNoLuaError }, // 2 FileRunnerParam{ "lane/tasking_cancelling", TestType::AssertNoLuaError }, // 3 FileRunnerParam{ "lane/tasking_comms_criss_cross", TestType::AssertNoLuaError }, // 4 diff --git a/unit_tests/scripts/lane/uncooperative_shutdown.lua b/unit_tests/scripts/lane/uncooperative_shutdown.lua index 51e5762..89e1ff8 100644 --- a/unit_tests/scripts/lane/uncooperative_shutdown.lua +++ b/unit_tests/scripts/lane/uncooperative_shutdown.lua @@ -8,7 +8,7 @@ local lanes = require "lanes".configure{shutdown_timeout = 0.001, on_state_creat -- launch lanes that blocks forever local lane = function() local fixture = require "fixture" - fixture.forever() + fixture.sleep_for() end -- the generator @@ -20,5 +20,5 @@ local h1 = g1() -- wait until the lane is running repeat until h1.status == "running" --- let the script end, Lanes should throw std::logic_error because the lane did not gracefully terminate +-- this finalizer returns an error string that telling Universe::__gc will use to raise an error when it detects the uncooperative lane lanes.finally(fixture.throwing_finalizer) diff --git a/unit_tests/shared.cpp b/unit_tests/shared.cpp index d139579..2e2af73 100644 --- a/unit_tests/shared.cpp +++ b/unit_tests/shared.cpp @@ -25,35 +25,19 @@ namespace STACK_CHECK(L_, 0); } - static std::map sFinalizerHits; static std::mutex sCallCountsLock; // a finalizer that we can detect even after closing the state - lua_CFunction sThrowingFinalizer = +[](lua_State* L_) { + lua_CFunction sFreezingFinalizer = +[](lua_State* const L_) { std::lock_guard _guard{ sCallCountsLock }; sFinalizerHits[L_].test_and_set(); - luaG_pushstring(L_, "throw"); + luaG_pushstring(L_, "freeze"); // just freeze the thread in place so that it can be debugged return 1; }; - // a finalizer that we can detect even after closing the state - lua_CFunction sYieldingFinalizer = +[](lua_State* L_) { - std::lock_guard _guard{ sCallCountsLock }; - sFinalizerHits[L_].test_and_set(); - return 0; - }; - - // a function that runs forever - lua_CFunction sForever = +[](lua_State* L_) { - while (true) { - std::this_thread::yield(); - } - return 0; - }; - // a function that returns immediately (so that LuaJIT issues a function call for it) - lua_CFunction sGiveMeBack = +[](lua_State* L_) { + lua_CFunction sGiveMeBack = +[](lua_State* const L_) { return lua_gettop(L_); }; @@ -74,14 +58,42 @@ namespace return 0; }; + // a function that sleeps for the specified duration (in seconds) + lua_CFunction sSleepFor = +[](lua_State* const L_) { + std::chrono::time_point _until{ std::chrono::time_point::max() }; + lua_settop(L_, 1); + if (luaG_type(L_, kIdxTop) == LuaType::NUMBER) { // we don't want to use lua_isnumber() because of autocoercion + lua_Duration const _duration{ lua_tonumber(L_, kIdxTop) }; + if (_duration.count() >= 0.0) { + _until = std::chrono::steady_clock::now() + std::chrono::duration_cast(_duration); + } else { + raise_luaL_argerror(L_, kIdxTop, "duration cannot be < 0"); + } + + } else if (!lua_isnoneornil(L_, 2)) { + raise_luaL_argerror(L_, StackIndex{ 2 }, "incorrect duration type"); + } + std::this_thread::sleep_until(_until); + return 0; + }; + + // a finalizer that we can detect even after closing the state + lua_CFunction sThrowingFinalizer = +[](lua_State* const L_) { + std::lock_guard _guard{ sCallCountsLock }; + sFinalizerHits[L_].test_and_set(); + bool const _allLanesTerminated = lua_toboolean(L_, kIdxTop); + luaG_pushstring(L_, "Finalizer%s", _allLanesTerminated ? "" : ": Uncooperative lanes detected"); + return 1; + }; + static luaL_Reg const sFixture[] = { - { "forever", sForever }, + { "freezing_finalizer", sFreezingFinalizer }, { "give_me_back()", sGiveMeBack }, { "newlightuserdata", sNewLightUserData }, { "newuserdata", sNewUserData }, { "on_state_create", sOnStateCreate }, + { "sleep_for", sSleepFor }, { "throwing_finalizer", sThrowingFinalizer }, - { "yielding_finalizer", sYieldingFinalizer }, { nullptr, nullptr } }; } // namespace local @@ -448,16 +460,34 @@ FileRunner::FileRunner(std::string_view const& where_) void FileRunner::performTest(FileRunnerParam const& testParam_) { + static constexpr auto _atPanic = [](lua_State* const L_) { + throw std::logic_error("panic!"); + return 0; + }; + +#if LUA_VERSION_NUM >= 504 // // warnings are a Lua 5.4 feature + std::string _warnMessage; + static constexpr auto _onWarn = [](void* const opaque_, char const* const msg_, int const tocont_) { + std::string& _warnMessage = *static_cast(opaque_); + _warnMessage += msg_; + }; +#endif // LUA_VERSION_NUM + INFO(testParam_.script); switch (testParam_.test) { case TestType::AssertNoLuaError: requireSuccess(root, testParam_.script); break; - case TestType::AssertNoThrow: - REQUIRE_NOTHROW((std::ignore = doFile(root, testParam_.script), close())); - break; - case TestType::AssertThrows: - REQUIRE_THROWS_AS((std::ignore = doFile(root, testParam_.script), close()), std::logic_error); + +#if LUA_VERSION_NUM >= 504 // // warnings are a Lua 5.4 feature + case TestType::AssertWarns: + lua_atpanic(L, _atPanic); + lua_setwarnf(L, _onWarn, &_warnMessage); + std::ignore = doFile(root, testParam_.script); + close(); + WARN(_warnMessage); + REQUIRE(_warnMessage != std::string_view{}); break; +#endif // LUA_VERSION_NUM } } diff --git a/unit_tests/shared.h b/unit_tests/shared.h index 8a84a94..b884df0 100644 --- a/unit_tests/shared.h +++ b/unit_tests/shared.h @@ -66,8 +66,9 @@ class LuaState enum class [[nodiscard]] TestType { AssertNoLuaError, - AssertNoThrow, - AssertThrows, +#if LUA_VERSION_NUM >= 504 // warnings are a Lua 5.4 feature + AssertWarns, +#endif // LUA_VERSION_NUM }; struct FileRunnerParam -- cgit v1.2.3-55-g6feb