ifw-daq  2.1.0-pre1
IFW Data Acquisition modules
testOcmDaqService.cpp
Go to the documentation of this file.
1 /**
2  * @file
3  * @ingroup daq_ocm_server_test
4  * @copyright 2022 ESO - European Southern Observatory
5  *
6  * @brief Unit tests for OcmDaqService
7  */
8 #include <ocmDaqService.hpp>
9 
10 #include <fmt/format.h>
11 #include <gtest/gtest.h>
12 
13 #include <daq/error.hpp>
14 #include <malMock.hpp>
15 #include <metadaqifMock.hpp>
16 #include <recifMock.hpp>
17 
18 #include "daqifFake.hpp"
19 #include "mock/daqifMock.hpp"
20 #include "mock/managerMock.hpp"
21 
22 using namespace ::testing;
23 using namespace std::literals;
24 
25 /**
26  * Fixture that sets up a OcmDaqService instance with following mockups:
27  * - mal::Mal
28  * - daq::Manager
29  *
30  * And prepared request string containing metadata sources
31  *
32  * @ingroup daq_ocm_server_test
33  */
34 struct TestOcmDaqService : Test {
36  : m_io_ctx()
37  , m_mal_mock()
38  , m_mgr_mock()
39  , m_event_log(std::make_shared<daq::ObservableEventLog>())
40  , m_daq_impl(std::make_shared<OcmDaqService>(
41  m_io_ctx, m_mal_mock, m_mgr_mock, "ocm", "/tmp", m_event_log)) {
42  }
43  void SetUp() override {
44  m_metadata_sources = "meta@zpb.rr://uri";
45  m_prim_sources = "prim@zpb.rr://uri";
46  m_daq_properties = R"(
47  {
48  "keywords": [
49  {
50  "type": "esoKeyword",
51  "name": "FOO BAR",
52  "value": true
53  },
54  {
55  "type": "valueKeyword",
56  "name": "FOOBAR",
57  "value": true
58  }
59  ]
60  }
61  )";
62  }
63  void TearDown() override {
64  }
65 
66  boost::asio::io_context m_io_ctx; // NOLINT
67  MalMock m_mal_mock; // NOLINT
69  std::shared_ptr<daq::ObservableEventLog> m_event_log;
70  std::shared_ptr<OcmDaqService> m_daq_impl; // NOLINT
71  std::string m_prim_sources; // NOLINT
72  std::string m_metadata_sources; // NOLINT
73  std::string m_daq_properties; // NOLINT
74 };
75 
76 /**
77  * Fixture for testing when OcmDaqService is abandoned
78  * (e.g. when service is de-registered).
79  *
80  * @ingroup daq_ocm_server_test
81  */
83 
84 TEST(TestParseSingleSource, Successful) {
85  ParsedSource expected("name", "zpb://rr.uri");
86 
87  ParsedSource parsed = ParseSourceUri(fmt::format("{}@{}", expected.name, expected.rr_uri));
88  EXPECT_EQ(expected, parsed);
89 }
90 
91 TEST(TestParseSingleSource, LeadingOrTrailingSpacesAreAllowed) {
92  ParsedSource expected("name", "zpb://rr.uri");
93  EXPECT_EQ(expected, ParseSourceUri(fmt::format("{}@{} ", expected.name, expected.rr_uri)));
94  EXPECT_EQ(expected, ParseSourceUri(fmt::format(" {}@{}", expected.name, expected.rr_uri)));
95  EXPECT_EQ(expected, ParseSourceUri(fmt::format(" {}@{} ", expected.name, expected.rr_uri)));
96 }
97 
98 TEST(TestParseSingleSource, Incomplete) {
99  ParsedSource expected("name", "zpb://rr.uri");
100  EXPECT_THROW(ParseSourceUri(fmt::format("{}@,{} ", expected.name, expected.rr_uri)),
101  std::invalid_argument);
102  EXPECT_THROW(ParseSourceUri(fmt::format("{}@,{} ", expected.name, expected.rr_uri)),
103  std::invalid_argument);
104  EXPECT_THROW(ParseSourceUri(fmt::format("@,")), std::invalid_argument);
105  EXPECT_THROW(ParseSourceUri(fmt::format("{},{}", expected.name, expected.rr_uri)),
106  std::invalid_argument);
107  EXPECT_THROW(ParseSourceUri(fmt::format("{}@,{}", expected.name, expected.rr_uri)),
108  std::invalid_argument);
109 }
110 
111 TEST(TestParseMultipleSources, Successful) {
112  ParsedSource expected1("name", "zpb://rr.uri");
113  ParsedSource expected2("name", "zpb://rr.uri");
114  std::vector<ParsedSource> result{expected1, expected2};
115 
116  EXPECT_EQ(
117  result,
118  ParseSourceUris(fmt::format(
119  "{}@{} {}@{}", expected1.name, expected1.rr_uri, expected2.name, expected2.rr_uri)));
120  EXPECT_EQ(
121  result,
122  ParseSourceUris(fmt::format(
123  "{}@{} {}@{}", expected1.name, expected1.rr_uri, expected2.name, expected2.rr_uri)));
124 
125  EXPECT_EQ(
126  result,
127  ParseSourceUris(fmt::format(
128  " {}@{} {}@{} ", expected1.name, expected1.rr_uri, expected2.name, expected2.rr_uri)));
129 }
130 
131 TEST(TestParseSingleSource, Empty) {
132  ParsedSource expected("name", "zpb://rr.uri");
133  EXPECT_THROW(ParseSourceUri(fmt::format("{}@,{} ", "", expected.rr_uri)),
134  std::invalid_argument);
135  EXPECT_THROW(ParseSourceUri(fmt::format("{}@,{} ", expected.name, "")), std::invalid_argument);
136  EXPECT_THROW(ParseSourceUri(fmt::format("{}@,{} ", expected.name, expected.rr_uri)),
137  std::invalid_argument);
138 }
139 
140 TEST(TestParseDaqContext, Successful) {
141  auto max_err_ms = 0.001;
142  {
143  auto properties_str = R"(
144  {
145  "keywords": [
146  {
147  "type": "esoKeyword",
148  "name": "FOO BAR",
149  "value": true
150  },
151  {
152  "type": "valueKeyword",
153  "name": "FOOBAR",
154  "value": true
155  }
156  ],
157  "awaitInterval": 0.1
158  }
159  )";
160  auto properties = ParseStartDaqContext(properties_str);
161  EXPECT_THAT(static_cast<double>(properties.await_interval.count()),
162  DoubleNear(100, max_err_ms));
163  }
164  {
165  auto properties_str = R"(
166  {
167  "awaitInterval": 1
168  }
169  )";
170  auto properties = ParseStartDaqContext(properties_str);
171  EXPECT_THAT(static_cast<double>(properties.await_interval.count()),
172  DoubleNear(1000, max_err_ms));
173  }
174 }
175 
176 TEST(TestParseDaqContext, Failures) {
177  EXPECT_THROW(ParseStartDaqContext(R"({"awaitInterval": "0.1"})"), std::invalid_argument);
178  EXPECT_THROW(ParseStartDaqContext(R"({"awaitInterval": -0.1})"), std::invalid_argument);
179 }
180 
181 TEST_F(TestOcmDaqService, StartDaqShouldTestIfIdAlreadyExistsAndReturnFailureIfItDoes) {
182  // Setup
183  // Setup so that ID already exist
184  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).WillOnce(Return(true));
185 
186  // Run
187  auto fut = m_daq_impl->StartDaq("id", "prefix", m_prim_sources, m_metadata_sources, "");
188  m_io_ctx.poll();
189  ASSERT_TRUE(fut.is_ready()) << "future should be ready by now since daq with id already exist";
190  EXPECT_THROW(fut.get(), daqif::DaqException);
191 }
192 
193 TEST_F(TestOcmDaqService, StartDaqShouldCreateAndAssignIdIfNotProvided) {
194  // Setup
195  auto* fake_reply(new daqif::DaqReplyFake);
196 
197  EXPECT_CALL(m_mgr_mock, MakeDaqId(_)).WillOnce(Return("id"));
198  EXPECT_CALL(m_mgr_mock, StartDaqAsync(Field(&daq::DaqContext::id, std::string("id"))))
199  .WillOnce(Return(ByMove(boost::make_ready_future<daq::State>(daq::State::Acquiring))));
200  // Have to construct and return void* and still keep a reference to it..
201  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqReply"))
202  .WillOnce(Return(fake_reply))
203  .RetiresOnSaturation();
204 
205  // Run
206  auto fut = m_daq_impl->StartDaq("", "prefix", m_prim_sources, m_metadata_sources, "");
207  // Requred to trigger any .then continuations, even though future is ready
208  m_io_ctx.poll();
209 
210  ASSERT_TRUE(fut.is_ready()) << "future should be ready immediately since we faked a "
211  "synchronous start with make_ready_future";
212  EXPECT_TRUE(fut.has_value());
213  auto reply = fut.get();
214  ASSERT_TRUE(reply);
215  EXPECT_EQ(reply->getId(), "id");
216 }
217 
218 TEST_F(TestOcmDaqService, StartDaqFailsIfJsonIsInvalid) {
219  // Setup
220 
221  // Run
222  auto fut = m_daq_impl->StartDaq("id", "prefix", "", m_metadata_sources, "INVALID JSON");
223  m_io_ctx.poll();
224  ASSERT_TRUE(fut.is_ready()) << "future should be ready by now since daq with id already exist";
225  EXPECT_THROW(fut.get(), daqif::DaqException);
226 }
227 
228 TEST_F(TestOcmDaqService, StartDaqFailsIfJsonSchemaIsInvalid) {
229  // Setup
230 
231  // Run
232  // note: Expects JSON object
233  auto fut = m_daq_impl->StartDaq("id", "prefix", "", m_metadata_sources, "[]");
234  m_io_ctx.poll();
235  ASSERT_TRUE(fut.is_ready());
236  EXPECT_THROW(fut.get(), daqif::DaqException);
237 }
238 
239 TEST_F(TestOcmDaqService, StartDaqFailsIfPrefixContainsParentPaths) {
240  // Setup
241 
242  // Run
243  // note: Expects JSON object
244  auto fut = m_daq_impl->StartDaq("id", "/path/prefix", "", m_metadata_sources, m_daq_properties);
245  m_io_ctx.poll();
246  ASSERT_TRUE(fut.is_ready());
247  EXPECT_THROW(fut.get(), daqif::DaqException);
248 }
249 
250 TEST_F(TestOcmDaqService, StartDaqShouldAddAndStartDaqControllerIfArgumentsAreOk) {
251  // Setup
252  auto* fake_reply(new daqif::DaqReplyFake);
253 
254  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).WillOnce(Return(false));
255  EXPECT_CALL(m_mgr_mock, StartDaqAsync(Field(&daq::DaqContext::id, "id")))
256  .WillOnce(Return(ByMove(boost::make_ready_future<daq::State>(daq::State::Acquiring))));
257  // Have to construct and return void* and still keep a reference to it..
258  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqReply"))
259  .WillOnce(Return(fake_reply))
260  .RetiresOnSaturation();
261 
262  // Run
263  auto fut = m_daq_impl->StartDaq("id", "prefix", "", m_metadata_sources, m_daq_properties);
264 
265  // Requred to trigger any .then continuations, even though future is ready
266  m_io_ctx.poll();
267 
268  ASSERT_TRUE(fut.is_ready()) << "future should be ready immediately since we faked a "
269  "synchronous start with make_ready_future";
270  EXPECT_TRUE(fut.has_value());
271  auto reply = fut.get();
272  ASSERT_TRUE(reply);
273  EXPECT_EQ(reply->getId(), "id");
274 }
275 
276 TEST_F(TestOcmDaqService, StartDaqShouldFailIfAddDaqFails) {
277  // Setup
278  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).WillOnce(Return(false));
279  EXPECT_CALL(m_mgr_mock, StartDaqAsync(_)).WillOnce(Throw(std::runtime_error("error")));
280 
281  // Run
282  auto fut = m_daq_impl->StartDaq("id", "prefix", "", m_metadata_sources, "");
283  m_io_ctx.poll();
284  ASSERT_TRUE(fut.is_ready()) << "future should be ready immediately since we faked a "
285  "synchronous start with make_ready_future";
286  EXPECT_THROW(fut.get(), daqif::DaqException)
287  << "future should have contained the ICD exception type";
288 }
289 
290 TEST_F(TestOcmDaqService, StartDaqShouldFailIfManagerStartFails) {
291  // Setup
292 
293  EXPECT_CALL(m_mgr_mock, MakeDaqId(_)).WillOnce(Return("fileid"));
294  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).WillOnce(Return(false));
295  EXPECT_CALL(m_mgr_mock, StartDaqAsync(Field(&daq::DaqContext::id, "id")))
296  .WillOnce(Return(ByMove(boost::make_exceptional_future<daq::State>(
297  daq::DaqSourceErrors({daq::DaqSourceError("start", "source", "message")})))));
298 
299  // Run
300  auto fut = m_daq_impl->StartDaq("id", "prefix", "", m_metadata_sources, "");
301  m_io_ctx.poll();
302  ASSERT_TRUE(fut.is_ready())
303  << "future should be ready now since we faked a synchronous start with make_ready_future";
304  EXPECT_THROW(fut.get(), daqif::DaqException)
305  << "future should have contained the ICD exception type";
306 }
307 
308 TEST_F(TestOcmDaqService, StopDaqShouldFailIfDaqDoesNotExist) {
309  // Setup
310  // Implementation might, or might not check if ID exist before issuing request to stop
311  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(false));
312  // Implementation might, or might not stop directly, leaving the id check to mgr
313  EXPECT_CALL(m_mgr_mock, StopDaqAsync("id"sv, daq::ErrorPolicy::Strict))
314  .Times(Between(0, 1))
315  .WillOnce(Return(ByMove(
316  boost::make_exceptional_future<daq::Status>(std::invalid_argument("no such id")))));
317 
318  // Run
319  auto reply_fut = m_daq_impl->StopDaq("id");
320  m_io_ctx.poll();
321  ASSERT_TRUE(reply_fut.is_ready());
322  EXPECT_THROW(reply_fut.get(), daqif::DaqException);
323 }
324 
325 TEST_F(TestOcmDaqService, StopDaqShouldSucceedIfMgrOpSuceeds) {
326  // Setup
327  auto* fake_reply(new daqif::DaqReplyFake); // Note: Raw pointer since MAL use unsafe APIs
328  daq::Status status("id", "fileid", daq::State::Stopped, false, daq::Status::TimePoint());
329 
330  // Implementation might, or might not check if ID exist before issuing request to stop
331  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
332  EXPECT_CALL(m_mgr_mock, StopDaqAsync("id"sv, daq::ErrorPolicy::Strict))
333  .WillOnce(Return(ByMove(boost::make_ready_future<daq::Status>(status))));
334  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqReply"))
335  .WillOnce(Return(fake_reply))
336  .RetiresOnSaturation();
337 
338  // Run
339  auto reply_fut = m_daq_impl->StopDaq("id");
340  m_io_ctx.poll();
341  ASSERT_TRUE(reply_fut.is_ready());
342  std::shared_ptr<daqif::DaqReply> reply = reply_fut.get();
343  EXPECT_EQ(reply->getId(), "id");
344 }
345 
346 TEST_F(TestOcmDaqService, ForceStopDaqShouldSucceedIfMgrOpSuceeds) {
347  // Setup
348  auto* fake_reply(new daqif::DaqReplyFake); // Note: Raw pointer since MAL use unsafe APIs
349  daq::Status status("id", "fileid", daq::State::Stopped, false, daq::Status::TimePoint());
350 
351  // Implementation might, or might not check if ID exist before issuing request to stop
352  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
353  EXPECT_CALL(m_mgr_mock, StopDaqAsync("id"sv, daq::ErrorPolicy::Tolerant))
354  .WillOnce(Return(ByMove(boost::make_ready_future<daq::Status>(status))));
355  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqReply"))
356  .WillOnce(Return(fake_reply))
357  .RetiresOnSaturation();
358 
359  // Run
360  auto reply_fut = m_daq_impl->ForceStopDaq("id");
361  m_io_ctx.poll();
362  ASSERT_TRUE(reply_fut.is_ready());
363  std::shared_ptr<daqif::DaqReply> reply = reply_fut.get();
364  EXPECT_EQ(reply->getId(), "id");
365 }
366 
367 TEST_F(TestOcmDaqService, AbortDaqShouldFailIfDaqDoesNotExist) {
368  // Setup
369  // Implementation might, or might not check if ID exist before issuing request to abort
370  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(false));
371  EXPECT_CALL(m_mgr_mock, AbortDaqAsync("id"sv, daq::ErrorPolicy::Strict))
372  .Times(Between(0, 1))
373  .WillOnce(Return(ByMove(
374  boost::make_exceptional_future<daq::Status>(std::invalid_argument("no such id")))));
375 
376  // Run
377  auto reply_fut = m_daq_impl->AbortDaq("id");
378  m_io_ctx.poll();
379  ASSERT_TRUE(reply_fut.is_ready());
380  EXPECT_THROW(reply_fut.get(), daqif::DaqException);
381 }
382 
383 TEST_F(TestOcmDaqService, AbortDaqShouldSucceedIfMgrOpSuceeds) {
384  // Setup
385  auto* fake_reply(new daqif::DaqReplyFake); // Note: Raw pointer since MAL use unsafe APIs
386 
387  auto reply_status = daq::Status("id", "fileid");
388  reply_status.state = daq::State::Aborted;
389 
390  // Implementation might, or might not check if ID exist before issuing request to stop
391  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
392  EXPECT_CALL(m_mgr_mock, AbortDaqAsync("id"sv, daq::ErrorPolicy::Strict))
393  .WillOnce(Return(ByMove(boost::make_ready_future<daq::Status>(reply_status))));
394  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqReply"))
395  .WillOnce(Return(fake_reply))
396  .RetiresOnSaturation();
397 
398  // Run
399  auto reply_fut = m_daq_impl->AbortDaq("id");
400  m_io_ctx.poll();
401  ASSERT_TRUE(reply_fut.is_ready());
402  std::shared_ptr<daqif::DaqReply> reply = reply_fut.get();
403  EXPECT_EQ(reply->getId(), "id");
404 }
405 
406 TEST_F(TestOcmDaqService, ForceAbortDaqShouldUseTolerantPolicy) {
407  // Setup
408  auto* fake_reply(new daqif::DaqReplyFake); // Note: Raw pointer since MAL use unsafe APIs
409  auto reply_status = daq::Status("id", "fileid");
410  reply_status.state = daq::State::Aborted;
411 
412  // Implementation might, or might not check if ID exist before issuing request to stop
413  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
414  EXPECT_CALL(m_mgr_mock, AbortDaqAsync("id"sv, daq::ErrorPolicy::Tolerant))
415  .WillOnce(Return(ByMove(boost::make_ready_future<daq::Status>(reply_status))));
416  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqReply"))
417  .WillOnce(Return(fake_reply))
418  .RetiresOnSaturation();
419 
420  // Run
421  auto reply_fut = m_daq_impl->ForceAbortDaq("id");
422  m_io_ctx.poll();
423  ASSERT_TRUE(reply_fut.is_ready());
424  std::shared_ptr<daqif::DaqReply> reply = reply_fut.get();
425  EXPECT_EQ(reply->getId(), "id");
426 }
427 
428 TEST_F(TestOcmDaqService, UpdateKeywordsShouldSucceedIfMgrOpSucceeds) {
429  // Setup
430  std::string keywords = R"(
431  [
432  {
433  "type":"valueKeyword",
434  "name":"OBJECT",
435  "value":"OBJECT,SKY"
436  },
437  {
438  "type":"esoKeyword",
439  "name":"OBS TPLNO",
440  "value":2
441  }
442  ]
443  )";
444  // This is what `keywords` should contain when parsed.
445  daq::fits::KeywordVector parsed_keywords = {
446  daq::fits::ValueKeyword("OBJECT", "OBJECT,SKY"),
447  daq::fits::EsoKeyword("OBS TPLNO", static_cast<uint64_t>(2))};
448  auto* fake_reply(new daqif::DaqReplyFake); // Note: Raw pointer since MAL use unsafe APIs
449  // Implementation might, or might not check if ID exist before issuing request to stop
450  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
451  EXPECT_CALL(m_mgr_mock, UpdateKeywords("id"sv, parsed_keywords));
452  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqReply"))
453  .WillOnce(Return(fake_reply))
454  .RetiresOnSaturation();
455 
456  // Run
457  auto reply_fut = m_daq_impl->UpdateKeywords("id", keywords);
458  m_io_ctx.poll();
459  ASSERT_TRUE(reply_fut.is_ready());
460  std::shared_ptr<daqif::DaqReply> reply = reply_fut.get();
461  EXPECT_EQ(reply->getId(), "id");
462  EXPECT_EQ(reply->getError(), false);
463 }
464 
465 TEST_F(TestOcmDaqService, UpdateKeywordsFailsIfJsonParseFails) {
466  // Setup
467  std::string keywords_with_trailing_comma = R"(
468  [
469  {
470  "type":"valueKeyword",
471  "name":"OBJECT",
472  "value":"OBJECT,SKY"
473  },
474  {
475  "type":"esoKeyword",
476  "name":"OBS TPLNO",
477  "value":2
478  },
479  ]
480  )";
481  // Implementation might, or might not check if ID exist before issuing request to stop
482  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
483  EXPECT_CALL(m_mgr_mock, UpdateKeywords("id"sv, _)).Times(0);
484 
485  // Run
486  auto reply_fut = m_daq_impl->UpdateKeywords("id", keywords_with_trailing_comma);
487  m_io_ctx.poll();
488  ASSERT_TRUE(reply_fut.is_ready());
489  EXPECT_THROW(reply_fut.get(), daqif::DaqException);
490 }
491 
492 TEST_F(TestOcmDaqService, UpdateKeywordsFailsIfSchemaParsingFails) {
493  // Setup
494  std::string keywords_with_unknown_type = R"(
495  [
496  {
497  "type":"unknownKeywordHere",
498  "name":"OBJECT",
499  "value":"OBJECT,SKY"
500  }
501  ]
502  )";
503  // Implementation might, or might not check if ID exist before issuing request to stop
504  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
505  EXPECT_CALL(m_mgr_mock, UpdateKeywords("id"sv, _)).Times(0);
506 
507  // Run
508  auto reply_fut = m_daq_impl->UpdateKeywords("id", keywords_with_unknown_type);
509  m_io_ctx.poll();
510  ASSERT_TRUE(reply_fut.is_ready());
511  EXPECT_THROW(reply_fut.get(), daqif::DaqException);
512 }
513 
514 TEST_F(TestOcmDaqService, GetStatusFailsIfDaqDoesNotExist) {
515  // Setup
516  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(false));
517  EXPECT_CALL(m_mgr_mock, GetStatus("id"sv))
518  .Times(Between(0, 1))
519  .WillOnce(Throw(std::invalid_argument("no such id")));
520 
521  // Run
522  auto reply_fut = m_daq_impl->GetStatus("id");
523  EXPECT_FALSE(reply_fut.is_ready())
524  << "future cannot be ready since implementation should use provided executor to provide "
525  "thread safety";
526  m_io_ctx.poll();
527  ASSERT_TRUE(reply_fut.is_ready());
528  EXPECT_THROW(reply_fut.get(), daqif::DaqException);
529 }
530 
531 TEST_F(TestOcmDaqService, GetStatusSuccedsIfMgrOpSucceeds) {
532  // Setup
533  daq::Status status("id", "fileid", daq::State::Acquiring, false, daq::Status::TimePoint());
534  auto* fake_reply(new daqif::DaqStatusFake); // Note: Raw pointer since MAL use unsafe APIs
535 
536  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
537  EXPECT_CALL(m_mgr_mock, GetStatus("id"sv)).Times(Between(0, 1)).WillOnce(Return(status));
538  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqStatus"))
539  .WillOnce(Return(fake_reply))
540  .RetiresOnSaturation();
541 
542  // Run
543  auto reply_fut = m_daq_impl->GetStatus("id");
544  EXPECT_FALSE(reply_fut.is_ready())
545  << "future cannot be ready since implementation should use provided executor to provide "
546  "thread safety";
547  m_io_ctx.poll();
548  ASSERT_TRUE(reply_fut.is_ready());
549  std::shared_ptr<daqif::DaqStatus> reply = reply_fut.get();
550  EXPECT_EQ(reply->getId(), "id");
551  EXPECT_EQ(reply->getFileId(), "fileid");
552  EXPECT_EQ(reply->getError(), false);
553  EXPECT_EQ(reply->getState(), daqif::StateAcquiring);
554  EXPECT_EQ(reply->getSubState(), daqif::Acquiring);
555 }
556 
557 TEST_F(TestOcmDaqService, GetActiveReturnsActive) {
558  // Setup
559  auto status1 = std::make_shared<daq::ObservableStatus>("completed", "fileid1");
560  auto status2 = std::make_shared<daq::ObservableStatus>("active1", "fileid2");
561  auto status3 = std::make_shared<daq::ObservableStatus>("active2", "fileid3");
562  auto status4 = std::make_shared<daq::ObservableStatus>("completed", "fileid4");
563  // Make DAQ completed
564  status1->SetState(daq::State::Aborted);
565  status4->SetState(daq::State::Aborted);
566 
567  auto daq1 = std::make_shared<DaqControllerFake>(status1);
568  auto daq2 = std::make_shared<DaqControllerFake>(status2);
569  auto daq3 = std::make_shared<DaqControllerFake>(status3);
570  auto daq4 = std::make_shared<DaqControllerFake>(status4);
571  std::vector<std::shared_ptr<daq::DaqController const>> daqs;
572  daqs.push_back(daq1);
573  daqs.push_back(daq2);
574  daqs.push_back(daq3);
575  daqs.push_back(daq4);
576 
577  auto* st1 = new daqif::DaqStatusFake;
578  auto* st2 = new daqif::DaqStatusFake;
579 
580  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqStatus"))
581  .WillOnce(Return(st1))
582  .WillOnce(Return(st2))
583  .RetiresOnSaturation();
584 
585  EXPECT_CALL(m_mgr_mock, GetDaqControllers()).WillOnce(Return(daqs));
586 
587  // Run
588  auto reply_fut = m_daq_impl->GetActiveList();
589  m_io_ctx.poll();
590  ASSERT_TRUE(reply_fut.is_ready());
591  auto reply = reply_fut.get();
592  EXPECT_EQ(reply.size(), 2u);
593  EXPECT_EQ(reply[0]->getId(), "active1");
594  EXPECT_EQ(reply[1]->getId(), "active2");
595 }
596 
597 TEST_F(TestOcmDaqService, AwaitDaqstateFailsWithInvalidArguments) {
598  // Setup
599 
600  // Run
601  {
602  // Invalid timeout
603  auto fut = m_daq_impl->AwaitDaqState("id", daqif::StateAcquiring, daqif::Acquiring, 0.0);
604  m_io_ctx.poll();
605  ASSERT_TRUE(fut.is_ready());
606  EXPECT_THROW(fut.get(), daqif::DaqException);
607  }
608  m_io_ctx.reset();
609  {
610  // Invalid state combination
611  auto fut = m_daq_impl->AwaitDaqState("id", daqif::StateMerging, daqif::Acquiring, 1.0);
612  m_io_ctx.poll();
613  ASSERT_TRUE(fut.is_ready());
614  EXPECT_THROW(fut.get(), daqif::DaqException);
615  }
616 }
617 
618 TEST_F(TestOcmDaqService, AwaitDaqstateSucceds) {
619  // Setup
620  daq::Status status("id", "fileid");
621  status.state = daq::State::Acquiring;
622 
623  boost::promise<daq::Result<daq::Status>> promise;
624  EXPECT_CALL(m_mgr_mock, AwaitDaqStateAsync("id"sv, daq::State::Acquiring, 1000ms))
625  .WillOnce(Return(ByMove(promise.get_future())));
626 
627  auto* reply = new daqif::AwaitDaqReplyFake;
628 
629  EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_AwaitDaqReply"))
630  .WillOnce(Return(reply))
631  .RetiresOnSaturation();
632 
633  // Run
634  auto fut = m_daq_impl->AwaitDaqState("id", daqif::StateAcquiring, daqif::Acquiring, 1.0);
635  ASSERT_FALSE(fut.is_ready());
636  promise.set_value({false, status});
637  m_io_ctx.poll();
638  {
639  auto val = fut.get();
640  EXPECT_FALSE(val->getTimeout());
641  auto status = val->getStatus();
642  EXPECT_EQ(status->getId(), "id");
643  EXPECT_EQ(status->getError(), false);
644  EXPECT_EQ(status->getState(), daqif::StateAcquiring);
645  EXPECT_EQ(status->getSubState(), daqif::Acquiring);
646  }
647 }
648 
650  // Setup
651 
652  // This is the promise used to create result from daq::Manager
653  boost::promise<daq::State> mgr_promise;
654  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).WillOnce(Return(false));
655  EXPECT_CALL(m_mgr_mock, MakeDaqId(_)).WillOnce(Return("fileid"));
656  EXPECT_CALL(m_mgr_mock, StartDaqAsync(_)).WillOnce(Return(ByMove(mgr_promise.get_future())));
657 
658  // Run
659  auto reply_fut = m_daq_impl->StartDaq("id", "prefix", m_prim_sources, m_metadata_sources, "");
660  // Abandon the service
661  m_daq_impl.reset();
662 
663  // Fulfull promise from daq::Manager:
664  mgr_promise.set_value(daq::State::Acquiring);
665 
666  // Trigger handlers
667  m_io_ctx.poll();
668  ASSERT_TRUE(reply_fut.is_ready());
669  EXPECT_THROW(reply_fut.get(), daqif::DaqException);
670 }
671 
673  // Setup
674 
675  // This is the promise used to create result from daq::Manager
676  boost::promise<daq::Status> mgr_promise;
677 
678  // Implementation might, or might not check if ID exist before issuing request to stop
679  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
680  EXPECT_CALL(m_mgr_mock, StopDaqAsync("id"sv, daq::ErrorPolicy::Strict))
681  .WillOnce(Return(ByMove(mgr_promise.get_future())));
682 
683  // Run
684  auto reply_fut = m_daq_impl->StopDaq("id");
685  // Abandon the service
686  m_daq_impl.reset();
687 
688  // Fulfull promise from daq::Manager:
689  mgr_promise.set_value(
690  daq::Status("id", "fileid", daq::State::Stopped, true, daq::Status::TimePoint()));
691 
692  // Trigger handlers
693  m_io_ctx.poll();
694  ASSERT_TRUE(reply_fut.is_ready());
695  EXPECT_THROW(reply_fut.get(), daqif::DaqException);
696 }
697 
699  // Setup
700 
701  // This is the promise used to create result from daq::Manager
702  boost::promise<daq::Status> mgr_promise;
703 
704  auto reply_status = daq::Status("id", "fileid");
705  reply_status.state = daq::State::Aborted;
706 
707  // Implementation might, or might not check if ID exist before issuing request to stop
708  EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
709  EXPECT_CALL(m_mgr_mock, AbortDaqAsync("id"sv, daq::ErrorPolicy::Strict))
710  .WillOnce(Return(ByMove(mgr_promise.get_future())));
711 
712  // Run
713  auto reply_fut = m_daq_impl->AbortDaq("id");
714  // Abandon the service
715  m_daq_impl.reset();
716 
717  // Fulfull promise from daq::Manager:
718  mgr_promise.set_value(reply_status);
719 
720  // Trigger handlers
721  m_io_ctx.poll();
722  ASSERT_TRUE(reply_fut.is_ready());
723  EXPECT_THROW(reply_fut.get(), daqif::DaqException);
724 }
TestOcmDaqService::m_daq_impl
std::shared_ptr< OcmDaqService > m_daq_impl
Definition: testOcmDaqService.cpp:70
ParseSourceUri
ParsedSource ParseSourceUri(std::string_view s)
Parse user provided string in the format "<name>@<rr-uri>".
Definition: ocmDaqService.cpp:93
TestOcmDaqServiceAbandoned
Fixture for testing when OcmDaqService is abandoned (e.g.
Definition: testOcmDaqService.cpp:82
TestOcmDaqService::SetUp
void SetUp() override
Definition: testOcmDaqService.cpp:43
ParseSourceUris
std::vector< ParsedSource > ParseSourceUris(std::string_view s)
Parse user provided string in the format "<name>@<rr-uri>[ <name>@...]".
Definition: ocmDaqService.cpp:130
metadaqifMock.hpp
Mockup of metadaqif classes.
daq::DaqSourceError
Represents error in single source.
Definition: error.hpp:67
ManagerMock
Definition: managerMock.hpp:9
TestOcmDaqService::TearDown
void TearDown() override
Definition: testOcmDaqService.cpp:63
daqif::AwaitDaqReplyFake
Definition: daqifFake.hpp:113
daq::DaqSourceErrors
Exception thrown to carry reply errors.
Definition: error.hpp:84
malMock.hpp
TestOcmDaqService::m_daq_properties
std::string m_daq_properties
Definition: testOcmDaqService.cpp:73
daq::DaqContext::id
std::string id
DAQ identfier, possibly provided by user.
Definition: daqContext.hpp:64
TEST
TEST(TestParseSingleSource, Successful)
Definition: testOcmDaqService.cpp:84
recifMock.hpp
Mockup of metadaqif classes.
TestOcmDaqService::m_mgr_mock
ManagerMock m_mgr_mock
Definition: testOcmDaqService.cpp:68
MalMock
Definition: malMock.hpp:5
TestOcmDaqService::m_metadata_sources
std::string m_metadata_sources
Definition: testOcmDaqService.cpp:72
daq::fits::EsoKeyword
BasicKeyword< EsoKeywordTraits > EsoKeyword
ESO hiearchical keyword.
Definition: keyword.hpp:337
daq
Definition: asyncProcess.cpp:15
daq::Status::TimePoint
std::chrono::time_point< std::chrono::steady_clock > TimePoint
Definition: status.hpp:121
OcmDaqService
Implements the MAL interface daqif::OcmDaq (async version).
Definition: ocmDaqService.hpp:88
ParsedSource::name
std::string name
Definition: ocmDaqService.hpp:34
daqifFake.hpp
ocmDaqService.hpp
Declaration of OcmDaqService.
TestOcmDaqService::m_mal_mock
MalMock m_mal_mock
Definition: testOcmDaqService.cpp:67
managerMock.hpp
TestOcmDaqService::m_prim_sources
std::string m_prim_sources
Definition: testOcmDaqService.cpp:71
daqif::DaqStatusFake
Definition: daqifFake.hpp:39
ParseStartDaqContext
daq::DaqContext ParseStartDaqContext(std::string const &json_properties)
Parse the JSON properties user provides with StartDaq.
Definition: ocmDaqService.cpp:46
daq::fits::ValueKeyword
BasicKeyword< ValueKeywordTraits > ValueKeyword
Standard FITS value keyword.
Definition: keyword.hpp:330
daq::Status
Non observable status object that keeps stores status of data acquisition.
Definition: status.hpp:120
TestOcmDaqService::TestOcmDaqService
TestOcmDaqService()
Definition: testOcmDaqService.cpp:35
daqif::DaqReplyFake
Definition: daqifFake.hpp:7
daq::Status::state
State state
Definition: status.hpp:138
daq::fits::KeywordVector
std::vector< KeywordVariant > KeywordVector
Vector of keywords.
Definition: keyword.hpp:414
TestOcmDaqService
Fixture that sets up a OcmDaqService instance with following mockups:
Definition: testOcmDaqService.cpp:34
daq::UpdateKeywords
void UpdateKeywords(DaqContext &ctx, fits::KeywordVector const &keywords)
Updates (adds or replaces) primary HDU keywords.
Definition: daqContext.cpp:28
TestOcmDaqService::m_event_log
std::shared_ptr< daq::ObservableEventLog > m_event_log
Definition: testOcmDaqService.cpp:69
daq::ErrorPolicy::Strict
@ Strict
Any error is considered fatal and may lead to the operation being aborted.
TestOcmDaqService::m_io_ctx
boost::asio::io_context m_io_ctx
Definition: testOcmDaqService.cpp:66
ParsedSource::rr_uri
std::string rr_uri
Definition: ocmDaqService.hpp:35
ParsedSource
Definition: ocmDaqService.hpp:25
TEST_F
TEST_F(TestOcmDaqService, StartDaqShouldTestIfIdAlreadyExistsAndReturnFailureIfItDoes)
Definition: testOcmDaqService.cpp:181
error.hpp
Contains error related declarations for DAQ.