/* Copyright (C) 2017 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "lib/self_test.h" #include "maths/Matrix3D.h" #include "maths/Vector3D.h" #include "ps/XML/Xeromyces.h" #include "simulation2/MessageTypes.h" #include "simulation2/system/Component.h" #include "simulation2/components/ICmpTerrain.h" #include "simulation2/serialization/DebugSerializer.h" #include "simulation2/serialization/HashSerializer.h" #include "simulation2/serialization/StdSerializer.h" #include "simulation2/serialization/StdDeserializer.h" #include /** * @file * Various common features for component test cases. */ /** * Class to test a single component. * - Create an instance of this class * - Use AddMock to add mock components that the tested component relies on * - Use Add to add the test component itself, and it returns a component pointer * - Call methods on the component pointer * - Use Roundtrip to test the consistency of serialization */ class ComponentTestHelper { CSimContext m_Context; CComponentManager m_ComponentManager; CParamNode m_Param; IComponent* m_Cmp; EComponentTypeId m_Cid; public: ComponentTestHelper(shared_ptr runtime) : m_Context(), m_ComponentManager(m_Context, runtime), m_Cmp(NULL) { m_ComponentManager.LoadComponentTypes(); } ScriptInterface& GetScriptInterface() { return m_ComponentManager.GetScriptInterface(); } CSimContext& GetSimContext() { return m_Context; } /** * Call this once to initialise the test helper with a component. */ template T* Add(EComponentTypeId cid, const std::string& xml, entity_id_t ent = 10) { TS_ASSERT(m_Cmp == NULL); CEntityHandle handle; if (ent == SYSTEM_ENTITY) { m_ComponentManager.InitSystemEntity(); handle = m_ComponentManager.GetSystemEntity(); m_Context.SetSystemEntity(handle); } else handle = m_ComponentManager.LookupEntityHandle(ent, true); m_Cid = cid; TS_ASSERT_EQUALS(CParamNode::LoadXMLString(m_Param, ("" + xml + "").c_str()), PSRETURN_OK); TS_ASSERT(m_ComponentManager.AddComponent(handle, m_Cid, m_Param.GetChild("test"))); m_Cmp = m_ComponentManager.QueryInterface(ent, T::GetInterfaceId()); TS_ASSERT(m_Cmp != NULL); return static_cast (m_Cmp); } void AddMock(entity_id_t ent, EInterfaceId iid, IComponent& component) { CEntityHandle handle; if (ent == SYSTEM_ENTITY) { m_ComponentManager.InitSystemEntity(); handle = m_ComponentManager.GetSystemEntity(); m_Context.SetSystemEntity(handle); } else handle = m_ComponentManager.LookupEntityHandle(ent, true); m_ComponentManager.AddMockComponent(handle, iid, component); } void HandleMessage(IComponent* cmp, const CMessage& msg, bool global) { cmp->HandleMessage(msg, global); } /** * Checks that the object roundtrips through its serialize/deserialize functions correctly. * Computes the debug output, hash, and binary serialization; then deserializes into a new * system and checks the serialization outputs are unchanged. */ void Roundtrip(bool verbose = false) { std::stringstream dbgstr1; CDebugSerializer dbg1(GetScriptInterface(), dbgstr1); m_Cmp->Serialize(dbg1); if (verbose) std::cout << "--------\n" << dbgstr1.str() << "--------\n"; CHashSerializer hash1(GetScriptInterface()); m_Cmp->Serialize(hash1); std::stringstream stdstr1; CStdSerializer std1(GetScriptInterface(), stdstr1); m_Cmp->Serialize(std1); ComponentTestHelper test2(GetScriptInterface().GetRuntime()); // (We should never need to add any mock objects etc to test2, since deserialization // mustn't depend on other components already existing) CEntityHandle ent = test2.m_ComponentManager.LookupEntityHandle(10, true); CStdDeserializer stdde2(test2.GetScriptInterface(), stdstr1); IComponent* cmp2 = test2.m_ComponentManager.ConstructComponent(ent, m_Cid); cmp2->Deserialize(m_Param.GetChild("test"), stdde2); TS_ASSERT(stdstr1.peek() == EOF); // Deserialize must read whole stream std::stringstream dbgstr2; CDebugSerializer dbg2(test2.GetScriptInterface(), dbgstr2); cmp2->Serialize(dbg2); if (verbose) std::cout << "--------\n" << dbgstr2.str() << "--------\n"; CHashSerializer hash2(test2.GetScriptInterface()); cmp2->Serialize(hash2); std::stringstream stdstr2; CStdSerializer std2(test2.GetScriptInterface(), stdstr2); cmp2->Serialize(std2); TS_ASSERT_EQUALS(dbgstr1.str(), dbgstr2.str()); TS_ASSERT_EQUALS(hash1.GetHashLength(), hash2.GetHashLength()); TS_ASSERT_SAME_DATA(hash1.ComputeHash(), hash2.ComputeHash(), hash1.GetHashLength()); TS_ASSERT_EQUALS(stdstr1.str(), stdstr2.str()); // TODO: need to extend this so callers can run methods on the cloned component // to check that all its data is still correct } }; /** * Simple terrain implementation with constant height of 50. */ class MockTerrain : public ICmpTerrain { public: DEFAULT_MOCK_COMPONENT() virtual bool IsLoaded() const { return true; } virtual CFixedVector3D CalcNormal(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) const { return CFixedVector3D(fixed::FromInt(0), fixed::FromInt(1), fixed::FromInt(0)); } virtual CVector3D CalcExactNormal(float UNUSED(x), float UNUSED(z)) const { return CVector3D(0.f, 1.f, 0.f); } virtual entity_pos_t GetGroundLevel(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) const { return entity_pos_t::FromInt(50); } virtual float GetExactGroundLevel(float UNUSED(x), float UNUSED(z)) const { return 50.f; } virtual u16 GetTilesPerSide() const { return 16; } virtual u16 GetVerticesPerSide() const { return 17; } virtual CTerrain* GetCTerrain() { return NULL; } virtual void MakeDirty(i32 UNUSED(i0), i32 UNUSED(j0), i32 UNUSED(i1), i32 UNUSED(j1)) { } virtual void ReloadTerrain(bool UNUSED(ReloadWater)) { } };