@@ -51,21 +51,31 @@ using namespace SilKit::Util;
5151using ::SilKit::Core::Tests::DummyParticipant;
5252
5353
54- class Test_TimeSyncService : public testing ::Test
54+ class MockParticipant : public DummyParticipant
5555{
56- protected:
57- struct Callbacks
58- {
59- MOCK_METHOD (void , CommunicationReadyHandler, ());
60- MOCK_METHOD (void , StartingHandler, ());
61- MOCK_METHOD (void , StopHandler, ());
62- MOCK_METHOD (void , ShutdownHandler, ());
63- MOCK_METHOD (void , SimTask, (std::chrono::nanoseconds));
64- };
56+ public:
57+ MOCK_METHOD (void , SendMsg,
58+ (const SilKit::Core::IServiceEndpoint* from, const SilKit::Services::Orchestration::NextSimTask& msg),
59+ (override ));
60+ };
61+
6562
63+ class Test_TimeSyncService : public testing ::Test
64+ {
6665protected: // CTor
6766 Test_TimeSyncService ()
6867 {
68+ ON_CALL (participant.mockServiceDiscovery , RegisterServiceDiscoveryHandler)
69+ .WillByDefault ([this ](SilKit::Core::Discovery::ServiceDiscoveryHandler handler) {
70+ serviceDiscoveryHandlers.emplace_back (std::move (handler));
71+ });
72+
73+ ON_CALL (participant,
74+ SendMsg (An<const SilKit::Core::IServiceEndpoint*>(), An<const Services::Orchestration::NextSimTask&>()))
75+ .WillByDefault (
76+ [this ](const SilKit::Core::IServiceEndpoint* /* from */ ,
77+ const Services::Orchestration::NextSimTask& msg) { sentNextSimTasks.emplace_back (msg); });
78+
6979 // this CTor calls CreateTimeSyncService implicitly
7080 lifecycleService = std::make_unique<LifecycleService>(&participant);
7181 lifecycleService->SetLifecycleConfiguration (LifecycleConfiguration{OperationMode::Coordinated});
@@ -96,10 +106,12 @@ class Test_TimeSyncService : public testing::Test
96106 // Members
97107 NiceMock<MockServiceEndpoint> endpoint{" P1" , " N1" , " C1" };
98108
99- NiceMock<DummyParticipant> participant;
100- Callbacks callbacks;
109+ NiceMock<MockParticipant> participant;
101110 Config::HealthCheck healthCheckConfig;
102111
112+ std::vector<SilKit::Core::Discovery::ServiceDiscoveryHandler> serviceDiscoveryHandlers;
113+ std::vector<SilKit::Services::Orchestration::NextSimTask> sentNextSimTasks;
114+
103115 std::unique_ptr<LifecycleService> lifecycleService;
104116 TimeProvider timeProvider{};
105117 std::unique_ptr<TimeSyncService> timeSyncService;
@@ -161,4 +173,111 @@ TEST_F(Test_TimeSyncService, async_simtask_mismatching_number_of_complete_calls)
161173 ASSERT_EQ (numAsyncTaskCalled, 3 ) << " Calling too many CompleteSimulationStep() should not wreak havoc" ;
162174}
163175
176+ auto MakeTimeSyncServiceDescriptor () -> SilKit::Core::ServiceDescriptor
177+ {
178+ SilKit::Core::ServiceDescriptor sd;
179+ sd.SetServiceType (Core::ServiceType::InternalController);
180+ sd.SetParticipantNameAndComputeId (" Hopper" );
181+ sd.SetNetworkName (" default" );
182+ sd.SetNetworkType (Config::NetworkType::Undefined);
183+ sd.SetServiceName (" XXX" );
184+ sd.SetServiceId (1234 );
185+ sd.SetSupplementalDataItem (SilKit::Core::Discovery::controllerType, " TimeSyncService" );
186+ sd.SetSupplementalDataItem (SilKit::Core::Discovery::timeSyncActive, " 1" );
187+
188+ return sd;
189+ }
190+
191+ TEST_F (Test_TimeSyncService, hop_on_during_simulation_step)
192+ {
193+ using SilKit::Core::Discovery::ServiceDiscoveryEvent;
194+
195+ ASSERT_EQ (serviceDiscoveryHandlers.size (), 1 );
196+
197+ std::vector<std::chrono::nanoseconds> simulationStepNows;
198+ timeSyncService->SetSimulationStepHandlerAsync ([&simulationStepNows](auto now, auto ) { simulationStepNows.emplace_back (now); }, 1ms);
199+
200+ PrepareLifecycle ();
201+
202+ // After 'starting' the first (now == 0ns) NextSimTask message has been sent (which indicates that we're ready to
203+ // execute the first step)
204+ ASSERT_EQ (sentNextSimTasks.size (), 1 );
205+ ASSERT_EQ (sentNextSimTasks[0 ].timePoint , 0ms);
206+ // The first simulation step is triggered by received NextSimTask message
207+ ASSERT_EQ (simulationStepNows.size (), 0 );
208+
209+ // Trigger the first simulation step
210+ timeSyncService->ReceiveMsg (&endpoint, {0ms});
211+ ASSERT_EQ (simulationStepNows.size (), 1 );
212+ ASSERT_EQ (simulationStepNows[0 ], 0ms);
213+
214+ // Complete the first simulation step
215+ timeSyncService->CompleteSimulationStep ();
216+ // Completion triggers sending the next simulation step message
217+ ASSERT_EQ (sentNextSimTasks.size (), 2 );
218+ ASSERT_EQ (sentNextSimTasks[1 ].timePoint , 1ms);
219+
220+ // Reception of the next simulation step message (from a remote peer) triggers the second simulation step
221+ timeSyncService->ReceiveMsg (&endpoint, {1ms});
222+ ASSERT_EQ (simulationStepNows.size (), 2 );
223+ ASSERT_EQ (simulationStepNows[1 ], 1ms);
224+
225+ // Trigger 'hop-on' which will cause an additional NextSimTask to be sent (with the 'current' timestamp)
226+ serviceDiscoveryHandlers[0 ](ServiceDiscoveryEvent::Type::ServiceCreated, MakeTimeSyncServiceDescriptor ());
227+ ASSERT_EQ (sentNextSimTasks.size (), 3 );
228+ ASSERT_EQ (sentNextSimTasks[2 ].timePoint , 1ms);
229+
230+ // Complete the second simulation step
231+ timeSyncService->CompleteSimulationStep ();
232+ // Completion triggers sending the next simulation step message
233+ ASSERT_EQ (sentNextSimTasks.size (), 4 );
234+ ASSERT_EQ (sentNextSimTasks[3 ].timePoint , 2ms);
235+ }
236+
237+ TEST_F (Test_TimeSyncService, hop_on_outside_simulation_step)
238+ {
239+ using SilKit::Core::Discovery::ServiceDiscoveryEvent;
240+
241+ ASSERT_EQ (serviceDiscoveryHandlers.size (), 1 );
242+
243+ std::vector<std::chrono::nanoseconds> simulationStepNows;
244+ timeSyncService->SetSimulationStepHandlerAsync ([&simulationStepNows](auto now, auto ) { simulationStepNows.emplace_back (now); }, 1ms);
245+
246+ PrepareLifecycle ();
247+
248+ // After 'starting' the first (now == 0ns) NextSimTask message has been sent (which indicates that we're ready to
249+ // execute the first step)
250+ ASSERT_EQ (sentNextSimTasks.size (), 1 );
251+ ASSERT_EQ (sentNextSimTasks[0 ].timePoint , 0ms);
252+ // The first simulation step is triggered by received NextSimTask message
253+ ASSERT_EQ (simulationStepNows.size (), 0 );
254+
255+ // Trigger the first simulation step
256+ timeSyncService->ReceiveMsg (&endpoint, {0ms});
257+ ASSERT_EQ (simulationStepNows.size (), 1 );
258+ ASSERT_EQ (simulationStepNows[0 ], 0ms);
259+
260+ // Complete the first simulation step
261+ timeSyncService->CompleteSimulationStep ();
262+ // Completion triggers sending the next simulation step message
263+ ASSERT_EQ (sentNextSimTasks.size (), 2 );
264+ ASSERT_EQ (sentNextSimTasks[1 ].timePoint , 1ms);
265+
266+ // Reception of the next simulation step message (from a remote peer) triggers the second simulation step
267+ timeSyncService->ReceiveMsg (&endpoint, {1ms});
268+ ASSERT_EQ (simulationStepNows.size (), 2 );
269+ ASSERT_EQ (simulationStepNows[1 ], 1ms);
270+
271+ // Complete the second simulation step
272+ timeSyncService->CompleteSimulationStep ();
273+ // Completion triggers sending the next simulation step message
274+ ASSERT_EQ (sentNextSimTasks.size (), 3 );
275+ ASSERT_EQ (sentNextSimTasks[2 ].timePoint , 2ms);
276+
277+ // Trigger 'hop-on' which will cause an additional NextSimTask to be sent (with the 'next' timestamp)
278+ serviceDiscoveryHandlers[0 ](ServiceDiscoveryEvent::Type::ServiceCreated, MakeTimeSyncServiceDescriptor ());
279+ ASSERT_EQ (sentNextSimTasks.size (), 4 );
280+ ASSERT_EQ (sentNextSimTasks[3 ].timePoint , 2ms);
281+ }
282+
164283} // namespace
0 commit comments