1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00

Add unit tests for Lua vector reading

This commit is contained in:
sfan5 2025-05-19 17:41:13 +02:00
parent a5263dc7ed
commit ec16fb33d0
5 changed files with 197 additions and 20 deletions

View file

@ -19,12 +19,14 @@ function meta:__newindex(name, value)
return
end
local info = getinfo(2, "Sl")
local desc = ("%s:%d"):format(info.short_src, info.currentline)
local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name)
if not warned[warn_key] and info.what ~= "main" and info.what ~= "C" then
core.log("warning", ("Assignment to undeclared global %q inside a function at %s.")
:format(name, desc))
warned[warn_key] = true
if info ~= nil then
local desc = ("%s:%d"):format(info.short_src, info.currentline)
local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name)
if not warned[warn_key] and info.what ~= "main" and info.what ~= "C" then
core.log("warning", ("Assignment to undeclared global %q inside a function at %s.")
:format(name, desc))
warned[warn_key] = true
end
end
declared[name] = true
end
@ -35,6 +37,9 @@ function meta:__index(name)
return
end
local info = getinfo(2, "Sl")
if info == nil then
return
end
local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name)
if not warned[warn_key] and info.what ~= "C" then
core.log("warning", ("Undeclared global variable %q accessed at %s:%s")

View file

@ -67,18 +67,6 @@ local function test_dynamic_media(cb, player)
end
unittests.register("test_dynamic_media", test_dynamic_media, {async=true, player=true})
local function test_v3f_metatable(player)
assert(vector.check(player:get_pos()))
end
unittests.register("test_v3f_metatable", test_v3f_metatable, {player=true})
local function test_v3s16_metatable(player, pos)
local node = core.get_node(pos)
local found_pos = core.find_node_near(pos, 0, node.name, true)
assert(vector.check(found_pos))
end
unittests.register("test_v3s16_metatable", test_v3s16_metatable, {map=true})
local function test_clear_meta(_, pos)
local ref = core.get_meta(pos)

View file

@ -36,6 +36,7 @@ set (UNITTEST_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/test_random.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_sao.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_schematic.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_scriptapi.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_serialization.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_serveractiveobjectmgr.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_server_shutdown_state.cpp

View file

@ -41,7 +41,7 @@ public:
#define UTEST(x, fmt, ...) \
if (!(x)) { \
char utest_buf[1024]; \
snprintf(utest_buf, sizeof(utest_buf), fmt, __VA_ARGS__); \
porting::mt_snprintf(utest_buf, sizeof(utest_buf), fmt, __VA_ARGS__); \
throw TestFailedException(utest_buf, __FILE__, __LINE__); \
}
@ -68,7 +68,7 @@ public:
} catch (EType &e) { \
exception_thrown = true; \
} \
UASSERT(exception_thrown); \
UTEST(exception_thrown, "Exception %s not thrown", #EType); \
}
class IGameDef;

View file

@ -0,0 +1,183 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2022 Minetest core developers & community
#include "test.h"
#include <cmath>
#include "script/cpp_api/s_base.h"
#include "script/lua_api/l_util.h"
#include "script/lua_api/l_settings.h"
#include "script/common/c_converter.h"
#include "irrlicht_changes/printing.h"
#include "server.h"
namespace {
class MyScriptApi : virtual public ScriptApiBase {
public:
MyScriptApi() : ScriptApiBase(ScriptingType::Async) {};
void init();
using ScriptApiBase::getStack;
};
}
class TestScriptApi : public TestBase
{
public:
TestScriptApi() { TestManager::registerTestModule(this); }
const char *getName() { return "TestScriptApi"; }
void runTests(IGameDef *gamedef);
void testVectorMetatable(MyScriptApi *script);
void testVectorRead(MyScriptApi *script);
void testVectorReadErr(MyScriptApi *script);
void testVectorReadMix(MyScriptApi *script);
};
static TestScriptApi g_test_instance;
void MyScriptApi::init()
{
lua_State *L = getStack();
lua_getglobal(L, "core");
int top = lua_gettop(L);
// By creating an environment of 'async' type we have the fewest amount
// of external classes needed.
lua_pushstring(L, "async");
lua_setglobal(L, "INIT");
LuaSettings::Register(L);
ModApiUtil::InitializeAsync(L, top);
lua_pop(L, 1);
loadMod(Server::getBuiltinLuaPath() + DIR_DELIM + "init.lua", BUILTIN_MOD_NAME);
checkSetByBuiltin();
}
void TestScriptApi::runTests(IGameDef *gamedef)
{
MyScriptApi script;
try {
script.init();
} catch (ModError &e) {
rawstream << e.what() << std::endl;
num_tests_failed = 1;
return;
}
TEST(testVectorMetatable, &script);
TEST(testVectorRead, &script);
TEST(testVectorReadErr, &script);
TEST(testVectorReadMix, &script);
}
// Runs Lua code and leaves `nresults` return values on the stack
static void run(lua_State *L, const char *code, int nresults)
{
if (luaL_loadstring(L, code) != 0) {
rawstream << lua_tostring(L, -1) << std::endl;
UASSERT(false);
}
if (lua_pcall(L, 0, nresults, 0) != 0) {
throw LuaError(lua_tostring(L, -1));
}
}
void TestScriptApi::testVectorMetatable(MyScriptApi *script)
{
lua_State *L = script->getStack();
StackUnroller unroller(L);
const auto &call_vector_check = [&] () -> bool {
lua_setglobal(L, "tmp");
run(L, "return vector.check(tmp)", 1);
return lua_toboolean(L, -1);
};
push_v3s16(L, {1, 2, 3});
UASSERT(call_vector_check());
push_v3f(L, {1, 2, 3});
UASSERT(call_vector_check());
// 2-component vectors must not have this metatable
push_v2s32(L, {0, 0});
UASSERT(!call_vector_check());
push_v2f(L, {0, 0});
UASSERT(!call_vector_check());
}
void TestScriptApi::testVectorRead(MyScriptApi *script)
{
lua_State *L = script->getStack();
StackUnroller unroller(L);
// both methods should parse these
const std::pair<const char*, v3s16> pairs1[] = {
{"return {x=1, y=-2, z=3}", {1, -2, 3}},
{"return {x=1.1, y=0, z=0}", {1, 0, 0}},
{"return {x=1.5, y=0, z=0}", {2, 0, 0}},
{"return {x=-1.1, y=0, z=0}", {-1, 0, 0}},
{"return {x=-1.5, y=0, z=0}", {-2, 0, 0}},
{"return vector.new(5, 6, 7)", {5, 6, 7}},
{"return vector.new(32767, 0, -32768)", {S16_MAX, 0, S16_MIN}},
};
for (auto &it : pairs1) {
run(L, it.first, 1);
v3s16 v = read_v3s16(L, -1);
UASSERTEQ(auto, v, it.second);
v = check_v3s16(L, -1);
UASSERTEQ(auto, v, it.second);
lua_pop(L, 1);
}
}
void TestScriptApi::testVectorReadErr(MyScriptApi *script)
{
lua_State *L = script->getStack();
StackUnroller unroller(L);
// both methods should reject these
const char *errs1[] = {
"return {y=1, z=3}",
"return {x=1, z=3}",
"return {x=1, y=3}",
"return {}",
"return 'bamboo'",
"return function() end",
"return nil",
};
for (auto &it : errs1) {
infostream << it << std::endl;
run(L, it, 1);
EXCEPTION_CHECK(LuaError, read_v3s16(L, -1));
EXCEPTION_CHECK(LuaError, check_v3s16(L, -1));
}
}
void TestScriptApi::testVectorReadMix(MyScriptApi *script)
{
lua_State *L = script->getStack();
StackUnroller unroller(L);
// read_v3s16 should allow these, but check_v3s16 should not
const std::pair<const char*, v3s16> pairs2[] = {
{"return {x='3', y='2.9', z=3}", {3, 3, 3}},
{"return {x=false, y=0, z=0}", {0, 0, 0}},
{"return {x='?', y=0, z=0}", {0, 0, 0}},
{"return {x={'baguette'}, y=0, z=0}", {0, 0, 0}},
};
for (auto &it : pairs2) {
infostream << it.first << std::endl;
run(L, it.first, 1);
v3s16 v = read_v3s16(L, -1);
UASSERTEQ(auto, v, it.second);
EXCEPTION_CHECK(LuaError, check_v3s16(L, -1));
lua_pop(L, 1);
}
}