mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 13:23:56 -07:00
250 lines
6.7 KiB
C++
250 lines
6.7 KiB
C++
/* Copyright (C) 2023 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 <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#ifndef INCLUDED_COMPONENTTEST
|
||
#define INCLUDED_COMPONENTTEST
|
||
|
||
#include "lib/self_test.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 <iostream>
|
||
|
||
/**
|
||
* @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;
|
||
bool m_isSystemEntityInit = false;
|
||
|
||
public:
|
||
ComponentTestHelper(ScriptContext& scriptContext) :
|
||
m_Context(), m_ComponentManager(m_Context, scriptContext), m_Cmp(NULL)
|
||
{
|
||
m_ComponentManager.LoadComponentTypes();
|
||
}
|
||
|
||
const ScriptInterface& GetScriptInterface()
|
||
{
|
||
return m_ComponentManager.GetScriptInterface();
|
||
}
|
||
|
||
CSimContext& GetSimContext()
|
||
{
|
||
return m_Context;
|
||
}
|
||
|
||
/**
|
||
* Call this once to initialise the test helper with a component.
|
||
*/
|
||
template<typename T>
|
||
T* Add(EComponentTypeId cid, const std::string& xml, entity_id_t ent = 10)
|
||
{
|
||
TS_ASSERT(m_Cmp == NULL);
|
||
|
||
CEntityHandle handle;
|
||
if (ent == SYSTEM_ENTITY)
|
||
{
|
||
if (!m_isSystemEntityInit)
|
||
{
|
||
m_ComponentManager.InitSystemEntity();
|
||
m_isSystemEntityInit = true;
|
||
}
|
||
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, ("<test>" + xml + "</test>").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<T*> (m_Cmp);
|
||
}
|
||
|
||
void AddMock(entity_id_t ent, EInterfaceId iid, IComponent& component)
|
||
{
|
||
CEntityHandle handle;
|
||
if (ent == SYSTEM_ENTITY)
|
||
{
|
||
if (!m_isSystemEntityInit)
|
||
{
|
||
m_ComponentManager.InitSystemEntity();
|
||
m_isSystemEntityInit = true;
|
||
}
|
||
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().GetContext());
|
||
// (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()
|
||
|
||
bool IsLoaded() const override
|
||
{
|
||
return true;
|
||
}
|
||
|
||
CFixedVector3D CalcNormal(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) const override
|
||
{
|
||
return CFixedVector3D(fixed::FromInt(0), fixed::FromInt(1), fixed::FromInt(0));
|
||
}
|
||
|
||
CVector3D CalcExactNormal(float UNUSED(x), float UNUSED(z)) const override
|
||
{
|
||
return CVector3D(0.f, 1.f, 0.f);
|
||
}
|
||
|
||
entity_pos_t GetGroundLevel(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) const override
|
||
{
|
||
return entity_pos_t::FromInt(50);
|
||
}
|
||
|
||
float GetExactGroundLevel(float UNUSED(x), float UNUSED(z)) const override
|
||
{
|
||
return 50.f;
|
||
}
|
||
|
||
u16 GetTilesPerSide() const override
|
||
{
|
||
return 16;
|
||
}
|
||
|
||
u32 GetMapSize() const override
|
||
{
|
||
return GetTilesPerSide() * TERRAIN_TILE_SIZE;
|
||
}
|
||
|
||
u16 GetVerticesPerSide() const override
|
||
{
|
||
return 17;
|
||
}
|
||
|
||
CTerrain* GetCTerrain() override
|
||
{
|
||
return nullptr;
|
||
}
|
||
|
||
void MakeDirty(i32 UNUSED(i0), i32 UNUSED(j0), i32 UNUSED(i1), i32 UNUSED(j1)) override
|
||
{
|
||
}
|
||
|
||
void ReloadTerrain(bool UNUSED(ReloadWater)) override
|
||
{
|
||
}
|
||
};
|
||
|
||
#endif // INCLUDED_COMPONENTTEST
|