aboutsummaryrefslogtreecommitdiff
path: root/src/unittest/test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/unittest/test.cpp')
-rw-r--r--src/unittest/test.cpp547
1 files changed, 547 insertions, 0 deletions
diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp
new file mode 100644
index 000000000..6fea0b8a0
--- /dev/null
+++ b/src/unittest/test.cpp
@@ -0,0 +1,547 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "test.h"
+
+#include "debug.h"
+#include "log.h"
+#include "nodedef.h"
+#include "itemdef.h"
+#include "gamedef.h"
+
+content_t CONTENT_STONE;
+content_t CONTENT_GRASS;
+content_t CONTENT_TORCH;
+
+////////////////////////////////////////////////////////////////////////////////
+
+////
+//// TestGameDef
+////
+
+class TestGameDef : public IGameDef {
+public:
+ TestGameDef();
+ ~TestGameDef();
+
+ IItemDefManager *getItemDefManager() { return m_itemdef; }
+ INodeDefManager *getNodeDefManager() { return m_nodedef; }
+ ICraftDefManager *getCraftDefManager() { return m_craftdef; }
+ ITextureSource *getTextureSource() { return m_texturesrc; }
+ IShaderSource *getShaderSource() { return m_shadersrc; }
+ ISoundManager *getSoundManager() { return m_soundmgr; }
+ MtEventManager *getEventManager() { return m_eventmgr; }
+ scene::ISceneManager *getSceneManager() { return m_scenemgr; }
+ IRollbackManager *getRollbackManager() { return m_rollbackmgr; }
+ EmergeManager *getEmergeManager() { return m_emergemgr; }
+
+ scene::IAnimatedMesh *getMesh(const std::string &filename) { return NULL; }
+ bool checkLocalPrivilege(const std::string &priv) { return false; }
+ u16 allocateUnknownNodeId(const std::string &name) { return 0; }
+
+ void defineSomeNodes();
+
+private:
+ IItemDefManager *m_itemdef;
+ INodeDefManager *m_nodedef;
+ ICraftDefManager *m_craftdef;
+ ITextureSource *m_texturesrc;
+ IShaderSource *m_shadersrc;
+ ISoundManager *m_soundmgr;
+ MtEventManager *m_eventmgr;
+ scene::ISceneManager *m_scenemgr;
+ IRollbackManager *m_rollbackmgr;
+ EmergeManager *m_emergemgr;
+};
+
+
+TestGameDef::TestGameDef()
+{
+ m_itemdef = createItemDefManager();
+ m_nodedef = createNodeDefManager();
+
+ defineSomeNodes();
+}
+
+
+TestGameDef::~TestGameDef()
+{
+ delete m_itemdef;
+ delete m_nodedef;
+}
+
+
+void TestGameDef::defineSomeNodes()
+{
+ IWritableItemDefManager *idef = (IWritableItemDefManager *)m_itemdef;
+ IWritableNodeDefManager *ndef = (IWritableNodeDefManager *)m_nodedef;
+
+ ItemDefinition itemdef;
+ ContentFeatures f;
+
+ //// Stone
+ itemdef = ItemDefinition();
+ itemdef.type = ITEM_NODE;
+ itemdef.name = "default:stone";
+ itemdef.description = "Stone";
+ itemdef.groups["cracky"] = 3;
+ itemdef.inventory_image = "[inventorycube"
+ "{default_stone.png"
+ "{default_stone.png"
+ "{default_stone.png";
+ f = ContentFeatures();
+ f.name = itemdef.name;
+ for(int i = 0; i < 6; i++)
+ f.tiledef[i].name = "default_stone.png";
+ f.is_ground_content = true;
+ idef->registerItem(itemdef);
+ CONTENT_STONE = ndef->set(f.name, f);
+
+ //// Grass
+ itemdef = ItemDefinition();
+ itemdef.type = ITEM_NODE;
+ itemdef.name = "default:dirt_with_grass";
+ itemdef.description = "Dirt with grass";
+ itemdef.groups["crumbly"] = 3;
+ itemdef.inventory_image = "[inventorycube"
+ "{default_grass.png"
+ "{default_dirt.png&default_grass_side.png"
+ "{default_dirt.png&default_grass_side.png";
+ f = ContentFeatures();
+ f.name = itemdef.name;
+ f.tiledef[0].name = "default_grass.png";
+ f.tiledef[1].name = "default_dirt.png";
+ for(int i = 2; i < 6; i++)
+ f.tiledef[i].name = "default_dirt.png^default_grass_side.png";
+ f.is_ground_content = true;
+ idef->registerItem(itemdef);
+ CONTENT_GRASS = ndef->set(f.name, f);
+
+ //// Torch (minimal definition for lighting tests)
+ itemdef = ItemDefinition();
+ itemdef.type = ITEM_NODE;
+ itemdef.name = "default:torch";
+ f = ContentFeatures();
+ f.name = itemdef.name;
+ f.param_type = CPT_LIGHT;
+ f.light_propagates = true;
+ f.sunlight_propagates = true;
+ f.light_source = LIGHT_MAX-1;
+ idef->registerItem(itemdef);
+ CONTENT_TORCH = ndef->set(f.name, f);
+}
+
+////
+//// run_tests
+////
+
+void run_tests()
+{
+ DSTACK(__FUNCTION_NAME);
+
+ u32 t1 = porting::getTime(PRECISION_MILLI);
+ TestGameDef gamedef;
+
+ log_set_lev_silence(LMT_ERROR, true);
+
+ u32 num_modules_failed = 0;
+ u32 num_total_tests_failed = 0;
+ u32 num_total_tests_run = 0;
+ std::vector<TestBase *> &testmods = TestManager::getTestModules();
+ for (size_t i = 0; i != testmods.size(); i++) {
+ if (!testmods[i]->testModule(&gamedef))
+ num_modules_failed++;
+
+ num_total_tests_failed += testmods[i]->num_tests_failed;
+ num_total_tests_run += testmods[i]->num_tests_run;
+ }
+
+ u32 tdiff = porting::getTime(PRECISION_MILLI) - t1;
+
+ log_set_lev_silence(LMT_ERROR, false);
+
+ const char *overall_status = (num_modules_failed == 0) ? "PASSED" : "FAILED";
+
+ dstream
+ << "++++++++++++++++++++++++++++++++++++++++"
+ << "++++++++++++++++++++++++++++++++++++++++" << std::endl
+ << "Unit Test Results: " << overall_status << std::endl
+ << " " << num_modules_failed << " / " << testmods.size()
+ << " failed modules (" << num_total_tests_failed << " / "
+ << num_total_tests_run << " failed individual tests)." << std::endl
+ << " Testing took " << tdiff << "ms total." << std::endl
+ << "++++++++++++++++++++++++++++++++++++++++"
+ << "++++++++++++++++++++++++++++++++++++++++" << std::endl;
+
+ if (num_modules_failed)
+ abort();
+}
+
+////
+//// TestBase
+////
+
+bool TestBase::testModule(IGameDef *gamedef)
+{
+ dstream << "======== Testing module " << getName() << std::endl;
+ u32 t1 = porting::getTime(PRECISION_MILLI);
+
+
+ runTests(gamedef);
+
+ u32 tdiff = porting::getTime(PRECISION_MILLI) - t1;
+ dstream << "======== Module " << getName() << " "
+ << (num_tests_failed ? "failed" : "passed") << " (" << num_tests_failed
+ << " failures / " << num_tests_run << " tests) - " << tdiff
+ << "ms" << std::endl;
+
+ return num_tests_failed == 0;
+}
+
+
+/*
+ NOTE: These tests became non-working then NodeContainer was removed.
+ These should be redone, utilizing some kind of a virtual
+ interface for Map (IMap would be fine).
+*/
+#if 0
+struct TestMapBlock: public TestBase
+{
+ class TC : public NodeContainer
+ {
+ public:
+
+ MapNode node;
+ bool position_valid;
+ core::list<v3s16> validity_exceptions;
+
+ TC()
+ {
+ position_valid = true;
+ }
+
+ virtual bool isValidPosition(v3s16 p)
+ {
+ //return position_valid ^ (p==position_valid_exception);
+ bool exception = false;
+ for(core::list<v3s16>::Iterator i=validity_exceptions.begin();
+ i != validity_exceptions.end(); i++)
+ {
+ if(p == *i)
+ {
+ exception = true;
+ break;
+ }
+ }
+ return exception ? !position_valid : position_valid;
+ }
+
+ virtual MapNode getNode(v3s16 p)
+ {
+ if(isValidPosition(p) == false)
+ throw InvalidPositionException();
+ return node;
+ }
+
+ virtual void setNode(v3s16 p, MapNode & n)
+ {
+ if(isValidPosition(p) == false)
+ throw InvalidPositionException();
+ };
+
+ virtual u16 nodeContainerId() const
+ {
+ return 666;
+ }
+ };
+
+ void Run()
+ {
+ TC parent;
+
+ MapBlock b(&parent, v3s16(1,1,1));
+ v3s16 relpos(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
+
+ UASSERT(b.getPosRelative() == relpos);
+
+ UASSERT(b.getBox().MinEdge.X == MAP_BLOCKSIZE);
+ UASSERT(b.getBox().MaxEdge.X == MAP_BLOCKSIZE*2-1);
+ UASSERT(b.getBox().MinEdge.Y == MAP_BLOCKSIZE);
+ UASSERT(b.getBox().MaxEdge.Y == MAP_BLOCKSIZE*2-1);
+ UASSERT(b.getBox().MinEdge.Z == MAP_BLOCKSIZE);
+ UASSERT(b.getBox().MaxEdge.Z == MAP_BLOCKSIZE*2-1);
+
+ UASSERT(b.isValidPosition(v3s16(0,0,0)) == true);
+ UASSERT(b.isValidPosition(v3s16(-1,0,0)) == false);
+ UASSERT(b.isValidPosition(v3s16(-1,-142,-2341)) == false);
+ UASSERT(b.isValidPosition(v3s16(-124,142,2341)) == false);
+ UASSERT(b.isValidPosition(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1)) == true);
+ UASSERT(b.isValidPosition(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE,MAP_BLOCKSIZE-1)) == false);
+
+ /*
+ TODO: this method should probably be removed
+ if the block size isn't going to be set variable
+ */
+ /*UASSERT(b.getSizeNodes() == v3s16(MAP_BLOCKSIZE,
+ MAP_BLOCKSIZE, MAP_BLOCKSIZE));*/
+
+ // Changed flag should be initially set
+ UASSERT(b.getModified() == MOD_STATE_WRITE_NEEDED);
+ b.resetModified();
+ UASSERT(b.getModified() == MOD_STATE_CLEAN);
+
+ // All nodes should have been set to
+ // .d=CONTENT_IGNORE and .getLight() = 0
+ for(u16 z=0; z<MAP_BLOCKSIZE; z++)
+ for(u16 y=0; y<MAP_BLOCKSIZE; y++)
+ for(u16 x=0; x<MAP_BLOCKSIZE; x++)
+ {
+ //UASSERT(b.getNode(v3s16(x,y,z)).getContent() == CONTENT_AIR);
+ UASSERT(b.getNode(v3s16(x,y,z)).getContent() == CONTENT_IGNORE);
+ UASSERT(b.getNode(v3s16(x,y,z)).getLight(LIGHTBANK_DAY) == 0);
+ UASSERT(b.getNode(v3s16(x,y,z)).getLight(LIGHTBANK_NIGHT) == 0);
+ }
+
+ {
+ MapNode n(CONTENT_AIR);
+ for(u16 z=0; z<MAP_BLOCKSIZE; z++)
+ for(u16 y=0; y<MAP_BLOCKSIZE; y++)
+ for(u16 x=0; x<MAP_BLOCKSIZE; x++)
+ {
+ b.setNode(v3s16(x,y,z), n);
+ }
+ }
+
+ /*
+ Parent fetch functions
+ */
+ parent.position_valid = false;
+ parent.node.setContent(5);
+
+ MapNode n;
+
+ // Positions in the block should still be valid
+ UASSERT(b.isValidPositionParent(v3s16(0,0,0)) == true);
+ UASSERT(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1)) == true);
+ n = b.getNodeParent(v3s16(0,MAP_BLOCKSIZE-1,0));
+ UASSERT(n.getContent() == CONTENT_AIR);
+
+ // ...but outside the block they should be invalid
+ UASSERT(b.isValidPositionParent(v3s16(-121,2341,0)) == false);
+ UASSERT(b.isValidPositionParent(v3s16(-1,0,0)) == false);
+ UASSERT(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE)) == false);
+
+ {
+ bool exception_thrown = false;
+ try{
+ // This should throw an exception
+ MapNode n = b.getNodeParent(v3s16(0,0,-1));
+ }
+ catch(InvalidPositionException &e)
+ {
+ exception_thrown = true;
+ }
+ UASSERT(exception_thrown);
+ }
+
+ parent.position_valid = true;
+ // Now the positions outside should be valid
+ UASSERT(b.isValidPositionParent(v3s16(-121,2341,0)) == true);
+ UASSERT(b.isValidPositionParent(v3s16(-1,0,0)) == true);
+ UASSERT(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE)) == true);
+ n = b.getNodeParent(v3s16(0,0,MAP_BLOCKSIZE));
+ UASSERT(n.getContent() == 5);
+
+ /*
+ Set a node
+ */
+ v3s16 p(1,2,0);
+ n.setContent(4);
+ b.setNode(p, n);
+ UASSERT(b.getNode(p).getContent() == 4);
+ //TODO: Update to new system
+ /*UASSERT(b.getNodeTile(p) == 4);
+ UASSERT(b.getNodeTile(v3s16(-1,-1,0)) == 5);*/
+
+ /*
+ propagateSunlight()
+ */
+ // Set lighting of all nodes to 0
+ for(u16 z=0; z<MAP_BLOCKSIZE; z++){
+ for(u16 y=0; y<MAP_BLOCKSIZE; y++){
+ for(u16 x=0; x<MAP_BLOCKSIZE; x++){
+ MapNode n = b.getNode(v3s16(x,y,z));
+ n.setLight(LIGHTBANK_DAY, 0);
+ n.setLight(LIGHTBANK_NIGHT, 0);
+ b.setNode(v3s16(x,y,z), n);
+ }
+ }
+ }
+ {
+ /*
+ Check how the block handles being a lonely sky block
+ */
+ parent.position_valid = true;
+ b.setIsUnderground(false);
+ parent.node.setContent(CONTENT_AIR);
+ parent.node.setLight(LIGHTBANK_DAY, LIGHT_SUN);
+ parent.node.setLight(LIGHTBANK_NIGHT, 0);
+ core::map<v3s16, bool> light_sources;
+ // The bottom block is invalid, because we have a shadowing node
+ UASSERT(b.propagateSunlight(light_sources) == false);
+ UASSERT(b.getNode(v3s16(1,4,0)).getLight(LIGHTBANK_DAY) == LIGHT_SUN);
+ UASSERT(b.getNode(v3s16(1,3,0)).getLight(LIGHTBANK_DAY) == LIGHT_SUN);
+ UASSERT(b.getNode(v3s16(1,2,0)).getLight(LIGHTBANK_DAY) == 0);
+ UASSERT(b.getNode(v3s16(1,1,0)).getLight(LIGHTBANK_DAY) == 0);
+ UASSERT(b.getNode(v3s16(1,0,0)).getLight(LIGHTBANK_DAY) == 0);
+ UASSERT(b.getNode(v3s16(1,2,3)).getLight(LIGHTBANK_DAY) == LIGHT_SUN);
+ UASSERT(b.getFaceLight2(1000, p, v3s16(0,1,0)) == LIGHT_SUN);
+ UASSERT(b.getFaceLight2(1000, p, v3s16(0,-1,0)) == 0);
+ UASSERT(b.getFaceLight2(0, p, v3s16(0,-1,0)) == 0);
+ // According to MapBlock::getFaceLight,
+ // The face on the z+ side should have double-diminished light
+ //UASSERT(b.getFaceLight(p, v3s16(0,0,1)) == diminish_light(diminish_light(LIGHT_MAX)));
+ // The face on the z+ side should have diminished light
+ UASSERT(b.getFaceLight2(1000, p, v3s16(0,0,1)) == diminish_light(LIGHT_MAX));
+ }
+ /*
+ Check how the block handles being in between blocks with some non-sunlight
+ while being underground
+ */
+ {
+ // Make neighbours to exist and set some non-sunlight to them
+ parent.position_valid = true;
+ b.setIsUnderground(true);
+ parent.node.setLight(LIGHTBANK_DAY, LIGHT_MAX/2);
+ core::map<v3s16, bool> light_sources;
+ // The block below should be valid because there shouldn't be
+ // sunlight in there either
+ UASSERT(b.propagateSunlight(light_sources, true) == true);
+ // Should not touch nodes that are not affected (that is, all of them)
+ //UASSERT(b.getNode(v3s16(1,2,3)).getLight() == LIGHT_SUN);
+ // Should set light of non-sunlighted blocks to 0.
+ UASSERT(b.getNode(v3s16(1,2,3)).getLight(LIGHTBANK_DAY) == 0);
+ }
+ /*
+ Set up a situation where:
+ - There is only air in this block
+ - There is a valid non-sunlighted block at the bottom, and
+ - Invalid blocks elsewhere.
+ - the block is not underground.
+
+ This should result in bottom block invalidity
+ */
+ {
+ b.setIsUnderground(false);
+ // Clear block
+ for(u16 z=0; z<MAP_BLOCKSIZE; z++){
+ for(u16 y=0; y<MAP_BLOCKSIZE; y++){
+ for(u16 x=0; x<MAP_BLOCKSIZE; x++){
+ MapNode n;
+ n.setContent(CONTENT_AIR);
+ n.setLight(LIGHTBANK_DAY, 0);
+ b.setNode(v3s16(x,y,z), n);
+ }
+ }
+ }
+ // Make neighbours invalid
+ parent.position_valid = false;
+ // Add exceptions to the top of the bottom block
+ for(u16 x=0; x<MAP_BLOCKSIZE; x++)
+ for(u16 z=0; z<MAP_BLOCKSIZE; z++)
+ {
+ parent.validity_exceptions.push_back(v3s16(MAP_BLOCKSIZE+x, MAP_BLOCKSIZE-1, MAP_BLOCKSIZE+z));
+ }
+ // Lighting value for the valid nodes
+ parent.node.setLight(LIGHTBANK_DAY, LIGHT_MAX/2);
+ core::map<v3s16, bool> light_sources;
+ // Bottom block is not valid
+ UASSERT(b.propagateSunlight(light_sources) == false);
+ }
+ }
+};
+
+struct TestMapSector: public TestBase
+{
+ class TC : public NodeContainer
+ {
+ public:
+
+ MapNode node;
+ bool position_valid;
+
+ TC()
+ {
+ position_valid = true;
+ }
+
+ virtual bool isValidPosition(v3s16 p)
+ {
+ return position_valid;
+ }
+
+ virtual MapNode getNode(v3s16 p)
+ {
+ if(position_valid == false)
+ throw InvalidPositionException();
+ return node;
+ }
+
+ virtual void setNode(v3s16 p, MapNode & n)
+ {
+ if(position_valid == false)
+ throw InvalidPositionException();
+ };
+
+ virtual u16 nodeContainerId() const
+ {
+ return 666;
+ }
+ };
+
+ void Run()
+ {
+ TC parent;
+ parent.position_valid = false;
+
+ // Create one with no heightmaps
+ ServerMapSector sector(&parent, v2s16(1,1));
+
+ UASSERT(sector.getBlockNoCreateNoEx(0) == 0);
+ UASSERT(sector.getBlockNoCreateNoEx(1) == 0);
+
+ MapBlock * bref = sector.createBlankBlock(-2);
+
+ UASSERT(sector.getBlockNoCreateNoEx(0) == 0);
+ UASSERT(sector.getBlockNoCreateNoEx(-2) == bref);
+
+ //TODO: Check for AlreadyExistsException
+
+ /*bool exception_thrown = false;
+ try{
+ sector.getBlock(0);
+ }
+ catch(InvalidPositionException &e){
+ exception_thrown = true;
+ }
+ UASSERT(exception_thrown);*/
+
+ }
+};
+#endif