ChimeraTK-ApplicationCore 04.07.01
Loading...
Searching...
No Matches
testStatusAggregator.cc
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4#include "Application.h"
5#include "ModuleGroup.h"
6#include "StatusAggregator.h"
7#include "StatusWithMessage.h"
8#include "TestFacility.h"
9
10#define BOOST_NO_EXCEPTIONS
11#define BOOST_TEST_MODULE testStatusAggregator
12#include <boost/test/included/unit_test.hpp>
13#undef BOOST_NO_EXCEPTIONS
14
16
17 using namespace boost::unit_test_framework;
18
19 namespace ctk = ChimeraTK;
20
21 /********************************************************************************************************************/
22
24 using ctk::ApplicationModule::ApplicationModule;
25
26 StatusGenerator(ctk::ModuleGroup* owner, const std::string& name, const std::string& description,
27 const std::unordered_set<std::string>& tags = {},
28 ctk::StatusOutput::Status initialStatus = ctk::StatusOutput::Status::OFF)
29 : ApplicationModule(owner, name, description, tags), initialValue(initialStatus) {}
30
32
33 ctk::StatusOutput::Status initialValue;
34
35 void prepare() override {
37 status.write();
38 }
39 void mainLoop() override {}
40 };
41
42 /********************************************************************************************************************/
43
45 using ctk::ApplicationModule::ApplicationModule;
46
47 StatusWithMessageGenerator(ctk::ModuleGroup* owner, const std::string& name, const std::string& description,
48 const std::unordered_set<std::string>& tags = {},
49 ctk::StatusOutput::Status initialStatus = ctk::StatusOutput::Status::OFF)
50 : ApplicationModule(owner, name, description, tags), initialValue(initialStatus) {}
51
53
54 ctk::StatusOutput::Status initialValue;
55
56 void prepare() override {
57 if(initialValue == ctk::StatusOutput::Status::OK) {
59 }
60 else {
62 }
63 }
64 void mainLoop() override {}
65 };
66
67 /********************************************************************************************************************/
68
70 TestApplication() : Application("testApp") {}
71 ~TestApplication() override { shutdown(); }
72
73 StatusGenerator s{this, "s", "Status"};
74
76 using ctk::ModuleGroup::ModuleGroup;
77
78 StatusGenerator s1{this, "s1", "Status 1"};
79 StatusGenerator s2{this, "s2", "Status 2"};
80
82 using ctk::ModuleGroup::ModuleGroup;
83 StatusGenerator s{this, "s", "Status"};
84 StatusGenerator deep{this, "deep", "Status"};
85 };
86 InnerGroup innerGroup1{this, "InnerGroup1", ""};
87 InnerGroup innerGroup2{this, "InnerGroup2", ""};
88
89 } outerGroup{this, "OuterGroup", ""};
90
92 this, "Aggregated/status", "aggregated status description", ctk::StatusAggregator::PriorityMode::fwko};
93 };
94
95 /********************************************************************************************************************/
96
97 BOOST_AUTO_TEST_CASE(testSingleNoTags) {
98 std::cout << "testSingleNoTags" << std::endl;
99
100 TestApplication app;
101 ctk::TestFacility test(app);
102
103 auto status = test.getScalar<int>("/Aggregated/status");
104
105 test.runApplication();
106
107 // check that statuses on different levels are correctly aggregated
108 auto check = [&](auto& var) {
109 var = ctk::StatusOutput::Status::OK;
110 var.write();
111 test.stepApplication();
112 BOOST_CHECK(status.readNonBlocking() == true);
113 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OK));
114 var = ctk::StatusOutput::Status::OFF;
115 var.write();
116 test.stepApplication();
117 BOOST_CHECK(status.readNonBlocking() == true);
118 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OFF));
119 };
120 check(app.s.status);
121 check(app.outerGroup.s1.status);
122 check(app.outerGroup.s2.status);
123 check(app.outerGroup.innerGroup1.s.status);
124 check(app.outerGroup.innerGroup1.deep.status);
125 check(app.outerGroup.innerGroup2.s.status);
126 check(app.outerGroup.innerGroup2.deep.status);
127 }
128
129 /********************************************************************************************************************/
130
131 BOOST_AUTO_TEST_CASE(testDataValidity) {
132 std::cout << "testDataValidity" << std::endl;
133
134 TestApplication app;
135 ctk::TestFacility test(app);
136
137 auto status = test.getScalar<int>("/Aggregated/status");
138
139 test.runApplication();
140
141 // one of the inputs goes faulty, output is faulty
143 app.s.status = ctk::StatusOutput::Status::OK;
144 app.s.status.write();
145 test.stepApplication();
146 BOOST_CHECK(status.readNonBlocking() == true);
147 BOOST_CHECK(status.dataValidity() == ctk::DataValidity::faulty);
148 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OK));
149
150 // proper aggregation still taking place
151 app.s.status = ctk::StatusOutput::Status::OFF;
152 app.s.status.write();
153 test.stepApplication();
154 BOOST_CHECK(status.readNonBlocking() == true);
155 BOOST_CHECK(status.dataValidity() == ctk::DataValidity::faulty);
156 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OFF));
157
158 // one of the valid inputs has the highest priority value (i.e. Status::FAULT in this case), output will be valid
159 // (because other inputs do not matter in this case, the result will be always FAULT)
160 app.outerGroup.s1.status = ctk::StatusOutput::Status::FAULT;
161 app.outerGroup.s1.status.write();
162 test.stepApplication();
163 BOOST_CHECK(status.readNonBlocking() == true);
164 BOOST_CHECK(status.dataValidity() == ctk::DataValidity::ok);
165 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::FAULT));
166 }
167
168 /********************************************************************************************************************/
169
171 explicit TestPrioApplication(ctk::StatusOutput::Status theInitialValue)
172 : Application("testApp"), initialValue(theInitialValue) {}
174
175 ctk::StatusOutput::Status initialValue;
176
177 StatusGenerator s1{this, "sg1/internal", "Status 1", {}, initialValue};
178 StatusGenerator s2{this, "sg2/external", "Status 2", {}, initialValue};
179
181 };
182
183 /********************************************************************************************************************/
184
185 BOOST_AUTO_TEST_CASE(testPriorities) {
186 std::cout << "testPriorities" << std::endl;
187
188 // Define repeated check for a given priority mode
189 auto check = [&](ctk::StatusAggregator::PriorityMode mode, auto prio0, auto prio1, auto prio2, auto prio3,
190 bool warnMixed01 = false) {
191 // create app with initial values set to lowest prio value
192 TestPrioApplication app(prio0);
193
194 app.aggregator = ctk::StatusAggregator{&app, "Aggregated/status", "aggregated status description", mode};
195
196 ctk::TestFacility test(app);
197
198 auto status = test.getScalar<int>("/Aggregated/status");
199
200 test.runApplication();
201
202 // check initial value
203 status.readNonBlocking(); // do not check return value, as it will only be written when changed
204 BOOST_CHECK_EQUAL(int(status), int(prio0));
205
206 // Define repeated check to test all combinations of two given values with different priority.
207 // This kind of a whitebox test. We know that
208 // - the first aggregates variable has a different code path
209 // - the code path depends on the VersionNumber, so the write order matters and we have to set the version number
210 auto subcheck = [&](auto lower, auto higher, bool writeS2First, bool warnMixed = false) {
211 StatusGenerator* first;
212 StatusGenerator* second;
213 if(writeS2First) {
214 first = &app.s2;
215 second = &app.s1;
216 }
217 else {
218 first = &app.s1;
219 second = &app.s2;
220 }
221 std::cout << int(lower) << " vs. " << int(higher) << std::endl;
222 first->status = lower;
223 first->setCurrentVersionNumber({});
224 first->status.write();
225 second->status = lower;
226 second->setCurrentVersionNumber({});
227 second->status.write();
228 test.stepApplication();
229 status.readLatest();
230 BOOST_CHECK_EQUAL(int(status), int(lower));
231 first->status = lower;
232 first->setCurrentVersionNumber({});
233 first->status.write();
234 second->status = higher;
235 second->setCurrentVersionNumber({});
236 second->status.write();
237 test.stepApplication();
238 status.readLatest();
239 if(!warnMixed) {
240 BOOST_CHECK_EQUAL(int(status), int(higher));
241 }
242 else {
243 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::WARNING));
244 }
245 first->status = higher;
246 first->setCurrentVersionNumber({});
247 first->status.write();
248 second->status = lower;
249 second->setCurrentVersionNumber({});
250 second->status.write();
251 test.stepApplication();
252 status.readLatest();
253 if(!warnMixed) {
254 BOOST_CHECK_EQUAL(int(status), int(higher));
255 }
256 else {
257 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::WARNING));
258 }
259 first->status = higher;
260 first->setCurrentVersionNumber({});
261 first->status.write();
262 second->status = higher;
263 second->setCurrentVersionNumber({});
264 second->status.write();
265 test.stepApplication();
266 status.readLatest();
267 BOOST_CHECK_EQUAL(int(status), int(higher));
268 };
269
270 // all prios against each other
271 auto runAllSubchecks = [&](bool writeS2First) {
272 subcheck(prio0, prio1, writeS2First, warnMixed01);
273 subcheck(prio0, prio2, writeS2First);
274 subcheck(prio0, prio3, writeS2First);
275 subcheck(prio1, prio2, writeS2First);
276 subcheck(prio1, prio3, writeS2First);
277 subcheck(prio2, prio3, writeS2First);
278 };
279 runAllSubchecks(false);
280 runAllSubchecks(true);
281 };
282
283 // check all priority modes
284 std::cout << "PriorityMode::fwko" << std::endl;
285 check(ctk::StatusAggregator::PriorityMode::fwko, ctk::StatusOutput::Status::OFF, ctk::StatusOutput::Status::OK,
286 ctk::StatusOutput::Status::WARNING, ctk::StatusOutput::Status::FAULT);
287 std::cout << "PriorityMode::fwok" << std::endl;
288 check(ctk::StatusAggregator::PriorityMode::fwok, ctk::StatusOutput::Status::OK, ctk::StatusOutput::Status::OFF,
289 ctk::StatusOutput::Status::WARNING, ctk::StatusOutput::Status::FAULT);
290 std::cout << "PriorityMode::ofwk" << std::endl;
291 check(ctk::StatusAggregator::PriorityMode::ofwk, ctk::StatusOutput::Status::OK, ctk::StatusOutput::Status::WARNING,
292 ctk::StatusOutput::Status::FAULT, ctk::StatusOutput::Status::OFF);
293 std::cout << "PriorityMode::fw_warn_mixed" << std::endl;
294 check(ctk::StatusAggregator::PriorityMode::fw_warn_mixed, ctk::StatusOutput::Status::OFF,
295 ctk::StatusOutput::Status::OK, ctk::StatusOutput::Status::WARNING, ctk::StatusOutput::Status::FAULT, true);
296 }
297
298 /********************************************************************************************************************/
299
300 BOOST_AUTO_TEST_CASE(testCustomMixedWarnMessage) {
301 std::cout << "testCustomMixedWarnMessage" << std::endl;
302
303 const std::string customMessage1{"My custom warn mixed message"};
304
305 TestPrioApplication app(ctk::StatusOutput::Status::OK);
306 app.aggregator = ctk::StatusAggregator{&app, "Aggregated/status", "aggregated status description",
307 ctk::StatusAggregator::PriorityMode::fw_warn_mixed, {}, {}, customMessage1};
308
309 ctk::TestFacility test(app);
310 test.runApplication();
311
312 auto statusMessage = test.getScalar<std::string>("/Aggregated/status_message");
313
314 // check test pre-condition: No message when OK.
315 BOOST_TEST(std::string(statusMessage) == "");
316
318 app.s1.status.setAndWrite(int(ctk::StatusOutput::Status::OFF));
319 test.stepApplication();
320 BOOST_TEST(statusMessage.readAndGet() == customMessage1);
321
322 // Change custom message at run time
323 const std::string customMessage2{"Another warn mixed message"};
324 app.aggregator.setWarnMixedMessage(customMessage2);
325
327 app.s1.status.setAndWrite(int(ctk::StatusOutput::Status::OFF));
328 test.stepApplication();
329 BOOST_TEST(statusMessage.readAndGet() == customMessage2);
330 }
331
332 /********************************************************************************************************************/
333
337
338 StatusGenerator s{this, "s", "Status"};
339
341 using ctk::ModuleGroup::ModuleGroup;
342
343 // Set one of the inputs for the extraAggregator to fault, which has no effect, since one other is OFF which is
344 // prioritised. If the top-level aggregator would wrongly aggregate this input directly, it would go to FAULT.
345 StatusGenerator s1{this, "s1", "Status 1", {}, ctk::StatusOutput::Status::FAULT};
346
347 StatusGenerator s2{this, "s2", "Status 2"};
348
350 this, "/Aggregated/extraStatus", "aggregated status description", ctk::StatusAggregator::PriorityMode::ofwk};
351
352 } outerGroup{this, "OuterGroup", ""};
353
355 this, "Aggregated/status", "aggregated status description", ctk::StatusAggregator::PriorityMode::fwko};
356 };
357
359
360 BOOST_AUTO_TEST_CASE(testTwoLevels) {
361 std::cout << "testTwoLevels" << std::endl << std::endl << std::endl;
363
364 ctk::TestFacility test(app);
365
366 auto status = test.getScalar<int>("/Aggregated/status");
367 auto extraStatus = test.getScalar<int>("/Aggregated/extraStatus");
368
369 test.runApplication();
370
371 // check the initial values
372 extraStatus.readLatest();
373 BOOST_CHECK_EQUAL(int(extraStatus), int(ctk::StatusOutput::Status::OFF));
374 status.readLatest();
375 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OFF));
376
377 // change status which goes directly into the upper aggregator
378 app.s.status = ctk::StatusOutput::Status::OK;
379 app.s.status.write();
380 test.stepApplication();
381 status.readLatest();
382 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OK));
383 app.s.status = ctk::StatusOutput::Status::OFF;
384 app.s.status.write();
385 test.stepApplication();
386 status.readLatest();
387 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OFF));
388
389 // change status which goes into the lower aggregator (then the FAULT of s1 will win)
390 app.outerGroup.s2.status = ctk::StatusOutput::Status::OK;
391 app.outerGroup.s2.status.write();
392 test.stepApplication();
393 status.readLatest();
394 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::FAULT));
395 app.outerGroup.s2.status = ctk::StatusOutput::Status::OFF;
396 app.outerGroup.s2.status.write();
397 test.stepApplication();
398 status.readLatest();
399 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OFF));
400 }
401
402 /********************************************************************************************************************/
403
407
409 using ctk::ModuleGroup::ModuleGroup;
410
411 StatusGenerator sA{this, "sA", "Status 1", ctk::TAGS{"A"}, ctk::StatusOutput::Status::WARNING};
412 StatusGenerator sAB{this, "sAB", "Status 2", {"A", "B"}, ctk::StatusOutput::Status::OFF};
413
414 // First level of aggregation: Input and output tags are identical
416 this, "aggregateA", "aggregated status description", ctk::StatusAggregator::PriorityMode::fwko, {"A"}, {"A"}};
418 this, "aggregateB", "aggregated status description", ctk::StatusAggregator::PriorityMode::fwko, {"B"}, {"B"}};
419
420 } group{this, "Group", ""};
421
422 // Use other priority mode here to make sure only the aggregators are aggregated, not the generators
424 this, "aggregateA", "aggregated status description", ctk::StatusAggregator::PriorityMode::ofwk, {"A"}};
426 this, "aggregateB", "aggregated status description", ctk::StatusAggregator::PriorityMode::ofwk, {"B"}};
428 this, "aggregateAll", "aggregated status description", ctk::StatusAggregator::PriorityMode::fw_warn_mixed};
429 };
430
431 /********************************************************************************************************************/
432
434 std::cout << "testTags" << std::endl;
435
437 ctk::TestFacility test(app);
438
439 auto aggregateA = test.getScalar<int>("/aggregateA");
440 auto aggregateB = test.getScalar<int>("/aggregateB");
441 auto aggregateAll = test.getScalar<int>("/aggregateAll");
442 auto Group_aggregateA = test.getScalar<int>("/Group/aggregateA");
443 auto Group_aggregateB = test.getScalar<int>("/Group/aggregateB");
444
445 test.runApplication();
446
447 // check initial values
448 aggregateA.readLatest();
449 aggregateB.readLatest();
450 aggregateAll.readLatest();
451 Group_aggregateA.readLatest();
452 Group_aggregateB.readLatest();
453 BOOST_CHECK_EQUAL(int(aggregateA), int(ctk::StatusOutput::Status::WARNING));
454 BOOST_CHECK_EQUAL(int(aggregateB), int(ctk::StatusOutput::Status::OFF));
455 BOOST_CHECK_EQUAL(int(aggregateAll), int(ctk::StatusOutput::Status::WARNING));
456 BOOST_CHECK_EQUAL(int(Group_aggregateA), int(ctk::StatusOutput::Status::WARNING));
457 BOOST_CHECK_EQUAL(int(Group_aggregateB), int(ctk::StatusOutput::Status::OFF));
458
459 app.group.sAB.status = ctk::StatusOutput::Status::FAULT;
460 app.group.sAB.status.write();
461
462 test.stepApplication();
463
464 // change value tagged with 'A' and 'B', affecting all aggregators to highest priority value, so it is visible
465 // everywhere
466 aggregateA.readLatest();
467 aggregateB.readLatest();
468 aggregateAll.readLatest();
469 Group_aggregateA.readLatest();
470 Group_aggregateB.readLatest();
471 BOOST_CHECK_EQUAL(int(aggregateA), int(ctk::StatusOutput::Status::FAULT));
472 BOOST_CHECK_EQUAL(int(aggregateB), int(ctk::StatusOutput::Status::FAULT));
473 BOOST_CHECK_EQUAL(int(aggregateAll), int(ctk::StatusOutput::Status::FAULT));
474 BOOST_CHECK_EQUAL(int(Group_aggregateA), int(ctk::StatusOutput::Status::FAULT));
475 BOOST_CHECK_EQUAL(int(Group_aggregateB), int(ctk::StatusOutput::Status::FAULT));
476 }
477
478 /********************************************************************************************************************/
479
483
485 using ctk::ModuleGroup::ModuleGroup;
486
487 StatusGenerator sA{this, "sA", "Status A", ctk::TAGS{"A"}};
488 StatusGenerator sB1{this, "sB1", "Status B1", ctk::TAGS{"B"}};
489 StatusGenerator sB2{this, "sB2", "Status B2", ctk::TAGS{"B"}};
490 StatusGenerator sC{this, "sC", "Status C", ctk::TAGS{"C"}};
491
493 this, "aggregatedA", "", ctk::StatusAggregator::PriorityMode::fwko, {"A"}, {"AGG_A"}};
494 // Same input and output tag. When aggregating tag "B" the status outputs themselves should not be taken again.
496 this, "aggregatedB", "", ctk::StatusAggregator::PriorityMode::fwko, {"B"}, {"B"}};
497
498 // Missing: Test of multiple aggregation tags. Currently only one tag is allowed due to the missing
499 // design decision whether multiple tags should be a logical AND or OR (#13256).
500 } group{this, "Group", ""};
501
502 // Aggregates everything that does not have the B tag
503 ctk::StatusAggregator aggNotB{this, "aggNotB", "", ctk::StatusAggregator::PriorityMode::fwko, {"!B"}};
504
505 // Does not aggregate an aggregator
506 ctk::StatusAggregator aggregateA{this, "aggA", "", ctk::StatusAggregator::PriorityMode::fwko, {"A"}};
507 // Aggregates the A aggregator
508 ctk::StatusAggregator aggAggA{this, "aggAggA", "", ctk::StatusAggregator::PriorityMode::fwko, {"AGG_A"}};
509 // Aggregates the B aggregator
510 ctk::StatusAggregator aggAggB{this, "aggAggB", "", ctk::StatusAggregator::PriorityMode::fwko, {"B"}};
511 };
512
513 /********************************************************************************************************************/
514
515 BOOST_AUTO_TEST_CASE(testAggregatorTags) {
516 std::cout << "testAggregatorTags" << std::endl;
517
519 ctk::TestFacility test(app);
520
521 auto checkForName = [](auto& accessors, std::string name) {
522 return std::any_of(accessors.begin(), accessors.end(), [&name](auto acc) { return acc.getName() == name; });
523 };
524
525 auto accessorsAggA = app.aggregateA.getAccessorListRecursive();
526 // One aggregated input ("sA") + plus 3 inputs/outputs from the aggregator itself.
527 // "aggregatedA" is not used because the tag "A" is its input. The output tag is "AGG_A"
528 BOOST_TEST(accessorsAggA.size() == 4);
529 BOOST_CHECK(checkForName(accessorsAggA, "sA"));
530 BOOST_CHECK(!checkForName(accessorsAggA, "aggregatedA"));
531
532 auto accessorsAggAggA = app.aggAggA.getAccessorListRecursive();
533 // One aggregated input ("aggregatedA") + according status message + plus 3 inputs/outputs from the aggregator
534 // itself. "sA" is not used because the tag "A" is its input. The output tag is "AGG_A"
535 BOOST_TEST(accessorsAggAggA.size() == 5);
536 BOOST_CHECK(checkForName(accessorsAggAggA, "aggregatedA"));
537 BOOST_CHECK(!checkForName(accessorsAggAggA, "sA"));
538
539 auto accessorsAggAggB = app.aggAggB.getAccessorListRecursive();
540 // One aggregated input ("aggregatedB") + according status message + plus 3 inputs/outputs from the aggregator
541 // itself. "sB1" and "sB2" are not used because their input is already aggregated.
542 BOOST_TEST(accessorsAggAggB.size() == 5);
543 BOOST_CHECK(checkForName(accessorsAggAggB, "aggregatedB"));
544 BOOST_CHECK(!checkForName(accessorsAggAggB, "sB1"));
545 BOOST_CHECK(!checkForName(accessorsAggAggB, "sB2"));
546
547 auto accessorsAggNotB = app.aggNotB.getAccessorListRecursive();
548 // 3 inputs/outputs from the aggregator itself, "aggregatedA" with its message, and "sC" => 6 accessors
549 // "sA" is not included, as it is already aggregated by "aggregatedA".
550 BOOST_TEST(accessorsAggNotB.size() == 6);
551 BOOST_CHECK(checkForName(accessorsAggNotB, "aggregatedA"));
552 BOOST_CHECK(checkForName(accessorsAggNotB, "sC"));
553 BOOST_CHECK(!checkForName(accessorsAggNotB, "sB1"));
554 BOOST_CHECK(!checkForName(accessorsAggNotB, "sB2"));
555 BOOST_CHECK(!checkForName(accessorsAggNotB, "aggregatedB"));
556 }
557
558 /********************************************************************************************************************/
559
563
564 StatusGenerator s{this, "s", "Status", {}, ctk::StatusOutput::Status::OK};
565
567 using ctk::ModuleGroup::ModuleGroup;
568
569 StatusGenerator s1{this, "s1", "Status 1", {}, ctk::StatusOutput::Status::OK};
570
571 StatusWithMessageGenerator s2{this, "s2", "Status 2", {}, ctk::StatusOutput::Status::OK};
572
574 this, "/Aggregated/extraStatus", "aggregated status description", ctk::StatusAggregator::PriorityMode::ofwk};
575
576 } outerGroup{this, "OuterGroup", ""};
577
579 this, "Aggregated/status", "aggregated status description", ctk::StatusAggregator::PriorityMode::fwko};
580 };
581
582 /********************************************************************************************************************/
583
584 // test behavior for status+string:
585 // test that status aggregator always has a message output and hands it over to next status aggregator
586 BOOST_AUTO_TEST_CASE(testStatusMessage) {
587 std::cout << "testStatusMessage" << std::endl;
589
590 ctk::TestFacility test(app);
591
592 auto status = test.getScalar<int>("/Aggregated/status");
593 auto statusMessage = test.getScalar<std::string>("/Aggregated/status_message");
594 auto innerStatus = test.getScalar<int>("/Aggregated/extraStatus");
595 auto innerStatusMessage = test.getScalar<std::string>("/Aggregated/extraStatus_message");
596
597 test.runApplication();
598
599 // check the initial values
600 innerStatus.readLatest();
601 BOOST_CHECK_EQUAL(int(innerStatus), int(ctk::StatusOutput::Status::OK));
602 innerStatusMessage.readLatest();
603 BOOST_CHECK_EQUAL(std::string(innerStatusMessage), "");
604 status.readLatest();
605 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OK));
606 statusMessage.readLatest();
607 BOOST_CHECK_EQUAL(std::string(statusMessage), "");
608
609 // check normal status (without message) going to fault
610 app.outerGroup.s1.status = ctk::StatusOutput::Status::FAULT;
611 app.outerGroup.s1.status.write();
612 test.stepApplication();
613 status.readLatest();
614 statusMessage.readLatest();
615 innerStatus.readLatest();
616 innerStatusMessage.readLatest();
617 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::FAULT));
618 std::string faultString1 = "/OuterGroup/s1/s1 switched to FAULT";
619 BOOST_CHECK_EQUAL(std::string(statusMessage), faultString1);
620 BOOST_CHECK_EQUAL(int(innerStatus), int(ctk::StatusOutput::Status::FAULT));
621 BOOST_CHECK_EQUAL(std::string(innerStatusMessage), faultString1);
622
623 // go back to OK
624 app.outerGroup.s1.status = ctk::StatusOutput::Status::OK;
625 app.outerGroup.s1.status.write();
626 test.stepApplication();
627 status.readLatest();
628 statusMessage.readLatest();
629 innerStatus.readLatest();
630 innerStatusMessage.readLatest();
631 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OK));
632 BOOST_CHECK_EQUAL(std::string(statusMessage), "");
633 BOOST_CHECK_EQUAL(int(innerStatus), int(ctk::StatusOutput::Status::OK));
634 BOOST_CHECK_EQUAL(std::string(innerStatusMessage), "");
635
636 // check StatusWithMessage going to fault
637 std::string faultString2 = "Status 2 at fault";
638 app.outerGroup.s2.setCurrentVersionNumber({});
639 app.outerGroup.s2.status.write(ctk::StatusOutput::Status::FAULT, faultString2);
640 test.stepApplication();
641 status.readLatest();
642 statusMessage.readLatest();
643 innerStatus.readLatest();
644 innerStatusMessage.readLatest();
645 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::FAULT));
646 BOOST_CHECK_EQUAL(std::string(statusMessage), faultString2);
647 BOOST_CHECK_EQUAL(int(innerStatus), int(ctk::StatusOutput::Status::FAULT));
648 BOOST_CHECK_EQUAL(std::string(innerStatusMessage), faultString2);
649
650 // set normal status to fault, too, to see the right message "wins" (first message should stay)
651 app.outerGroup.s1.setCurrentVersionNumber({});
652 app.outerGroup.s1.status = ctk::StatusOutput::Status::FAULT;
653 app.outerGroup.s1.status.write();
654 test.stepApplication();
655 status.readLatest();
656 statusMessage.readLatest();
657 innerStatus.readLatest();
658 innerStatusMessage.readLatest();
659 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::FAULT));
660 BOOST_CHECK_EQUAL(std::string(statusMessage), faultString2);
661 BOOST_CHECK_EQUAL(int(innerStatus), int(ctk::StatusOutput::Status::FAULT));
662 BOOST_CHECK_EQUAL(std::string(innerStatusMessage), faultString2);
663
664 // go back to OK
665 app.outerGroup.s1.setCurrentVersionNumber({});
666 app.outerGroup.s1.status = ctk::StatusOutput::Status::OK;
667 app.outerGroup.s1.status.write();
668 app.outerGroup.s2.setCurrentVersionNumber({});
669 app.outerGroup.s2.status.writeOk();
670 test.stepApplication();
671 status.readLatest();
672 statusMessage.readLatest();
673 innerStatus.readLatest();
674 innerStatusMessage.readLatest();
675 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OK));
676 BOOST_CHECK_EQUAL(std::string(statusMessage), "");
677 BOOST_CHECK_EQUAL(int(innerStatus), int(ctk::StatusOutput::Status::OK));
678 BOOST_CHECK_EQUAL(std::string(innerStatusMessage), "");
679
680 // set both status to fault in alternate order (compared to before), again the first message should "win"
681 app.outerGroup.s1.setCurrentVersionNumber({});
682 app.outerGroup.s1.status = ctk::StatusOutput::Status::FAULT;
683 app.outerGroup.s1.status.write();
684 app.outerGroup.s2.setCurrentVersionNumber({});
685 app.outerGroup.s2.status.write(ctk::StatusOutput::Status::FAULT, faultString2);
686 test.stepApplication();
687 status.readLatest();
688 statusMessage.readLatest();
689 innerStatus.readLatest();
690 innerStatusMessage.readLatest();
691 BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::FAULT));
692 BOOST_CHECK_EQUAL(std::string(statusMessage), faultString1);
693 BOOST_CHECK_EQUAL(int(innerStatus), int(ctk::StatusOutput::Status::FAULT));
694 BOOST_CHECK_EQUAL(std::string(innerStatusMessage), faultString1);
695 }
696
697 /********************************************************************************************************************/
698
699} // namespace Tests::testStatusAggregator
void shutdown() override
This will remove the global pointer to the instance and allows creating another instance afterwards.
void setCurrentVersionNumber(VersionNumber) override
Set the current version number.
ApplicationModule()=default
Default constructor: Allows late initialisation of modules (e.g.
void setCurrentVersionNumber(VersionNumber versionNumber) override
Set the current version number.
void incrementDataFaultCounter() override
Set the data validity flag to fault and increment the fault counter.
const std::string & getName() const
Get the name of the module instance.
Definition EntityOwner.h:59
const std::string & getDescription() const
Get the description of the module instance.
Definition EntityOwner.h:69
std::list< VariableNetworkNode > getAccessorListRecursive() const
Obtain the list of accessors/variables associated with this instance and any submodules.
friend class Application
Definition ModuleGroup.h:47
bool write(ChimeraTK::VersionNumber versionNumber)=delete
void setAndWrite(UserType newValue, VersionNumber versionNumber)=delete
Special ScalarOutput which represents a status which can be aggregated by the StatusAggregator.
Helper class to facilitate tests of applications based on ApplicationCore.
void stepApplication(bool waitForDeviceInitialisation=true) const
Perform a "step" of the application.
ChimeraTK::ScalarRegisterAccessor< T > getScalar(const ChimeraTK::RegisterPath &name) const
Obtain a scalar process variable from the application, which is published to the control system.
void runApplication() const
Start the application in testable mode.
InvalidityTracer application module.
const std::unordered_set< std::string > TAGS
Convenience type definition which can optionally be used as a shortcut for the type which defines a l...
Definition EntityOwner.h:27
The StatusAggregator collects results of multiple StatusMonitor instances and aggregates them into a ...
void setWarnMixedMessage(std::string message)
Set a custom message for the warn mixed state.
A VariableGroup for error status and message reporting.
void writeOk()
Set status to OK, clear the message and write the outputs.
void write(StatusOutput::Status status, std::string message)
Set the status and the message and write the outputs.
void prepare() override
Prepare the execution of the module.
StatusGenerator(ctk::ModuleGroup *owner, const std::string &name, const std::string &description, const std::unordered_set< std::string > &tags={}, ctk::StatusOutput::Status initialStatus=ctk::StatusOutput::Status::OFF)
void mainLoop() override
To be implemented by the user: function called in a separate thread executing the main loop of the mo...
StatusWithMessageGenerator(ctk::ModuleGroup *owner, const std::string &name, const std::string &description, const std::unordered_set< std::string > &tags={}, ctk::StatusOutput::Status initialStatus=ctk::StatusOutput::Status::OFF)
void prepare() override
Prepare the execution of the module.
void mainLoop() override
To be implemented by the user: function called in a separate thread executing the main loop of the mo...
TestPrioApplication(ctk::StatusOutput::Status theInitialValue)