ChimeraTK-ApplicationCore 04.06.00
Loading...
Searching...
No Matches
testUserInputValidator.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#define BOOST_TEST_MODULE testUserInputValidator
5
6#include "Application.h"
7#include "ScalarAccessor.h"
8#include "TestFacility.h"
10
11#include <boost/mpl/list.hpp>
12#include <boost/test/included/unit_test.hpp>
13
15
16 using namespace boost::unit_test_framework;
17 namespace ctk = ChimeraTK;
18
19 /********************************************************************************************************************/
20 /* Test module with a single validated input, used stand alone or as a downstream module */
21 /********************************************************************************************************************/
22
24 using ctk::ApplicationModule::ApplicationModule;
25
26 ctk::ScalarPushInputWB<int> in1{this, "in1", "", "First validated input"};
27
29
30 std::string in1ErrorMessage;
31
32 void prepare() override {
33 in1ErrorMessage = "(" + getName() + ") in1 needs to be smaller than 10";
34 validator.add(in1ErrorMessage, [&] { return in1 < 10; }, in1);
35 }
36
37 void mainLoop() override {
38 auto group = readAnyGroup();
39 ctk::TransferElementID change;
40 while(true) {
41 validator.validate(change);
42 change = group.readAny();
43 }
44 }
45 };
46
47 /********************************************************************************************************************/
48 /* Variant of ModuleA with a second input */
49 /********************************************************************************************************************/
50
52 using ModuleA::ModuleA;
53
54 ctk::ScalarPushInputWB<int> in2{this, "in2", "", "Second validated input"};
55
56 std::string in2ErrorMessage;
57
58 void prepare() override {
60 in2ErrorMessage = "(" + getName() + ") in2 needs to be bigger than 10";
61 validator.add(in2ErrorMessage, [&] { return in2 > 10; }, in2);
62 }
63 };
64
65 /********************************************************************************************************************/
66 /* Test module with a single validated input and one output for connection to another validated input */
67 /********************************************************************************************************************/
68
70 using ctk::ApplicationModule::ApplicationModule;
71
72 ctk::ScalarPushInputWB<int> in1{this, "in1", "", "First validated input"};
73 ctk::ScalarOutputPushRB<int> out1{this, "/Downstream/in1", "", "Output"};
74
76
77 void prepare() override {
78 validator.add("(" + getName() + ") in1 needs to be smaller than 20", [&] { return in1 < 20; }, in1);
79 }
80
81 void mainLoop() override {
82 auto group = readAnyGroup();
83 ctk::TransferElementID change;
84 while(true) {
85 validator.validate(change);
87 change = group.readAny();
88 }
89 }
90 };
91 /********************************************************************************************************************/
92 /* Test module with a single validated input and two outputs for connection to another validated input */
93 /********************************************************************************************************************/
94
96 using ctk::ApplicationModule::ApplicationModule;
97
98 ctk::ScalarPushInputWB<int> in1{this, "in1", "", "First validated input"};
99 ctk::ScalarOutputPushRB<int> out1{this, "/Downstream1/in1", "", "Output"};
100 ctk::ScalarOutputPushRB<int> out2{this, "/Downstream2/in1", "", "Output"};
101
103
104 void prepare() override {
105 validator.add("(" + getName() + ") in1 needs to be smaller than 20", [&] { return in1 < 20; }, in1);
106 }
107
108 void mainLoop() override {
109 auto group = readAnyGroup();
110 ctk::TransferElementID change;
111 while(true) {
112 validator.validate(change);
113
116
117 change = group.readAny();
118 }
119 }
120 };
121
122 /********************************************************************************************************************/
123 /* Test cases */
124 /********************************************************************************************************************/
125
126 BOOST_AUTO_TEST_CASE(testSingleVariable) {
127 std::cout << "testSingleVariable" << std::endl;
128
129 struct TestApplication : public ctk::Application {
130 using ctk::Application::Application;
131 ~TestApplication() override { shutdown(); }
132 ModuleA moduleA{this, "ModuleA", ""};
133 };
134
135 TestApplication app("TestApp");
136 ctk::TestFacility test(app);
137
138 auto modAin1 = test.getScalar<int>("/ModuleA/in1");
139
140 test.runApplication();
141
142 modAin1.setAndWrite(8);
143 test.stepApplication();
144 BOOST_TEST(!modAin1.readLatest());
145 BOOST_TEST(app.moduleA.in1 == 8);
146
147 modAin1.setAndWrite(10);
148 test.stepApplication();
149 BOOST_TEST(modAin1.readLatest());
150 BOOST_TEST(modAin1 == 8);
151 BOOST_TEST(app.moduleA.in1 == 8);
152 }
153
154 /********************************************************************************************************************/
155
156 BOOST_AUTO_TEST_CASE(testFallback) {
157 std::cout << "testFallback" << std::endl;
158
159 struct ModuleAmod : public ModuleA {
160 using ModuleA::ModuleA;
161 void prepare() override {
162 ModuleA::prepare();
163 validator.setFallback(in1, 7);
164 }
165 };
166
167 struct TestApplication : public ctk::Application {
168 using ctk::Application::Application;
169 ~TestApplication() override { shutdown(); }
170 ModuleAmod moduleA{this, "ModuleA", ""};
171 };
172
173 TestApplication app("TestApp");
174 ctk::TestFacility test(app);
175
176 auto modAin1 = test.getScalar<int>("/ModuleA/in1");
177
178 test.setScalarDefault<int>("/ModuleA/in1", 12);
179
180 test.runApplication();
181
182 BOOST_TEST(!modAin1.readLatest());
183 BOOST_TEST(app.moduleA.in1 == 7);
184 }
185
186 /********************************************************************************************************************/
187
188 BOOST_AUTO_TEST_CASE(testMultipleVariablesDifferentChecks) {
189 std::cout << "testMultipleVariablesDifferentChecks" << std::endl;
190 // add another input which is validated with another UserInputValidator::add() call
191
192 struct TestApplication : public ctk::Application {
193 using ctk::Application::Application;
194 ~TestApplication() override { shutdown(); }
195 ModuleAwithSecondInput moduleA{this, "ModuleA", ""};
196 };
197
198 TestApplication app("TestApp");
199 ctk::TestFacility test(app);
200
201 auto in1 = test.getScalar<int>("/ModuleA/in1");
202 auto in2 = test.getScalar<int>("/ModuleA/in2");
203
204 test.setScalarDefault<int>("/ModuleA/in1", 3);
205 test.setScalarDefault<int>("/ModuleA/in2", 12);
206
207 test.runApplication();
208
209 BOOST_TEST(!in1.readLatest());
210 BOOST_TEST(!in2.readLatest());
211
212 in1.setAndWrite(15);
213 test.stepApplication();
214 BOOST_TEST(in1.readLatest());
215 BOOST_TEST(in1 == 3);
216 BOOST_TEST(app.moduleA.in1 == 3);
217 BOOST_TEST(app.moduleA.in2 == 12);
218
219 in1.setAndWrite(9);
220 test.stepApplication();
221 BOOST_TEST(!in1.readLatest());
222 BOOST_TEST(app.moduleA.in1 == 9);
223 BOOST_TEST(app.moduleA.in2 == 12);
224
225 BOOST_TEST(!in2.readLatest());
226
227 in2.setAndWrite(7);
228 test.stepApplication();
229 BOOST_TEST(in2.readLatest());
230 BOOST_TEST(in2 == 12);
231 BOOST_TEST(app.moduleA.in1 == 9);
232 BOOST_TEST(app.moduleA.in2 == 12);
233
234 in2.setAndWrite(13);
235 test.stepApplication();
236 BOOST_TEST(!in2.readLatest());
237 BOOST_TEST(app.moduleA.in1 == 9);
238 BOOST_TEST(app.moduleA.in2 == 13);
239
240 BOOST_TEST(!in1.readLatest());
241 }
242
243 /********************************************************************************************************************/
244
245 BOOST_AUTO_TEST_CASE(testMultipleVariablesSameCheck) {
246 std::cout << "testMultipleVariablesSameCheck" << std::endl;
247 // add another input which is validated in the same UserInputValidator::add() call as the existing input, replacing
248 // the existing add() call
249
250 struct ModuleAmod : public ModuleA {
251 using ModuleA::ModuleA;
252
253 ctk::ScalarPushInputWB<int> in2{this, "in2", "", "Second validated input"};
254
255 void prepare() override {
256 // Do not call ModuleA::prepare() here, we do not want the original check!
257 validator.add(
258 "in1 needs to be smaller than 10 and in2 needs to be bigger than 10", [&] { return in1 < 10 && in2 > 10; },
259 in1, in2);
260 }
261 };
262
263 struct TestApplication : public ctk::Application {
264 using ctk::Application::Application;
265 ~TestApplication() override { shutdown(); }
266 ModuleAmod moduleA{this, "ModuleA", ""};
267 };
268
269 // Implementation note about the test: Except for the setting (one single add() call combining both checks instead of
270 // two separate add() calls) this test can be identical to testMultipleVariablesDifferentChecks. The only difference
271 // in behaviour is the different message, which is defined in the add() call (and hence outside the code under test).
272
273 TestApplication app("TestApp");
274 ctk::TestFacility test(app);
275
276 auto in1 = test.getScalar<int>("/ModuleA/in1");
277 auto in2 = test.getScalar<int>("/ModuleA/in2");
278
279 test.setScalarDefault<int>("/ModuleA/in1", 3);
280 test.setScalarDefault<int>("/ModuleA/in2", 12);
281
282 test.runApplication();
283
284 BOOST_TEST(!in1.readLatest());
285 BOOST_TEST(!in2.readLatest());
286
287 in1.setAndWrite(15);
288 test.stepApplication();
289 BOOST_TEST(in1.readLatest());
290 BOOST_TEST(in1 == 3);
291 BOOST_TEST(app.moduleA.in1 == 3);
292 BOOST_TEST(app.moduleA.in2 == 12);
293
294 in1.setAndWrite(9);
295 test.stepApplication();
296 BOOST_TEST(!in1.readLatest());
297 BOOST_TEST(app.moduleA.in1 == 9);
298 BOOST_TEST(app.moduleA.in2 == 12);
299
300 BOOST_TEST(!in2.readLatest());
301
302 in2.setAndWrite(7);
303 test.stepApplication();
304 BOOST_TEST(in2.readLatest());
305 BOOST_TEST(in2 == 12);
306 BOOST_TEST(app.moduleA.in1 == 9);
307 BOOST_TEST(app.moduleA.in2 == 12);
308
309 in2.setAndWrite(13);
310 test.stepApplication();
311 BOOST_TEST(!in2.readLatest());
312 BOOST_TEST(app.moduleA.in1 == 9);
313 BOOST_TEST(app.moduleA.in2 == 13);
314
315 BOOST_TEST(!in1.readLatest());
316 }
317
318 /********************************************************************************************************************/
319
320 BOOST_AUTO_TEST_CASE(testMultipleChecksSameVariable) {
321 std::cout << "testMultipleChecksSameVariable" << std::endl;
322 // add multiple UserInputValidator::add() calls all checking the same variable i1
323
324 struct ModuleAmod : public ModuleA {
325 using ModuleA::ModuleA;
326
327 void prepare() override {
328 ModuleA::prepare(); // defines check for in1 < 10
329 validator.add("in1 needs to be greater than -5", [&] { return in1 > -5; }, in1);
330 }
331 };
332
333 struct TestApplication : public ctk::Application {
334 using ctk::Application::Application;
335 ~TestApplication() override { shutdown(); }
336 ModuleAmod moduleA{this, "ModuleA", ""};
337 };
338
339 TestApplication app("TestApp");
340 ctk::TestFacility test(app);
341
342 auto in1 = test.getScalar<int>("/ModuleA/in1");
343
344 test.setScalarDefault<int>("/ModuleA/in1", 3);
345
346 test.runApplication();
347
348 BOOST_TEST(!in1.readLatest());
349
350 in1.setAndWrite(15);
351 test.stepApplication();
352 BOOST_TEST(in1.readLatest());
353 BOOST_TEST(in1 == 3);
354 BOOST_TEST(app.moduleA.in1 == 3);
355
356 in1.setAndWrite(9);
357 test.stepApplication();
358 BOOST_TEST(!in1.readLatest());
359 BOOST_TEST(app.moduleA.in1 == 9);
360
361 in1.setAndWrite(-7);
362 test.stepApplication();
363 BOOST_TEST(in1.readLatest());
364 BOOST_TEST(in1 == 9);
365 BOOST_TEST(app.moduleA.in1 == 9);
366 }
367
368 /********************************************************************************************************************/
369
370 BOOST_AUTO_TEST_CASE(testSetErrorFunction) {
371 std::cout << "testSetErrorFunction" << std::endl;
372 // check that setErrorFunction is called with the right message (need multiple checks with different messages)
373
374 struct TestApplication : public ctk::Application {
375 using ctk::Application::Application;
376 ~TestApplication() override { shutdown(); }
377 ModuleAwithSecondInput moduleA{this, "ModuleA", ""};
378 };
379
380 TestApplication app("TestApp");
381 ctk::TestFacility test(app);
382
383 std::string errorMessage;
384
385 app.moduleA.validator.setErrorFunction([&](const std::string& msg) { errorMessage = msg; });
386
387 auto modAin1 = test.getScalar<int>("/ModuleA/in1");
388 auto modAin2 = test.getScalar<int>("/ModuleA/in2");
389
390 test.setScalarDefault<int>("/ModuleA/in2", 20);
391
392 test.runApplication();
393
394 modAin1.setAndWrite(8);
395 test.stepApplication();
396 BOOST_TEST(!modAin1.readLatest());
397 BOOST_TEST(app.moduleA.in1 == 8);
398 BOOST_TEST(errorMessage.empty());
399
400 modAin1.setAndWrite(10);
401 test.stepApplication();
402 BOOST_TEST(modAin1.readLatest());
403 BOOST_TEST(modAin1 == 8);
404 BOOST_TEST(app.moduleA.in1 == 8);
405 BOOST_TEST(errorMessage == app.moduleA.in1ErrorMessage);
406
407 modAin2.setAndWrite(1);
408 test.stepApplication();
409 BOOST_TEST(modAin2.readLatest());
410 BOOST_TEST(modAin2 == 20);
411 BOOST_TEST(app.moduleA.in2 == 20);
412 BOOST_TEST(errorMessage == app.moduleA.in2ErrorMessage);
413 }
414
415 /********************************************************************************************************************/
416
417 BOOST_AUTO_TEST_CASE(testBackwardsPropagationSingleDownstream) {
418 std::cout << "testBackwardsPropagationSingleDownstream" << std::endl;
419 // check that two modules with each one validator connected to each other propagate rejections from the downstream
420 // module to the upstream and the control system eventually
421 // Note: This is new functionality implemeted as part of #11558
422
423 struct TestApplication : public ctk::Application {
424 using ctk::Application::Application;
425 ~TestApplication() override { shutdown(); }
426 UpstreamSingleOut upstream{this, "Upstream", ""};
427 ModuleA downstream{this, "Downstream", ""};
428 };
429
430 TestApplication app("TestApp");
431 ctk::TestFacility test(app);
432
433 auto upstrIn = test.getScalar<int>("/Upstream/in1");
434 auto downstrIn = test.getScalar<int>("/Downstream/in1");
435
436 test.setScalarDefault<int>("/Upstream/in1", 5);
437
438 test.runApplication();
439
440 // discard initial values
441 downstrIn.readLatest();
442 BOOST_TEST(downstrIn == 6);
443
444 upstrIn.setAndWrite(30);
445 test.stepApplication();
446 BOOST_TEST(upstrIn.readNonBlocking());
447 BOOST_TEST(upstrIn == 5);
448 BOOST_TEST(!downstrIn.readNonBlocking()); // validation happens in upstream, not really part of this test case
449
450 upstrIn.setAndWrite(12);
451 test.stepApplication();
452 BOOST_TEST(upstrIn.readNonBlocking());
453 BOOST_TEST(upstrIn == 5);
454 BOOST_TEST(downstrIn.readLatest());
455 BOOST_TEST(downstrIn == 6);
456 }
457
458 /********************************************************************************************************************/
459
460 BOOST_AUTO_TEST_CASE(testBackwardsPropagationTwoDownstream) {
461 std::cout << "testBackwardsPropagationTwoDownstream" << std::endl;
462 // Same as testBackwardsPropagationSingleDownstream but with two downstream modules (different PVs)
463 // Note: This is new functionality implemeted as part of #11558
464
465 struct TestApplication : public ctk::Application {
466 using ctk::Application::Application;
467 ~TestApplication() override { shutdown(); }
468 UpstreamTwinOut upstream{this, "Upstream", ""};
469 ModuleA downstream1{this, "Downstream1", ""};
470 ModuleA downstream2{this, "Downstream2", ""};
471 };
472
473 TestApplication app("TestApp");
474 ctk::TestFacility test(app);
475
476 auto upstrIn = test.getScalar<int>("/Upstream/in1");
477 auto downstr1In = test.getScalar<int>("/Downstream1/in1");
478 auto downstr2In = test.getScalar<int>("/Downstream2/in1");
479
480 test.setScalarDefault<int>("/Upstream/in1", 5);
481
482 test.runApplication();
483
484 // discard initial values
485 downstr1In.readLatest();
486 BOOST_TEST(downstr1In == 6);
487 downstr2In.readLatest();
488 BOOST_TEST(downstr2In == 7);
489
490 upstrIn.setAndWrite(30);
491 test.stepApplication();
492 BOOST_TEST(upstrIn.readNonBlocking());
493 BOOST_TEST(upstrIn == 5);
494 BOOST_TEST(!downstr1In.readNonBlocking()); // validation happens in upstream, not really part of this test case
495 BOOST_TEST(!downstr2In.readNonBlocking());
496
497 upstrIn.setAndWrite(12);
498 test.stepApplication();
499 BOOST_TEST(upstrIn.readNonBlocking());
500 BOOST_TEST(upstrIn == 5);
501 BOOST_TEST(downstr1In.readLatest());
502 BOOST_TEST(downstr1In == 6);
503 BOOST_TEST(downstr2In.readLatest());
504 BOOST_TEST(downstr2In == 7);
505 }
506
507 /********************************************************************************************************************/
508
509 BOOST_AUTO_TEST_CASE(testFunnelThreadedFanOut) {
510 std::cout << "testFunnelThreadedFanOut" << std::endl;
511 // Similar to testFunnelThreadedFanOut but with an upstream module the return channel is funneled into rather
512 // than the control system
513
514 struct TestApplication : public ctk::Application {
515 using ctk::Application::Application;
516 ~TestApplication() override { shutdown(); }
517 ModuleA module1{this, "Module", ""};
518 ModuleA module2{this, "Module", ""};
519 };
520
521 TestApplication app("TestApp");
522 ctk::TestFacility test(app);
523
524 auto in1 = test.getScalar<int>("/Module/in1");
525
526 test.setScalarDefault<int>("/Module/in1", 5);
527
528 test.runApplication();
529
530 in1.setAndWrite(30);
531 test.stepApplication();
532 BOOST_TEST(in1.readNonBlocking());
533 BOOST_TEST(in1 == 5);
534 BOOST_TEST(app.module1.in1 == 5);
535 BOOST_TEST(app.module2.in1 == 5);
536 }
537
538 /********************************************************************************************************************/
539
540 BOOST_AUTO_TEST_CASE(testFunnelFeedingFanOut) {
541 std::cout << "testFunnelFeedingFanOut" << std::endl;
542 // Two modules both having the same PV as an input (with return channel) which is validated
543 // Note: This is new functionality implemeted as part of #11558
544
545 struct TestApplication : public ctk::Application {
546 using ctk::Application::Application;
547 ~TestApplication() override { shutdown(); }
548 UpstreamSingleOut upstream{this, "Upstream", ""};
549 ModuleA module1{this, "Downstream", ""};
550 ModuleA module2{this, "Downstream", ""};
551 };
552
553 TestApplication app("TestApp");
554 ctk::TestFacility test(app);
555
556 auto in1 = test.getScalar<int>("/Upstream/in1");
557
558 test.setScalarDefault<int>("/Upstream/in1", 5);
559
560 test.runApplication();
561
562 in1.setAndWrite(30);
563 test.stepApplication();
564 BOOST_TEST(in1.readNonBlocking());
565 BOOST_TEST(in1 == 5);
566 BOOST_TEST(app.module1.in1 == 6);
567 BOOST_TEST(app.module2.in1 == 6);
568 }
569
570 /********************************************************************************************************************/
571
572 BOOST_AUTO_TEST_CASE(testDeepBackwardsPropagation) {
573 std::cout << "testDeepBackwardsPropagation" << std::endl;
574 // Like testBackwardsPropagationSingleDownstream, but with deeper validation chain and new input values arriving at
575 // the upstream module before rejections from downstream.
576
577 struct TestApplication : public ctk::Application {
578 using ctk::Application::Application;
579 ~TestApplication() override { shutdown(); }
580 UpstreamSingleOut upstream{this, "Upstream", ""};
581 UpstreamSingleOut midstream{this, "Midstream", ""};
582 ModuleA downstream{this, "Downstream", ""};
583 };
584
585 TestApplication app("TestApp");
586 app.upstream.out1 = {&app.upstream, "/Midstream/in1", "", "First validated input"};
587
588 ctk::TestFacility test(app);
589
590 auto upstrIn = test.getScalar<int>("/Upstream/in1");
591 auto midstreamIn = test.getScalar<int>("/Midstream/in1");
592 auto downstrIn = test.getScalar<int>("/Downstream/in1");
593
594 test.setScalarDefault<int>("/Upstream/in1", 5);
595
596 test.runApplication();
597
598 // discard initial values
599 midstreamIn.readLatest();
600 downstrIn.readLatest();
601 BOOST_TEST(midstreamIn == 6);
602 BOOST_TEST(downstrIn == 7);
603 // test a single value being discarded at the lowest level (Downstream)
604 upstrIn.setAndWrite(12);
605 test.stepApplication();
606 BOOST_TEST(midstreamIn.readNonBlocking());
607 BOOST_TEST(midstreamIn == 13); // first value is coming from Upstream
608 BOOST_TEST(downstrIn.readNonBlocking());
609 BOOST_TEST(downstrIn == 14); // first value is passed through by Midstream
610 BOOST_TEST(downstrIn.readNonBlocking());
611 BOOST_TEST(downstrIn == 7); // correction value coming back from Downstream
612 BOOST_TEST(!downstrIn.readNonBlocking());
613 BOOST_TEST(midstreamIn.readNonBlocking());
614 BOOST_TEST(midstreamIn == 6); // correction value coming back from Midstream
615 BOOST_TEST(!midstreamIn.readNonBlocking());
616 BOOST_TEST(upstrIn.readNonBlocking());
617 BOOST_TEST(upstrIn == 5); // correction value coming back from Upstream
618 BOOST_TEST(!upstrIn.readNonBlocking());
619
620 // test two consecutive values both being discarded at the lowest level
621 // Note: Writing two values into the upstrIn queue will make Upstream process the second value before the correction
622 // for the first value coming from Downstream, because readAny() will process updates in sequences of arrival
623 // (on notification queue). Apart from this it is not well defined (aka subject to race condition) where the second
624 // value from Upstream and the correction of the first value from Downstream cross.
625 upstrIn.setAndWrite(12);
626 upstrIn.setAndWrite(13);
627 test.stepApplication();
628 BOOST_TEST(midstreamIn.readNonBlocking());
629 BOOST_TEST(midstreamIn == 13); // first value is coming from upstream
630 BOOST_TEST(downstrIn.readNonBlocking());
631 BOOST_TEST(downstrIn == 14); // first value is coming from upstream/midstream
632
633 BOOST_TEST(downstrIn.readLatest()); // just observe final state, because intermediate states might be subject
634 BOOST_TEST(downstrIn == 7); // to race conditions
635 BOOST_TEST(midstreamIn.readLatest());
636 BOOST_TEST(midstreamIn == 6);
637 BOOST_TEST(upstrIn.readLatest());
638 BOOST_TEST(upstrIn == 5);
639
640 // test two consecutive values, only the first being discarded at the lowest level and the second is accepted
641 size_t retry = 0;
642 repeat:
643 upstrIn.setAndWrite(12);
644 upstrIn.setAndWrite(3);
645
646 test.stepApplication();
647
648 // There are two acceptable outcomes of this test:
649 //
650 // 1) Likely: The first value was rejected and the second was accepted
651 //
652 // 2) Unlikely: Both values are rejected. This can happen because the UserInputValidator of midstream needs to use a
653 // fresh VersionNumber to propagate the rejection of the first value from downstream (otherwise the scenario in
654 // testBackwardsPropagationTwoDownstream would break). Since that fresh VersionNumber is bigger then the one of
655 // the second, valid value, it can overwrite the second value and hence that gets effectively rejected.
656 //
657 // Currently, we accept both scenarios but require that the likely scenario is observed (by retrying a couple of
658 // times if we see the second scenario). This problem can be solved by extending the VersionNumber with a
659 // sub-version, so we can both distinguish the corrected from the rejected values as well as find out to which
660 // original VersionNumber the corrected value belongs. Due to the fact that this problem only occurs when writing
661 // inputs faster than the values get rejected and the UserInputValidator being designed for inputs by users, this
662 // problem does not seem to play a big role in real scenarios.
663
664 BOOST_TEST(downstrIn.readLatest()); // just observe final state, to avoid intermediate races
665 BOOST_TEST(midstreamIn.readLatest());
666 if(downstrIn == 7) {
667 BOOST_TEST(upstrIn.readLatest());
668 BOOST_TEST(midstreamIn == 6);
669 BOOST_TEST(upstrIn == 5);
670 if(++retry < 100) {
671 goto repeat;
672 }
673 BOOST_ERROR("The wanted 'likely' scenario could not be observed, only the unwanted 'unlikely'.");
674 }
675 BOOST_TEST(!upstrIn.readLatest());
676 BOOST_TEST(downstrIn == 5);
677 BOOST_TEST(midstreamIn == 4);
678 BOOST_TEST(upstrIn == 3);
679 }
680
681 /********************************************************************************************************************/
682
683} // namespace Tests::testUserInputValidator
const std::string & getName() const
Get the name of the module instance.
Definition EntityOwner.h:59
ChimeraTK::ReadAnyGroup readAnyGroup()
Create a ChimeraTK::ReadAnyGroup for all readable variables in this Module.
Definition Module.cc:54
void writeIfDifferent(UserType newValue, VersionNumber versionNumber, DataValidity validity)=delete
Convenience class for input scalar accessors with return channel ("write back") and UpdateMode::push.
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.
void setScalarDefault(const ChimeraTK::RegisterPath &name, const T &value)
Set default value for scalar process variable.
InvalidityTracer application module.
Convenience class for output scalar accessors with return channel ("read back") (always UpdateMode::p...
Class to realise the validation of user input values.
bool validate(const ChimeraTK::TransferElementID &change)
Execute all validations for the given change.
void add(const std::string &errorMessage, const std::function< bool(void)> &isValidFunction, ACCESSORTYPES &... accessors)
Add new condition to validate the given accessors against.
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 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 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 prepare() override
Prepare the execution of the module.