ChimeraTK-ApplicationCore 04.06.00
Loading...
Searching...
No Matches
testCircularDependencyFaultyFlags.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 testCircularDependencyFaultyFlags
4
5// Tests never terminate when an exception is caught and BOOST_NO_EXCEPTIONS is set, but the exeption's what() message
6// is printed. Without the define the what() message is not printed, but the test ist not stuck... I leave it commented
7// but in the code so you activate when debugging.
8// #define BOOST_NO_EXCEPTIONS
9#include <boost/test/included/unit_test.hpp>
10// #undef BOOST_NO_EXCEPTIONS
11
12#include "ApplicationModule.h"
13#include "DeviceModule.h"
14#include "ScalarAccessor.h"
15#include "TestFacility.h"
16#include "VariableGroup.h"
17namespace ctk = ChimeraTK;
18
20
21 // The basic setup has 4 modules connected in a circle
22
28 struct /*InputGroup*/ : public ctk::VariableGroup {
29 using ctk::VariableGroup::VariableGroup;
30 ctk::ScalarPushInput<int> circularInput1{this, "circularOutput1", "", ""};
32
33 struct /*OutputGroup*/ : public ctk::VariableGroup {
34 using ctk::VariableGroup::VariableGroup;
35 ctk::ScalarOutput<int> circularOutput2{this, "circularInput2", "", ""};
37
38 ctk::ScalarPushInput<int> circularInput2{this, "circularInput2", "", ""};
39 ctk::ScalarOutput<int> circularOutput1{this, "circularOutput1", "", ""};
40
41 TestModuleBase(const std::string& inputName, const std::string& outputName, ctk::ModuleGroup* owner,
42 const std::string& name, const std::string& description)
43 : ApplicationModule(owner, name, description), inputGroup(this, "../" + inputName, ""),
44 outputGroup(this, "../" + outputName, "") {}
45
46 void mainLoop() override {
47 while(true) {
48 circularOutput1 = static_cast<int>(inputGroup.circularInput1);
49 outputGroup.circularOutput2 = static_cast<int>(circularInput2);
50
51 writeAll();
52 readAll();
53 }
54 }
55 };
56
60
61 ctk::ScalarPushInput<int> a{this, "a", "", ""};
62 ctk::ScalarPushInput<int> b{this, "b", "", ""};
63 ctk::ScalarOutput<int> circleResult{this, "circleResult", "", ""};
64
65 void prepare() override { writeAll(); }
66
67 void mainLoop() override {
68 // The circular inputs always are both coming as a pair, but we only want to write once.
69 // Hence we only put one of them into the ReadAnyGroup and always read the second one manually if the first one
70 // is read by the group.
71 ctk::ReadAnyGroup rag({a, b, inputGroup.circularInput1});
72
73 while(true) {
74 auto id = rag.readAny();
75
76 // A module with circular inputs and readAny must always actively break the circle. Otherwise for each external
77 // input and n-1 internal inputs and additional data element is inserted into the circle, which will let queues
78 // run over and re-trigger the circle all the time.
79 // This is a very typical scenario for circular connections: A module gets some input, triggers a helper module
80 // which calculates a value that is read back by the first module, and then the first module continues without
81 // re-triggering the circle.
82
83 assert((id == a.getId() || id == b.getId()) || id == inputGroup.circularInput1.getId() ||
84 id == circularInput2.getId());
85
86 if(id == inputGroup.circularInput1.getId()) {
87 // Read the other circular input as well. They always come in pairs.
88 circularInput2.read();
89 }
90
91 if(id == a.getId() || id == b.getId()) {
92 circularOutput1 = static_cast<int>(inputGroup.circularInput1) + a;
93 outputGroup.circularOutput2 = static_cast<int>(circularInput2) + b;
94
96 outputGroup.circularOutput2.write();
97 }
98 else { // new data is from the circular inputs
99 circleResult = static_cast<int>(inputGroup.circularInput1) + circularInput2;
101 }
102
103 } // while(true)
104 } // mainLoop
105 }; // ModuleA
106
111 ctk::ScalarPushInput<int> trigger{this, "trigger", "", ""};
112
113 // Special loop to guarantee that the internal inputs are read first, so we don't have unread data in the queue and
114 // can use the testable mode
115 void mainLoop() override {
116 while(true) {
117 circularOutput1 = static_cast<int>(inputGroup.circularInput1);
118 outputGroup.circularOutput2 = static_cast<int>(circularInput2);
119 writeAll();
120 // readAll();
121 inputGroup.circularInput1.read();
122 circularInput2.read();
123 trigger.read();
124 }
125 }
126 };
127
131 ctk::ScalarPollInput<int> i1{this, "/m1/i1", "", ""};
132 ctk::ScalarPollInput<int> i3{this, "/m1/i3", "", ""};
133 ctk::ScalarOutput<int> o1{this, "/m1/o1", "", ""};
134 };
135
137 TestApplication1() : Application("testSuite") {}
138 ~TestApplication1() override { shutdown(); }
139
140 ModuleA a{"D", "B", this, "A", ""}; // reads like: This is A, gets input from D and writes to B
141 TestModuleBase b{"A", "C", this, "B", ""};
142 ModuleC c{"B", "D", this, "C", ""};
143 ModuleD d{"C", "A", this, "D", ""};
144
145 ctk::DeviceModule device{this, "(dummy?map=testDataValidity1.map)"};
146 };
147
148 template<typename APP_TYPE>
150 APP_TYPE app;
152
153 ctk::ScalarRegisterAccessor<int> a{test.getScalar<int>("A/a")};
154 ctk::ScalarRegisterAccessor<int> b{test.getScalar<int>("A/b")};
155 ctk::ScalarRegisterAccessor<int> cTrigger{test.getScalar<int>("C/trigger")};
156 ctk::ScalarRegisterAccessor<int> aOut1{test.getScalar<int>("A/circularOutput1")};
157 ctk::ScalarRegisterAccessor<int> bOut1{test.getScalar<int>("B/circularOutput1")};
158 ctk::ScalarRegisterAccessor<int> cOut1{test.getScalar<int>("C/circularOutput1")};
159 ctk::ScalarRegisterAccessor<int> dOut1{test.getScalar<int>("D/circularOutput1")};
160 ctk::ScalarRegisterAccessor<int> aIn2{test.getScalar<int>("A/circularInput2")};
161 ctk::ScalarRegisterAccessor<int> bIn2{test.getScalar<int>("B/circularInput2")};
162 ctk::ScalarRegisterAccessor<int> cIn2{test.getScalar<int>("C/circularInput2")};
163 ctk::ScalarRegisterAccessor<int> dIn2{test.getScalar<int>("D/circularInput2")};
164 ctk::ScalarRegisterAccessor<int> circleResult{test.getScalar<int>("A/circleResult")};
165
167 aOut1.readLatest();
168 bOut1.readLatest();
169 cOut1.readLatest();
170 dOut1.readLatest();
171 aIn2.readLatest();
172 bIn2.readLatest();
173 cIn2.readLatest();
174 dIn2.readLatest();
175 circleResult.readLatest();
176 }
177
178 void checkAllDataValidity(ctk::DataValidity validity) {
179 BOOST_CHECK(aOut1.dataValidity() == validity);
180 BOOST_CHECK(bOut1.dataValidity() == validity);
181 BOOST_CHECK(cOut1.dataValidity() == validity);
182 BOOST_CHECK(dOut1.dataValidity() == validity);
183 BOOST_CHECK(aIn2.dataValidity() == validity);
184 BOOST_CHECK(bIn2.dataValidity() == validity);
185 BOOST_CHECK(cIn2.dataValidity() == validity);
186 BOOST_CHECK(dIn2.dataValidity() == validity);
187 BOOST_CHECK(circleResult.dataValidity() == validity);
188 }
189
191 };
192
200 BOOST_AUTO_TEST_CASE(TestCircularInputDetection) {
202 ctk::TestFacility test(app);
203
204 test.runApplication();
205 // app.dumpConnections();
206 // app.dump();
207
208 // just test that the circular inputs have been detected correctly
209 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.a.inputGroup.circularInput1).isCircularInput() == true);
210 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.a.circularInput2).isCircularInput() == true);
211 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.a.a).isCircularInput() == false);
212 // Check that the circular outputs are not marked as circular inputs. They are in the circle, but they are not inputs.
213 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.a.circularOutput1).isCircularInput() == false);
214 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.a.outputGroup.circularOutput2).isCircularInput() == false);
215
216 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.b.inputGroup.circularInput1).isCircularInput() == true);
217 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.b.circularInput2).isCircularInput() == true);
218 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.b.circularOutput1).isCircularInput() == false);
219 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.b.outputGroup.circularOutput2).isCircularInput() == false);
220
221 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.c.inputGroup.circularInput1).isCircularInput() == true);
222 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.c.circularInput2).isCircularInput() == true);
223 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.c.trigger).isCircularInput() == false);
224 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.c.circularOutput1).isCircularInput() == false);
225 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.c.outputGroup.circularOutput2).isCircularInput() == false);
226
227 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.d.inputGroup.circularInput1).isCircularInput() == true);
228 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.d.circularInput2).isCircularInput() == true);
229 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.d.circularOutput1).isCircularInput() == false);
230 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.d.outputGroup.circularOutput2).isCircularInput() == false);
231 // although there are inputs from and outputs to the same device this is not part of the circular network
232 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.d.i1).isCircularInput() == false);
233 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.d.i3).isCircularInput() == false);
234 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.d.o1).isCircularInput() == false);
235 }
236
246 a.setDataValidity(ctk::DataValidity::faulty);
247 a.write();
248 cTrigger.write();
249 test.stepApplication();
250
251 readAllLatest();
252 checkAllDataValidity(ctk::DataValidity::faulty);
253
254 // getting a valid variable in the same module does not resolve the flag
255 b.write();
256 cTrigger.write();
257 test.stepApplication();
258
259 readAllLatest();
260 checkAllDataValidity(ctk::DataValidity::faulty);
261
262 // now resolve the faulty condition
263 a.setDataValidity(ctk::DataValidity::ok);
264 a.write();
265 test.stepApplication();
266
267 readAllLatest();
268 // we check in the app that the input is still invalid, not in the CS
269 BOOST_CHECK(app.a.inputGroup.circularInput1.dataValidity() == ctk::DataValidity::faulty);
270 BOOST_CHECK(app.a.circularInput2.dataValidity() == ctk::DataValidity::faulty);
271 // the circular outputs of A and B are now valid
272 BOOST_CHECK(aOut1.dataValidity() == ctk::DataValidity::ok);
273 BOOST_CHECK(bOut1.dataValidity() == ctk::DataValidity::ok);
274 BOOST_CHECK(bIn2.dataValidity() == ctk::DataValidity::ok);
275 BOOST_CHECK(cIn2.dataValidity() == ctk::DataValidity::ok);
276 // the outputs of C, D and the circularResult have not been written yet
277 BOOST_CHECK(cOut1.dataValidity() == ctk::DataValidity::faulty);
278 BOOST_CHECK(dOut1.dataValidity() == ctk::DataValidity::faulty);
279 BOOST_CHECK(aIn2.dataValidity() == ctk::DataValidity::faulty);
280 BOOST_CHECK(dIn2.dataValidity() == ctk::DataValidity::faulty);
281 BOOST_CHECK(circleResult.dataValidity() == ctk::DataValidity::faulty);
282
283 // Now trigger C. The whole circle resolves
284 cTrigger.write();
285 test.stepApplication();
286 readAllLatest();
287
288 checkAllDataValidity(ctk::DataValidity::ok);
289 }
290
296 a.setDataValidity(ctk::DataValidity::faulty);
297 a.write();
298 cTrigger.write();
299 test.stepApplication();
300 // new in this test: an additional variable comes in while the internal and other external inputs are invalid
301 b.setDataValidity(ctk::DataValidity::faulty);
302 b.write();
303 cTrigger.write();
304 test.stepApplication();
305
306 // just a cross check
307 readAllLatest();
308 checkAllDataValidity(ctk::DataValidity::faulty);
309
310 a.setDataValidity(ctk::DataValidity::ok);
311 a.write();
312 cTrigger.write();
313 test.stepApplication();
314
315 // everything still faulty as b is faulty
316 readAllLatest();
317 checkAllDataValidity(ctk::DataValidity::faulty);
318
319 b.setDataValidity(ctk::DataValidity::ok);
320 b.write();
321 cTrigger.write();
322 test.stepApplication();
323
324 readAllLatest();
325 checkAllDataValidity(ctk::DataValidity::ok);
326 }
327
333 app.a.circularOutput1.setDataValidity(ctk::DataValidity::faulty);
334 a.write();
335 test.stepApplication();
336
337 readAllLatest();
338 // The data validity flag is not ignored, although only circular inputs are invalid
339 // B transports the flag. The A.outputGroup.circularOutput2 is still valid because all inputs are valid.
340 BOOST_CHECK(aOut1.dataValidity() == ctk::DataValidity::faulty);
341 BOOST_CHECK(bOut1.dataValidity() == ctk::DataValidity::faulty);
342 BOOST_CHECK(bIn2.dataValidity() == ctk::DataValidity::ok); // this is A.outputGroup.circularOutput2
343 BOOST_CHECK(cIn2.dataValidity() == ctk::DataValidity::faulty);
344 // the outputs of C, D and the circularResult have already been written yet
345 BOOST_CHECK(cOut1.dataValidity() == ctk::DataValidity::ok);
346 BOOST_CHECK(dOut1.dataValidity() == ctk::DataValidity::ok);
347 BOOST_CHECK(aIn2.dataValidity() == ctk::DataValidity::ok);
348 BOOST_CHECK(dIn2.dataValidity() == ctk::DataValidity::ok);
349 BOOST_CHECK(circleResult.dataValidity() == ctk::DataValidity::ok);
350
351 cTrigger.write();
352 test.stepApplication();
353
354 // Now the whole circle is invalid, except for A.outputGroup.circularOutput2 which has not been written again yet.
355 // (Module A stops the circular propagation because it is using readAny(), which otherwises would lead to more and more
356 // data packages piling up in the cirle because each external read adds one)
357 readAllLatest();
358 BOOST_CHECK(aOut1.dataValidity() == ctk::DataValidity::faulty);
359 BOOST_CHECK(bOut1.dataValidity() == ctk::DataValidity::faulty);
360 BOOST_CHECK(bIn2.dataValidity() == ctk::DataValidity::ok); // this is A.outputGroup.circularOutput2
361 BOOST_CHECK(cIn2.dataValidity() == ctk::DataValidity::faulty);
362 // the outputs of C, D and the circularResult have already been written yet
363 BOOST_CHECK(cOut1.dataValidity() == ctk::DataValidity::faulty);
364 BOOST_CHECK(dOut1.dataValidity() == ctk::DataValidity::faulty);
365 BOOST_CHECK(aIn2.dataValidity() == ctk::DataValidity::faulty);
366 BOOST_CHECK(dIn2.dataValidity() == ctk::DataValidity::faulty);
367 BOOST_CHECK(circleResult.dataValidity() == ctk::DataValidity::faulty);
368
369 // If we now complete the circle again, the faulty flag is propagated everywhere
370 a.write();
371 cTrigger.write();
372 test.stepApplication();
373
374 readAllLatest();
375 checkAllDataValidity(ctk::DataValidity::faulty);
376
377 // Check that the situation resolved when the data validity of the output is back to ok
378 app.a.circularOutput1.setDataValidity(ctk::DataValidity::ok);
379 a.write();
380 test.stepApplication();
381
382 readAllLatest();
383 // Module A goes to valid immediately and ignored the invalid circular inputs
384 BOOST_CHECK(aOut1.dataValidity() == ctk::DataValidity::ok);
385 BOOST_CHECK(bOut1.dataValidity() == ctk::DataValidity::ok);
386 BOOST_CHECK(bIn2.dataValidity() == ctk::DataValidity::ok);
387 BOOST_CHECK(cIn2.dataValidity() == ctk::DataValidity::ok);
388 // the outputs of C, D and the circularResult have not been written yet
389 BOOST_CHECK(cOut1.dataValidity() == ctk::DataValidity::faulty);
390 BOOST_CHECK(dOut1.dataValidity() == ctk::DataValidity::faulty);
391 BOOST_CHECK(aIn2.dataValidity() == ctk::DataValidity::faulty);
392 BOOST_CHECK(dIn2.dataValidity() == ctk::DataValidity::faulty);
393 BOOST_CHECK(circleResult.dataValidity() == ctk::DataValidity::faulty);
394
395 cTrigger.write();
396 test.stepApplication();
397
398 readAllLatest();
399 checkAllDataValidity(ctk::DataValidity::ok);
400 }
401
407 a.setDataValidity(ctk::DataValidity::faulty);
408 a.write();
409 cTrigger.write();
410 test.stepApplication();
411 // new in this test: the trigger in C bring an additional invalidity flag.
412 a.write();
413 cTrigger.setDataValidity(ctk::DataValidity::faulty);
414 cTrigger.write();
415 test.stepApplication();
416
417 // just a cross check
418 readAllLatest();
419 checkAllDataValidity(ctk::DataValidity::faulty);
420
421 a.setDataValidity(ctk::DataValidity::ok);
422 a.write();
423 cTrigger.write();
424 test.stepApplication();
425
426 // everything still faulty as b is faulty
427 readAllLatest();
428 checkAllDataValidity(ctk::DataValidity::faulty);
429
430 a.write();
431 cTrigger.setDataValidity(ctk::DataValidity::ok);
432 cTrigger.write();
433 test.stepApplication();
434
435 readAllLatest();
436 // the first half of the circle is not OK yet because no external triggers have arrived at A since
437 // the faultly condition was resolved
438 BOOST_CHECK(aOut1.dataValidity() == ctk::DataValidity::faulty);
439 BOOST_CHECK(bOut1.dataValidity() == ctk::DataValidity::faulty);
440 BOOST_CHECK(bIn2.dataValidity() == ctk::DataValidity::faulty);
441 BOOST_CHECK(cIn2.dataValidity() == ctk::DataValidity::faulty);
442 // the outputs of C, D and the circularResult have already been written yet
443 BOOST_CHECK(cOut1.dataValidity() == ctk::DataValidity::ok);
444 BOOST_CHECK(dOut1.dataValidity() == ctk::DataValidity::ok);
445 BOOST_CHECK(aIn2.dataValidity() == ctk::DataValidity::ok);
446 BOOST_CHECK(dIn2.dataValidity() == ctk::DataValidity::ok);
447 BOOST_CHECK(circleResult.dataValidity() == ctk::DataValidity::ok);
448
449 // writing a resolves the remainging variables
450 a.write();
451 test.stepApplication();
452 readAllLatest();
453 checkAllDataValidity(ctk::DataValidity::ok);
454 }
455
456 // A more complicated network with three entangled circles and one separate circle.
457 // AA-->BB-->CC-->DD-->AA /->HH
458 // ^ | | ^ GG<-/
459 // |-EE<-| |->FF-|
460 //
461 // The important part of this test is to check that the whole network AA,..,FF is always detected for each input,
462 // even if the scan is only for a variable that starts the scan in only in a local circle (like AA/fromEE).
463 // In addition it tests that not everything is mixed into a single circular network (GG,HH is detected as separate
464 // circular network).
465
466 // Don't try to pass any data through the network. It will be stuck because there are no real main loops. Only the initial
467 // value is passed (write exaclty once, then never read). It's just used to test the static circular network detection.
468
470 using ApplicationModule::ApplicationModule;
471
472 // available in all modules
473 ctk::ScalarPushInput<int> fromCS{this, "fromCS", "", ""};
474
475 // default main loop which provides initial values, but does not read or write anything else
476 void mainLoop() override { writeAll(); }
477 };
478
480 using TestModuleBase2::TestModuleBase2;
481
482 ctk::ScalarPushInput<int> fromEE{this, "fromEE", "", ""};
483 ctk::ScalarPushInput<int> fromDD{this, "fromDD", "", ""};
484
485 struct /*OutputGroup*/ : public ctk::VariableGroup {
486 using ctk::VariableGroup::VariableGroup;
487 ctk::ScalarOutput<int> fromAA{this, "fromAA", "", ""};
488 } outputGroup{this, "../BB", ""};
489
490 void prepare() override { writeAll(); } // break circular waiting for initial values
491 void mainLoop() override {}
492 };
493
495 using TestModuleBase2::TestModuleBase2;
496
497 ctk::ScalarPushInput<int> fromAA{this, "fromAA", "", ""};
498
499 struct /*OutputGroup*/ : public ctk::VariableGroup {
500 using ctk::VariableGroup::VariableGroup;
501 ctk::ScalarOutput<int> fromBB{this, "fromBB", "", ""};
502 } outputGroup{this, "../CC", ""};
503
505 using ctk::VariableGroup::VariableGroup;
506 ctk::ScalarOutput<int> fromBB{this, "fromBB", "", ""};
507 } outputGroup2{this, "../EE", ""};
508 };
509
511 using TestModuleBase2::TestModuleBase2;
512
513 ctk::ScalarPushInput<int> fromBB{this, "fromBB", "", ""};
514
515 struct /*OutputGroup*/ : public ctk::VariableGroup {
516 using ctk::VariableGroup::VariableGroup;
517 ctk::ScalarOutput<int> fromEE{this, "fromEE", "", ""};
518 } outputGroup{this, "../AA", ""};
519 };
520
522 using TestModuleBase2::TestModuleBase2;
523
524 ctk::ScalarPushInput<int> fromBB{this, "fromBB", "", ""};
525
526 struct /*OutputGroup*/ : public ctk::VariableGroup {
527 using ctk::VariableGroup::VariableGroup;
528 ctk::ScalarOutput<int> fromCC{this, "fromCC", "", ""};
529 } outputGroup{this, "../DD", ""};
530
532 using ctk::VariableGroup::VariableGroup;
533 ctk::ScalarOutput<int> fromCC{this, "fromCC", "", ""};
534 } outputGroup2{this, "../FF", ""};
535 };
536
538 using TestModuleBase2::TestModuleBase2;
539
540 ctk::ScalarPushInput<int> fromCC{this, "fromCC", "", ""};
541 ctk::ScalarPushInput<int> fromFF{this, "fromFF", "", ""};
542
543 struct /*OutputGroup*/ : public ctk::VariableGroup {
544 using ctk::VariableGroup::VariableGroup;
545 ctk::ScalarOutput<int> fromDD{this, "fromDD", "", ""};
546 } outputGroup{this, "../AA", ""};
547 };
548
550 using TestModuleBase2::TestModuleBase2;
551
552 ctk::ScalarPushInput<int> fromCC{this, "fromCC", "", ""};
553
554 struct /*OutputGroup*/ : public ctk::VariableGroup {
555 using ctk::VariableGroup::VariableGroup;
556 ctk::ScalarOutput<int> fromFF{this, "fromFF", "", ""};
557 } outputGroup{this, "../DD", ""};
558 };
559
561 using TestModuleBase2::TestModuleBase2;
562
563 ctk::ScalarPushInput<int> fromHH{this, "fromHH", "", ""};
564
565 struct /*OutputGroup*/ : public ctk::VariableGroup {
566 using ctk::VariableGroup::VariableGroup;
567 ctk::ScalarOutput<int> fromGG{this, "fromGG", "", ""};
568 } outputGroup{this, "../HH", ""};
569
570 void prepare() override { writeAll(); } // break circular waiting for initial values
571 void mainLoop() override {}
572 };
573
575 using TestModuleBase2::TestModuleBase2;
576
577 ctk::ScalarPushInput<int> fromGG{this, "fromGG", "", ""};
578
579 struct /*OutputGroup*/ : public ctk::VariableGroup {
580 using ctk::VariableGroup::VariableGroup;
581 ctk::ScalarOutput<int> fromHH{this, "fromHH", "", ""};
582 } outputGroup{this, "../GG", ""};
583 };
584
586 TestApplication2() : Application("connectionTestSuite") {}
587 ~TestApplication2() override { shutdown(); }
588
589 AA aa{this, "AA", ""};
590 BB bb{this, "BB", ""};
591 CC cc{this, "CC", ""};
592 DD dd{this, "DD", ""};
593 EE ee{this, "EE", ""};
594 FF ff{this, "FF", ""};
595 GG gg{this, "GG", ""};
596 HH hh{this, "HH", ""};
597
598 public:
599 // get a copy of the protected circularDependencyNetworks
600 std::map<size_t, std::list<EntityOwner*>> getCircularDependencyNetworks() {
601 return Application::_circularDependencyNetworks;
602 }
603 };
604
611 BOOST_AUTO_TEST_CASE(TestCircularInputDetection2) {
613 ctk::TestFacility test(app);
614
615 test.runApplication();
616 // app.dumpConnections();
617
618 // Check that all inputs have been identified correctly
619 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.aa.fromEE).isCircularInput() == true);
620 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.aa.fromDD).isCircularInput() == true);
621 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.aa.fromCS).isCircularInput() == false);
622
623 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.bb.fromAA).isCircularInput() == true);
624 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.bb.fromCS).isCircularInput() == false);
625
626 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.cc.fromBB).isCircularInput() == true);
627 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.cc.fromCS).isCircularInput() == false);
628
629 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.cc.fromBB).isCircularInput() == true);
630 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.cc.fromCS).isCircularInput() == false);
631
632 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.dd.fromCC).isCircularInput() == true);
633 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.dd.fromFF).isCircularInput() == true);
634 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.dd.fromCS).isCircularInput() == false);
635
636 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.ee.fromBB).isCircularInput() == true);
637 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.ee.fromCS).isCircularInput() == false);
638
639 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.ff.fromCC).isCircularInput() == true);
640 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.ff.fromCS).isCircularInput() == false);
641
642 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.gg.fromHH).isCircularInput() == true);
643 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.gg.fromCS).isCircularInput() == false);
644
645 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.hh.fromGG).isCircularInput() == true);
646 BOOST_CHECK(static_cast<ctk::VariableNetworkNode>(app.hh.fromCS).isCircularInput() == false);
647
648 // Check that the networks have been identified correctly
649 auto circularNetworks = app.getCircularDependencyNetworks();
650 BOOST_CHECK_EQUAL(circularNetworks.size(), 2);
651 for(auto& networkIter : app.getCircularDependencyNetworks()) {
652 const auto& id = networkIter.first;
653 auto& network = networkIter.second;
654 // networks have the correct size
655 if(network.size() == 6) {
656 std::list<ctk::EntityOwner*> modules = {&app.aa, &app.bb, &app.cc, &app.dd, &app.ee, &app.ff};
657 for(auto* module : modules) {
658 // all modules are in the network
659 BOOST_CHECK(std::count(network.begin(), network.end(), module) == 1);
660 // each module has the correct network associated
661 BOOST_CHECK_EQUAL(module->getCircularNetworkHash(), id);
662 }
663 }
664 else if(network.size() == 2) {
665 std::list<ctk::EntityOwner*> modules = {&app.gg, &app.hh};
666 for(auto* module : modules) {
667 BOOST_CHECK(std::count(network.begin(), network.end(), module) == 1);
668 BOOST_CHECK_EQUAL(module->getCircularNetworkHash(), id);
669 }
670 }
671 else {
672 BOOST_CHECK_MESSAGE(false, "Network with wrong number of modules detected: " + std::to_string(network.size()));
673 }
674 }
675 }
676
677} // namespace Tests::testCircularDependencyFaultyFlags
void shutdown() override
This will remove the global pointer to the instance and allows creating another instance afterwards.
ApplicationModule()=default
Default constructor: Allows late initialisation of modules (e.g.
friend class Application
Definition ModuleGroup.h:47
void readAll(bool includeReturnChannels=false)
Read all readable variables in the group.
Definition Module.cc:72
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.
Class describing a node of a variable network.
bool isCircularInput() const
Returns true if a circular dependency has been detected and the node is a consumer.
InvalidityTracer application module.
BOOST_FIXTURE_TEST_CASE(OneInvalidVariable, CircularAppTestFixcture< TestApplication1 >)
Tests Technical specification: data validity propagation
BOOST_AUTO_TEST_CASE(TestCircularInputDetection)
Tests Technical specification: data validity propagation
Convenience class for output scalar accessors (always UpdateMode::push)
Convenience class for input scalar accessors with UpdateMode::poll.
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...
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.
ModuleA has two additional inputs to get invalidity flags. It is reading all inputs with ReadAny.
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.
ModuleC has a trigger together with a readAll.; (it's a trigger for the circle because there is alway...
void mainLoop() override
To be implemented by the user: function called in a separate thread executing the main loop of the mo...
Involve the DeviceModule. Here are some variables from a test device.
std::map< size_t, std::list< EntityOwner * > > getCircularDependencyNetworks()
void mainLoop() override
To be implemented by the user: function called in a separate thread executing the main loop of the mo...
The base module has the inputs and outputs for the circular dependency.
TestModuleBase(const std::string &inputName, const std::string &outputName, ctk::ModuleGroup *owner, const std::string &name, const std::string &description)
void mainLoop() override
To be implemented by the user: function called in a separate thread executing the main loop of the mo...