ChimeraTK-ApplicationCore 04.06.00
Loading...
Searching...
No Matches
testDataValidityPropagation.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#define BOOST_TEST_MODULE testDataValidityPropagation
4
5#include "Application.h"
6#include "ApplicationModule.h"
7#include "DeviceModule.h"
8#include "ScalarAccessor.h"
9#include "TestFacility.h"
10
11#include <ChimeraTK/Device.h>
12#include <ChimeraTK/DummyRegisterAccessor.h>
13#include <ChimeraTK/ExceptionDummyBackend.h>
14#include <ChimeraTK/NDRegisterAccessor.h>
15
16#include <boost/mpl/list.hpp>
17
18#include <chrono>
19#include <cstring>
20
21// this #include must come last
22#define BOOST_NO_EXCEPTIONS
23#include <boost/test/included/unit_test.hpp>
24#undef BOOST_NO_EXCEPTIONS
25
26using namespace boost::unit_test_framework;
27namespace ctk = ChimeraTK;
28
30
31 /********************************************************************************************************************/
32
33 // A module used for initial value tests:
34 // It has an output which is never written to.
36 using ctk::ApplicationModule::ApplicationModule;
37 ctk::ScalarPushInput<int> i1{this, "i1", "", ""};
38 ctk::ScalarOutput<int> oNothing{this, "oNothing", "", ""};
39 void mainLoop() override {
40 auto group = readAnyGroup();
41 while(true) {
42 group.readAny();
43 }
44 }
45 };
46
47 /********************************************************************************************************************/
48
49 // module for most of hte data validity propagation tests
51 using ctk::ApplicationModule::ApplicationModule;
52
53 ctk::ScalarPushInput<int> i1{this, "i1", "", ""};
54
55 ctk::ScalarOutput<int> o1{this, "o1", "", ""};
56
57 ctk::ScalarOutput<int> oconst{this, "oconst", "", ""};
58
59 void prepare() override { writeAll(); }
60
61 void mainLoop() override {
62 oconst = 1;
63 oconst.setDataValidity(outputValidity);
64 oconst.write();
65 // also provide initial value for o1, in case some module waits on it.
66 // this is important for the testable mode
67 o1 = -1;
68 o1.setDataValidity(outputValidity);
69 o1.write();
70 auto group = readAnyGroup();
71 while(true) {
72 group.readAny();
73 o1 = int(i1);
74 o1.setDataValidity(outputValidity);
75 o1.write();
76 }
77 }
78
79 // used for overwriting the outputs validities.
80 ctk::DataValidity outputValidity = ctk::DataValidity::ok;
81 int incCalled = 0;
82 int decCalled = 0;
83
84 void incrementDataFaultCounter() override {
85 incCalled++;
86 ctk::ApplicationModule::incrementDataFaultCounter();
87 }
88
89 void decrementDataFaultCounter() override {
90 decCalled++;
91 ctk::ApplicationModule::decrementDataFaultCounter();
92 }
93 };
94
95 /********************************************************************************************************************/
96
98 using ctk::ApplicationModule::ApplicationModule;
99 ctk::ScalarPushInput<int> i1{this, "i1", "", ""};
100 bool dataValidity1 = true, dataValidity2 = true;
101 void mainLoop() override {
102 auto group = readAnyGroup();
103 while(true) {
104 dataValidity1 = this->getDataValidity() == ctk::DataValidity::ok;
106 dataValidity2 = this->getDataValidity() == ctk::DataValidity::ok;
108
109 group.readAny();
110 }
111 }
112 };
113
114 /********************************************************************************************************************/
115
117 using ctk::ApplicationModule::ApplicationModule;
118 ctk::ScalarOutput<int> o1{this, "o1", "", ""};
119 void mainLoop() override {
120 // do nothing, we trigger o1 manually in the tests
121 // while(true) {
122 // // using boost sleep helps for the interruption point
123 // boost::this_thread::sleep_for(boost::chrono::milliseconds(200));
124 // o1=1;
125 // o1.write();
126 // }
127 }
128 };
129
130 /********************************************************************************************************************/
131
132 template<typename ModuleT>
134 TestApplication1() : Application("testSuite") {}
135 ~TestApplication1() override { shutdown(); }
136
137 ModuleT mod{this, "m1", ""};
138
139 const std::string dummyCdd{"(ExceptionDummy?map=testDataValidityPropagation.map)"};
140
141 TriggerModule m2{this, "m2", ""};
143 std::atomic_bool deviceError = false;
145 bool err = ((TestApplication1&)Application::getInstance()).deviceError;
146 if(err) {
147 throw ChimeraTK::runtime_error("device is not ready.");
148 }
149 }
150 };
151
152 /********************************************************************************************************************/
153
155 constexpr static char const* ExceptionDummyCDD = "(ExceptionDummy?map=testDataValidityPropagation2.map)";
156 TestApplication3() : Application("testPartiallyInvalidDevice") {}
157 ~TestApplication3() override { shutdown(); }
158
159 TriggerModule m2{this, "m2", ""};
160
162 };
163
164 /********************************************************************************************************************/
165 /********************************************************************************************************************/
166
167 /*
168 * \anchor testDataValidity_1_1 \ref dataValidity_1_1 "1.1"
169 * In ApplicationCore each variable has a data validiy flag attached to it. DataValidity can be 'ok' or 'faulty'.
170 *
171 * Explicit test does not make sense since this is clear if this suite compiles, i.e. expressions
172 * testmod1.i1.dataValidity() and testmod1.o1.dataValidity();
173 */
174
175 /********************************************************************************************************************/
176
177 /*
178 * \anchor testDataValidity_1_2 \ref dataValidity_1_2 "1.2"
179 * This flag is automatically propagated: If any of the inputs of an ApplicationModule is faulty, the data validity of
180 * the module becomes faulty, which means all outputs of this module will automatically be flagged as faulty. Fan-outs
181 * might be special cases (see 2.4).
182 *
183 * See \ref testDataValidity_2_3_3
184 */
185
186 /********************************************************************************************************************/
187
193 BOOST_AUTO_TEST_CASE(testDataValidity_1_3) {
194 // set up an application with a faulty device
196 app.deviceError = true;
197 // testable mode cannot be used here, since it would wait on initial values (which are not provided)
198 ctk::TestFacility test(app, false);
199
200 auto i1 = test.getScalar<int>("/dev/i1");
201 test.runApplication();
202
203 // i1.read() would block here
204 i1.readLatest();
205
206 BOOST_CHECK(i1.dataValidity() == ctk::DataValidity::faulty);
207
208 // if Application does not shutdown, this could be an applicationcore bug that requires a workaround for this test
209 // redmine issue: #8550
210 }
211
212 /********************************************************************************************************************/
213
214 /*
215 * \anchor testDataValidity_1_4 \ref dataValidity_1_4 "1.4"
216 * The user code has the possibility to query the data validity of the module
217 *
218 * No explicit test, if test suite compiles it's ok.
219 */
220
221 /********************************************************************************************************************/
222
223 /*
224 * \anchor testDataValidity_1_5 \ref dataValidity_1_5 "1.5"
225 * The user code has the possibility to set the data validity of the module to 'faulty'. However, the user code cannot
226 * actively set the module to 'ok' if any of the module inputs are 'faulty'.
227 *
228 * No explicit test. The module should use increment/decrement mechanism to set invalid state,
229 * which implies it cannot override faulty state.
230 * BUT it is actually possible to override getDataValidity in the Module, so spec does not hold strictly!
231 *
232 */
233
234 /********************************************************************************************************************/
235
240 TestApplication16() : Application("testSuite") {}
241 ~TestApplication16() override { shutdown(); }
242
243 TestModule1 mod1{this, "m1", ""};
244 TestModule1 mod2{this, "m2", ""};
245 };
246
252 BOOST_AUTO_TEST_CASE(testDataValidity_1_6) {
254 app.mod2.i1 = {&app.mod2, "/m1/o1", "", ""};
255
256 app.mod1.outputValidity = ctk::DataValidity::faulty;
257 app.mod2.outputValidity = ctk::DataValidity::ok;
258 ctk::TestFacility test{app};
259
260 // app.dumpConnections();
261 auto input = test.getScalar<int>("/m1/i1");
262 auto result = test.getScalar<int>("/m2/o1");
263 test.runApplication();
264 result.readLatest();
265
266 input.write();
267 test.stepApplication();
268 input.write();
269 test.stepApplication();
270
271 // module 2 must be flagged bad because of the faulty input from module 1
272 BOOST_CHECK(app.mod2.getDataValidity() == ctk::DataValidity::faulty);
273 result.read();
274 // output of module 2 cannot be valid, even if module tries to set it to valid
275 BOOST_CHECK(result.dataValidity() == ctk::DataValidity::faulty);
276 }
277
278 /********************************************************************************************************************/
279
280 /*
281 * \anchor testDataValidity_1_7 \ref dataValidity_1_7 "1.7"
282 * The user code can get the data validity flag of individual inputs and take special actions.
283 *
284 * No explicit test required.
285 */
286
287 /********************************************************************************************************************/
288
294 BOOST_AUTO_TEST_CASE(testDataValidity_1_8) {
296 // testable mode cannot be used here, since it would wait on initial values (which are not provided)
297 ctk::TestFacility test(app, false);
298
299 auto o0 = test.getScalar<int>("/m1/oNothing");
300 test.runApplication();
301
302 // o0.read() would block here
303 BOOST_CHECK(o0.readNonBlocking() == false);
304
305 BOOST_CHECK(o0.dataValidity() == ctk::DataValidity::faulty);
306 }
307
308 /********************************************************************************************************************/
309
315 BOOST_AUTO_TEST_CASE(testDataValidity_2_1_1) {
317 ctk::TestFacility test{app};
318 auto i1 = test.getScalar<int>("/m1/i1");
319 test.runApplication();
320 assert(app.mod.getDataValidity() == ctk::DataValidity::ok);
321 // we cannot check inputs via dynamic_cast to MetaDataPropagatingRegisterDecorator, since implementation detail is
322 // hidden by TransferElementAbstractor instead, check what the decorator is supposed to do. check that the
323 // MetaDataPropagatingRegisterDecorator counts data validity changes ( in doPostRead)
324 i1 = 0;
325 i1.write();
326 test.stepApplication(); // triggers m1.i1.read()
327 i1.setDataValidity(ctk::DataValidity::faulty);
328 i1.write();
329 test.stepApplication(); // triggers m1.i1.read()
330 BOOST_CHECK(app.mod.incCalled == 1);
331 BOOST_CHECK(app.mod.decCalled == 0);
332
333 // check that the MetaDataPropagatingRegisterDecorator takes over faulty data validity from owning module ( in doPreWrite)
334 BOOST_CHECK(app.mod.getDataValidity() == ctk::DataValidity::faulty);
335 }
336
337 /********************************************************************************************************************/
338
339 /*
340 * \anchor testDataValidity_2_1_2 \ref dataValidity_2_1_2 "2.1.2"
341 * The decorator knows about the module it is connected to. It is called the 'owner'.
342 *
343 * There is no public function for getting the owner, but implicitly this was tested in \ref testDataValidity_2_1_1.
344 */
345
346 /********************************************************************************************************************/
347
353 BOOST_AUTO_TEST_CASE(testDataValidity_2_1_3) {
355 ctk::TestFacility test{app};
356
357 auto i1 = test.getScalar<int>("/m1/i1");
358
359 test.runApplication();
360 // mark input faulty
361 i1 = 1;
362 i1.setDataValidity(ctk::DataValidity::faulty);
363 i1.write();
364 // propagate value
365 test.stepApplication();
366 // check that fault counter was incremented
367 BOOST_CHECK(app.mod.incCalled == 1);
368 // mark input ok
369 i1.setDataValidity(ctk::DataValidity::ok);
370 i1.write();
371 // propagate value
372 test.stepApplication();
373 // check that fault counter was decremented
374 BOOST_CHECK(app.mod.decCalled == 1);
375 }
376
377 /********************************************************************************************************************/
378
379 /*
380 * \anchor testDataValidity_2_1_5 \ref dataValidity_2_1_5 "2.1.5"
381 * **write:** When writing, the decorator is checking the validity of the owner and the individual flag of the output
382 * set by the user. Only if both are 'ok' the output validity is 'ok', otherwise the outgoing data is send as 'faulty'.
383 *
384 * Test ist identical to \ref testDataValidity_1_6
385 */
386
387 /********************************************************************************************************************/
388
394 BOOST_AUTO_TEST_CASE(testDataValidity_2_3_1) {
395 TestModule1 testmod1;
396 BOOST_CHECK(testmod1.getDataValidity() == ctk::DataValidity::ok);
397 testmod1.incrementDataFaultCounter();
398 BOOST_CHECK(testmod1.getDataValidity() == ctk::DataValidity::faulty);
399 testmod1.decrementDataFaultCounter();
400 BOOST_CHECK(testmod1.getDataValidity() == ctk::DataValidity::ok);
401 }
402
403 /********************************************************************************************************************/
404
405 /*
406 * \anchor testDataValidity_2_3_2 \ref dataValidity_2_3_2 "2.3.2"
407 * All inputs and outputs have a MetaDataPropagatingRegisterDecorator.
408 *
409 * Tested in \ref testDataValidity_2_1_1
410 */
411
412 /********************************************************************************************************************/
413
419 BOOST_AUTO_TEST_CASE(testDataValidity_2_3_3) {
421 ctk::TestFacility test{app};
422
423 auto i1 = test.getScalar<int>("/m1/i1");
424 auto o1 = test.getScalar<int>("/m1/o1");
425 auto oconst = test.getScalar<int>("/m1/oconst");
426 test.runApplication();
427 o1.readLatest();
428 oconst.readLatest();
429
430 i1 = 1;
431 i1.setDataValidity(ctk::DataValidity::faulty);
432 i1.write();
433
434 test.stepApplication();
435
436 // check that an output which is re-calculated becomes invalid
437 // we need to get the destination of o1.write(), which is unlike o1.dataValidity() inside the module main loop
438 o1.read();
439 BOOST_CHECK(o1.dataValidity() == ctk::DataValidity::faulty);
440 // check that an output which is not re-calculated stays valid
441 BOOST_CHECK(oconst.readLatest() == false);
442 }
443
444 /********************************************************************************************************************/
445
452 BOOST_AUTO_TEST_CASE(testDataValidity_2_3_4) {
454 ctk::TestFacility test{app};
455
456 auto i1 = test.getScalar<int>("/m1/i1");
457 test.runApplication();
458 i1.write();
459 test.stepApplication();
460 // check that module variable v2 reports faulty
461 BOOST_CHECK(app.mod.dataValidity2 == false);
462
463 i1 = 1;
464 i1.setDataValidity(ctk::DataValidity::faulty);
465 i1.write();
466 test.stepApplication();
467 // check that module variable v1 now also reports faulty
468 BOOST_CHECK(app.mod.dataValidity1 == false);
469 }
470
471 /********************************************************************************************************************/
472
477 BOOST_AUTO_TEST_CASE(testDataValidity_2_4_1) {
479 ctk::TestFacility test{app};
480 // app.debugTestableMode();
481
482 // the TriggerFanOut is realized via device module connected to control system,
483 // using a trigger from TriggerModule
484 auto result1 = test.getScalar<int>("dev/i3"); // RO
485
486 test.runApplication();
487
488 // check that setting trigger to invalid propagates to outputs of TriggerFanOut
489 app.m2.o1.setDataValidity(ctk::DataValidity::faulty);
490 app.m2.o1.write();
491
492 test.stepApplication();
493 result1.read();
494 BOOST_CHECK(result1.dataValidity() == ctk::DataValidity::faulty);
495 }
496
497 /********************************************************************************************************************/
498
499 /*
500 * \anchor testDataValidity_2_4_2 \ref dataValidity_2_4_2 "2.4.2"
501 * The poll-type data inputs do not have a MetaDataPropagatingRegisterDecorator.
502 *
503 * No functionality that needs testing
504 */
505
506 /********************************************************************************************************************/
507
512 BOOST_AUTO_TEST_CASE(testDataValidity_2_4_3) {
514 ctk::TestFacility test{app};
515
516 // the TriggerFanOut is realized via device module connected to control system,
517 // using a trigger from TriggerModule
518 auto result1 = test.getScalar<int>("/dev/i1");
519 auto result2 = test.getScalar<int>("/dev/i3");
520
521 ctk::Device dev(app.dummyCdd);
522
523 test.runApplication();
524 auto exceptionDummy = boost::dynamic_pointer_cast<ctk::ExceptionDummy>(dev.getBackend());
525 assert(exceptionDummy);
526 exceptionDummy->setValidity("/dev/i1", ctk::DataValidity::faulty);
527
528 app.m2.o1.write();
529 test.stepApplication();
530 result1.read();
531 BOOST_CHECK(result1.dataValidity() == ctk::DataValidity::faulty);
532 result2.read();
533 BOOST_CHECK(result2.dataValidity() == ctk::DataValidity::ok);
534 }
535
536 /********************************************************************************************************************/
537
538 /*
539 * \anchor testDataValidity_2_4_4 \ref dataValidity_2_4_4 "2.4.4"
540 * Although the trigger conceptually has data type 'void', it can also be `faulty`. An invalid trigger is processed,
541 * but all read out data is flagged as `faulty`.
542 *
543 * Already tested in \ref testDataValidity_2_4_1
544 */
545
546 /********************************************************************************************************************/
547
548 /*
549 * \anchor testDataValidity_2_5_1 \ref dataValidity_2_5_1 "2.5.1"
550 * The MetaDataPropagatingRegisterDecorator is always placed *around* the ExceptionHandlingDecorator if both
551 * decorators are used on a process variable. Like this a `faulty` flag raised by the ExceptionHandlingDecorator is
552 * automatically picked up by the MetaDataPropagatingRegisterDecorator.
553 *
554 * Already tested in \ref testDataValidity_1_3
555 */
556
557 /********************************************************************************************************************/
558
559 /*
560 * \anchor testDataValidity_2_5_2 \ref dataValidity_2_5_2 "2.5.2"
561 * The first failing read returns with the old data and the 'faulty' flag. Like this the flag is propagated to the
562 * outputs. Only further reads might freeze until the device is available again.
563 *
564 * Already tested in \ref testDataValidity_1_3
565 */
566
567 /********************************************************************************************************************/
568
569 /*
570 * \anchor testDataValidity_2_6_1 \ref dataValidity_2_6_1 "2.6.1"
571 * For device variables, the requirement of setting receiving endpoints to 'faulty' on construction can not be
572 * fulfilled. In DeviceAccess the accessors are bidirectional and provide no possibility to distinguish sender and
573 * receiver. Instead, the validity is set right after construction in Application::createDeviceVariable() for receivers.
574 *
575 * Already tested in \ref testDataValidity_1_3
576 */
577
578 /********************************************************************************************************************/
579
580 /*
581 * \anchor testDataValidiy_3_1 \ref dataValidity_3_1 "3.1"
582 * The decorators which manipulate the data fault counter are responsible for counting up and down in pairs, such that
583 * the counter goes back to 0 if all data is ok, and never becomes negative.
584 *
585 * Not tested since it's an implementation detail.
586 */
587
588 /********************************************************************************************************************/
589
590 // BOOST_AUTO_TEST_SUITE_END()
591
592} // namespace Tests::testDataValidityPropagation
void shutdown() override
This will remove the global pointer to the instance and allows creating another instance afterwards.
DataValidity getDataValidity() const override
Return the data validity flag.
void decrementDataFaultCounter() override
Decrement the fault counter and set the data validity flag to ok if the counter has reached 0.
void incrementDataFaultCounter() override
Set the data validity flag to fault and increment the fault counter.
friend class Application
Definition ModuleGroup.h:47
ChimeraTK::ReadAnyGroup readAnyGroup()
Create a ChimeraTK::ReadAnyGroup for all readable variables in this Module.
Definition Module.cc:54
void writeAll(bool includeReturnChannels=false)
Just call write() on all writable variables in the group.
Definition Module.cc:157
bool write(ChimeraTK::VersionNumber versionNumber)=delete
Convenience class for input scalar accessors with UpdateMode::push.
Helper class to facilitate tests of applications based on ApplicationCore.
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.
BOOST_AUTO_TEST_CASE(testDataValidity_1_3)
1.3 If a device is in error state, all variables which are read from it shall be marked as 'faulty'.
Convenience class for output scalar accessors (always UpdateMode::push)
app with two chained modules, for testDataValidity_1_6
void mainLoop() override
To be implemented by the user: function called in a separate thread executing the main loop of the mo...
void prepare() override
Prepare the execution of the module.
void incrementDataFaultCounter() override
Set the data validity flag to fault and increment the fault counter.
void decrementDataFaultCounter() override
Decrement the fault counter and set the data validity flag to ok if the counter has reached 0.
void mainLoop() override
To be implemented by the user: function called in a separate thread executing the main loop of the mo...
void mainLoop() override
To be implemented by the user: function called in a separate thread executing the main loop of the mo...
void mainLoop() override
To be implemented by the user: function called in a separate thread executing the main loop of the mo...