From 2dba29ebf20e95068872b758a9c16daeb4c74440 Mon Sep 17 00:00:00 2001 From: kwolekr Date: Fri, 8 May 2015 00:05:08 -0400 Subject: [PATCH] Tests: Add schematic unittests Improve schematic file-saving interface Add ability to create temporary test files --- build/android/jni/Android.mk | 1 + src/mg_schematic.cpp | 28 ++-- src/mg_schematic.h | 27 ++-- src/script/lua_api/l_mapgen.cpp | 7 +- src/unittest/CMakeLists.txt | 1 + src/unittest/test.cpp | 26 ++++ src/unittest/test.h | 6 + src/unittest/test_schematic.cpp | 265 ++++++++++++++++++++++++++++++++ 8 files changed, 329 insertions(+), 32 deletions(-) create mode 100644 src/unittest/test_schematic.cpp diff --git a/build/android/jni/Android.mk b/build/android/jni/Android.mk index 17c6fa7dd..722efa42b 100644 --- a/build/android/jni/Android.mk +++ b/build/android/jni/Android.mk @@ -224,6 +224,7 @@ LOCAL_SRC_FILES := \ jni/src/unittest/test_objdef.cpp \ jni/src/unittest/test_profiler.cpp \ jni/src/unittest/test_random.cpp \ + jni/src/unittest/test_schematic.cpp \ jni/src/unittest/test_serialization.cpp \ jni/src/unittest/test_settings.cpp \ jni/src/unittest/test_socket.cpp \ diff --git a/src/mg_schematic.cpp b/src/mg_schematic.cpp index bea08ef12..33f82a74c 100644 --- a/src/mg_schematic.cpp +++ b/src/mg_schematic.cpp @@ -94,8 +94,7 @@ void Schematic::resolveNodeNames() } -void Schematic::blitToVManip(v3s16 p, MMVManip *vm, - Rotation rot, bool force_placement) +void Schematic::blitToVManip(v3s16 p, MMVManip *vm, Rotation rot, bool force_place) { sanity_check(m_ndef != NULL); @@ -151,7 +150,7 @@ void Schematic::blitToVManip(v3s16 p, MMVManip *vm, if (schemdata[i].param1 == MTSCHEM_PROB_NEVER) continue; - if (!force_placement) { + if (!force_place) { content_t c = vm->m_data[vi].getContent(); if (c != CONTENT_AIR && c != CONTENT_IGNORE) continue; @@ -174,7 +173,7 @@ void Schematic::blitToVManip(v3s16 p, MMVManip *vm, void Schematic::placeStructure(Map *map, v3s16 p, u32 flags, - Rotation rot, bool force_placement) + Rotation rot, bool force_place) { assert(schemdata != NULL); // Pre-condition sanity_check(m_ndef != NULL); @@ -198,7 +197,7 @@ void Schematic::placeStructure(Map *map, v3s16 p, u32 flags, v3s16 bp2 = getNodeBlockPos(p + s - v3s16(1,1,1)); vm->initialEmerge(bp1, bp2); - blitToVManip(p, vm, rot, force_placement); + blitToVManip(p, vm, rot, force_place); std::map lighting_modified_blocks; std::map modified_blocks; @@ -405,15 +404,17 @@ bool Schematic::loadSchematicFromFile(const std::string &filename, } -bool Schematic::saveSchematicToFile(const std::string &filename) +bool Schematic::saveSchematicToFile(const std::string &filename, + INodeDefManager *ndef) { MapNode *orig_schemdata = schemdata; std::vector ndef_nodenames; std::vector *names; - // Only carry out the modification if we know the nodes - // were resolved at this point - if (m_resolve_done) { + if (m_resolve_done && ndef == NULL) + ndef = m_ndef; + + if (ndef) { names = &ndef_nodenames; u32 volume = size.X * size.Y * size.Z; @@ -421,19 +422,22 @@ bool Schematic::saveSchematicToFile(const std::string &filename) for (u32 i = 0; i != volume; i++) schemdata[i] = orig_schemdata[i]; - generate_nodelist_and_update_ids(schemdata, volume, names, m_ndef); + generate_nodelist_and_update_ids(schemdata, volume, names, ndef); } else { // otherwise, use the names we have on hand in the list names = &m_nodenames; } std::ostringstream os(std::ios_base::binary); - serializeToMts(&os, *names); + bool status = serializeToMts(&os, *names); - if (m_resolve_done) { + if (ndef) { delete []schemdata; schemdata = orig_schemdata; } + if (!status) + return false; + return fs::safeWriteToFile(filename, os.str()); } diff --git a/src/mg_schematic.h b/src/mg_schematic.h index 5b546f879..3d3e328d3 100644 --- a/src/mg_schematic.h +++ b/src/mg_schematic.h @@ -85,26 +85,14 @@ enum SchematicFormatType { class Schematic : public ObjDef, public NodeResolver { public: - std::vector c_nodes; - - u32 flags; - v3s16 size; - MapNode *schemdata; - u8 *slice_probs; - Schematic(); virtual ~Schematic(); virtual void resolveNodeNames(); - void updateContentIds(); - - void blitToVManip(v3s16 p, MMVManip *vm, - Rotation rot, bool force_placement); - bool loadSchematicFromFile(const std::string &filename, INodeDefManager *ndef, - StringMap *replace_names); - bool saveSchematicToFile(const std::string &filename); + StringMap *replace_names=NULL); + bool saveSchematicToFile(const std::string &filename, INodeDefManager *ndef); bool getSchematicFromMap(Map *map, v3s16 p1, v3s16 p2); bool deserializeFromMts(std::istream *is, std::vector *names); @@ -112,11 +100,18 @@ public: bool serializeToLua(std::ostream *os, const std::vector &names, bool use_comments, u32 indent_spaces); - void placeStructure(Map *map, v3s16 p, u32 flags, - Rotation rot, bool force_placement); + void blitToVManip(v3s16 p, MMVManip *vm, Rotation rot, bool force_place); + void placeStructure(Map *map, v3s16 p, u32 flags, Rotation rot, bool force_place); + void applyProbabilities(v3s16 p0, std::vector > *plist, std::vector > *splist); + + std::vector c_nodes; + u32 flags; + v3s16 size; + MapNode *schemdata; + u8 *slice_probs; }; class SchematicManager : public ObjDefManager { diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index 7e9c07939..5422447ed 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -1031,10 +1031,9 @@ int ModApiMapgen::l_generate_decorations(lua_State *L) // create_schematic(p1, p2, probability_list, filename, y_slice_prob_list) int ModApiMapgen::l_create_schematic(lua_State *L) { - Schematic schem; - schem.m_ndef = getServer(L)->getNodeDefManager(); - + INodeDefManager *ndef = getServer(L)->getNodeDefManager(); Map *map = &(getEnv(L)->getMap()); + Schematic schem; v3s16 p1 = check_v3s16(L, 1); v3s16 p2 = check_v3s16(L, 2); @@ -1081,7 +1080,7 @@ int ModApiMapgen::l_create_schematic(lua_State *L) schem.applyProbabilities(p1, &prob_list, &slice_prob_list); - schem.saveSchematicToFile(filename); + schem.saveSchematicToFile(filename, ndef); actionstream << "create_schematic: saved schematic file '" << filename << "'." << std::endl; diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 6ec5eaea5..dcc68f9c8 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -12,6 +12,7 @@ set (UNITTEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/test_objdef.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_profiler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_random.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_schematic.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_serialization.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_settings.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_socket.cpp diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 57843da5e..d0ffb423f 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -276,9 +276,35 @@ bool TestBase::testModule(IGameDef *gamedef) << " failures / " << num_tests_run << " tests) - " << tdiff << "ms" << std::endl; + if (!m_test_dir.empty()) + fs::RecursiveDelete(m_test_dir); + return num_tests_failed == 0; } +std::string TestBase::getTestTempDirectory() +{ + if (!m_test_dir.empty()) + return m_test_dir; + + char buf[32]; + snprintf(buf, sizeof(buf), "%08X", myrand()); + + m_test_dir = fs::TempPath() + DIR_DELIM "mttest_" + buf; + if (!fs::CreateDir(m_test_dir)) + throw TestFailedException(); + + return m_test_dir; +} + +std::string TestBase::getTestTempFile() +{ + char buf[32]; + snprintf(buf, sizeof(buf), "%08X", myrand()); + + return getTestTempDirectory() + DIR_DELIM + buf + ".tmp"; +} + /* NOTE: These tests became non-working then NodeContainer was removed. diff --git a/src/unittest/test.h b/src/unittest/test.h index 8219e30fc..e1f1721f9 100644 --- a/src/unittest/test.h +++ b/src/unittest/test.h @@ -102,11 +102,17 @@ class IGameDef; class TestBase { public: bool testModule(IGameDef *gamedef); + std::string getTestTempDirectory(); + std::string getTestTempFile(); + virtual void runTests(IGameDef *gamedef) = 0; virtual const char *getName() = 0; u32 num_tests_failed; u32 num_tests_run; + +private: + std::string m_test_dir; }; class TestManager { diff --git a/src/unittest/test_schematic.cpp b/src/unittest/test_schematic.cpp new file mode 100644 index 000000000..c9e970459 --- /dev/null +++ b/src/unittest/test_schematic.cpp @@ -0,0 +1,265 @@ + /* +Minetest +Copyright (C) 2010-2014 kwolekr, Ryan Kwolek + +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 "mg_schematic.h" +#include "gamedef.h" +#include "nodedef.h" + +class TestSchematic : public TestBase { +public: + TestSchematic() { TestManager::registerTestModule(this); } + const char *getName() { return "TestSchematic"; } + + void runTests(IGameDef *gamedef); + + void testMtsSerializeDeserialize(INodeDefManager *ndef); + void testLuaTableSerialize(INodeDefManager *ndef); + void testFileSerializeDeserialize(INodeDefManager *ndef); + + static const content_t test_schem_data[7 * 6 * 4]; + static const content_t test_schem_data2[3 * 3 * 3]; + static const char *expected_lua_output; +}; + +static TestSchematic g_test_instance; + +void TestSchematic::runTests(IGameDef *gamedef) +{ + IWritableNodeDefManager *ndef = + (IWritableNodeDefManager *)gamedef->getNodeDefManager(); + + ndef->setNodeRegistrationStatus(true); + + TEST(testMtsSerializeDeserialize, ndef); + TEST(testLuaTableSerialize, ndef); + TEST(testFileSerializeDeserialize, ndef); + + ndef->resetNodeResolveState(); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestSchematic::testMtsSerializeDeserialize(INodeDefManager *ndef) +{ + static const v3s16 size(7, 6, 4); + static const u32 volume = size.X * size.Y * size.Z; + + std::stringstream ss(std::ios_base::binary | + std::ios_base::in | std::ios_base::out); + + std::vector names; + names.push_back("foo"); + names.push_back("bar"); + names.push_back("baz"); + names.push_back("qux"); + + Schematic schem, schem2; + + schem.flags = 0; + schem.size = size; + schem.schemdata = new MapNode[volume]; + schem.slice_probs = new u8[size.Y]; + for (size_t i = 0; i != volume; i++) + schem.schemdata[i] = MapNode(test_schem_data[i], MTSCHEM_PROB_ALWAYS, 0); + for (size_t y = 0; y != size.Y; y++) + schem.slice_probs[y] = MTSCHEM_PROB_ALWAYS; + + UASSERT(schem.serializeToMts(&ss, names)); + + ss.seekg(0); + names.clear(); + + UASSERT(schem2.deserializeFromMts(&ss, &names)); + + UASSERTEQ(size_t, names.size(), 4); + UASSERTEQ(std::string, names[0], "foo"); + UASSERTEQ(std::string, names[1], "bar"); + UASSERTEQ(std::string, names[2], "baz"); + UASSERTEQ(std::string, names[3], "qux"); + + UASSERT(schem2.size == size); + for (size_t i = 0; i != volume; i++) + UASSERT(schem2.schemdata[i] == schem.schemdata[i]); + for (size_t y = 0; y != size.Y; y++) + UASSERTEQ(u8, schem2.slice_probs[y], schem.slice_probs[y]); +} + + +void TestSchematic::testLuaTableSerialize(INodeDefManager *ndef) +{ + static const v3s16 size(3, 3, 3); + static const u32 volume = size.X * size.Y * size.Z; + + Schematic schem; + + schem.flags = 0; + schem.size = size; + schem.schemdata = new MapNode[volume]; + schem.slice_probs = new u8[size.Y]; + for (size_t i = 0; i != volume; i++) + schem.schemdata[i] = MapNode(test_schem_data2[i], MTSCHEM_PROB_ALWAYS, 0); + for (size_t y = 0; y != size.Y; y++) + schem.slice_probs[y] = MTSCHEM_PROB_ALWAYS; + + std::vector names; + names.push_back("air"); + names.push_back("default:lava_source"); + names.push_back("default:glass"); + + std::ostringstream ss(std::ios_base::binary); + + UASSERT(schem.serializeToLua(&ss, names, false, 0)); + UASSERTEQ(std::string, ss.str(), expected_lua_output); +} + + +void TestSchematic::testFileSerializeDeserialize(INodeDefManager *ndef) +{ + static const v3s16 size(3, 3, 3); + static const u32 volume = size.X * size.Y * size.Z; + static const content_t content_map[] = { + CONTENT_AIR, + t_CONTENT_STONE, + t_CONTENT_LAVA, + }; + static const content_t content_map2[] = { + CONTENT_AIR, + t_CONTENT_STONE, + t_CONTENT_WATER, + }; + StringMap replace_names; + replace_names["default:lava"] = "default:water"; + + Schematic schem1, schem2; + + //// Construct the schematic to save + schem1.flags = 0; + schem1.size = size; + schem1.schemdata = new MapNode[volume]; + schem1.slice_probs = new u8[size.Y]; + schem1.slice_probs[0] = 80; + schem1.slice_probs[1] = 160; + schem1.slice_probs[2] = 240; + + for (size_t i = 0; i != volume; i++) { + content_t c = content_map[test_schem_data2[i]]; + schem1.schemdata[i] = MapNode(c, MTSCHEM_PROB_ALWAYS, 0); + } + + std::string temp_file = getTestTempFile(); + UASSERT(schem1.saveSchematicToFile(temp_file, ndef)); + UASSERT(schem2.loadSchematicFromFile(temp_file, ndef, &replace_names)); + + UASSERT(schem2.size == size); + UASSERT(schem2.slice_probs[0] == 80); + UASSERT(schem2.slice_probs[1] == 160); + UASSERT(schem2.slice_probs[2] == 240); + + for (size_t i = 0; i != volume; i++) { + content_t c = content_map2[test_schem_data2[i]]; + UASSERT(schem2.schemdata[i] == MapNode(c, MTSCHEM_PROB_ALWAYS, 0)); + } +} + + +// Should form a cross-shaped-thing...? +const content_t TestSchematic::test_schem_data[7 * 6 * 4] = { + 3, 3, 1, 1, 1, 3, 3, // Y=0, Z=0 + 3, 0, 1, 2, 1, 0, 3, // Y=1, Z=0 + 3, 0, 1, 2, 1, 0, 3, // Y=2, Z=0 + 3, 1, 1, 2, 1, 1, 3, // Y=3, Z=0 + 3, 2, 2, 2, 2, 2, 3, // Y=4, Z=0 + 3, 1, 1, 2, 1, 1, 3, // Y=5, Z=0 + + 0, 0, 1, 1, 1, 0, 0, // Y=0, Z=1 + 0, 0, 1, 2, 1, 0, 0, // Y=1, Z=1 + 0, 0, 1, 2, 1, 0, 0, // Y=2, Z=1 + 1, 1, 1, 2, 1, 1, 1, // Y=3, Z=1 + 1, 2, 2, 2, 2, 2, 1, // Y=4, Z=1 + 1, 1, 1, 2, 1, 1, 1, // Y=5, Z=1 + + 0, 0, 1, 1, 1, 0, 0, // Y=0, Z=2 + 0, 0, 1, 2, 1, 0, 0, // Y=1, Z=2 + 0, 0, 1, 2, 1, 0, 0, // Y=2, Z=2 + 1, 1, 1, 2, 1, 1, 1, // Y=3, Z=2 + 1, 2, 2, 2, 2, 2, 1, // Y=4, Z=2 + 1, 1, 1, 2, 1, 1, 1, // Y=5, Z=2 + + 3, 3, 1, 1, 1, 3, 3, // Y=0, Z=3 + 3, 0, 1, 2, 1, 0, 3, // Y=1, Z=3 + 3, 0, 1, 2, 1, 0, 3, // Y=2, Z=3 + 3, 1, 1, 2, 1, 1, 3, // Y=3, Z=3 + 3, 2, 2, 2, 2, 2, 3, // Y=4, Z=3 + 3, 1, 1, 2, 1, 1, 3, // Y=5, Z=3 +}; + +const content_t TestSchematic::test_schem_data2[3 * 3 * 3] = { + 0, 0, 0, + 0, 2, 0, + 0, 0, 0, + + 0, 2, 0, + 2, 1, 2, + 0, 2, 0, + + 0, 0, 0, + 0, 2, 0, + 0, 0, 0, +}; + +const char *TestSchematic::expected_lua_output = + "schematic = {\n" + "\tsize = {x=3, y=3, z=3},\n" + "\tyslice_prob = {\n" + "\t\t{ypos=0, prob=255},\n" + "\t\t{ypos=1, prob=255},\n" + "\t\t{ypos=2, prob=255},\n" + "\t},\n" + "\tdata = {\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"default:glass\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"default:glass\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"default:glass\", param1=255, param2=0},\n" + "\t\t{name=\"default:lava_source\", param1=255, param2=0},\n" + "\t\t{name=\"default:glass\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"default:glass\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"default:glass\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t},\n" + "}\n";