aboutsummaryrefslogtreecommitdiff
path: root/unit_tests/shared.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--unit_tests/shared.cpp127
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
449void FileRunner::performTest(FileRunnerParam const& testParam_) 461void 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}