Line data Source code
1 : /* Copyright (C) 2022 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
4 : * 0 A.D. is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 2 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * 0 A.D. is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : #ifndef INCLUDED_COMPONENTTEST
19 : #define INCLUDED_COMPONENTTEST
20 :
21 : #include "lib/self_test.h"
22 :
23 : #include "maths/Vector3D.h"
24 : #include "ps/XML/Xeromyces.h"
25 : #include "simulation2/MessageTypes.h"
26 : #include "simulation2/system/Component.h"
27 : #include "simulation2/components/ICmpTerrain.h"
28 : #include "simulation2/serialization/DebugSerializer.h"
29 : #include "simulation2/serialization/HashSerializer.h"
30 : #include "simulation2/serialization/StdSerializer.h"
31 : #include "simulation2/serialization/StdDeserializer.h"
32 :
33 : #include <iostream>
34 :
35 : /**
36 : * @file
37 : * Various common features for component test cases.
38 : */
39 :
40 : /**
41 : * Class to test a single component.
42 : * - Create an instance of this class
43 : * - Use AddMock to add mock components that the tested component relies on
44 : * - Use Add to add the test component itself, and it returns a component pointer
45 : * - Call methods on the component pointer
46 : * - Use Roundtrip to test the consistency of serialization
47 : */
48 22 : class ComponentTestHelper
49 : {
50 : CSimContext m_Context;
51 : CComponentManager m_ComponentManager;
52 : CParamNode m_Param;
53 : IComponent* m_Cmp;
54 : EComponentTypeId m_Cid;
55 : bool m_isSystemEntityInit = false;
56 :
57 : public:
58 22 : ComponentTestHelper(std::shared_ptr<ScriptContext> scriptContext) :
59 22 : m_Context(), m_ComponentManager(m_Context, scriptContext), m_Cmp(NULL)
60 : {
61 22 : m_ComponentManager.LoadComponentTypes();
62 22 : }
63 :
64 46 : const ScriptInterface& GetScriptInterface()
65 : {
66 46 : return m_ComponentManager.GetScriptInterface();
67 : }
68 :
69 : CSimContext& GetSimContext()
70 : {
71 : return m_Context;
72 : }
73 :
74 : /**
75 : * Call this once to initialise the test helper with a component.
76 : */
77 : template<typename T>
78 17 : T* Add(EComponentTypeId cid, const std::string& xml, entity_id_t ent = 10)
79 : {
80 17 : TS_ASSERT(m_Cmp == NULL);
81 :
82 17 : CEntityHandle handle;
83 17 : if (ent == SYSTEM_ENTITY)
84 : {
85 14 : if (!m_isSystemEntityInit)
86 : {
87 14 : m_ComponentManager.InitSystemEntity();
88 14 : m_isSystemEntityInit = true;
89 : }
90 14 : handle = m_ComponentManager.GetSystemEntity();
91 14 : m_Context.SetSystemEntity(handle);
92 : }
93 : else
94 3 : handle = m_ComponentManager.LookupEntityHandle(ent, true);
95 :
96 17 : m_Cid = cid;
97 17 : TS_ASSERT_EQUALS(CParamNode::LoadXMLString(m_Param, ("<test>" + xml + "</test>").c_str()), PSRETURN_OK);
98 17 : TS_ASSERT(m_ComponentManager.AddComponent(handle, m_Cid, m_Param.GetChild("test")));
99 17 : m_Cmp = m_ComponentManager.QueryInterface(ent, T::GetInterfaceId());
100 17 : TS_ASSERT(m_Cmp != NULL);
101 17 : return static_cast<T*> (m_Cmp);
102 : }
103 :
104 19 : void AddMock(entity_id_t ent, EInterfaceId iid, IComponent& component)
105 : {
106 19 : CEntityHandle handle;
107 19 : if (ent == SYSTEM_ENTITY)
108 : {
109 5 : if (!m_isSystemEntityInit)
110 : {
111 3 : m_ComponentManager.InitSystemEntity();
112 3 : m_isSystemEntityInit = true;
113 : }
114 5 : handle = m_ComponentManager.GetSystemEntity();
115 5 : m_Context.SetSystemEntity(handle);
116 : }
117 : else
118 14 : handle = m_ComponentManager.LookupEntityHandle(ent, true);
119 :
120 19 : m_ComponentManager.AddMockComponent(handle, iid, component);
121 19 : }
122 :
123 1 : void HandleMessage(IComponent* cmp, const CMessage& msg, bool global)
124 : {
125 1 : cmp->HandleMessage(msg, global);
126 1 : }
127 :
128 : /**
129 : * Checks that the object roundtrips through its serialize/deserialize functions correctly.
130 : * Computes the debug output, hash, and binary serialization; then deserializes into a new
131 : * system and checks the serialization outputs are unchanged.
132 : */
133 5 : void Roundtrip(bool verbose = false)
134 : {
135 10 : std::stringstream dbgstr1;
136 10 : CDebugSerializer dbg1(GetScriptInterface(), dbgstr1);
137 5 : m_Cmp->Serialize(dbg1);
138 :
139 5 : if (verbose)
140 0 : std::cout << "--------\n" << dbgstr1.str() << "--------\n";
141 :
142 10 : CHashSerializer hash1(GetScriptInterface());
143 5 : m_Cmp->Serialize(hash1);
144 :
145 10 : std::stringstream stdstr1;
146 10 : CStdSerializer std1(GetScriptInterface(), stdstr1);
147 5 : m_Cmp->Serialize(std1);
148 :
149 10 : ComponentTestHelper test2(GetScriptInterface().GetContext());
150 : // (We should never need to add any mock objects etc to test2, since deserialization
151 : // mustn't depend on other components already existing)
152 :
153 5 : CEntityHandle ent = test2.m_ComponentManager.LookupEntityHandle(10, true);
154 :
155 10 : CStdDeserializer stdde2(test2.GetScriptInterface(), stdstr1);
156 :
157 5 : IComponent* cmp2 = test2.m_ComponentManager.ConstructComponent(ent, m_Cid);
158 5 : cmp2->Deserialize(m_Param.GetChild("test"), stdde2);
159 :
160 5 : TS_ASSERT(stdstr1.peek() == EOF); // Deserialize must read whole stream
161 :
162 10 : std::stringstream dbgstr2;
163 10 : CDebugSerializer dbg2(test2.GetScriptInterface(), dbgstr2);
164 5 : cmp2->Serialize(dbg2);
165 :
166 5 : if (verbose)
167 0 : std::cout << "--------\n" << dbgstr2.str() << "--------\n";
168 :
169 10 : CHashSerializer hash2(test2.GetScriptInterface());
170 5 : cmp2->Serialize(hash2);
171 :
172 10 : std::stringstream stdstr2;
173 10 : CStdSerializer std2(test2.GetScriptInterface(), stdstr2);
174 5 : cmp2->Serialize(std2);
175 :
176 5 : TS_ASSERT_EQUALS(dbgstr1.str(), dbgstr2.str());
177 :
178 5 : TS_ASSERT_EQUALS(hash1.GetHashLength(), hash2.GetHashLength());
179 5 : TS_ASSERT_SAME_DATA(hash1.ComputeHash(), hash2.ComputeHash(), hash1.GetHashLength());
180 :
181 5 : TS_ASSERT_EQUALS(stdstr1.str(), stdstr2.str());
182 :
183 : // TODO: need to extend this so callers can run methods on the cloned component
184 : // to check that all its data is still correct
185 5 : }
186 : };
187 :
188 : /**
189 : * Simple terrain implementation with constant height of 50.
190 : */
191 6 : class MockTerrain : public ICmpTerrain
192 : {
193 : public:
194 0 : DEFAULT_MOCK_COMPONENT()
195 :
196 0 : bool IsLoaded() const override
197 : {
198 0 : return true;
199 : }
200 :
201 0 : CFixedVector3D CalcNormal(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) const override
202 : {
203 0 : return CFixedVector3D(fixed::FromInt(0), fixed::FromInt(1), fixed::FromInt(0));
204 : }
205 :
206 0 : CVector3D CalcExactNormal(float UNUSED(x), float UNUSED(z)) const override
207 : {
208 0 : return CVector3D(0.f, 1.f, 0.f);
209 : }
210 :
211 18 : entity_pos_t GetGroundLevel(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) const override
212 : {
213 18 : return entity_pos_t::FromInt(50);
214 : }
215 :
216 43 : float GetExactGroundLevel(float UNUSED(x), float UNUSED(z)) const override
217 : {
218 43 : return 50.f;
219 : }
220 :
221 0 : u16 GetTilesPerSide() const override
222 : {
223 0 : return 16;
224 : }
225 :
226 0 : u32 GetMapSize() const override
227 : {
228 0 : return GetTilesPerSide() * TERRAIN_TILE_SIZE;
229 : }
230 :
231 0 : u16 GetVerticesPerSide() const override
232 : {
233 0 : return 17;
234 : }
235 :
236 0 : CTerrain* GetCTerrain() override
237 : {
238 0 : return nullptr;
239 : }
240 :
241 0 : void MakeDirty(i32 UNUSED(i0), i32 UNUSED(j0), i32 UNUSED(i1), i32 UNUSED(j1)) override
242 : {
243 0 : }
244 :
245 0 : void ReloadTerrain(bool UNUSED(ReloadWater)) override
246 : {
247 0 : }
248 : };
249 :
250 : #endif // INCLUDED_COMPONENTTEST
|