13 #include <gtest/gtest.h>
32 using namespace ::testing;
33 using namespace std::chrono;
44 m_prim_rr_client = std::make_shared<RecCmdsAsyncMock>();
45 m_meta_rr_client = std::make_shared<MetaDaqAsyncMock>();
46 m_ops.start = op::InitiateOperation<op::StartAsync, boost::future<void>,
op::AsyncOpParams>;
48 boost::future<Result<DpParts>>,
52 boost::future<Result<void>>,
56 boost::future<Result<DpParts>>,
69 template<
class Iterator>
72 for (; it != end; ++it) {
103 std::shared_ptr<DaqControllerImpl>
m_daq;
109 : m_mock_fits_ctl(nullptr)
116 m_files.emplace_back(
"foo",
"bar");
120 m_prim_rr_client = std::make_shared<RecCmdsAsyncMock>();
121 m_meta_rr_client = std::make_shared<MetaDaqAsyncMock>();
122 m_meta_rr_client2 = std::make_shared<NiceMock<MetaDaqAsyncMock>>();
124 m_mock_ops.swap(std::get<std::unique_ptr<MockAsyncOperations>>(tup));
125 auto fits_ctl = std::make_unique<FitsControllerMock>();
126 m_mock_fits_ctl = fits_ctl.get();
132 MetaSource s1(
"meta-source-1", m_meta_rr_client);
133 MetaSource s2(
"meta-source-2", m_meta_rr_client2);
136 m_props.
meta_sources = std::vector<daq::MetaSource>{s1, s2},
138 PreDaqControllerHook();
139 m_daq = std::make_shared<daq::DaqControllerImpl>(
140 m_io_ctx, m_props, std::move(fits_ctl), m_status, m_event_log, std::get<AsyncOperations>(tup));
143 ASSERT_EQ(m_status->GetState(), m_daq->GetState());
147 m_mock_fits_ctl =
nullptr;
149 m_meta_rr_client.reset();
150 m_meta_rr_client2.reset();
151 m_prim_rr_client.reset();
163 boost::promise<void> reply_promise;
164 std::optional<op::AsyncOpParams> params;
165 EXPECT_CALL(*m_mock_fits_ctl, Start());
166 EXPECT_CALL(*m_mock_ops, Start(_))
167 .WillOnce(DoAll(Invoke([&](
auto p) { params.emplace(p); }),
168 Return(ByMove(reply_promise.get_future()))));
172 auto fut = m_daq->StartAsync();
173 EXPECT_EQ(State::Starting, m_daq->GetState());
174 EXPECT_FALSE(fut.is_ready());
177 reply_promise.set_value();
184 ASSERT_TRUE(fut.is_ready());
185 EXPECT_EQ(State::Acquiring, fut.get());
191 std::optional<op::AsyncOpParams> params;
192 boost::promise<Result<void>> reply_promise;
193 EXPECT_CALL(*m_mock_ops, Abort(ErrorPolicy::Strict, _))
194 .WillOnce(DoAll(Invoke([&](
auto policy,
auto p) { params.emplace(p); }),
195 Return(ByMove(reply_promise.get_future()))));
196 EXPECT_CALL(*m_mock_fits_ctl, Abort(ErrorPolicy::Strict));
199 auto fut = m_daq->AbortAsync(ErrorPolicy::Strict);
201 EXPECT_EQ(State::Aborting, m_daq->GetState())
202 <<
"Expected state to be in Stopping after requesting to abort";
205 reply_promise.set_value({
false});
212 ASSERT_TRUE(fut.is_ready());
213 ASSERT_FALSE(fut.has_exception()) <<
"Future has unexpected exception!";
214 auto result = fut.get();
216 EXPECT_FALSE(result.error);
221 std::optional<op::AsyncOpParams> params;
222 boost::promise<Result<DpParts>> reply_promise;
225 EXPECT_CALL(*m_mock_fits_ctl, Stop(ErrorPolicy::Strict)).WillOnce(Return(
DpPart(
"OCM",
227 EXPECT_CALL(*m_mock_ops, Stop(ErrorPolicy::Strict, _))
228 .WillOnce(DoAll(Invoke([&](
auto policy,
auto p) { params.emplace(p); }),
229 Return(ByMove(reply_promise.get_future()))));
232 auto fut = m_daq->StopAsync(ErrorPolicy::Strict);
234 EXPECT_EQ(State::Stopping, m_daq->GetState())
235 <<
"Expected state to be in Stopping after requesting to stop";
239 reply_promise.set_value(reply);
246 ASSERT_TRUE(fut.is_ready());
247 ASSERT_FALSE(fut.has_exception()) <<
"Future has unexpected exception!";
248 auto status = fut.get();
249 EXPECT_EQ(State::Stopped, status.state)
250 <<
"Expected state to be Stopped since there were no errors";
251 EXPECT_FALSE(status.error);
252 EXPECT_EQ(2u, status.files.size()) <<
"One from m_files and 1 from FitsController";
253 EXPECT_EQ(State::Stopped, m_daq->GetState());
264 m_status->SetState(State::Acquiring);
270 m_status->SetState(State::Stopped);
276 PrimSource s1(
"prim-source-1", m_prim_rr_client);
277 m_props.prim_sources = std::vector<daq::PrimSource>{s1};
280 EXPECT_CALL(*m_mock_ops, AwaitPrim(_))
281 .WillOnce(Return(ByMove(m_await_promise.get_future())));
289 EXPECT_THROW(boost::make_ready_future()
290 .then([](
auto f) -> boost::future<void> {
292 throw std::runtime_error(
"Meow");
294 return boost::make_exceptional_future<void>();
306 std::invalid_argument);
312 m_props.meta_sources = {s};
313 m_props.id =
"not-id";
315 std::invalid_argument);
322 m_props.meta_sources = {s};
327 std::invalid_argument);
333 std::invalid_argument);
339 std::invalid_argument);
346 m_props.meta_sources = {s};
354 m_props.meta_sources = {s};
355 boost::future<State> fut;
358 fut =
daq->AwaitAsync({
"source-id"}, 100ms);
359 ASSERT_FALSE(fut.is_ready());
363 ASSERT_TRUE(fut.is_ready()) <<
"Future should have been cancelled since daq should have been deleted.";
364 EXPECT_TRUE(fut.has_exception());
370 ASSERT_EQ(State::NotStarted, m_daq->GetState()) <<
"The initial state should be NotStarted";
375 auto status_ptr = m_daq->GetStatus();
376 EXPECT_EQ(status_ptr.get(), m_status.get());
381 SCOPED_TRACE(
"CannotStopStoppedDaqControllerImpl");
385 ASSERT_EQ(State::Stopped, m_daq->GetState()) <<
"Setup failed";
388 auto fut = m_daq->StopAsync(ErrorPolicy::Strict);
389 EXPECT_TRUE(fut.has_exception());
390 EXPECT_THROW(fut.get(), std::exception);
395 SCOPED_TRACE(
"CannotAbortStoppedDaqControllerImpl");
399 ASSERT_EQ(State::Stopped, m_daq->GetState()) <<
"Setup failed";
402 auto fut = m_daq->AbortAsync(ErrorPolicy::Strict);
403 EXPECT_TRUE(fut.has_exception());
404 EXPECT_THROW(fut.get(), std::exception);
409 EXPECT_CALL(*m_mock_fits_ctl, Start()).WillOnce(Throw(std::runtime_error(
"error")));
412 auto fut = m_daq->StartAsync();
413 EXPECT_EQ(State::Starting, m_daq->GetState());
418 ASSERT_TRUE(fut.is_ready());
419 EXPECT_TRUE(fut.has_exception()) <<
"Expected future to contain exception";
420 EXPECT_THROW(fut.get(), std::exception) <<
"Expected exception to derive from std::exception";
421 EXPECT_EQ(
true, m_daq->GetErrorFlag());
424 TEST_F(
TestState, StartingFailsToSendStartDaqWillAbortAndSetErrorFlagAndStayInStarting) {
426 EXPECT_CALL(*m_mock_fits_ctl, Start());
427 boost::promise<void> reply_promise;
428 EXPECT_CALL(*m_mock_ops, Start(_))
429 .WillOnce(Return(ByMove(reply_promise.get_future())));
432 auto fut = m_daq->StartAsync();
433 EXPECT_EQ(State::Starting, m_daq->GetState());
436 reply_promise.set_exception(std::runtime_error(
"Fake test failure"));
441 ASSERT_TRUE(fut.is_ready());
442 EXPECT_TRUE(fut.has_exception()) <<
"Expected future to contain exception";
443 EXPECT_THROW(fut.get(), std::exception) <<
"Expected exception to derive from std::exception";
444 EXPECT_EQ(
true, m_daq->GetErrorFlag());
450 EXPECT_CALL(*m_mock_fits_ctl, Start());
451 boost::promise<void> reply_promise;
452 EXPECT_CALL(*m_mock_ops, Start(_))
453 .WillOnce(Return(ByMove(reply_promise.get_future())));
455 auto fut = m_daq->StartAsync();
456 ASSERT_EQ(State::Starting, m_daq->GetState());
457 EXPECT_FALSE(fut.is_ready());
461 auto fut2 = m_daq->StartAsync();
462 ASSERT_TRUE(fut2.has_exception());
463 EXPECT_THROW(fut2.get(), std::exception)
464 <<
"Multiple simultaneous start operations are not supported and an exception "
469 reply_promise.set_value();
480 auto fut = m_daq->StopAsync(ErrorPolicy::Strict);
481 EXPECT_THROW(fut.get(), std::exception)
482 <<
"It should not be possible to stop a data acquisition that has not started";
489 std::optional<op::AsyncOpParams> params;
490 boost::promise<Result<DpParts>> reply_promise;
492 .WillOnce(DoAll(Invoke([&](
auto policy,
auto p) { params.emplace(p); }),
493 Return(ByMove(reply_promise.get_future()))));
495 EXPECT_CALL(*m_mock_fits_ctl,
501 EXPECT_EQ(State::Stopping, m_daq->GetState())
502 <<
"Expected state to be in Stopping after requesting to stop";
506 reply_promise.set_value(reply);
514 ASSERT_TRUE(fut.is_ready());
515 ASSERT_FALSE(fut.has_exception()) <<
"Future has unexpected exception!";
516 auto status = fut.get();
517 EXPECT_EQ(State::Stopped, status.state)
518 <<
"Expected state to be Stopped since there were no errors";
519 EXPECT_TRUE(status.error) <<
"Error flag should be set since the reply_promise had an error";
520 EXPECT_EQ(State::Stopped, m_daq->GetState());
527 auto fut = m_daq->AbortAsync(ErrorPolicy::Strict);
528 ASSERT_TRUE(fut.is_ready())
529 <<
"Aborting a NotStarted data acquisition should be ready immediately";
530 EXPECT_FALSE(fut.has_exception()) <<
"Future should not have failed";
532 auto result = fut.get();
534 EXPECT_FALSE(result.error);
544 EXPECT_CALL(*m_mock_fits_ctl, Start());
545 boost::promise<void> reply_promise;
546 EXPECT_CALL(*m_mock_ops, Start(_)).WillOnce(Return(ByMove(reply_promise.get_future())));
548 auto fut = m_daq->StartAsync();
549 ASSERT_EQ(State::Starting, m_daq->GetState())
550 <<
"Setup failed, unexpected state, aborting test";
551 ASSERT_FALSE(fut.is_ready());
555 auto fut = m_daq->StopAsync(ErrorPolicy::Strict);
556 EXPECT_TRUE(fut.has_exception())
557 <<
"Cannot stop unless DAQ is in State::Acquiring, current state: "
558 << m_daq->GetState();
560 EXPECT_THROW(fut.get(), std::exception)
561 <<
"Cannot stop if data acquisition is `Starting`. An exeption was expected";
565 reply_promise.set_value();
581 SCOPED_TRACE(
"AbortingIsOkWhenStarting");
583 EXPECT_CALL(*m_mock_fits_ctl, Start());
585 boost::promise<void> start_promise;
586 EXPECT_CALL(*m_mock_ops, Start(_)).WillOnce(Return(ByMove(start_promise.get_future())));
591 auto start_fut = m_daq->StartAsync();
592 ASSERT_EQ(State::Starting, m_daq->GetState());
593 EXPECT_FALSE(start_fut.is_ready());
597 boost::promise<Result<void>> abort_promise;
598 EXPECT_CALL(*m_mock_ops, Abort(ErrorPolicy::Strict, _))
599 .WillOnce(Return(ByMove(abort_promise.get_future())));
600 EXPECT_CALL(*m_mock_fits_ctl, Abort(ErrorPolicy::Strict));
605 auto abort_fut = m_daq->AbortAsync(ErrorPolicy::Strict);
609 start_promise.set_value();
614 ASSERT_TRUE(start_fut.is_ready()) <<
"Cannot proceed with test since future is not ready";
615 EXPECT_FALSE(start_fut.has_exception())
616 <<
"Mock did not simulate failure so future should be ok";
621 abort_promise.set_value({
false });
626 ASSERT_TRUE(abort_fut.is_ready()) <<
"Cannot proceed with test since future is not ready";
627 EXPECT_FALSE(abort_fut.has_exception())
628 <<
"Mock didn't simulate failure so future should be OK";
629 auto result = abort_fut.get();
631 EXPECT_FALSE(result.error);
637 SCOPED_TRACE(
"Acquiring");
645 SCOPED_TRACE(
"AbortDaqControllerImplInStateAborting");
648 ASSERT_EQ(State::Acquiring, m_daq->GetState()) <<
"Test Setup failed";
651 ASSERT_EQ(
State::Aborted, m_daq->GetState()) <<
"Test setup failed";
654 auto fut = m_daq->AbortAsync(ErrorPolicy::Strict);
655 ASSERT_TRUE(fut.is_ready());
656 EXPECT_THROW(fut.get(), std::runtime_error);
662 SCOPED_TRACE(
"AbortDaqControllerImplInStateStarting");
664 ASSERT_EQ(State::Acquiring, m_daq->GetState()) <<
"Test Setup failed";
673 SCOPED_TRACE(
"NewAbortSupersedesFailedAbort");
676 boost::promise<Result<void>> abort_promise_1;
680 .WillOnce(Return(ByMove(abort_promise_1.get_future())));
682 EXPECT_CALL(*m_mock_fits_ctl,
688 EXPECT_EQ(State::Aborting, m_daq->GetState())
689 <<
"Expected state to be in Stopping after requesting to abort";
692 abort_promise_1.set_value({
true });
695 ASSERT_TRUE(fut1.has_value());
697 auto result1 = fut1.get();
699 EXPECT_TRUE(result1.error);
711 SCOPED_TRACE(
"NewAbortSupersedesSuccessfulAbort");
716 boost::promise<Result<void>> abort_promise_1;
718 boost::promise<Result<void>> abort_promise_2;
721 EXPECT_CALL(*m_mock_ops, Abort(ErrorPolicy::Strict, _))
723 .WillOnce(Return(ByMove(abort_promise_1.get_future())))
724 .WillOnce(Return(ByMove(abort_promise_2.get_future())));
725 EXPECT_CALL(*m_mock_fits_ctl, Abort(ErrorPolicy::Strict));
729 auto fut1 = m_daq->AbortAsync(ErrorPolicy::Strict);
730 auto fut2 = m_daq->AbortAsync(ErrorPolicy::Strict);
732 EXPECT_EQ(State::Aborting, m_daq->GetState())
733 <<
"Expected state to be in Stopping after requesting to abort";
736 abort_promise_1.set_value({
false });
739 ASSERT_TRUE(fut1.is_ready());
740 ASSERT_FALSE(fut1.has_exception()) <<
"Future has unexpected exception!";
741 auto result1 = fut1.get();
743 EXPECT_FALSE(result1.error);
747 abort_promise_2.set_value({
false });
749 auto result2 = fut2.get();
751 EXPECT_FALSE(result2.error);
758 SCOPED_TRACE(
"NewAbortSupersedesFailedAbort");
761 boost::promise<Result<void>> abort_promise_1;
763 boost::promise<Result<void>> abort_promise_2;
766 EXPECT_CALL(*m_mock_ops, Abort(ErrorPolicy::Strict, _))
768 .WillOnce(Return(ByMove(abort_promise_1.get_future())))
769 .WillOnce(Return(ByMove(abort_promise_2.get_future())));
773 EXPECT_CALL(*m_mock_fits_ctl, Abort(ErrorPolicy::Strict)).Times(1);
776 auto fut1 = m_daq->AbortAsync(ErrorPolicy::Strict);
778 EXPECT_EQ(State::Aborting, m_daq->GetState())
779 <<
"Expected state to be in Stopping after requesting to abort";
782 abort_promise_1.set_exception(
DaqSourceErrors(std::vector<std::exception_ptr>()));
785 ASSERT_TRUE(fut1.is_ready());
786 ASSERT_TRUE(fut1.has_exception()) <<
"Future has unexpected exception!";
788 EXPECT_EQ(State::Aborting, m_daq->GetState());
791 auto fut2 = m_daq->AbortAsync(ErrorPolicy::Strict);
794 abort_promise_2.set_value({
false });
797 ASSERT_TRUE(fut2.has_value());
799 auto result2 = fut2.get();
801 EXPECT_FALSE(result2.error);
808 SCOPED_TRACE(
"StopDaqControllerImplSuccessfully");
811 ASSERT_EQ(State::Acquiring, m_daq->GetState()) <<
"Test Setup failed";
819 auto fut = m_daq->AwaitAsync({
"non-existant"}, 0ms);
820 ASSERT_TRUE(fut.has_exception());
821 EXPECT_THROW(fut.get(), std::invalid_argument);
827 auto fut = m_daq->AwaitAsync({
"meta-source-1"}, 1ms);
830 ASSERT_TRUE(fut.has_exception());
836 SCOPED_TRACE(
"AwaitSingleSourceIsOk");
838 auto fut = m_daq->AwaitAsync({
"meta-source-1"}, 150ms);
839 EXPECT_FALSE(fut.is_ready())
840 <<
"The future shouldn't be ready yet as we haven't started the data acquisition!";
844 EXPECT_FALSE(fut.is_ready()) <<
"Wait condition not fulfilled, so future should not be ready yet";
847 ASSERT_TRUE(fut.is_ready());
848 ASSERT_FALSE(fut.has_exception());
849 auto state = fut.get();
850 EXPECT_TRUE(state == State::Stopping || state == State::Stopped)
851 <<
"Await condition should have been ready once source is stopped (meaning that DAQ is "
852 "Stopping or Stopped, depending on the order of the source)";
857 SCOPED_TRACE(
"AwaitSingleSourceIsOk");
859 auto fut = m_daq->AwaitAsync({
"meta-source-1",
"meta-source-2"}, 150ms);
860 EXPECT_FALSE(fut.is_ready())
861 <<
"The future shouldn't be ready yet as we haven't started the data acquisition!";
865 EXPECT_FALSE(fut.is_ready());
869 EXPECT_TRUE(fut.is_ready());
870 ASSERT_FALSE(fut.has_exception());
871 auto state = fut.get();
873 <<
"Await condition should have been ready once source is stopped (meaning that DAQ is "
874 "Aborting or Aborted, depending on the order of the source)";
879 SCOPED_TRACE(
"AwaitSingleSourceIsOk");
885 auto fut = m_daq->AwaitAsync({
"meta-source-1"}, 150ms);
887 EXPECT_TRUE(fut.is_ready()) <<
"Condition already fulfilled so future should be ready";
888 ASSERT_FALSE(fut.has_exception());
889 EXPECT_EQ(State::Stopped, fut.get());
896 m_daq->UpdateKeywords(m_keywords);
898 EXPECT_EQ(m_keywords, m_daq->GetStatus()->GetKeywords());
906 EXPECT_THROW(m_daq->UpdateKeywords(m_keywords), std::runtime_error);
910 SCOPED_TRACE(
"StartWillAwait");
920 SCOPED_TRACE(
"AutomaticStop");
927 DpPart prim_part{
"s1",
"/tmp/file.fits"};
928 m_await_promise.set_value({
false, {prim_part}});
933 EXPECT_CALL(*m_mock_fits_ctl, Stop(ErrorPolicy::Strict)).WillOnce(Return(
DpPart(
"OCM",
935 EXPECT_CALL(*m_mock_ops, Stop(ErrorPolicy::Strict, _))
936 .WillOnce(Return(ByMove(boost::make_ready_future<
Result<DpParts>>(stop_op_reply))));
937 ASSERT_EQ(State::Acquiring, this->m_status->GetState());
938 EXPECT_EQ(0u, m_status->GetFiles().size());
943 return this->m_status->GetState() == State::Stopped;
945 EXPECT_FALSE(m_status->GetError());
946 EXPECT_EQ(3u, m_status->GetFiles().size()) <<
"One from m_files (metadata), "
947 "1 from FitsController and 1 from primary";
948 EXPECT_THAT(m_status->GetFiles(), Contains(prim_part));