Skip to content

Commit

Permalink
Unittest: Add inventory callback tests
Browse files Browse the repository at this point in the history
  • Loading branch information
SmallJoker committed Mar 16, 2024
1 parent 4245a76 commit 61a5733
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 41 deletions.
84 changes: 84 additions & 0 deletions builtin/game/tests/test_moveaction.lua
@@ -0,0 +1,84 @@
-- Table to keep track of callback executions
-- [i + 0] = count of expected patterns of index (i + 1)
-- [i + 1] = pattern to check
local PATTERN_NORMAL = { 4, "allow_%w", 2, "on_take", 1, "on_put", 1 }
local PATTERN_SWAP = { 8, "allow_%w", 4, "on_take", 2, "on_put", 2 }
local exec_listing = {} -- List of logged callbacks (e.g. "on_take", "allow_put")

-- Checks whether the logged callbacks equal the expected pattern
core.__helper_check_callbacks = function(expect_swap)
local exec_pattern = expect_swap and PATTERN_SWAP or PATTERN_NORMAL
local ok = #exec_listing == exec_pattern[1]
if ok then
local list_index = 1
for i = 2, #exec_pattern, 2 do
for n = 1, exec_pattern[i + 1] do
-- Match the list for "n" occurrences of the wanted callback name pattern
ok = exec_listing[list_index]:find(exec_pattern[i])
list_index = list_index + 1
if not ok then break end
end
if not ok then break end
end
end

if not ok then
print("Execution order mismatch!")
print("Expected patterns: ", dump(exec_pattern))
print("Got list: ", dump(exec_listing))
end
exec_listing = {}
return ok
end

-- Uncomment the other line for easier callback debugging
local log = function(...) end
--local log = print

minetest.register_allow_player_inventory_action(function(_, action, inv, info)
log("\tallow " .. action, info.count or info.stack:to_string())

if action == "move" then
-- testMoveFillStack
return info.count
end

if action == "take" or action == "put" then
assert(not info.stack:is_empty(), "Stack empty in: " .. action)

-- testMoveUnallowed
-- testSwapFromUnallowed
-- testSwapToUnallowed
if info.stack:get_name() == "default:takeput_deny" then
return 0
end

-- testMovePartial
if info.stack:get_name() == "default:takeput_max_5" then
return 5
end

-- testCallbacks
if info.stack:get_name():find("default:takeput_cb_%d") then
-- Log callback as executed
table.insert(exec_listing, "allow_" .. action)
return -- Unlimited
end
end

return -- Unlimited
end)

