mirror of
				https://github.com/luanti-org/luanti.git
				synced 2025-11-04 09:15:29 +01:00 
			
		
		
		
	Add unit tests for Lua vector reading
This commit is contained in:
		@@ -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")
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										183
									
								
								src/unittest/test_scriptapi.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								src/unittest/test_scriptapi.cpp
									
									
									
									
									
										Normal 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);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user