ifw-daq  1.0.0
IFW Data Acquisition modules
testManager.cpp
Go to the documentation of this file.
1 /**
2  * @file
3  * @ingroup daq_ocm_libdaq_test
4  * @copyright 2021 ESO - European Southern Observatory
5  *
6  * @brief daq::ManagerImpl tests
7  */
8 #include <regex>
9 #include <daq/manager.hpp>
10 #include <fmt/ostream.h>
11 
12 #include <gtest/gtest.h>
13 #include "mock/daqController.hpp"
14 #include "statusObserver.hpp"
15 #include "utils.hpp"
16 
17 
18 using namespace daq;
19 using namespace ::testing;
20 using namespace std::literals::string_view_literals;
21 using namespace std::chrono_literals;
22 
23 /**
24  * @ingroup daq_ocm_libdaq_test
25  */
26 class TestManagerImplLifecycle : public ::testing::Test {
27 public:
28  void SetUp() override {
29  }
30  void TearDown() override {
31  }
32 
33 };
34 
35 /**
36  * @ingroup daq_ocm_libdaq_test
37  */
38 class TestManagerImpl : public ::testing::Test {
39 public:
40  TestManagerImpl() : m_io_ctx(), m_executor(m_io_ctx), m_manager(m_executor, "INS") {
41  }
42  /**
43  * Creates manager and adds two data acquisitions.
44  */
45  void SetUp() override {
46  m_daq_id_1 = "daq1";
47  m_daq_id_2 = "daq2";
48  m_daq1_status = std::make_shared<ObservableStatus>(m_daq_id_1);
49  m_daq2_status = std::make_shared<ObservableStatus>(m_daq_id_2);
50  m_daq1 = std::make_shared<DaqControllerMock>();
51  m_daq2 = std::make_shared<DaqControllerMock>();
52 
53  EXPECT_CALL(*m_daq1, GetId()).WillRepeatedly(ReturnRef(m_daq_id_1));
54  EXPECT_CALL(*m_daq2, GetId()).WillRepeatedly(ReturnRef(m_daq_id_2));
55  EXPECT_CALL(*m_daq1, GetStatus()).Times(AnyNumber()).WillRepeatedly(Return(m_daq1_status));
56  EXPECT_CALL(*m_daq2, GetStatus()).Times(AnyNumber()).WillRepeatedly(Return(m_daq2_status));
57  EXPECT_CALL(Const(*m_daq1),
58  GetStatus()).Times(AnyNumber()).WillRepeatedly(Return(m_daq1_status));
59  EXPECT_CALL(Const(*m_daq2),
60  GetStatus()).Times(AnyNumber()).WillRepeatedly(Return(m_daq2_status));
61 
62  m_manager.AddDaq(m_daq1);
63  m_manager.AddDaq(m_daq2);
64  }
65  void TearDown() override {
66  }
67 
68  boost::asio::io_context m_io_ctx;
70  std::string m_daq_id_1;
71  std::string m_daq_id_2;
72  std::shared_ptr<DaqControllerMock> m_daq1;
73  std::shared_ptr<DaqControllerMock> m_daq2;
74  std::shared_ptr<ObservableStatus> m_daq1_status;
75  std::shared_ptr<ObservableStatus> m_daq2_status;
77 };
78 
79 
80 TEST_F(TestManagerImplLifecycle, ConstructionIsOk) {
81  boost::asio::io_context io_ctx;
82  rad::IoExecutor executor(io_ctx);
83 
84  // Test
85  ManagerImpl(executor, "INS");
86 }
87 
88 TEST_F(TestManagerImplLifecycle, AddDaqNotifiesObserver) {
89  // Setup
90  boost::asio::io_context io_ctx;
91  rad::IoExecutor executor(io_ctx);
92 
93  ManagerImpl mgr(executor, "INS");
94  auto id = std::string("id");
95  auto status = std::make_shared<ObservableStatus>(id);;
96  auto daq = std::make_shared<DaqControllerMock>();
97  EXPECT_CALL(*daq, GetId()).WillRepeatedly(ReturnRef(id));
98  EXPECT_CALL(*daq, GetStatus()).WillRepeatedly(Return(status));
99 
101  EXPECT_CALL(o, CallOperator(_));
102  mgr.GetStatusSignal().ConnectObserver(std::reference_wrapper(o));
103 
104  // Run
105  mgr.AddDaq(daq);
106 }
107 
108 TEST_F(TestManagerImplLifecycle, AwaitStateCompletesWithAbandonedManager) {
109  // Setup
110  boost::asio::io_context io_ctx;
111  rad::IoExecutor executor(io_ctx);
112  auto id = std::string("id");
113  auto status = std::make_shared<ObservableStatus>(id);;
114  auto daq = std::make_shared<DaqControllerMock>();
115  EXPECT_CALL(*daq, GetId()).Times(AnyNumber()).WillRepeatedly(ReturnRef(id));
116  EXPECT_CALL(*daq, GetStatus()).Times(AnyNumber()).WillRepeatedly(Return(status));
117  EXPECT_CALL(Const(*daq),
118  GetStatus()).Times(AnyNumber()).WillRepeatedly(Return(status));
119 
120  boost::future<Result<Status>> res;
121  {
122  ManagerImpl mgr(executor, "INS");
123  mgr.AddDaq(daq);
124 
125  // Initiate await that should be aborted when manager is destroyed
126  mgr.AwaitDaqStateAsync("id"sv, State::Acquiring, 5ms).swap(res);
127  ASSERT_FALSE(res.is_ready());
128  }
129 
130  // Run
131  MakeTestProgress(io_ctx, &res);
132  ASSERT_TRUE(res.is_ready());
133  EXPECT_THROW(res.get(), DaqOperationAborted);
134 
135 }
136 
138  {
139  std::regex regex("INSTR\\.\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}");
140  auto id = MakeDaqIdCandidate("INSTRUMENT", 0);
141  EXPECT_TRUE(std::regex_match(id, regex))
142  << "Instrument ID should be truncated to 5 characters if too long";
143  }
144  {
145  std::regex regex("INS\\.\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}");
146  auto id = m_manager.MakeDaqId();
147  EXPECT_TRUE(std::regex_match(id, regex));
148  }
149  {
150  auto id1 = MakeDaqIdCandidate("INSTRUMENT", 0);
151  auto id2 = MakeDaqIdCandidate("INSTRUMENT", 1);
152  EXPECT_NE(id1, id2) << "Adding jitter should have made the ID different";
153  }
154 }
155 
156 TEST_F(TestManagerImpl, AddingDaqWithSameIdThrowsInvalidArgument) {
157  EXPECT_THROW(m_manager.AddDaq(m_daq1), std::invalid_argument);
158 }
159 
160 
161 TEST_F(TestManagerImpl, StartNonexistingDaqThrowsInvalidArgument) {
162  auto fut = m_manager.StartDaqAsync("nonexistant-id"sv);
163  ASSERT_TRUE(fut.is_ready());
164  EXPECT_THROW(fut.get(), std::invalid_argument);
165 }
166 
167 
168 TEST_F(TestManagerImpl, StartDaqAsyncStartsDaq) {
169  // Setup
170  EXPECT_CALL(*m_daq1, StartAsync())
171  .WillOnce(Return(ByMove(boost::make_ready_future<State>(State::Starting))));
172 
173  // Run
174  auto fut = m_manager.StartDaqAsync("daq1"sv);
175  ASSERT_TRUE(fut.is_ready());
176  EXPECT_EQ(fut.get(), State::Starting);
177 }
178 
179 
180 TEST_F(TestManagerImpl, StopNonexistingDaqThrowsInvalidArgument) {
181  auto fut = m_manager.StopDaqAsync("nonexistant-id"sv, daq::ErrorPolicy::Strict);
182  ASSERT_TRUE(fut.is_ready());
183  EXPECT_THROW(fut.get(), std::invalid_argument);
184 }
185 
186 
187 TEST_F(TestManagerImpl, StopDaqAsyncStopsDaq) {
188  // Setup
190  EXPECT_CALL(*m_daq1, StopAsync(daq::ErrorPolicy::Strict))
191  .WillOnce(Return(ByMove(boost::make_ready_future<Status>(Status("daq1", State::Stopped,
192  false, {}, {}, t)))));
193 
194  // Run
195  auto fut = m_manager.StopDaqAsync("daq1"sv, daq::ErrorPolicy::Strict);
196  ASSERT_TRUE(fut.is_ready());
197  auto status = fut.get();
198  EXPECT_EQ(status.state, State::Stopped);
199  EXPECT_EQ(status.error, false);
200 }
201 
202 
203 TEST_F(TestManagerImpl, AbortNonexistingDaqThrowsInvalidArgumentEvenIfTolerant) {
204  auto fut = m_manager.AbortDaqAsync("nonexistant-id"sv, ErrorPolicy::Tolerant);
205  ASSERT_TRUE(fut.is_ready());
206  EXPECT_THROW(fut.get(), std::invalid_argument);
207 }
208 
209 
210 TEST_F(TestManagerImpl, AbortDaqAsyncAbortsDaq) {
211  // Setup
212  auto reply_status = daq::Status("daq1");
213  reply_status.state = State::Aborting;
214  EXPECT_CALL(*m_daq1, AbortAsync(ErrorPolicy::Strict))
215  .WillOnce(Return(ByMove(boost::make_ready_future<Status>(reply_status))));
216 
217  // Run
218  auto fut = m_manager.AbortDaqAsync("daq1"sv, ErrorPolicy::Strict);
219  ASSERT_TRUE(fut.is_ready());
220  auto result = fut.get();
221  EXPECT_EQ(result.state, State::Aborting);
222  EXPECT_FALSE(result.error);
223 }
224 
225 TEST_F(TestManagerImpl, UpdateKeywordsUpdatesKeywords) {
226  // Setup
227  daq::fits::KeywordVector keywords = {
228  daq::fits::ValueKeyword("OBJECT", "OBJECT,SKY"),
229  daq::fits::EsoKeyword("OBS TPLNO", static_cast<uint64_t>(2))};
230  EXPECT_CALL(*m_daq1, UpdateKeywords(keywords));
231 
232  // Run
233  m_manager.UpdateKeywords("daq1"sv, keywords);
234 }
235 
236 TEST_F(TestManagerImpl, UpdateKeywordsForNonexistingDaqThrowsInvalidArgument) {
237  daq::fits::KeywordVector keywords = {
238  daq::fits::ValueKeyword("OBJECT", "OBJECT,SKY"),
239  daq::fits::EsoKeyword("OBS TPLNO", static_cast<uint64_t>(2))};
240  EXPECT_THROW(m_manager.UpdateKeywords("nonexistant-id"sv, keywords), std::invalid_argument);
241 }
242 
243 TEST_F(TestManagerImpl, GetStatus) {
244  // Setup
245 
246  // Run
247  auto status = m_manager.GetStatus("daq1"sv);
248  EXPECT_EQ(status.id, "daq1");
249  EXPECT_EQ(status.state, daq::State::NotStarted);
250  EXPECT_FALSE(status.error);
251 }
252 
253 TEST_F(TestManagerImpl, GetStatusThrowsIfDaqDoesNotExist) {
254  // Run
255  EXPECT_THROW(m_manager.GetStatus("nonexistant"sv), std::invalid_argument);
256 }
257 
258 
259 TEST_F(TestManagerImpl, AwaitDaqStateReturnsReadyFutureIfConditionIsFulfilled) {
260 
261  // Run
262  auto fut = m_manager.AwaitDaqStateAsync("daq1"sv, daq::State::NotStarted, 10ms);
263  m_io_ctx.poll();
264 
265  ASSERT_TRUE(fut.is_ready());
266  auto [timeout, result] = fut.get();
267  EXPECT_FALSE(timeout);
268  EXPECT_EQ(result, m_daq1->GetStatus()->GetStatus());
269 }
270 
271 TEST_F(TestManagerImpl, AwaitDaqStateIsReadyWhenConditionIsFulfilled) {
272 
273  // Run
274  auto fut = m_manager.AwaitDaqStateAsync("daq1"sv, daq::State::Acquiring, 10ms);
275  ASSERT_FALSE(fut.is_ready());
276  m_daq1_status->SetState(daq::State::Acquiring);
277  MakeTestProgress(m_io_ctx, &fut);
278 
279  ASSERT_TRUE(fut.is_ready());
280  auto [timeout, result] = fut.get();
281  EXPECT_FALSE(timeout);
282  EXPECT_EQ(result, m_daq1->GetStatus()->GetStatus());
283 }
284 
285 TEST_F(TestManagerImpl, AwaitDaqStateIsReadyWhenItTimesout) {
286 
287  // Run
288  auto fut = m_manager.AwaitDaqStateAsync("daq1"sv, daq::State::Acquiring, 0ms);
289  ASSERT_FALSE(fut.is_ready());
290  MakeTestProgress(m_io_ctx, &fut);
291 
292  ASSERT_TRUE(fut.is_ready()) << "Timer should have triggered to make the future ready";
293  auto [timeout, result] = fut.get();
294  EXPECT_TRUE(timeout);
295  EXPECT_EQ(result, m_daq1->GetStatus()->GetStatus());
296 }
TestManagerImpl::m_daq2_status
std::shared_ptr< ObservableStatus > m_daq2_status
Definition: testManager.cpp:75
TestManagerImplLifecycle::TearDown
void TearDown() override
Definition: testManager.cpp:30
TestManagerImpl::m_daq1
std::shared_ptr< DaqControllerMock > m_daq1
Definition: testManager.cpp:72
utils.hpp
Defines shared test utilities.
TestManagerImpl::TestManagerImpl
TestManagerImpl()
Definition: testManager.cpp:40
daq::ManagerImpl
Implements daq::Manager.
Definition: manager.hpp:211
TestManagerImpl::m_daq2
std::shared_ptr< DaqControllerMock > m_daq2
Definition: testManager.cpp:73
TestManagerImpl::TearDown
void TearDown() override
Definition: testManager.cpp:65
TestManagerImpl::m_daq_id_1
std::string m_daq_id_1
Definition: testManager.cpp:70
rad::IoExecutor
Adapts boost::asio::io_context into a compatible boost::thread Executor type.
Definition: ioExecutor.hpp:12
manager.hpp
Declaration of daq::Manager
daq::TEST_F
TEST_F(TestSource, Constructors)
Definition: testSource.cpp:34
daq::fits::UpdateKeywords
void UpdateKeywords(KeywordVector &to, KeywordVector const &from)
Updates a with keywords from b.
Definition: keyword.cpp:120
daq::fits::EsoKeyword
BasicKeyword< EsoKeywordTraits > EsoKeyword
ESO hiearchical keyword.
Definition: keyword.hpp:99
daq
Definition: daqController.cpp:18
TestManagerImpl::m_executor
rad::IoExecutor m_executor
Definition: testManager.cpp:69
daq::StatusSignal::ConnectObserver
boost::signals2::connection ConnectObserver(Observer o)
Definition: manager.hpp:51
daq::ManagerImpl::AwaitDaqStateAsync
boost::future< Result< Status > > AwaitDaqStateAsync(std::string_view id, State, std::chrono::milliseconds timeout) override
Await DAQ state.
Definition: manager.cpp:179
daq::Status::TimePoint
std::chrono::time_point< std::chrono::steady_clock > TimePoint
Definition: status.hpp:33
TestManagerImpl::SetUp
void SetUp() override
Creates manager and adds two data acquisitions.
Definition: testManager.cpp:45
TestManagerImpl::m_io_ctx
boost::asio::io_context m_io_ctx
Definition: testManager.cpp:68
TestManagerImpl
Definition: testManager.cpp:38
daq::DaqOperationAborted
Started operation was aborted.
Definition: error.hpp:47
TestManagerImpl::m_daq_id_2
std::string m_daq_id_2
Definition: testManager.cpp:71
daq::ManagerImpl::GetStatusSignal
StatusSignal & GetStatusSignal() override
Definition: manager.cpp:223
statusObserver.hpp
daqController.hpp
Mock of DaqController.
StatusObserverMock
Simple observer used for testing.
Definition: statusObserver.hpp:22
TestManagerImplLifecycle::SetUp
void SetUp() override
Definition: testManager.cpp:28
daq::ManagerImpl::AddDaq
void AddDaq(std::shared_ptr< DaqController > daq) override
Add data acquisition.
Definition: manager.cpp:100
TestManagerImpl::m_daq1_status
std::shared_ptr< ObservableStatus > m_daq1_status
Definition: testManager.cpp:74
daq::fits::ValueKeyword
BasicKeyword< ValueKeywordTraits > ValueKeyword
Standard FITS value keyword.
Definition: keyword.hpp:92
daq::Status
Non observable status object that keeps stores status of data acquisition.
Definition: status.hpp:32
daq::fits::KeywordVector
std::vector< KeywordVariant > KeywordVector
Vector of keywords.
Definition: keyword.hpp:138
TestManagerImplLifecycle
Definition: testManager.cpp:26
daq::ErrorPolicy::Strict
@ Strict
Any error is considered fatal and may lead to the operation being aborted.
daq::MakeDaqIdCandidate
std::string MakeDaqIdCandidate(char const *instrument_id, unsigned jitter=0)
Creates a DAQ id candidate that may or may not be unique.
Definition: manager.cpp:27
TestManagerImpl::m_manager
ManagerImpl m_manager
Definition: testManager.cpp:76
MakeTestProgress
void MakeTestProgress(boost::asio::io_context &io_ctx, Future *fut=nullptr)
Test helper that progress the test by executing pending jobs and optionally wait for a future to be r...
Definition: utils.hpp:42