ifw-daq 3.1.0
IFW Data Acquisition modules
Loading...
Searching...
No Matches
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
22using namespace ::testing;
23using 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 */
34struct 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";
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 m_daqv2_spec = R"(
63 {
64 "id": "id",
65 "sources": [
66 {
67 "type": "metadataSource",
68 "sourceName": "name",
69 "rrUri": "uri"
70 }
71 ]
72 }
73 )";
74 }
75 void TearDown() override {
76 }
77
78 boost::asio::io_context m_io_ctx; // NOLINT
81 std::shared_ptr<daq::ObservableEventLog> m_event_log;
82 std::shared_ptr<OcmDaqService> m_daq_impl; // NOLINT
83 std::string m_prim_sources; // NOLINT
84 std::string m_metadata_sources; // NOLINT
85 std::string m_daq_properties; // NOLINT
86 std::string m_daqv2_spec; // NOLINT
87};
88
89/**
90 * Fixture for testing when OcmDaqService is abandoned
91 * (e.g. when service is de-registered).
92 *
93 * @ingroup daq_ocm_server_test
94 */
96
97TEST(TestParseSingleSource, Successful) {
98 ParsedSource expected("name", "zpb://rr.uri");
99
100 ParsedSource parsed = ParseSourceUri(fmt::format("{}@{}", expected.name, expected.rr_uri));
101 EXPECT_EQ(expected, parsed);
102}
103
104TEST(TestParseSingleSource, LeadingOrTrailingSpacesAreAllowed) {
105 ParsedSource expected("name", "zpb://rr.uri");
106 EXPECT_EQ(expected, ParseSourceUri(fmt::format("{}@{} ", expected.name, expected.rr_uri)));
107 EXPECT_EQ(expected, ParseSourceUri(fmt::format(" {}@{}", expected.name, expected.rr_uri)));
108 EXPECT_EQ(expected, ParseSourceUri(fmt::format(" {}@{} ", expected.name, expected.rr_uri)));
109}
110
111TEST(TestParseSingleSource, Incomplete) {
112 ParsedSource expected("name", "zpb://rr.uri");
113 EXPECT_THROW(ParseSourceUri(fmt::format("{}@,{} ", expected.name, expected.rr_uri)),
114 std::invalid_argument);
115 EXPECT_THROW(ParseSourceUri(fmt::format("{}@,{} ", expected.name, expected.rr_uri)),
116 std::invalid_argument);
117 EXPECT_THROW(ParseSourceUri(fmt::format("@,")), std::invalid_argument);
118 EXPECT_THROW(ParseSourceUri(fmt::format("{},{}", expected.name, expected.rr_uri)),
119 std::invalid_argument);
120 EXPECT_THROW(ParseSourceUri(fmt::format("{}@,{}", expected.name, expected.rr_uri)),
121 std::invalid_argument);
122}
123
124TEST(TestParseMultipleSources, Successful) {
125 ParsedSource expected1("name", "zpb://rr.uri");
126 ParsedSource expected2("name", "zpb://rr.uri");
127 std::vector<ParsedSource> result{expected1, expected2};
128
129 EXPECT_EQ(
130 result,
131 ParseSourceUris(fmt::format(
132 "{}@{} {}@{}", expected1.name, expected1.rr_uri, expected2.name, expected2.rr_uri)));
133 EXPECT_EQ(
134 result,
135 ParseSourceUris(fmt::format(
136 "{}@{} {}@{}", expected1.name, expected1.rr_uri, expected2.name, expected2.rr_uri)));
137
138 EXPECT_EQ(
139 result,
140 ParseSourceUris(fmt::format(
141 " {}@{} {}@{} ", expected1.name, expected1.rr_uri, expected2.name, expected2.rr_uri)));
142}
143
144TEST(TestParseSingleSource, Empty) {
145 ParsedSource expected("name", "zpb://rr.uri");
146 EXPECT_THROW(ParseSourceUri(fmt::format("{}@,{} ", "", expected.rr_uri)),
147 std::invalid_argument);
148 EXPECT_THROW(ParseSourceUri(fmt::format("{}@,{} ", expected.name, "")), std::invalid_argument);
149 EXPECT_THROW(ParseSourceUri(fmt::format("{}@,{} ", expected.name, expected.rr_uri)),
150 std::invalid_argument);
151}
152
153TEST(TestParseDaqContext, Successful) {
154 auto max_err_ms = 0.001;
155 {
156 auto properties_str = R"(
157 {
158 "keywords": [
159 {
160 "type": "esoKeyword",
161 "name": "FOO BAR",
162 "value": true
163 },
164 {
165 "type": "valueKeyword",
166 "name": "FOOBAR",
167 "value": true
168 }
169 ],
170 "awaitInterval": 0.1
171 }
172 )";
173 auto properties = ParseStartDaqContext(properties_str);
174 EXPECT_THAT(static_cast<double>(properties.await_interval.count()),
175 DoubleNear(100, max_err_ms));
176 }
177 {
178 auto properties_str = R"(
179 {
180 "awaitInterval": 1
181 }
182 )";
183 auto properties = ParseStartDaqContext(properties_str);
184 EXPECT_THAT(static_cast<double>(properties.await_interval.count()),
185 DoubleNear(1000, max_err_ms));
186 }
187}
188
189TEST(TestParseDaqContext, Failures) {
190 EXPECT_THROW(ParseStartDaqContext(R"({"awaitInterval": "0.1"})"), std::invalid_argument);
191 EXPECT_THROW(ParseStartDaqContext(R"({"awaitInterval": -0.1})"), std::invalid_argument);
192}
193
194TEST_F(TestOcmDaqService, StartDaqShouldTestIfIdAlreadyExistsAndReturnFailureIfItDoes) {
195 // Setup
196 // Setup so that ID already exist
197 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).WillOnce(Return(true));
198 // MakeDaqId is possibly called since a file_id is required.
199 EXPECT_CALL(m_mgr_mock, MakeDaqId(_)).Times(Between(0, 1)).WillOnce(Return("file-id"));
200
201 // Run
202 auto fut = m_daq_impl->StartDaq("id", "prefix", m_prim_sources, m_metadata_sources, "");
203 m_io_ctx.poll();
204 ASSERT_TRUE(fut.is_ready()) << "future should be ready by now since daq with id already exist";
205 EXPECT_THROW(fut.get(), daqif::DaqException);
206}
207
208TEST_F(TestOcmDaqService, StartDaqShouldCreateAndAssignIdIfNotProvided) {
209 // Setup
210 auto* fake_reply(new daqif::DaqReplyFake);
211
212 EXPECT_CALL(m_mgr_mock, MakeDaqId(_)).WillOnce(Return("id"));
213 EXPECT_CALL(m_mgr_mock, StartDaqAsync(Field(&daq::DaqContext::id, std::string("id"))))
214 .WillOnce(Return(ByMove(boost::make_ready_future<daq::State>(daq::State::Acquiring))));
215 // Have to construct and return void* and still keep a reference to it..
216 EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqReply"))
217 .WillOnce(Return(fake_reply))
218 .RetiresOnSaturation();
219
220 // Run
221 auto fut = m_daq_impl->StartDaq("", "prefix", m_prim_sources, m_metadata_sources, "");
222 // Requred to trigger any .then continuations, even though future is ready
223 m_io_ctx.poll();
224
225 ASSERT_TRUE(fut.is_ready()) << "future should be ready immediately since we faked a "
226 "synchronous start with make_ready_future";
227 EXPECT_TRUE(fut.has_value());
228 auto reply = fut.get();
229 ASSERT_TRUE(reply);
230 EXPECT_EQ(reply->getId(), "id");
231}
232
233TEST_F(TestOcmDaqService, StartDaqV2ShouldTestIfIdAlreadyExistsAndReturnFailureIfItDoes) {
234 // Setup
235 // Setup so that ID already exist
236 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).WillOnce(Return(true));
237 // MakeDaqId is possibly called since a file_id is required.
238 EXPECT_CALL(m_mgr_mock, MakeDaqId(_)).Times(Between(0, 1)).WillOnce(Return("file-id"));
239
240 // Run
241 auto fut = m_daq_impl->StartDaqV2(m_daqv2_spec);
242 m_io_ctx.poll();
243 ASSERT_TRUE(fut.is_ready()) << "future should be ready by now since daq with id already exist";
244 EXPECT_THROW(fut.get(), daqif::DaqException);
245}
246
247TEST_F(TestOcmDaqService, StartDaqFailsIfJsonIsInvalid) {
248 // Setup
249
250 // Run
251 auto fut = m_daq_impl->StartDaq("id", "prefix", "", m_metadata_sources, "INVALID JSON");
252 m_io_ctx.poll();
253 ASSERT_TRUE(fut.is_ready()) << "future should be ready by now since daq with id already exist";
254 EXPECT_THROW(fut.get(), daqif::DaqException);
255}
256
257TEST_F(TestOcmDaqService, StartDaqFailsIfJsonSchemaIsInvalid) {
258 // Setup
259
260 // Run
261 // note: Expects JSON object
262 auto fut = m_daq_impl->StartDaq("id", "prefix", "", m_metadata_sources, "[]");
263 m_io_ctx.poll();
264 ASSERT_TRUE(fut.is_ready());
265 EXPECT_THROW(fut.get(), daqif::DaqException);
266}
267
268TEST_F(TestOcmDaqService, StartDaqFailsIfPrefixContainsParentPaths) {
269 // Setup
270
271 // Run
272 // note: Expects JSON object
273 auto fut = m_daq_impl->StartDaq("id", "/path/prefix", "", m_metadata_sources, m_daq_properties);
274 m_io_ctx.poll();
275 ASSERT_TRUE(fut.is_ready());
276 EXPECT_THROW(fut.get(), daqif::DaqException);
277}
278
279TEST_F(TestOcmDaqService, StartDaqShouldAddAndStartDaqControllerIfArgumentsAreOk) {
280 // Setup
281 auto* fake_reply(new daqif::DaqReplyFake);
282
283 EXPECT_CALL(m_mgr_mock, MakeDaqId(_)).Times(Between(0, 1)).WillOnce(Return("file-id"));
284 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).WillOnce(Return(false));
285 EXPECT_CALL(m_mgr_mock, StartDaqAsync(Field(&daq::DaqContext::id, "id")))
286 .WillOnce(Return(ByMove(boost::make_ready_future<daq::State>(daq::State::Acquiring))));
287 // Have to construct and return void* and still keep a reference to it..
288 EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqReply"))
289 .WillOnce(Return(fake_reply))
290 .RetiresOnSaturation();
291
292 // Run
293 auto fut = m_daq_impl->StartDaq("id", "prefix", "", m_metadata_sources, m_daq_properties);
294
295 // Requred to trigger any .then continuations, even though future is ready
296 m_io_ctx.poll();
297
298 ASSERT_TRUE(fut.is_ready()) << "future should be ready immediately since we faked a "
299 "synchronous start with make_ready_future";
300 EXPECT_TRUE(fut.has_value());
301 auto reply = fut.get();
302 ASSERT_TRUE(reply);
303 EXPECT_EQ(reply->getId(), "id");
304}
305
306TEST_F(TestOcmDaqService, StartDaqShouldFailIfAddDaqFails) {
307 // Setup
308 EXPECT_CALL(m_mgr_mock, MakeDaqId(_)).Times(Between(0, 1)).WillOnce(Return("file-id"));
309 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).WillOnce(Return(false));
310 EXPECT_CALL(m_mgr_mock, StartDaqAsync(_)).WillOnce(Throw(std::runtime_error("error")));
311
312 // Run
313 auto fut = m_daq_impl->StartDaq("id", "prefix", "", m_metadata_sources, "");
314 m_io_ctx.poll();
315 ASSERT_TRUE(fut.is_ready()) << "future should be ready immediately since we faked a "
316 "synchronous start with make_ready_future";
317 EXPECT_THROW(fut.get(), daqif::DaqException)
318 << "future should have contained the ICD exception type";
319}
320
321TEST_F(TestOcmDaqService, StartDaqShouldFailIfManagerStartFails) {
322 // Setup
323
324 EXPECT_CALL(m_mgr_mock, MakeDaqId(_)).WillOnce(Return("fileid"));
325 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).WillOnce(Return(false));
326 EXPECT_CALL(m_mgr_mock, StartDaqAsync(Field(&daq::DaqContext::id, "id")))
327 .WillOnce(Return(ByMove(boost::make_exceptional_future<daq::State>(
328 daq::DaqSourceErrors({daq::DaqSourceError("start", "source", "message")})))));
329 EXPECT_CALL(m_mgr_mock, GetStatus("id"sv)).WillOnce(Return(daq::Status()));
330
331 // Run
332 auto fut = m_daq_impl->StartDaq("id", "prefix", "", m_metadata_sources, "");
333 m_io_ctx.poll();
334 ASSERT_TRUE(fut.is_ready())
335 << "future should be ready now since we faked a synchronous start with make_ready_future";
336 EXPECT_THROW(fut.get(), daqif::DaqException)
337 << "future should have contained the ICD exception type";
338}
339
340TEST_F(TestOcmDaqService, StartDaqV2ShouldCreateAndAssignIdIfNotProvided) {
341 // Setup
342 auto* fake_reply(new daqif::DaqReplyFake);
343
344 EXPECT_CALL(m_mgr_mock, MakeDaqId(_)).WillOnce(Return("id"));
345 // Impl should check if id is already used.
346 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).WillOnce(Return(false));
347 EXPECT_CALL(m_mgr_mock, StartDaqAsync(Field(&daq::DaqContext::id, std::string("id"))))
348 .WillOnce(Return(ByMove(boost::make_ready_future<daq::State>(daq::State::Acquiring))));
349 // Have to construct and return void* and still keep a reference to it..
350 EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqReply"))
351 .WillOnce(Return(fake_reply))
352 .RetiresOnSaturation();
353
354 // Run
355
356 auto fut = m_daq_impl->StartDaqV2(R"(
357 {
358 "sources": [
359 {
360 "type": "metadataSource",
361 "sourceName": "name",
362 "rrUri": "uri"
363 }
364 ]
365 }
366 )");
367 // Requred to trigger any .then continuations, even though future is ready
368 m_io_ctx.poll();
369
370 ASSERT_TRUE(fut.is_ready()) << "future should be ready immediately since we faked a "
371 "synchronous start with make_ready_future";
372 try {
373 EXPECT_TRUE(fut.has_value());
374 auto reply = fut.get();
375 ASSERT_TRUE(reply);
376 EXPECT_EQ(reply->getId(), "id");
377 } catch (daqif::DaqException const& e) {
378 FAIL() << e.getMessage();
379 } catch (std::exception const& e) {
380 FAIL() << e.what();
381 }
382}
383
384TEST_F(TestOcmDaqService, StartDaqV2FailsIfJsonParsingFails) {
385 // Setup
386
387 // Run
388 auto fut = m_daq_impl->StartDaqV2(",]{");
389 m_io_ctx.poll();
390 ASSERT_TRUE(fut.is_ready()) << "future should be ready now as parsing failed";
391 EXPECT_THROW(fut.get(), daqif::DaqException)
392 << "future should have contained the ICD exception type";
393}
394
395TEST_F(TestOcmDaqService, StartDaqV2FailsIfSchemaParsingFails) {
396 // Setup
397
398 // Run
399 auto fut = m_daq_impl->StartDaqV2("{}");
400 m_io_ctx.poll();
401 ASSERT_TRUE(fut.is_ready()) << "future should be ready now as schema validation failed";
402 EXPECT_THROW(fut.get(), daqif::DaqException)
403 << "future should have contained the ICD exception type";
404}
405
406TEST_F(TestOcmDaqService, StartDaqV2ShouldAddAndStartDaqControllerIfArgumentsAreOk) {
407 // Setup
408 auto* fake_reply(new daqif::DaqReplyFake);
409
410 // MakeDaqId is used for file-id anyway
411 EXPECT_CALL(m_mgr_mock, MakeDaqId(_)).WillOnce(Return("file-id"));
412 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).WillOnce(Return(false));
413 EXPECT_CALL(m_mgr_mock,
414 StartDaqAsync(AllOf(Field(&daq::DaqContext::id, "id"),
415 Field(&daq::DaqContext::file_id, "file-id"))))
416 .WillOnce(Return(ByMove(boost::make_ready_future<daq::State>(daq::State::Acquiring))));
417 // Have to construct and return void* and still keep a reference to it..
418 EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqReply"))
419 .WillOnce(Return(fake_reply))
420 .RetiresOnSaturation();
421
422 // Run
423 auto fut = m_daq_impl->StartDaqV2(m_daqv2_spec);
424
425 // Requred to trigger any .then continuations, even though future is ready
426 m_io_ctx.poll();
427
428 ASSERT_TRUE(fut.is_ready()) << "future should be ready immediately since we faked a "
429 "synchronous start with make_ready_future";
430 EXPECT_TRUE(fut.has_value());
431 auto reply = fut.get();
432 ASSERT_TRUE(reply);
433 EXPECT_EQ(reply->getId(), "id");
434}
435
436TEST_F(TestOcmDaqService, StopDaqShouldFailIfDaqDoesNotExist) {
437 // Setup
438 // Implementation might, or might not check if ID exist before issuing request to stop
439 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(false));
440 // Implementation might, or might not stop directly, leaving the id check to mgr
441 EXPECT_CALL(m_mgr_mock, StopDaqAsync("id"sv, daq::ErrorPolicy::Strict))
442 .Times(Between(0, 1))
443 .WillOnce(Return(ByMove(
444 boost::make_exceptional_future<daq::Status>(std::invalid_argument("no such id")))));
445
446 EXPECT_CALL(m_mgr_mock, GetStatus("id"sv)).WillOnce(Throw(std::invalid_argument("no such id")));
447
448 // Run
449 auto reply_fut = m_daq_impl->StopDaq("id");
450 m_io_ctx.poll();
451 ASSERT_TRUE(reply_fut.is_ready());
452 EXPECT_THROW(reply_fut.get(), daqif::DaqException);
453}
454
455TEST_F(TestOcmDaqService, StopDaqShouldSucceedIfMgrOpSuceeds) {
456 // Setup
457 auto* fake_reply(new daqif::DaqReplyFake); // Note: Raw pointer since MAL use unsafe APIs
458 daq::Status status("id", "fileid", daq::State::Stopped, daq::Status::TimePoint());
459
460 // Implementation might, or might not check if ID exist before issuing request to stop
461 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
462 EXPECT_CALL(m_mgr_mock, StopDaqAsync("id"sv, daq::ErrorPolicy::Strict))
463 .WillOnce(Return(ByMove(boost::make_ready_future<daq::Status>(status))));
464 EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqReply"))
465 .WillOnce(Return(fake_reply))
466 .RetiresOnSaturation();
467
468 // Run
469 auto reply_fut = m_daq_impl->StopDaq("id");
470 m_io_ctx.poll();
471 ASSERT_TRUE(reply_fut.is_ready());
472 std::shared_ptr<daqif::DaqReply> reply = reply_fut.get();
473 EXPECT_EQ(reply->getId(), "id");
474}
475
476TEST_F(TestOcmDaqService, ForceStopDaqShouldSucceedIfMgrOpSuceeds) {
477 // Setup
478 auto* fake_reply(new daqif::DaqReplyFake); // Note: Raw pointer since MAL use unsafe APIs
479 daq::Status status("id", "fileid", daq::State::Stopped, daq::Status::TimePoint());
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, StopDaqAsync("id"sv, daq::ErrorPolicy::Tolerant))
484 .WillOnce(Return(ByMove(boost::make_ready_future<daq::Status>(status))));
485 EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqReply"))
486 .WillOnce(Return(fake_reply))
487 .RetiresOnSaturation();
488
489 // Run
490 auto reply_fut = m_daq_impl->ForceStopDaq("id");
491 m_io_ctx.poll();
492 ASSERT_TRUE(reply_fut.is_ready());
493 std::shared_ptr<daqif::DaqReply> reply = reply_fut.get();
494 EXPECT_EQ(reply->getId(), "id");
495}
496
497TEST_F(TestOcmDaqService, AbortDaqShouldFailIfDaqDoesNotExist) {
498 // Setup
499 // Implementation might, or might not check if ID exist before issuing request to abort
500 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(false));
501 EXPECT_CALL(m_mgr_mock, AbortDaqAsync("id"sv, daq::ErrorPolicy::Strict))
502 .Times(Between(0, 1))
503 .WillOnce(Return(ByMove(
504 boost::make_exceptional_future<daq::Status>(std::invalid_argument("no such id")))));
505
506 EXPECT_CALL(m_mgr_mock, GetStatus("id"sv)).WillOnce(Throw(std::invalid_argument("no such id")));
507
508 // Run
509 auto reply_fut = m_daq_impl->AbortDaq("id");
510 m_io_ctx.poll();
511 ASSERT_TRUE(reply_fut.is_ready());
512 EXPECT_THROW(reply_fut.get(), daqif::DaqException);
513}
514
515TEST_F(TestOcmDaqService, AbortDaqShouldSucceedIfMgrOpSuceeds) {
516 // Setup
517 auto* fake_reply(new daqif::DaqReplyFake); // Note: Raw pointer since MAL use unsafe APIs
518
519 auto reply_status = daq::Status("id", "fileid");
520 reply_status.state = daq::State::Aborted;
521
522 // Implementation might, or might not check if ID exist before issuing request to stop
523 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
524 EXPECT_CALL(m_mgr_mock, AbortDaqAsync("id"sv, daq::ErrorPolicy::Strict))
525 .WillOnce(Return(ByMove(boost::make_ready_future<daq::Status>(reply_status))));
526 EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqReply"))
527 .WillOnce(Return(fake_reply))
528 .RetiresOnSaturation();
529
530 // Run
531 auto reply_fut = m_daq_impl->AbortDaq("id");
532 m_io_ctx.poll();
533 ASSERT_TRUE(reply_fut.is_ready());
534 std::shared_ptr<daqif::DaqReply> reply = reply_fut.get();
535 EXPECT_EQ(reply->getId(), "id");
536}
537
538TEST_F(TestOcmDaqService, ForceAbortDaqShouldUseTolerantPolicy) {
539 // Setup
540 auto* fake_reply(new daqif::DaqReplyFake); // Note: Raw pointer since MAL use unsafe APIs
541 auto reply_status = daq::Status("id", "fileid");
542 reply_status.state = daq::State::Aborted;
543
544 // Implementation might, or might not check if ID exist before issuing request to stop
545 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
546 EXPECT_CALL(m_mgr_mock, AbortDaqAsync("id"sv, daq::ErrorPolicy::Tolerant))
547 .WillOnce(Return(ByMove(boost::make_ready_future<daq::Status>(reply_status))));
548 EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqReply"))
549 .WillOnce(Return(fake_reply))
550 .RetiresOnSaturation();
551
552 // Run
553 auto reply_fut = m_daq_impl->ForceAbortDaq("id");
554 m_io_ctx.poll();
555 ASSERT_TRUE(reply_fut.is_ready());
556 std::shared_ptr<daqif::DaqReply> reply = reply_fut.get();
557 EXPECT_EQ(reply->getId(), "id");
558}
559
560TEST_F(TestOcmDaqService, UpdateKeywordsShouldSucceedIfMgrOpSucceeds) {
561 // Setup
562 std::string keywords = R"(
563 [
564 {
565 "type":"valueKeyword",
566 "name":"OBJECT",
567 "value":"OBJECT,SKY"
568 },
569 {
570 "type":"esoKeyword",
571 "name":"OBS TPLNO",
572 "value":2
573 }
574 ]
575 )";
576 // This is what `keywords` should contain when parsed.
577 daq::fits::KeywordVector parsed_keywords = {
578 daq::fits::ValueKeyword("OBJECT", "OBJECT,SKY"),
579 daq::fits::EsoKeyword("OBS TPLNO", static_cast<std::uint64_t>(2))};
580 auto* fake_reply(new daqif::DaqReplyFake); // Note: Raw pointer since MAL use unsafe APIs
581 // Implementation might, or might not check if ID exist before issuing request to stop
582 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
583 EXPECT_CALL(m_mgr_mock, UpdateKeywords("id"sv, parsed_keywords));
584 EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqReply"))
585 .WillOnce(Return(fake_reply))
586 .RetiresOnSaturation();
587
588 // Run
589 auto reply_fut = m_daq_impl->UpdateKeywords("id", keywords);
590 m_io_ctx.poll();
591 ASSERT_TRUE(reply_fut.is_ready());
592 std::shared_ptr<daqif::DaqReply> reply = reply_fut.get();
593 EXPECT_EQ(reply->getId(), "id");
594 EXPECT_EQ(reply->getError(), false);
595}
596
597TEST_F(TestOcmDaqService, UpdateKeywordsFailsIfJsonParseFails) {
598 // Setup
599 std::string keywords_with_trailing_comma = R"(
600 [
601 {
602 "type":"valueKeyword",
603 "name":"OBJECT",
604 "value":"OBJECT,SKY"
605 },
606 {
607 "type":"esoKeyword",
608 "name":"OBS TPLNO",
609 "value":2
610 },
611 ]
612 )";
613 // Implementation might, or might not check if ID exist before issuing request to stop
614 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
615 EXPECT_CALL(m_mgr_mock, UpdateKeywords("id"sv, _)).Times(0);
616
617 // Run
618 auto reply_fut = m_daq_impl->UpdateKeywords("id", keywords_with_trailing_comma);
619 m_io_ctx.poll();
620 ASSERT_TRUE(reply_fut.is_ready());
621 EXPECT_THROW(reply_fut.get(), daqif::DaqException);
622}
623
624TEST_F(TestOcmDaqService, UpdateKeywordsFailsIfSchemaParsingFails) {
625 // Setup
626 std::string keywords_with_unknown_type = R"(
627 [
628 {
629 "type":"unknownKeywordHere",
630 "name":"OBJECT",
631 "value":"OBJECT,SKY"
632 }
633 ]
634 )";
635 // Implementation might, or might not check if ID exist before issuing request to stop
636 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
637 EXPECT_CALL(m_mgr_mock, UpdateKeywords("id"sv, _)).Times(0);
638
639 // Run
640 auto reply_fut = m_daq_impl->UpdateKeywords("id", keywords_with_unknown_type);
641 m_io_ctx.poll();
642 ASSERT_TRUE(reply_fut.is_ready());
643 EXPECT_THROW(reply_fut.get(), daqif::DaqException);
644}
645
646TEST_F(TestOcmDaqService, GetStatusFailsIfDaqDoesNotExist) {
647 // Setup
648 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(false));
649 EXPECT_CALL(m_mgr_mock, GetStatus("id"sv))
650 .Times(Between(0, 1))
651 .WillOnce(Throw(std::invalid_argument("no such id")));
652
653 // Run
654 auto reply_fut = m_daq_impl->GetStatus("id");
655 EXPECT_FALSE(reply_fut.is_ready())
656 << "future cannot be ready since implementation should use provided executor to provide "
657 "thread safety";
658 m_io_ctx.poll();
659 ASSERT_TRUE(reply_fut.is_ready());
660 EXPECT_THROW(reply_fut.get(), daqif::DaqException);
661}
662
663TEST_F(TestOcmDaqService, GetStatusSuccedsIfMgrOpSucceeds) {
664 // Setup
666 auto* fake_reply(new daqif::DaqStatusFake); // Note: Raw pointer since MAL use unsafe APIs
667
668 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
669 EXPECT_CALL(m_mgr_mock, GetStatus("id"sv)).Times(Between(0, 1)).WillOnce(Return(status));
670 EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqStatus"))
671 .WillOnce(Return(fake_reply))
672 .RetiresOnSaturation();
673
674 // Run
675 auto reply_fut = m_daq_impl->GetStatus("id");
676 EXPECT_FALSE(reply_fut.is_ready())
677 << "future cannot be ready since implementation should use provided executor to provide "
678 "thread safety";
679 m_io_ctx.poll();
680 ASSERT_TRUE(reply_fut.is_ready());
681 std::shared_ptr<daqif::DaqStatus> reply = reply_fut.get();
682 EXPECT_EQ(reply->getId(), "id");
683 EXPECT_EQ(reply->getFileId(), "fileid");
684 EXPECT_EQ(reply->getError(), false);
685 EXPECT_EQ(reply->getState(), daqif::StateAcquiring);
686 EXPECT_EQ(reply->getSubState(), daqif::Acquiring);
687}
688
689TEST_F(TestOcmDaqService, GetActiveReturnsActive) {
690 // Setup
691 auto status1 = std::make_shared<daq::ObservableStatus>("completed", "fileid1");
692 auto status2 = std::make_shared<daq::ObservableStatus>("active1", "fileid2");
693 auto status3 = std::make_shared<daq::ObservableStatus>("active2", "fileid3");
694 auto status4 = std::make_shared<daq::ObservableStatus>("completed", "fileid4");
695 // Make DAQ completed
696 status1->SetState(daq::State::Aborted);
697 status4->SetState(daq::State::Aborted);
698
699 auto daq1 = std::make_shared<DaqControllerFake>(status1);
700 auto daq2 = std::make_shared<DaqControllerFake>(status2);
701 auto daq3 = std::make_shared<DaqControllerFake>(status3);
702 auto daq4 = std::make_shared<DaqControllerFake>(status4);
703 std::vector<std::shared_ptr<daq::DaqController const>> daqs;
704 daqs.push_back(daq1);
705 daqs.push_back(daq2);
706 daqs.push_back(daq3);
707 daqs.push_back(daq4);
708
709 auto* st1 = new daqif::DaqStatusFake;
710 auto* st2 = new daqif::DaqStatusFake;
711
712 EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_DaqStatus"))
713 .WillOnce(Return(st1))
714 .WillOnce(Return(st2))
715 .RetiresOnSaturation();
716
717 EXPECT_CALL(m_mgr_mock, GetDaqControllers()).WillOnce(Return(daqs));
718
719 // Run
720 auto reply_fut = m_daq_impl->GetActiveList();
721 m_io_ctx.poll();
722 ASSERT_TRUE(reply_fut.is_ready());
723 auto reply = reply_fut.get();
724 EXPECT_EQ(reply.size(), 2u);
725 EXPECT_EQ(reply[0]->getId(), "active1");
726 EXPECT_EQ(reply[1]->getId(), "active2");
727}
728
729TEST_F(TestOcmDaqService, AwaitDaqstateFailsWithInvalidArguments) {
730 // Setup
731 EXPECT_CALL(m_mgr_mock, GetStatus("id"sv)).WillRepeatedly(Return(daq::Status()));
732
733 // Run
734 {
735 // Invalid timeout
736 auto fut = m_daq_impl->AwaitDaqState("id", daqif::StateAcquiring, daqif::Acquiring, 0.0);
737 m_io_ctx.poll();
738 ASSERT_TRUE(fut.is_ready());
739 EXPECT_THROW(fut.get(), daqif::DaqException);
740 }
741 m_io_ctx.reset();
742 {
743 // Invalid state combination
744 auto fut = m_daq_impl->AwaitDaqState("id", daqif::StateMerging, daqif::Acquiring, 1.0);
745 m_io_ctx.poll();
746 ASSERT_TRUE(fut.is_ready());
747 EXPECT_THROW(fut.get(), daqif::DaqException);
748 }
749}
750
751TEST_F(TestOcmDaqService, AwaitDaqstateSucceds) {
752 // Setup
753 daq::Status status("id", "fileid");
755
756 boost::promise<daq::Result<daq::Status>> promise;
757 EXPECT_CALL(m_mgr_mock, AwaitDaqStateAsync("id"sv, daq::State::Acquiring, 1000ms))
758 .WillOnce(Return(ByMove(promise.get_future())));
759
760 auto* reply = new daqif::AwaitDaqReplyFake;
761
762 EXPECT_CALL(m_mal_mock, getDataEntityUnsafe(_, "daqif_AwaitDaqReply"))
763 .WillOnce(Return(reply))
764 .RetiresOnSaturation();
765
766 // Run
767 auto fut = m_daq_impl->AwaitDaqState("id", daqif::StateAcquiring, daqif::Acquiring, 1.0);
768 ASSERT_FALSE(fut.is_ready());
769 promise.set_value({false, status});
770 m_io_ctx.poll();
771 {
772 auto val = fut.get();
773 EXPECT_FALSE(val->getTimeout());
774 auto status = val->getStatus();
775 EXPECT_EQ(status->getId(), "id");
776 EXPECT_EQ(status->getError(), false);
777 EXPECT_EQ(status->getState(), daqif::StateAcquiring);
778 EXPECT_EQ(status->getSubState(), daqif::Acquiring);
779 }
780}
781
783 // Setup
784
785 // This is the promise used to create result from daq::Manager
786 boost::promise<daq::State> mgr_promise;
787 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).WillOnce(Return(false));
788 EXPECT_CALL(m_mgr_mock, MakeDaqId(_)).WillOnce(Return("fileid"));
789 EXPECT_CALL(m_mgr_mock, StartDaqAsync(_)).WillOnce(Return(ByMove(mgr_promise.get_future())));
790
791 // Run
792 auto reply_fut = m_daq_impl->StartDaq("id", "prefix", m_prim_sources, m_metadata_sources, "");
793 // Abandon the service
794 m_daq_impl.reset();
795
796 // Fulfull promise from daq::Manager:
797 mgr_promise.set_value(daq::State::Acquiring);
798
799 // Trigger handlers
800 m_io_ctx.poll();
801 ASSERT_TRUE(reply_fut.is_ready());
802 EXPECT_THROW(reply_fut.get(), daqif::DaqException);
803}
804
806 // Setup
807
808 // This is the promise used to create result from daq::Manager
809 boost::promise<daq::Status> mgr_promise;
810
811 // Implementation might, or might not check if ID exist before issuing request to stop
812 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
813 EXPECT_CALL(m_mgr_mock, StopDaqAsync("id"sv, daq::ErrorPolicy::Strict))
814 .WillOnce(Return(ByMove(mgr_promise.get_future())));
815
816 // Run
817 auto reply_fut = m_daq_impl->StopDaq("id");
818 // Abandon the service
819 m_daq_impl.reset();
820
821 // Fulfull promise from daq::Manager:
822 auto status = daq::Status("id", "fileid", daq::State::Stopped, daq::Status::TimePoint());
823 status.alerts.push_back(daq::MakeAlert("test", "key", ""));
824 mgr_promise.set_value(status);
825
826 // Trigger handlers
827 m_io_ctx.poll();
828 ASSERT_TRUE(reply_fut.is_ready());
829 EXPECT_THROW(reply_fut.get(), daqif::DaqException);
830}
831
833 // Setup
834
835 // This is the promise used to create result from daq::Manager
836 boost::promise<daq::Status> mgr_promise;
837
838 auto reply_status = daq::Status("id", "fileid");
839 reply_status.state = daq::State::Aborted;
840
841 // Implementation might, or might not check if ID exist before issuing request to stop
842 EXPECT_CALL(m_mgr_mock, HaveDaq("id"sv, _)).Times(Between(0, 1)).WillRepeatedly(Return(true));
843 EXPECT_CALL(m_mgr_mock, AbortDaqAsync("id"sv, daq::ErrorPolicy::Strict))
844 .WillOnce(Return(ByMove(mgr_promise.get_future())));
845
846 // Run
847 auto reply_fut = m_daq_impl->AbortDaq("id");
848 // Abandon the service
849 m_daq_impl.reset();
850
851 // Fulfull promise from daq::Manager:
852 mgr_promise.set_value(reply_status);
853
854 // Trigger handlers
855 m_io_ctx.poll();
856 ASSERT_TRUE(reply_fut.is_ready());
857 EXPECT_THROW(reply_fut.get(), daqif::DaqException);
858}
Represents error in single source.
Definition: error.hpp:68
Exception thrown to carry reply errors.
Definition: error.hpp:85
Contains error related declarations for DAQ.
Implements the MAL interface daqif::OcmDaq (async version).
Mockup of metadaqif classes.
std::vector< KeywordVariant > KeywordVector
Vector of keywords.
Definition: keyword.hpp:423
BasicKeyword< ValueKeywordTraits > ValueKeyword
Standard FITS value keyword.
Definition: keyword.hpp:339
BasicKeyword< EsoKeywordTraits > EsoKeyword
ESO hiearchical keyword.
Definition: keyword.hpp:346
@ Strict
Any error is considered fatal and may lead to the operation being aborted.
@ Tolerant
Errors that can be ignored with partial completion of a command will be tolerated and is reported as ...
@ Aborted
Data acquisition has been aborted by user.
@ Acquiring
All data sources have reported data acquisition is in progress.
@ Stopped
All data sources have reported they have stopped acquiring data.
Alert MakeAlert(std::string_view category, std::string key, std::string description)
Construct alert.
Definition: status.cpp:45
daq::DaqContext ParseStartDaqContext(std::string const &json_properties)
Parse the JSON properties user provides with StartDaq.
ParsedSource ParseSourceUri(std::string_view s)
Parse user provided string in the format "<name>@<rr-uri>".
std::vector< ParsedSource > ParseSourceUris(std::string_view s)
Parse user provided string in the format "<name>@<rr-uri>[ <name>@...]".
Declaration of OcmDaqService.
Mockup of metadaqif classes.
std::string name
std::string rr_uri
Fixture for testing when OcmDaqService is abandoned (e.g.
Fixture that sets up a OcmDaqService instance with following mockups:
std::shared_ptr< daq::ObservableEventLog > m_event_log
std::string m_metadata_sources
boost::asio::io_context m_io_ctx
std::shared_ptr< OcmDaqService > m_daq_impl
std::string m_daq_properties
void SetUp() override
std::string m_prim_sources
void TearDown() override
std::string file_id
Data Product FileId as specified by OLAS ICD.
Definition: daqContext.hpp:63
std::string id
DAQ identfier, possibly provided by user.
Definition: daqContext.hpp:58
Non observable status object that keeps stores status of data acquisition.
Definition: status.hpp:164
State state
Definition: status.hpp:186
std::chrono::time_point< std::chrono::system_clock > TimePoint
Definition: status.hpp:165
TEST_F(TestOcmDaqService, StartDaqShouldTestIfIdAlreadyExistsAndReturnFailureIfItDoes)
TEST(TestParseSingleSource, Successful)
EXPECT_EQ(meta.rr_uri, "zpb.rr://meta")
ASSERT_TRUE(std::holds_alternative< OlasReceiver >(spec.receivers[0]))