9 #include <fmt/ostream.h>
13 #include "mock/mockWorkspace.hpp"
16 #include <gtest/gtest.h>
17 #include <log4cplus/loggingmacros.h>
22 using namespace ::testing;
23 using namespace std::literals::string_view_literals;
24 using namespace std::chrono_literals;
30 ADD_FAILURE() <<
"Future is not ready";
31 throw std::runtime_error(
"test failure");
36 LOG4CPLUS_ERROR(
"test",
37 "Future contained exception\n"
63 , m_executor(m_io_ctx)
65 , m_manager(m_executor, m_params, m_workspace, m_event_log, m_daq_factory) {
73 m_daq_ctx_1.id = m_daq_id_1;
74 m_daq_ctx_1.file_id =
"fileid1";
75 m_daq_ctx_2.id = m_daq_id_2;
76 m_daq_ctx_2.file_id =
"fileid2";
78 m_daq1_status = std::make_shared<ObservableStatus>(m_daq_id_1,
"fileid1");
79 m_daq2_status = std::make_shared<ObservableStatus>(m_daq_id_2,
"fileid2");
80 m_daq1 = std::make_shared<DaqControllerMock>();
81 m_daq2 = std::make_shared<DaqControllerMock>();
82 m_daq_factory.ocm_mocks[
"daq1"] = m_daq1;
83 m_daq_factory.ocm_mocks[
"daq2"] = m_daq2;
85 m_dpm_daq1 = std::make_shared<DaqControllerMock>();
86 m_dpm_daq2 = std::make_shared<DaqControllerMock>();
87 m_daq_factory.dpm_mocks[
"daq1"] = m_dpm_daq1;
88 m_daq_factory.dpm_mocks[
"daq2"] = m_dpm_daq2;
90 EXPECT_CALL(*m_daq1, GetId()).Times(AnyNumber()).WillRepeatedly(ReturnRef(m_daq_id_1));
91 EXPECT_CALL(*m_daq2, GetId()).Times(AnyNumber()).WillRepeatedly(ReturnRef(m_daq_id_2));
92 EXPECT_CALL(*m_daq1, GetStatus()).Times(AnyNumber()).WillRepeatedly(Return(m_daq1_status));
93 EXPECT_CALL(*m_daq2, GetStatus()).Times(AnyNumber()).WillRepeatedly(Return(m_daq2_status));
94 EXPECT_CALL(Const(*m_daq1), GetStatus())
96 .WillRepeatedly(Return(m_daq1_status));
97 EXPECT_CALL(Const(*m_daq2), GetStatus())
99 .WillRepeatedly(Return(m_daq2_status));
100 EXPECT_CALL(Const(*m_daq1), GetContext())
102 .WillRepeatedly(ReturnRef(m_daq_ctx_1));
104 m_daq1_dpm_status = std::make_shared<ObservableStatus>(m_daq_id_1,
"fileid1");
106 m_daq2_dpm_status = std::make_shared<ObservableStatus>(m_daq_id_2,
"fileid2");
109 EXPECT_CALL(*m_dpm_daq1, GetId()).WillRepeatedly(ReturnRef(m_daq_id_1));
110 EXPECT_CALL(*m_dpm_daq1, GetStatus())
112 .WillRepeatedly(Return(m_daq1_status));
113 EXPECT_CALL(Const(*m_dpm_daq1), GetStatus())
115 .WillRepeatedly(Return(m_daq1_status));
116 EXPECT_CALL(Const(*m_dpm_daq1), GetContext())
118 .WillRepeatedly(ReturnRef(m_daq_ctx_1));
120 EXPECT_CALL(*m_dpm_daq2, GetId()).WillRepeatedly(ReturnRef(m_daq_id_2));
121 EXPECT_CALL(*m_dpm_daq2, GetStatus())
123 .WillRepeatedly(Return(m_daq2_dpm_status));
124 EXPECT_CALL(Const(*m_dpm_daq2), GetStatus())
126 .WillRepeatedly(Return(m_daq2_dpm_status));
127 EXPECT_CALL(Const(*m_dpm_daq2), GetContext())
129 .WillRepeatedly(ReturnRef(m_daq_ctx_2));
133 EXPECT_CALL(*m_daq1, StartAsync())
134 .WillOnce(Return(ByMove(boost::make_ready_future<State>(m_daq1_status->GetState()))));
135 EXPECT_CALL(m_workspace, StoreList(std::vector<std::string>({
"daq1"})));
136 EXPECT_CALL(m_workspace, StoreStatus(Field(&
Status::id,
"daq1"))).Times(AtLeast(1));
137 EXPECT_CALL(m_workspace, StoreContext(_));
138 return m_manager.StartDaqAsync(m_daq_ctx_1);
146 m_daq_factory.ocm_mocks.clear();
147 m_daq_factory.dpm_mocks.clear();
158 std::shared_ptr<DaqControllerMock>
m_daq1;
159 std::shared_ptr<DaqControllerMock>
m_daq2;
172 boost::asio::io_context io_ctx;
175 auto event_log = std::make_shared<ObservableEventLog>();
179 ManagerImpl mgr(executor, m_params, workspace, event_log, factory);
180 auto id = std::string(
"id");
181 auto status = std::make_shared<ObservableStatus>(
id,
"fileid");
184 auto daq = std::make_shared<DaqControllerMock>();
187 EXPECT_CALL(*
daq, StartAsync())
188 .WillOnce(Return(ByMove(boost::make_ready_future<State>(status->GetState()))));
189 EXPECT_CALL(*
daq, GetId()).WillRepeatedly(ReturnRef(
id));
190 EXPECT_CALL(*
daq, GetStatus()).WillRepeatedly(Return(status));
191 EXPECT_CALL(Const(*
daq), GetStatus()).WillRepeatedly(Return(status));
192 EXPECT_CALL(Const(*
daq), GetContext()).Times(AnyNumber()).WillRepeatedly(ReturnRef(daq_ctx));
194 EXPECT_CALL(workspace, StoreList(std::vector<std::string>({
"id"})));
195 EXPECT_CALL(workspace, StoreStatus(Field(&
Status::id,
"id")));
196 EXPECT_CALL(workspace, StoreContext(daq_ctx));
199 EXPECT_CALL(o, CallOperator(_));
205 ExpectNoException(f);
210 boost::asio::io_context io_ctx;
213 auto event_log = std::make_shared<ObservableEventLog>();
217 auto id = std::string(
"id");
220 auto status = std::make_shared<ObservableStatus>(
id,
"fileid");
221 auto daq = std::make_shared<DaqControllerMock>();
223 EXPECT_CALL(*
daq, StartAsync())
224 .WillOnce(Return(ByMove(boost::make_ready_future<State>(status->GetState()))));
225 EXPECT_CALL(*
daq, GetId()).Times(AnyNumber()).WillRepeatedly(ReturnRef(
id));
226 EXPECT_CALL(*
daq, GetStatus()).Times(AnyNumber()).WillRepeatedly(Return(status));
227 EXPECT_CALL(Const(*
daq), GetStatus()).Times(AnyNumber()).WillRepeatedly(Return(status));
228 EXPECT_CALL(Const(*
daq), GetContext()).Times(AnyNumber()).WillRepeatedly(ReturnRef(daq_ctx));
230 EXPECT_CALL(workspace, StoreList(std::vector<std::string>({
"id"})));
231 EXPECT_CALL(workspace, StoreStatus(Field(&
Status::id,
"id")));
232 EXPECT_CALL(workspace, StoreContext(daq_ctx));
234 boost::future<Result<Status>> res;
236 ManagerImpl mgr(executor, m_params, workspace, event_log, factory);
241 ASSERT_FALSE(res.is_ready());
246 ASSERT_TRUE(res.is_ready());
252 std::regex regex(
"INSTR\\.\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}");
254 EXPECT_TRUE(std::regex_match(
id, regex))
255 <<
"Instrument ID should be truncated to 5 characters if too long";
258 std::regex regex(
"INS\\.\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}");
259 auto id = m_manager.MakeDaqId();
260 EXPECT_TRUE(std::regex_match(
id, regex));
265 EXPECT_NE(id1, id2) <<
"Adding jitter should have made the ID different";
271 EXPECT_CALL(*m_daq1, StartAsync())
272 .WillOnce(Return(ByMove(boost::make_ready_future<State>(State::Starting))));
273 EXPECT_CALL(m_workspace, StoreList(std::vector<std::string>({
"daq1"})));
274 EXPECT_CALL(m_workspace, StoreStatus(Field(&
Status::id,
"daq1")));
275 EXPECT_CALL(m_workspace, StoreContext(_));
277 m_manager.StartDaqAsync(m_daq_ctx_1);
280 auto f = m_manager.StartDaqAsync(m_daq_ctx_1);
282 ASSERT_TRUE(f.is_ready());
283 EXPECT_THROW(f.get(), std::invalid_argument);
288 EXPECT_CALL(*m_daq1, StartAsync())
289 .WillOnce(Return(ByMove(boost::make_ready_future<State>(State::Starting))));
290 EXPECT_CALL(m_workspace, StoreList(std::vector<std::string>({
"daq1"})));
291 EXPECT_CALL(m_workspace, StoreStatus(Field(&
Status::id,
"daq1")));
292 EXPECT_CALL(m_workspace, StoreContext(_));
295 auto fut = m_manager.StartDaqAsync(m_daq_ctx_1);
297 ASSERT_TRUE(fut.is_ready());
298 EXPECT_EQ(fut.get(), State::Starting);
310 EXPECT_CALL(*m_daq1, StartAsync())
312 ByMove(boost::make_exceptional_future<State>(std::runtime_error(
"START FAILED")))));
313 boost::promise<daq::Status> abort_reply;
315 .WillOnce(Return(ByMove(abort_reply.get_future())));
316 EXPECT_CALL(m_workspace, StoreList(std::vector<std::string>({
"daq1"})));
317 EXPECT_CALL(m_workspace, StoreStatus(Field(&
Status::id,
"daq1")));
318 EXPECT_CALL(m_workspace, StoreContext(_));
321 auto f = m_manager.StartDaqAsync(m_daq_ctx_1);
323 ASSERT_FALSE(f.is_ready());
326 abort_reply.set_exception(std::logic_error(
"ABORT ALSO FAILED"));
329 ASSERT_TRUE(f.is_ready());
330 EXPECT_THROW(f.get(), std::runtime_error);
334 auto fut = m_manager.StopDaqAsync(
"nonexistant-id"sv, daq::ErrorPolicy::Strict);
335 ASSERT_TRUE(fut.is_ready());
336 EXPECT_THROW(fut.get(), std::invalid_argument);
344 EXPECT_CALL(*m_daq1, StopAsync(daq::ErrorPolicy::Strict))
345 .WillOnce(Return(ByMove(
346 boost::make_ready_future<Status>(
Status(
"daq1",
"fileid", State::Stopped,
false, t)))));
349 auto fut = m_manager.StopDaqAsync(
"daq1"sv, daq::ErrorPolicy::Strict);
350 auto status = ExpectNoException(fut);
351 EXPECT_EQ(status.state, State::Stopped);
352 EXPECT_EQ(status.error,
false);
357 ASSERT_TRUE(fut.is_ready());
358 EXPECT_THROW(fut.get(), std::invalid_argument);
366 reply_status.state = State::AbortingAcquiring;
367 EXPECT_CALL(*m_daq1, AbortAsync(ErrorPolicy::Strict))
368 .WillOnce(Return(ByMove(boost::make_ready_future<Status>(reply_status))));
371 auto fut = m_manager.AbortDaqAsync(
"daq1"sv, ErrorPolicy::Strict);
372 auto result = ExpectNoException(fut);
373 EXPECT_EQ(result.state, State::AbortingAcquiring);
374 EXPECT_FALSE(result.error);
387 m_manager.UpdateKeywords(
"daq1"sv, keywords);
394 EXPECT_THROW(m_manager.UpdateKeywords(
"nonexistant-id"sv, keywords), std::invalid_argument);
402 auto status = m_manager.GetStatus(
"daq1"sv);
403 EXPECT_EQ(status.id,
"daq1");
404 EXPECT_EQ(status.state, daq::State::NotStarted);
405 EXPECT_FALSE(status.error);
410 EXPECT_THROW(m_manager.GetStatus(
"nonexistant"sv), std::invalid_argument);
416 auto fut = m_manager.AwaitDaqStateAsync(
"daq1"sv, daq::State::NotStarted, 10ms);
419 ASSERT_TRUE(fut.is_ready());
420 auto [timeout, result] = fut.get();
421 EXPECT_FALSE(timeout);
422 EXPECT_EQ(result, m_daq1->GetStatus()->GetStatus());
428 auto fut = m_manager.AwaitDaqStateAsync(
"daq1"sv, daq::State::Acquiring, 10ms);
429 ASSERT_FALSE(fut.is_ready());
430 m_daq1_status->SetState(daq::State::Acquiring);
433 ASSERT_TRUE(fut.is_ready());
434 auto [timeout, result] = fut.get();
435 EXPECT_FALSE(timeout);
436 EXPECT_EQ(result, m_daq1->GetStatus()->GetStatus());
442 auto fut = m_manager.AwaitDaqStateAsync(
"daq1"sv, daq::State::Acquiring, 0ms);
443 ASSERT_FALSE(fut.is_ready());
446 ASSERT_TRUE(fut.is_ready()) <<
"Timer should have triggered to make the future ready";
447 auto [timeout, result] = fut.get();
448 EXPECT_TRUE(timeout);
449 EXPECT_EQ(result, m_daq1->GetStatus()->GetStatus());
458 m_daq1_status->SetState(State::Stopped);
460 EXPECT_CALL(*m_daq1, GetEventLog()).WillOnce(Return(m_event_log));
461 EXPECT_CALL(*m_daq1, GetContext()).WillOnce(ReturnRef(m_daq_ctx_1));
463 EXPECT_CALL(*m_dpm_daq1, ScheduleMergeAsync())
464 .WillOnce(Return(ByMove(boost::make_ready_future<State>(State::Scheduled))));
466 EXPECT_CALL(m_workspace, StoreList(_)).Times(AnyNumber());
467 EXPECT_CALL(m_workspace, StoreContext(_)).Times(AnyNumber());
474 EXPECT_EQ(m_daq1_status->GetState(), State::NotScheduled);
476 auto daqs = m_manager.GetDaqControllers();
477 ASSERT_EQ(daqs.size(), 1u);
478 EXPECT_EQ(daqs[0].get(), m_dpm_daq1.get());
487 m_daq_ctx_1.creation_time = std::chrono::system_clock::now();
488 m_daq_ctx_2.creation_time = std::chrono::system_clock::now();
489 m_daq1_status->SetState(State::Acquiring);
492 std::vector<std::string> to_load = {m_daq_ctx_1.id, m_daq_ctx_2.id};
494 EXPECT_CALL(m_workspace, LoadList()).WillOnce(Return(to_load));
496 EXPECT_CALL(m_workspace, LoadContext(m_daq_ctx_1.id)).WillOnce(Return(m_daq_ctx_1));
497 EXPECT_CALL(m_workspace, LoadStatus(m_daq_ctx_1.id))
498 .WillOnce(Return(m_daq1_status->GetStatus()));
500 EXPECT_CALL(m_workspace, LoadContext(m_daq_ctx_2.id)).WillOnce(Return(m_daq_ctx_2));
501 EXPECT_CALL(m_workspace, LoadStatus(m_daq_ctx_2.id))
502 .WillOnce(Return(m_daq2_status->GetStatus()));
504 EXPECT_CALL(m_workspace, StoreList(to_load)).Times(1);
507 m_manager.RestoreFromWorkspace();
516 m_daq_ctx_1.creation_time = std::chrono::system_clock::now() - m_params.acquiring_stale_age;
517 m_daq_ctx_2.creation_time = std::chrono::system_clock::now() - m_params.merging_stale_age;
519 std::vector<std::string> to_load = {m_daq_ctx_1.id, m_daq_ctx_2.id};
521 EXPECT_CALL(m_workspace, LoadList()).WillOnce(Return(to_load));
523 EXPECT_CALL(m_workspace, LoadContext(m_daq_ctx_1.id)).WillOnce(Return(m_daq_ctx_1));
524 EXPECT_CALL(m_workspace, LoadStatus(m_daq_ctx_1.id))
525 .WillOnce(Return(m_daq1_status->GetStatus()));
526 EXPECT_CALL(m_workspace, ArchiveDaq(m_daq_ctx_1.id));
528 EXPECT_CALL(m_workspace, LoadContext(m_daq_ctx_2.id)).WillOnce(Return(m_daq_ctx_2));
529 EXPECT_CALL(m_workspace, LoadStatus(m_daq_ctx_2.id))
530 .WillOnce(Return(m_daq2_dpm_status->GetStatus()));
531 EXPECT_CALL(m_workspace, ArchiveDaq(m_daq_ctx_2.id));
533 EXPECT_CALL(m_workspace, StoreList(std::vector<std::string>())).Times(1);
536 m_manager.RestoreFromWorkspace();
545 m_daq_ctx_1.creation_time = std::chrono::system_clock::now();
546 m_daq_ctx_2.creation_time = std::chrono::system_clock::now();
548 std::vector<std::string> to_load = {m_daq_ctx_1.id, m_daq_ctx_2.id};
549 std::vector<std::string> to_store = {m_daq_ctx_2.id};
551 EXPECT_CALL(m_workspace, LoadList()).WillOnce(Return(to_load));
553 EXPECT_CALL(m_workspace, LoadContext(m_daq_ctx_1.id))
554 .WillOnce(Throw(std::runtime_error(
"ouch")));
555 EXPECT_CALL(m_workspace, LoadStatus(m_daq_ctx_1.id))
556 .Times(Between(0, 1))
557 .WillRepeatedly(Return(m_daq1_status->GetStatus()));
558 EXPECT_CALL(m_workspace, ArchiveDaq(m_daq_ctx_1.id));
560 EXPECT_CALL(m_workspace, LoadContext(m_daq_ctx_2.id)).WillOnce(Return(m_daq_ctx_2));
561 EXPECT_CALL(m_workspace, LoadStatus(m_daq_ctx_2.id))
562 .WillOnce(Return(m_daq2_dpm_status->GetStatus()));
564 EXPECT_CALL(m_workspace, StoreList(to_store)).Times(1);
567 m_manager.RestoreFromWorkspace();
576 m_daq_ctx_1.creation_time = std::chrono::system_clock::now();
577 m_daq_ctx_2.creation_time = std::chrono::system_clock::now();
578 std::vector<std::string> to_load = {m_daq_ctx_1.id, m_daq_ctx_2.id};
579 std::vector<std::string> to_store = {m_daq_ctx_2.id};
581 EXPECT_CALL(m_workspace, LoadList()).WillOnce(Return(to_load));
583 EXPECT_CALL(m_workspace, LoadContext(m_daq_ctx_1.id))
584 .WillOnce(Throw(std::runtime_error(
"ouch")));
585 EXPECT_CALL(m_workspace, LoadStatus(m_daq_ctx_1.id))
586 .Times(Between(0, 1))
587 .WillRepeatedly(Return(m_daq1_status->GetStatus()));
588 EXPECT_CALL(m_workspace, ArchiveDaq(m_daq_ctx_1.id))
589 .WillOnce(Throw(std::runtime_error(
"ouch")));
591 EXPECT_CALL(m_workspace, LoadContext(m_daq_ctx_2.id)).WillOnce(Return(m_daq_ctx_2));
592 EXPECT_CALL(m_workspace, LoadStatus(m_daq_ctx_2.id))
593 .WillOnce(Return(m_daq2_dpm_status->GetStatus()));
595 EXPECT_CALL(m_workspace, StoreList(to_store)).Times(1);
598 m_manager.RestoreFromWorkspace();
605 EXPECT_CALL(m_workspace, StoreContext(Field(&
DaqContext::id,
"daq1")));
608 m_daq1->signal(m_daq_ctx_1);