minetest.register_on_player_inventory_action(function(_, action, inv, info)
log("\ton " .. action, info.count or info.stack:to_string())

if action == "take" or action == "put" then
assert(not info.stack:is_empty(), action)

if info.stack:get_name():find("default:takeput_cb_%d") then
-- Log callback as executed
table.insert(exec_listing, "on_" .. action)
return
end
end
end)
1 change: 1 addition & 0 deletions src/script/cpp_api/s_base.h
Expand Up @@ -146,6 +146,7 @@ class ScriptApiBase : protected LuaHelper {
friend class ModApiBase;
friend class ModApiEnv;
friend class LuaVoxelManip;
friend class TestMoveAction; // needs getStack()

/*
Subtle edge case with coroutines: If for whatever reason you have a
Expand Down
3 changes: 3 additions & 0 deletions src/server.h
Expand Up @@ -434,7 +434,10 @@ class Server : public con::PeerHandler, public MapEventReceiver,
private:
friend class EmergeThread;
friend class RemoteClient;

// unittest classes
friend class TestServerShutdownState;
friend class TestMoveAction;

struct ShutdownState {
friend class TestServerShutdownState;
Expand Down
7 changes: 4 additions & 3 deletions src/server/serverinventorymgr.h
Expand Up @@ -40,8 +40,9 @@ class ServerInventoryManager : public InventoryManager
m_env = env;
}

Inventory *getInventory(const InventoryLocation &loc);
void setInventoryModified(const InventoryLocation &loc);
// virtual: Overwritten by MockInventoryManager for the unittests
virtual Inventory *getInventory(const InventoryLocation &loc);
virtual void setInventoryModified(const InventoryLocation &loc);

// Creates or resets inventory
Inventory *createDetachedInventory(const std::string &name, IItemDefManager *idef,
Expand All @@ -52,7 +53,7 @@ class ServerInventoryManager : public InventoryManager
void sendDetachedInventories(const std::string &peer_name, bool incremental,
std::function<void(const std::string &, Inventory *)> apply_cb);

private:
protected:
struct DetachedInventory
{
std::unique_ptr<Inventory> inventory;
Expand Down
17 changes: 12 additions & 5 deletions src/unittest/mock_inventorymanager.h
Expand Up @@ -17,26 +17,33 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <gamedef.h>
#include <inventory.h>
#include <inventorymanager.h>
#pragma once

class MockInventoryManager : public InventoryManager
#include "gamedef.h"
#include "inventory.h"
#include "server/serverinventorymgr.h"

class ServerEnvironment;

class MockInventoryManager : public ServerInventoryManager
{
public:
MockInventoryManager(IGameDef *gamedef) :
p1(gamedef->getItemDefManager()),
p2(gamedef->getItemDefManager())
{};

virtual Inventory* getInventory(const InventoryLocation &loc){
Inventory *getInventory(const InventoryLocation &loc) override
{
if (loc.type == InventoryLocation::PLAYER && loc.name == "p1")
return &p1;
if (loc.type == InventoryLocation::PLAYER && loc.name == "p2")
return &p2;
return nullptr;
}
void setInventoryModified(const InventoryLocation &loc) override {}

Inventory p1;
Inventory p2;

};
2 changes: 2 additions & 0 deletions src/unittest/mock_server.h
Expand Up @@ -17,6 +17,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#pragma once

#include <server.h>

class MockServer : public Server
Expand Down
114 changes: 81 additions & 33 deletions src/unittest/test_moveaction.cpp
Expand Up @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mock_serveractiveobject.h"

#include "scripting_server.h"
#include "server/mods.h"


class TestMoveAction : public TestBase
Expand All @@ -39,50 +40,40 @@ class TestMoveAction : public TestBase
void testMoveSomewhere(ServerActiveObject *obj, IGameDef *gamedef);
void testMoveUnallowed(ServerActiveObject *obj, IGameDef *gamedef);
void testMovePartial(ServerActiveObject *obj, IGameDef *gamedef);

void testSwap(ServerActiveObject *obj, IGameDef *gamedef);
void testSwapFromUnallowed(ServerActiveObject *obj, IGameDef *gamedef);
void testSwapToUnallowed(ServerActiveObject *obj, IGameDef *gamedef);

void testCallbacks(ServerActiveObject *obj, Server *server);
void testCallbacksSwap(ServerActiveObject *obj, Server *server);
};

static TestMoveAction g_test_instance;

const static char *helper_lua_src = R"(
core.register_allow_player_inventory_action(function(player, action, inventory, info)
if action == "move" then
return info.count
end
if info.stack:get_name() == "default:water" then
return 0
end
if info.stack:get_name() == "default:lava" then
return 5
end
return info.stack:get_count()
end)
)";

void TestMoveAction::runTests(IGameDef *gamedef)
{
MockServer server(getTestTempDirectory());

const auto helper_lua = getTestTempFile();
std::ofstream ofs(helper_lua, std::ios::out | std::ios::binary);
ofs << helper_lua_src;
ofs.close();

ServerScripting server_scripting(&server);
try {
server_scripting.loadMod(Server::getBuiltinLuaPath() + DIR_DELIM "init.lua", BUILTIN_MOD_NAME);
server_scripting.loadMod(helper_lua, BUILTIN_MOD_NAME);
// FIXME: When removing the line below, the unittest does NOT crash
// (but it should) when running all unittests in order or registration.
// Some Lua API functions used in builtin require the Mgr to be present.
server.m_modmgr = std::make_unique<ServerModManager>(server.m_path_world);

std::string builtin = Server::getBuiltinLuaPath() + DIR_DELIM;
server_scripting.loadMod(builtin + "init.lua", BUILTIN_MOD_NAME);
server_scripting.loadMod(builtin + "game" DIR_DELIM "tests" DIR_DELIM "test_moveaction.lua", BUILTIN_MOD_NAME);
} catch (ModError &e) {
// Print backtrace in case of syntax errors
rawstream << e.what() << std::endl;
num_tests_failed = 1;
return;
}

server.m_script = &server_scripting;

MetricsBackend mb;
ServerEnvironment server_env(nullptr, &server_scripting, &server, "", &mb);
MockServerActiveObject obj(&server_env);
Expand All @@ -92,9 +83,15 @@ void TestMoveAction::runTests(IGameDef *gamedef)
TEST(testMoveSomewhere, &obj, gamedef);
TEST(testMoveUnallowed, &obj, gamedef);
TEST(testMovePartial, &obj, gamedef);

TEST(testSwap, &obj, gamedef);
TEST(testSwapFromUnallowed, &obj, gamedef);
TEST(testSwapToUnallowed, &obj, gamedef);

TEST(testCallbacks, &obj, &server);
TEST(testCallbacksSwap, &obj, &server);

server.m_script = nullptr; // Do not free stack memory
}

static ItemStack parse_itemstack(const char *s)
Expand Down Expand Up @@ -165,26 +162,27 @@ void TestMoveAction::testMoveUnallowed(ServerActiveObject *obj, IGameDef *gamede
{
MockInventoryManager inv(gamedef);

inv.p1.addList("main", 10)->addItem(0, parse_itemstack("default:water 50"));
inv.p1.addList("main", 10)->addItem(0, parse_itemstack("default:takeput_deny 50"));
inv.p2.addList("main", 10);

apply_action("Move 20 player:p1 main 0 player:p2 main 0", &inv, obj, gamedef);

UASSERT(inv.p1.getList("main")->getItem(0).getItemString() == "default:water 50");
UASSERT(inv.p1.getList("main")->getItem(0).getItemString() == "default:takeput_deny 50");
UASSERT(inv.p2.getList("main")->getItem(0).empty())
}

void TestMoveAction::testMovePartial(ServerActiveObject *obj, IGameDef *gamedef)
{
MockInventoryManager inv(gamedef);

inv.p1.addList("main", 10)->addItem(0, parse_itemstack("default:lava 50"));
inv.p1.addList("main", 10)->addItem(0, parse_itemstack("default:takeput_max_5 50"));
inv.p2.addList("main", 10);

// Lua: limited to 5 per transaction
apply_action("Move 20 player:p1 main 0 player:p2 main 0", &inv, obj, gamedef);

UASSERT(inv.p1.getList("main")->getItem(0).getItemString() == "default:lava 45");
UASSERT(inv.p2.getList("main")->getItem(0).getItemString() == "default:lava 5");
UASSERT(inv.p1.getList("main")->getItem(0).getItemString() == "default:takeput_max_5 45");
UASSERT(inv.p2.getList("main")->getItem(0).getItemString() == "default:takeput_max_5 5");
}

void TestMoveAction::testSwap(ServerActiveObject *obj, IGameDef *gamedef)
Expand All @@ -204,12 +202,12 @@ void TestMoveAction::testSwapFromUnallowed(ServerActiveObject *obj, IGameDef *ga
{
MockInventoryManager inv(gamedef);

inv.p1.addList("main", 10)->addItem(0, parse_itemstack("default:water 50"));
inv.p1.addList("main", 10)->addItem(0, parse_itemstack("default:takeput_deny 50"));
inv.p2.addList("main", 10)->addItem(0, parse_itemstack("default:brick 60"));

apply_action("Move 50 player:p1 main 0 player:p2 main 0", &inv, obj, gamedef);

UASSERT(inv.p1.getList("main")->getItem(0).getItemString() == "default:water 50");
UASSERT(inv.p1.getList("main")->getItem(0).getItemString() == "default:takeput_deny 50");
UASSERT(inv.p2.getList("main")->getItem(0).getItemString() == "default:brick 60");
}

Expand All @@ -218,10 +216,60 @@ void TestMoveAction::testSwapToUnallowed(ServerActiveObject *obj, IGameDef *game
MockInventoryManager inv(gamedef);

inv.p1.addList("main", 10)->addItem(0, parse_itemstack("default:stone 50"));
inv.p2.addList("main", 10)->addItem(0, parse_itemstack("default:water 60"));
inv.p2.addList("main", 10)->addItem(0, parse_itemstack("default:takeput_deny 60"));

apply_action("Move 50 player:p1 main 0 player:p2 main 0", &inv, obj, gamedef);

UASSERT(inv.p1.getList("main")->getItem(0).getItemString() == "default:stone 50");
UASSERT(inv.p2.getList("main")->getItem(0).getItemString() == "default:water 60");
UASSERT(inv.p2.getList("main")->getItem(0).getItemString() == "default:takeput_deny 60");
}

static bool check_function(lua_State *L, bool expect_swap)
{
bool ok = false;
int error_handler = PUSH_ERROR_HANDLER(L);

lua_getglobal(L, "core");
lua_getfield(L, -1, "__helper_check_callbacks");
lua_pushboolean(L, expect_swap);
int result = lua_pcall(L, 1, 1, error_handler);
if (result == 0)
ok = lua_toboolean(L, -1);
else
errorstream << lua_tostring(L, -1) << std::endl; // Error msg

lua_settop(L, 0);
return ok;
}

void TestMoveAction::testCallbacks(ServerActiveObject *obj, Server *server)
{
server->m_inventory_mgr = std::make_unique<MockInventoryManager>(server);
MockInventoryManager &inv = *(MockInventoryManager *)server->getInventoryMgr();

inv.p1.addList("main", 10)->addItem(0, parse_itemstack("default:takeput_cb_1 10"));
inv.p2.addList("main", 10);

apply_action("Move 10 player:p1 main 0 player:p2 main 1", &inv, obj, server);

// Expecting no swap. 4 callback executions in total. See Lua file for details.
UASSERT(check_function(server->getScriptIface()->getStack(), false));

server->m_inventory_mgr.reset();
}

void TestMoveAction::testCallbacksSwap(ServerActiveObject *obj, Server *server)
{
server->m_inventory_mgr = std::make_unique<MockInventoryManager>(server);
MockInventoryManager &inv = *(MockInventoryManager *)server->getInventoryMgr();

inv.p1.addList("main", 10)->addItem(0, parse_itemstack("default:takeput_cb_2 50"));
inv.p2.addList("main", 10)->addItem(1, parse_itemstack("default:takeput_cb_1 10"));

apply_action("Move 10 player:p1 main 0 player:p2 main 1", &inv, obj, server);

// Expecting swap. 8 callback executions in total. See Lua file for details.
UASSERT(check_function(server->getScriptIface()->getStack(), true));

server->m_inventory_mgr.reset();
}

0 comments on commit 61a5733

Please sign in to comment.