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 Jan 7, 2024
1 parent bb0928d commit 918904b
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 24 deletions.
1 change: 1 addition & 0 deletions src/script/cpp_api/s_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,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
1 change: 1 addition & 0 deletions src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ class Server : public con::PeerHandler, public MapEventReceiver,
friend class EmergeThread;
friend class RemoteClient;
friend class TestServerShutdownState;
friend class TestMoveAction;

struct ShutdownState {
friend class TestServerShutdownState;
Expand Down
7 changes: 4 additions & 3 deletions src/server/serverinventorymgr.h
Original file line number Diff line number Diff line change
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
91 changes: 84 additions & 7 deletions src/unittest/helpers/helper_moveaction.lua
Original file line number Diff line number Diff line change
@@ -1,15 +1,92 @@
minetest.register_allow_player_inventory_action(function(player, action, inventory, info)
-- 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")
local exec_pattern = nil -- The expected pattern table (PATTERN_NORMAL or PATTERN_SWAP)

-- Checks whether the logged callbacks equal the expected pattern
minetest.__helper_check_callbacks = function()
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 = {}
exec_pattern = nil
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 info.stack:get_name() == "default:water" then
return 0
end
if action == "take" or action == "put" then
assert(not info.stack:is_empty(), "Stack empty in: " .. action)

if info.stack:get_name() == "default:lava" then
return 5
-- 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
if #exec_listing == 0 then
if inv:get_stack(info.listname, info.index):is_empty() then
exec_pattern = PATTERN_NORMAL
else
exec_pattern = PATTERN_SWAP
end
end
-- Log callback as executed
table.insert(exec_listing, "allow_" .. action)
return -- Unlimited
end
end

return info.stack:get_count()
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)
17 changes: 12 additions & 5 deletions src/unittest/mock_inventorymanager.h
Original file line number Diff line number Diff line change
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;

};
85 changes: 76 additions & 9 deletions src/unittest/test_moveaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,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 @@ -40,9 +41,13 @@ 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;
Expand All @@ -53,6 +58,10 @@ void TestMoveAction::runTests(IGameDef *gamedef)

ServerScripting server_scripting(&server);
try {
// FIXME: When removing the line below, the unittest does NOT crash
// (but it should) when running all unittests in order or registration.
server.m_modmgr = std::make_unique<ServerModManager>(TEST_WORLDDIR);

server_scripting.loadMod(Server::getBuiltinLuaPath() + DIR_DELIM "init.lua", BUILTIN_MOD_NAME);
server_scripting.loadMod(
std::string(HELPERS_PATH) + DIR_DELIM "helper_moveaction.lua", BUILTIN_MOD_NAME
Expand All @@ -64,6 +73,8 @@ void TestMoveAction::runTests(IGameDef *gamedef)
return;
}

server.m_script = &server_scripting;

MetricsBackend mb;
ServerEnvironment server_env(nullptr, &server_scripting, &server, "", &mb);
MockServerActiveObject obj(&server_env);
Expand All @@ -73,9 +84,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 @@ -146,26 +163,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 @@ -185,12 +203,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 @@ -199,10 +217,59 @@ 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 ok = false;
int error_handler = PUSH_ERROR_HANDLER(L);

lua_getglobal(L, "minetest");
lua_getfield(L, -1, "__helper_check_callbacks");
int result = lua_pcall(L, 0, 1, error_handler);
if (result == 0)
ok = lua_toboolean(L, -1);
else
puts(lua_tostring(L, -1)); // 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 4 callback executions. See Lua file for details.
UASSERT(check_function(server->getScriptIface()->getStack()));

delete server->m_inventory_mgr.release();
}

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 8 callback executions run. See Lua file for details.
UASSERT(check_function(server->getScriptIface()->getStack()));

delete server->m_inventory_mgr.release();
}

0 comments on commit 918904b

Please sign in to comment.