ChimeraTK-ApplicationCore 04.06.00
Loading...
Searching...
No Matches
testTestFacilities.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 testTestFacilities
5
6#include "Application.h"
7#include "ApplicationModule.h"
8#include "check_timeout.h"
9#include "DeviceModule.h"
10#include "ScalarAccessor.h"
11#include "TestableMode.h"
12#include "TestFacility.h"
13#include "VariableGroup.h"
14
15#include <ChimeraTK/Device.h>
16
17#include <boost/mpl/list.hpp>
18#include <boost/thread/barrier.hpp>
19
20#include <latch>
21
22// #define BOOST_NO_EXCEPTIONS
23#include <boost/test/included/unit_test.hpp>
24// #undef BOOST_NO_EXCEPTIONS
25
27
28 using namespace boost::unit_test_framework;
29 namespace ctk = ChimeraTK;
30
31 constexpr std::string_view dummySdm{"(dummy?map=test_readonly.map)"};
32
33 /********************************************************************************************************************/
34 /* the BlockingReadTestModule blockingly reads its input in the main loop and writes the result to its output */
35
37 using ctk::ApplicationModule::ApplicationModule;
38
39 ctk::ScalarPushInput<int32_t> someInput{this, "/value", "cm", "This is just some input for testing"};
40 ctk::ScalarOutput<int32_t> someOutput{this, "someOutput", "cm", "Description"};
41
42 void mainLoop() override {
43 while(true) {
44 int32_t val = someInput;
45 someOutput = val;
46 usleep(10000); // wait some extra time to make sure we are really blocking
47 // the test procedure thread
49 someInput.read(); // read at the end to propagate the initial value
50 }
51 }
52 };
53
54 /********************************************************************************************************************/
55 /* the ReadAnyTestModule calls readAny on a bunch of inputs and outputs some information on the received data */
56
58 using ctk::ApplicationModule::ApplicationModule;
59
60 struct Inputs : public ctk::VariableGroup {
61 using ctk::VariableGroup::VariableGroup;
62 ctk::ScalarPushInput<int32_t> v1{this, "v1", "cm", "Input 1 for testing"};
63 ctk::ScalarPushInput<int32_t> v2{this, "/REG2", "cm", "Input 2 for testing"};
64 ctk::ScalarPushInput<int32_t> v3{this, "v3", "cm", "Input 3 for testing"};
65 ctk::ScalarPushInput<int32_t> v4{this, "v4", "cm", "Input 4 for testing"};
66 };
67 Inputs inputs{this, "inputs", "A group of inputs"};
68 ctk::ScalarOutput<int32_t> value{this, "/value", "cm", "The last value received from any of the inputs"};
70 this, "index", "", "The index (1..4) of the input where the last value was received"};
71
72 void prepare() override {
73 incrementDataFaultCounter(); // force all outputs to invalid
74 writeAll();
75 decrementDataFaultCounter(); // validity according to input validity
76 }
77
78 void mainLoop() override {
79 auto group = inputs.readAnyGroup();
80 while(true) {
81 auto justRead = group.readAny();
82 if(inputs.v1.getId() == justRead) {
83 index = 1;
84 value = int32_t(inputs.v1);
85 }
86 else if(inputs.v2.getId() == justRead) {
87 index = 2;
88 value = int32_t(inputs.v2);
89 }
90 else if(inputs.v3.getId() == justRead) {
91 index = 3;
92 value = int32_t(inputs.v3);
93 }
94 else if(inputs.v4.getId() == justRead) {
95 index = 4;
96 value = int32_t(inputs.v4);
97 }
98 else {
99 index = 0;
100 value = 0;
101 }
102 usleep(10000); // wait some extra time to make sure we are really blocking
103 // the test procedure thread
104 index.write();
105 value.write();
106 }
107 }
108 };
109
110 /********************************************************************************************************************/
111 /* the PollingReadModule is designed to test poll-type transfers (even mixed with push-type) */
112
114 using ctk::ApplicationModule::ApplicationModule;
115
116 ctk::ScalarPushInput<int32_t> push{this, "push", "cm", "A push-type input"};
117 ctk::ScalarPushInput<int32_t> push2{this, "push2", "cm", "A second push-type input"};
118 ctk::ScalarPollInput<int32_t> poll{this, "poll", "cm", "A poll-type input"};
119
120 ctk::ScalarOutput<int32_t> valuePush{this, "valuePush", "cm", "The last value received for 'push'"};
121 ctk::ScalarOutput<int32_t> valuePoll{this, "valuePoll", "cm", "The last value received for 'poll'"};
122 ctk::ScalarOutput<int32_t> state{this, "state", "", "State of the test mainLoop"};
123
124 void prepare() override {
125 incrementDataFaultCounter(); // foce all outputs to invalid
126 writeAll();
127 decrementDataFaultCounter(); // validity according to input validity
128 }
129
130 void mainLoop() override {
131 while(true) {
132 push.read();
133 poll.read();
134 valuePush = int32_t(push);
135 valuePoll = int32_t(poll);
138 state = 1;
139 state.write();
140
141 push2.read();
142 push.readNonBlocking();
143 poll.read();
144 valuePush = int32_t(push);
145 valuePoll = int32_t(poll);
148 state = 2;
149 state.write();
150
151 push2.read();
152 push.readLatest();
153 poll.read();
154 valuePush = int32_t(push);
155 valuePoll = int32_t(poll);
158 state = 3;
159 state.write();
160 }
161 }
162 };
163
164 /********************************************************************************************************************/
165 /* the PollingThroughFanoutsModule is designed to test poll-type transfers in combination with FanOuts */
166
168 using ctk::ApplicationModule::ApplicationModule;
169 ctk::ScalarPushInput<int> push1{this, "push1", "", ""};
170 ctk::ScalarPollInput<int> poll1{this, "poll1", "", ""};
171 ctk::ScalarPollInput<int> poll2{this, "poll2", "", ""};
172 ctk::ScalarOutput<int> out1{this, "out1", "", ""};
173 ctk::ScalarOutput<int> out2{this, "out2", "", ""};
174
175 std::mutex mForChecking;
176 bool hasRead{false};
177
178 void prepare() override { writeAll(); }
179
180 void run() override { ApplicationModule::run(); }
181
182 void mainLoop() override {
183 while(true) {
184 push1.read();
185
186 std::unique_lock<std::mutex> lock(mForChecking);
187 hasRead = true;
188 poll1.read();
189 poll2.read();
190 usleep(1000); // give try_lock() in tests a chance to fail if testable mode lock would not work
191 }
192 }
193 };
194
195 /********************************************************************************************************************/
196 /* test that no TestableModeAccessorDecorator is used if the testable mode is not enabled */
197
199 TestNoDecoratorApplication() : Application("testApplication") {}
201
202 BlockingReadTestModule blockingReadTestModule{this, "blockingReadTestModule", "Module for testing blocking read"};
203 ReadAnyTestModule readAnyTestModule{this, "readAnyTestModule", "Module for testing readAny()"};
204 };
205
206 BOOST_AUTO_TEST_CASE(testNoDecorator) {
207 std::cout << "***************************************************************"
208 "******************************************************"
209 << std::endl;
210 std::cout << "==> testNoDecorator" << std::endl;
211
213
214 auto pvManagers = ctk::createPVManager();
215 app.setPVManager(pvManagers.second);
216
217 app.initialise();
218 app.run();
219
220 // check if we got the decorator for the input
221 auto hlinput = app.blockingReadTestModule.someInput.getHighLevelImplElement();
222 BOOST_CHECK(boost::dynamic_pointer_cast<ctk::detail::TestableMode::AccessorDecorator<int32_t>>(hlinput) == nullptr);
223
224 // check that we did not get the decorator for the output
225 auto hloutput = app.blockingReadTestModule.someOutput.getHighLevelImplElement();
226 BOOST_CHECK(
227 boost::dynamic_pointer_cast<ctk::detail::TestableMode::AccessorDecorator<int32_t>>(hloutput) == nullptr);
228 }
229
230 /********************************************************************************************************************/
231 /* test blocking read in test mode */
232
234 TestBlockingReadApplication() : Application("testApplication") {}
236
237 BlockingReadTestModule blockingReadTestModule{this, "blockingReadTestModule", "Module for testing blocking read"};
238 };
239
240 BOOST_AUTO_TEST_CASE(testBlockingRead) {
241 std::cout << "***************************************************************"
242 "******************************************************"
243 << std::endl;
244 std::cout << "==> testBlockingRead" << std::endl;
245
247
248 ctk::TestFacility test{app};
249 auto pvInput = test.getScalar<int32_t>("/value");
250 auto pvOutput = test.getScalar<int32_t>("/blockingReadTestModule/someOutput");
251 test.runApplication();
252
253 // test blocking read when taking control in the test thread (note: the
254 // blocking read is executed in the app module!)
255 for(int i = 0; i < 5; ++i) {
256 pvInput = 120 + i;
257 pvInput.write();
258 usleep(10000);
259 BOOST_CHECK(pvOutput.readNonBlocking() == false);
260 test.stepApplication();
261 CHECK_TIMEOUT(pvOutput.readNonBlocking() == true, 10000);
262 int val = pvOutput;
263 BOOST_CHECK(val == 120 + i);
264 }
265 }
266
267 /********************************************************************************************************************/
268 /* test testReadAny in test mode */
269
271 TestReadAnyApplication() : Application("testApplication") {}
273
274 ReadAnyTestModule readAnyTestModule{this, "readAnyTestModule", "Module for testing readAny()"};
275 };
276
277 BOOST_AUTO_TEST_CASE(testReadAny) {
278 std::cout << "***************************************************************"
279 "******************************************************"
280 << std::endl;
281 std::cout << "==> testReadAny" << std::endl;
282
284
285 ctk::TestFacility test{app};
286 auto value = test.getScalar<int32_t>("/value");
287 auto index = test.getScalar<uint32_t>("/readAnyTestModule/index");
288 auto v1 = test.getScalar<int32_t>("/readAnyTestModule/inputs/v1");
289 auto v2 = test.getScalar<int32_t>("/REG2"); // just named irregularly, no device present!
290 auto v3 = test.getScalar<int32_t>("/readAnyTestModule/inputs/v3");
291 auto v4 = test.getScalar<int32_t>("/readAnyTestModule/inputs/v4");
292 test.runApplication();
293 // check that we don't receive anything yet
294 usleep(10000);
295 BOOST_CHECK(value.readNonBlocking() == false);
296 BOOST_CHECK(index.readNonBlocking() == false);
297
298 // send something to v4
299 v4 = 66;
300 v4.write();
301
302 // check that we still don't receive anything yet
303 usleep(10000);
304 BOOST_CHECK(value.readNonBlocking() == false);
305 BOOST_CHECK(index.readNonBlocking() == false);
306
307 // run the application and check that we got the expected result
308 test.stepApplication();
309 BOOST_CHECK(value.readNonBlocking() == true);
310 BOOST_CHECK(index.readNonBlocking() == true);
311 BOOST_CHECK_EQUAL(value, 66);
312 BOOST_CHECK_EQUAL(index, 4);
313
314 // send something to v1
315 v1 = 33;
316 v1.write();
317
318 // check that we still don't receive anything yet
319 usleep(10000);
320 BOOST_CHECK(value.readNonBlocking() == false);
321 BOOST_CHECK(index.readNonBlocking() == false);
322
323 // run the application and check that we got the expected result
324 test.stepApplication();
325 BOOST_CHECK(value.readNonBlocking() == true);
326 BOOST_CHECK(index.readNonBlocking() == true);
327 BOOST_CHECK_EQUAL(value, 33);
328 BOOST_CHECK_EQUAL(index, 1);
329
330 // send something to v1 again
331 v1 = 34;
332 v1.write();
333
334 // check that we still don't receive anything yet
335 usleep(10000);
336 BOOST_CHECK(value.readNonBlocking() == false);
337 BOOST_CHECK(index.readNonBlocking() == false);
338
339 // run the application and check that we got the expected result
340 test.stepApplication();
341
342 BOOST_CHECK(value.readNonBlocking() == true);
343 BOOST_CHECK(index.readNonBlocking() == true);
344 BOOST_CHECK_EQUAL(value, 34);
345 BOOST_CHECK_EQUAL(index, 1);
346
347 // send something to v3
348 v3 = 40;
349 v3.write();
350
351 // check that we still don't receive anything yet
352 usleep(10000);
353 BOOST_CHECK(value.readNonBlocking() == false);
354 BOOST_CHECK(index.readNonBlocking() == false);
355
356 // run the application and check that we got the expected result
357 test.stepApplication();
358 BOOST_CHECK(value.readNonBlocking() == true);
359 BOOST_CHECK(index.readNonBlocking() == true);
360 BOOST_CHECK_EQUAL(value, 40);
361 BOOST_CHECK_EQUAL(index, 3);
362
363 // send something to v2
364 v2 = 50;
365 v2.write();
366
367 // check that we still don't receive anything yet
368 usleep(10000);
369 BOOST_CHECK(value.readNonBlocking() == false);
370 BOOST_CHECK(index.readNonBlocking() == false);
371
372 // run the application and check that we got the expected result
373 test.stepApplication();
374 BOOST_CHECK(value.readNonBlocking() == true);
375 BOOST_CHECK(index.readNonBlocking() == true);
376 BOOST_CHECK_EQUAL(value, 50);
377 BOOST_CHECK_EQUAL(index, 2);
378
379 // check that stepApplication() throws an exception if no input data is
380 // available
381 try {
382 test.stepApplication();
383 BOOST_ERROR("IllegalParameter exception expected.");
384 }
385 catch(ChimeraTK::logic_error&) {
386 }
387
388 // check that we still don't receive anything anymore
389 usleep(10000);
390 BOOST_CHECK(value.readNonBlocking() == false);
391 BOOST_CHECK(index.readNonBlocking() == false);
392
393 // send something to v1 a 3rd time
394 v1 = 35;
395 v1.write();
396
397 // check that we still don't receive anything yet
398 usleep(10000);
399 BOOST_CHECK(value.readNonBlocking() == false);
400 BOOST_CHECK(index.readNonBlocking() == false);
401
402 // run the application and check that we got the expected result
403 test.stepApplication();
404 BOOST_CHECK(value.readNonBlocking() == true);
405 BOOST_CHECK(index.readNonBlocking() == true);
406 BOOST_CHECK_EQUAL(value, 35);
407 BOOST_CHECK_EQUAL(index, 1);
408 }
409
410 /********************************************************************************************************************/
411 /* test the interplay of multiple chained modules and their threads in test mode
412 */
413
417
418 BlockingReadTestModule blockingReadTestModule{this, "blockingReadTestModule", "Module for testing blocking read"};
419 ReadAnyTestModule readAnyTestModule{this, "readAnyTestModule", "Module for testing readAny()"};
420 };
421
422 BOOST_AUTO_TEST_CASE(testChainedModules) {
423 std::cout << "***************************************************************"
424 "******************************************************"
425 << std::endl;
426 std::cout << "==> testChainedModules" << std::endl;
427
429
430 ctk::TestFacility test{app};
431 auto value = test.getScalar<int32_t>("/blockingReadTestModule/someOutput");
432 auto index = test.getScalar<uint32_t>("/readAnyTestModule/index");
433 auto v1 = test.getScalar<int32_t>("/readAnyTestModule/inputs/v1");
434 auto v2 = test.getScalar<int32_t>("/REG2"); // just named irregularly, no device present!
435 auto v3 = test.getScalar<int32_t>("/readAnyTestModule/inputs/v3");
436 auto v4 = test.getScalar<int32_t>("/readAnyTestModule/inputs/v4");
437 test.runApplication();
438
439 // check that we don't receive anything yet
440 usleep(10000);
441 BOOST_CHECK(value.readNonBlocking() == false);
442 BOOST_CHECK(index.readNonBlocking() == false);
443
444 // send something to v2
445 v2 = 11;
446 v2.write();
447
448 // check that we still don't receive anything yet
449 usleep(10000);
450 BOOST_CHECK(value.readNonBlocking() == false);
451 BOOST_CHECK(index.readNonBlocking() == false);
452
453 // run the application and check that we got the expected result
454 test.stepApplication();
455 BOOST_CHECK(value.readNonBlocking() == true);
456 BOOST_CHECK(index.readNonBlocking() == true);
457 BOOST_CHECK(value == 11);
458 BOOST_CHECK(index == 2);
459
460 // send something to v3
461 v3 = 12;
462 v3.write();
463
464 // check that we still don't receive anything yet
465 usleep(10000);
466 BOOST_CHECK(value.readNonBlocking() == false);
467 BOOST_CHECK(index.readNonBlocking() == false);
468
469 // run the application and check that we got the expected result
470 test.stepApplication();
471 BOOST_CHECK(value.readNonBlocking() == true);
472 BOOST_CHECK(index.readNonBlocking() == true);
473 BOOST_CHECK(value == 12);
474 BOOST_CHECK(index == 3);
475
476 // send something to v3 again
477 v3 = 13;
478 v3.write();
479
480 // check that we still don't receive anything yet
481 usleep(10000);
482 BOOST_CHECK(value.readNonBlocking() == false);
483 BOOST_CHECK(index.readNonBlocking() == false);
484
485 // run the application and check that we got the expected result
486 test.stepApplication();
487 BOOST_CHECK(value.readNonBlocking() == true);
488 BOOST_CHECK(index.readNonBlocking() == true);
489 BOOST_CHECK(value == 13);
490 BOOST_CHECK(index == 3);
491
492 // check that stepApplication() throws an exception if no input data is
493 // available
494 try {
495 test.stepApplication();
496 BOOST_ERROR("IllegalParameter exception expected.");
497 }
498 catch(ChimeraTK::logic_error&) {
499 }
500
501 // check that we still don't receive anything anymore
502 usleep(10000);
503 BOOST_CHECK(value.readNonBlocking() == false);
504 BOOST_CHECK(index.readNonBlocking() == false);
505 }
506
507 /********************************************************************************************************************/
508 /* test combination with trigger */
509
511 TestWithTriggerApplication() : Application("testApplication") {}
513
514 ctk::DeviceModule dev{this, dummySdm.data(), "/trigger"};
515 BlockingReadTestModule blockingReadTestModule{this, "blockingReadTestModule", "Module for testing blocking read"};
516 ReadAnyTestModule readAnyTestModule{this, "readAnyTestModule", "Module for testing readAny()"};
517 };
518
519 BOOST_AUTO_TEST_CASE(testWithTrigger) {
520 std::cout << "***************************************************************"
521 "******************************************************"
522 << std::endl;
523 std::cout << "==> testWithTrigger" << std::endl;
524
526
527 ctk::TestFacility test{app};
528 ctk::Device dev;
529 dev.open(dummySdm.data());
530 auto valueFromBlocking = test.getScalar<int32_t>("/blockingReadTestModule/someOutput");
531 auto index = test.getScalar<uint32_t>("/readAnyTestModule/index");
532 auto trigger = test.getVoid("/trigger");
533 auto v2 = dev.getScalarRegisterAccessor<int32_t>("/REG2.DUMMY_WRITEABLE");
534 test.runApplication();
535
536 // check that we don't receive anything yet
537 usleep(10000);
538 BOOST_CHECK(valueFromBlocking.readNonBlocking() == false);
539 BOOST_CHECK(index.readNonBlocking() == false);
540
541 // send something to v2 and send the trigger
542 v2 = 11;
543 v2.write();
544 trigger.write();
545
546 // check that we still don't receive anything yet
547 usleep(10000);
548 BOOST_CHECK(valueFromBlocking.readNonBlocking() == false);
549 BOOST_CHECK(index.readNonBlocking() == false);
550
551 // run the application and check that we got the expected result
552 test.stepApplication();
553 BOOST_CHECK(valueFromBlocking.readNonBlocking() == true);
554 BOOST_CHECK(index.readNonBlocking() == true);
555 BOOST_CHECK(valueFromBlocking == 11);
556 BOOST_CHECK(index == 2);
557
558 // again send something to v2 and send the trigger
559 v2 = 22;
560 v2.write();
561 trigger.write();
562
563 // check that we still don't receive anything yet
564 usleep(10000);
565 BOOST_CHECK(valueFromBlocking.readNonBlocking() == false);
566 BOOST_CHECK(index.readNonBlocking() == false);
567
568 // run the application and check that we got the expected result
569 test.stepApplication();
570 BOOST_CHECK(valueFromBlocking.readNonBlocking() == true);
571 BOOST_CHECK(index.readNonBlocking() == true);
572 BOOST_CHECK(valueFromBlocking == 22);
573 BOOST_CHECK(index == 2);
574
575 // check that stepApplication() throws an exception if no input data is
576 // available
577 try {
578 test.stepApplication();
579 BOOST_ERROR("IllegalParameter exception expected.");
580 }
581 catch(ChimeraTK::logic_error&) {
582 }
583
584 // check that we still don't receive anything anymore
585 usleep(10000);
586 BOOST_CHECK(valueFromBlocking.readNonBlocking() == false);
587 // BOOST_CHECK(valueFromAsync.readNonBlocking() == false);
588 BOOST_CHECK(index.readNonBlocking() == false);
589 }
590
591 /********************************************************************************************************************/
592 /* test convenience read functions */
593
597
598 BlockingReadTestModule blockingReadTestModule{this, "blockingReadTestModule", "Module for testing blocking read"};
599 };
600
601 BOOST_AUTO_TEST_CASE(testConvenienceRead) {
602 std::cout << "***************************************************************"
603 "******************************************************"
604 << std::endl;
605 std::cout << "==> testConvenienceRead" << std::endl;
606
608
609 ctk::TestFacility test{app};
610 test.runApplication();
611
612 // test blocking read when taking control in the test thread (note: the blocking read is executed in the app module!)
613 for(int i = 0; i < 5; ++i) {
614 test.writeScalar<int32_t>("/value", 120 + i);
615 test.stepApplication();
616 CHECK_TIMEOUT(test.readScalar<int32_t>("/blockingReadTestModule/someOutput") == 120 + i, 10000);
617 }
618
619 // same with array function (still a scalar variable behind, but this does not matter)
620 for(int i = 0; i < 5; ++i) {
621 std::vector<int32_t> myValue{120 + i};
622 test.writeArray<int32_t>("/value", myValue);
623 test.stepApplication();
625 test.readArray<int32_t>("/blockingReadTestModule/someOutput") == std::vector<int32_t>{120 + i}, 10000);
626 }
627 }
628
629 /********************************************************************************************************************/
630 /* test poll-type transfers mixed with push-type */
631
633 TestPollingApplication() : Application("testApplication") {}
635
636 PollingReadModule pollingReadModule{this, "pollingReadModule", "Module for testing poll-type transfers"};
637 };
638
639 BOOST_AUTO_TEST_CASE(testPolling) {
640 std::cout << "***************************************************************"
641 "******************************************************"
642 << std::endl;
643 std::cout << "==> testPolling" << std::endl;
644
645 TestPollingApplication app; // app.pollingReadModule.connectTo(app.cs);
646
647 ctk::TestFacility test{app};
648 test.runApplication();
649
650 auto pv_push = test.getScalar<int32_t>("/pollingReadModule/push");
651 auto pv_push2 = test.getScalar<int32_t>("/pollingReadModule/push2");
652 auto pv_poll = test.getScalar<int32_t>("/pollingReadModule/poll");
653 auto pv_valuePush = test.getScalar<int32_t>("/pollingReadModule/valuePush");
654 auto pv_valuePoll = test.getScalar<int32_t>("/pollingReadModule/valuePoll");
655 auto pv_state = test.getScalar<int>("/pollingReadModule/state");
656
657 // write values to 'push' and 'poll' and check result
658 pv_push = 120;
659 pv_push.write();
660 pv_poll = 42;
661 pv_poll.write();
662 test.stepApplication();
663 pv_valuePoll.read();
664 pv_valuePush.read();
665 pv_state.read();
666 BOOST_CHECK_EQUAL((int32_t)pv_valuePoll, 42);
667 BOOST_CHECK_EQUAL((int32_t)pv_valuePush, 120);
668 BOOST_CHECK_EQUAL((int32_t)pv_state, 1);
669
670 // this time the application gets triggered by push2, push is read
671 // non-blockingly (single value only)
672 pv_push = 22;
673 pv_push.write();
674 pv_poll = 44;
675 pv_poll.write();
676 pv_poll = 45;
677 pv_poll.write();
678 pv_push2.write();
679 test.stepApplication();
680 pv_valuePoll.read();
681 pv_valuePush.read();
682 pv_state.read();
683 BOOST_CHECK_EQUAL((int32_t)pv_valuePoll, 45);
684 BOOST_CHECK_EQUAL((int32_t)pv_valuePush, 22);
685 BOOST_CHECK_EQUAL((int32_t)pv_state, 2);
686
687 // this time the application gets triggered by push2, push is read with
688 // readLatest()
689 pv_push = 24;
690 pv_push.write();
691 pv_poll = 46;
692 pv_poll.write();
693 pv_push2.write();
694 test.stepApplication();
695 pv_valuePoll.read();
696 pv_valuePush.read();
697 pv_state.read();
698 BOOST_CHECK_EQUAL((int32_t)pv_valuePoll, 46);
699 BOOST_CHECK_EQUAL((int32_t)pv_valuePush, 24);
700 BOOST_CHECK_EQUAL((int32_t)pv_state, 3);
701
702 // provoke internal queue overflow in poll-type variable (should not make any difference)
703 pv_push = 25;
704 pv_push.write();
705 for(int i = 0; i < 10; ++i) {
706 pv_poll = 50 + i;
707 }
708 pv_poll.write();
709 pv_push2.write();
710 test.stepApplication();
711 pv_valuePoll.read();
712 pv_valuePush.read();
713 pv_state.read();
714 BOOST_CHECK_EQUAL((int32_t)pv_valuePoll, 59);
715 BOOST_CHECK_EQUAL((int32_t)pv_valuePush, 25);
716 BOOST_CHECK_EQUAL((int32_t)pv_state, 1);
717 }
718
719 /********************************************************************************************************************/
720 /* test poll-type transfers in combination with various FanOuts */
721
723 TestPollingThroughFanOutsApplication() : Application("AnotherTestApplication") {}
725
726 ctk::DeviceModule dev{this, dummySdm.data(), "/fakeTriggerToSatisfyUnusedRegister"};
729 };
730
731 BOOST_AUTO_TEST_CASE(testPollingThroughFanOuts) {
732 std::cout << "***************************************************************"
733 "******************************************************"
734 << std::endl;
735 std::cout << "==> testPollingThroughFanOuts" << std::endl;
736
737 // Case 1: FeedingFanOut
738 // ---------------------
739 {
742 // app.getTestableMode().setEnableDebug();
743
744 app.m2.poll1 = {&app.m2, "/m1/out1", "", ""};
745 app.m2.poll2 = {&app.m2, "/m1/out1", "", ""};
746 app.m2.push1 = {&app.m2, "/m1/out2", "", ""};
747
748 std::unique_lock<std::mutex> lk1(app.m1.mForChecking, std::defer_lock);
749 std::unique_lock<std::mutex> lk2(app.m2.mForChecking, std::defer_lock);
750
751 ctk::TestFacility test{app};
752
753 test.runApplication();
754
755 // test single value
756 BOOST_REQUIRE(lk1.try_lock());
757 app.m1.out1 = 123;
758 app.m1.out1.write();
759 app.m1.out2.write();
760 lk1.unlock();
761
762 test.stepApplication();
763
764 BOOST_REQUIRE(lk2.try_lock());
765 BOOST_CHECK_EQUAL(app.m2.poll1, 123);
766 BOOST_CHECK_EQUAL(app.m2.poll2, 123);
767 lk2.unlock();
768
769 // test queue overrun
770 BOOST_REQUIRE(lk1.try_lock());
771 for(int i = 0; i < 10; ++i) {
772 app.m1.out1 = 191 + i;
773 app.m1.out1.write();
774 app.m1.out2.write();
775 }
776 lk1.unlock();
777
778 test.stepApplication();
779
780 BOOST_REQUIRE(lk2.try_lock());
781 BOOST_CHECK_EQUAL(app.m2.poll1, 200);
782 BOOST_CHECK_EQUAL(app.m2.poll2, 200);
783 lk2.unlock();
784 }
785
786 // Case 2: ConsumingFanOut
787 // -----------------------
788 {
790 app.m1.poll1 = {&app.m1, "/REG1", "", ""};
791 app.m2.push1 = {&app.m2, "/REG1", "", ""};
792 // app.dev("REG1") >> app.m1.poll1 >> app.m2.push1;
793
794 std::unique_lock<std::mutex> lk1(app.m1.mForChecking, std::defer_lock);
795 std::unique_lock<std::mutex> lk2(app.m2.mForChecking, std::defer_lock);
796
797 ctk::Device dev(dummySdm.data());
798 auto reg1 = dev.getScalarRegisterAccessor<int>("REG1.DUMMY_WRITEABLE");
799
800 ctk::TestFacility test{app};
801 test.runApplication();
802
803 reg1 = 42;
804 reg1.write();
805
806 BOOST_REQUIRE(lk1.try_lock());
807 app.m1.poll1.read();
808 BOOST_CHECK_EQUAL(app.m1.poll1, 42);
809 lk1.unlock();
810 BOOST_REQUIRE(lk2.try_lock());
811 BOOST_CHECK_NE(app.m2.push1, 42);
812 lk2.unlock();
813
814 test.stepApplication();
815
816 BOOST_REQUIRE(lk2.try_lock());
817 BOOST_CHECK_EQUAL(app.m2.push1, 42);
818 lk2.unlock();
819 }
820
821 // Case 3: ThreadedFanOut
822 // ----------------------
823 {
824 std::cout << "=== Case 3" << std::endl;
826 std::cout << " HIER 1" << std::endl;
827 app.m1.poll2 = {&app.m1, "poll1", "", ""};
828 std::cout << " HIER 2" << std::endl;
829 app.m1.push1 = {&app.m1, "/m2/out2", "", ""};
830 std::cout << " HIER 3" << std::endl;
831
832 std::unique_lock<std::mutex> lk1(app.m1.mForChecking, std::defer_lock);
833 std::unique_lock<std::mutex> lk2(app.m2.mForChecking, std::defer_lock);
834
835 ctk::TestFacility test{app};
836
837 auto var = test.getScalar<int>("/m1/poll1");
838 app.getModel().writeGraphViz("testPollingThroughFanOuts.dot");
839 test.runApplication();
840
841 // test with single value
842 var = 666;
843 var.write();
844 BOOST_REQUIRE(lk2.try_lock());
845 app.m2.out2.write();
846 lk2.unlock();
847
848 test.stepApplication();
849
850 BOOST_REQUIRE(lk1.try_lock());
851 app.m1.poll1.read();
852 BOOST_CHECK_EQUAL(app.m1.poll1, 666);
853 app.m1.poll2.read();
854 BOOST_CHECK_EQUAL(app.m1.poll2, 666);
855 lk1.unlock();
856
857 // test with queue overrun
858 for(int i = 0; i < 10; ++i) {
859 var = 691 + i;
860 var.write();
861 }
862 BOOST_REQUIRE(lk2.try_lock());
863 app.m2.out2.write();
864 lk2.unlock();
865
866 test.stepApplication();
867
868 BOOST_REQUIRE(lk1.try_lock());
869 app.m1.poll1.read();
870 BOOST_CHECK_EQUAL(app.m1.poll1, 700);
871 app.m1.poll2.read();
872 BOOST_CHECK_EQUAL(app.m1.poll2, 700);
873 lk1.unlock();
874 }
875 }
876
877 /********************************************************************************************************************/
878 /* test device variables */
879
881 TestDeviceApplication() : Application("testApplication") {}
883
884 ctk::DeviceModule dev{this, dummySdm.data(), "/fakeTriggerToSatisfyUnusedRegister"};
885 PollingReadModule pollingReadModule{this, "pollingReadModule", "Module for testing poll-type transfers"};
886 };
887
889 std::cout << "***************************************************************"
890 "******************************************************"
891 << std::endl;
892 std::cout << "==> testDevice" << std::endl;
893
895 app.pollingReadModule.poll = {&app.pollingReadModule, "/REG1", "cm", "A poll-type input"};
896
897 ctk::TestFacility test(app);
898 auto push = test.getScalar<int32_t>("/pollingReadModule/push");
899 auto push2 = test.getScalar<int32_t>("/pollingReadModule/push2");
900 auto valuePoll = test.getScalar<int32_t>("/pollingReadModule/valuePoll");
901
902 ctk::Device dev(dummySdm.data());
903 auto r1 = dev.getScalarRegisterAccessor<int32_t>("/REG1.DUMMY_WRITEABLE");
904
905 test.runApplication();
906
907 // this is state 1 in PollingReadModule -> read()
908 r1 = 42;
909 r1.write();
910 push.write();
911 test.stepApplication();
912 valuePoll.read();
913 BOOST_CHECK_EQUAL(valuePoll, 42);
914
915 // this is state 2 in PollingReadModule -> readNonBlocking()
916 r1 = 43;
917 r1.write();
918 push2.write();
919 test.stepApplication();
920 valuePoll.read();
921 BOOST_CHECK_EQUAL(valuePoll, 43);
922
923 // this is state 2 in PollingReadModule -> readLatest()
924 r1 = 44;
925 r1.write();
926 push2.write();
927 test.stepApplication();
928 valuePoll.read();
929 BOOST_CHECK_EQUAL(valuePoll, 44);
930 }
931
932 /********************************************************************************************************************/
933 /* test initial values (from control system variables) */
934
936 TestInitialApplication() : Application("AnotherTestApplication") {}
938
941 };
942
943 BOOST_AUTO_TEST_CASE(testInitialValues) {
944 std::cout << "***************************************************************"
945 "******************************************************"
946 << std::endl;
947 std::cout << "==> testInitialValues" << std::endl;
948
950 // app.findTag(".*").connectTo(app.cs);
951
952 std::unique_lock<std::mutex> lk1(app.m1.mForChecking, std::defer_lock);
953 std::unique_lock<std::mutex> lk2(app.m2.mForChecking, std::defer_lock);
954
955 ctk::TestFacility test{app};
956
957 test.setScalarDefault<int>("/m1/push1", 42);
958 test.setScalarDefault<int>("/m1/poll1", 43);
959 test.setScalarDefault<int>("/m2/poll2", 44);
960
961 test.runApplication();
962
963 BOOST_REQUIRE(lk1.try_lock());
964 BOOST_CHECK(!app.m1.hasRead);
965 BOOST_CHECK_EQUAL(app.m1.push1, 42);
966 BOOST_CHECK_EQUAL(app.m1.poll1, 43);
967 BOOST_CHECK_EQUAL(app.m1.poll2, 0);
968 lk1.unlock();
969 BOOST_REQUIRE(lk2.try_lock());
970 BOOST_CHECK(!app.m2.hasRead);
971 BOOST_CHECK_EQUAL(app.m2.push1, 0);
972 BOOST_CHECK_EQUAL(app.m2.poll1, 0);
973 BOOST_CHECK_EQUAL(app.m2.poll2, 44);
974 lk2.unlock();
975 }
976
977 /********************************************************************************************************************/
978
992 BOOST_AUTO_TEST_CASE(TestableModeLockShared) {
993 std::cout << "TestableModeLockShared" << std::endl;
994
995 const static std::string CDD1 = "(ExceptionDummy:1?map=recoveryGroups.map)";
996 static std::latch blockInitHandler{2};
997
998 class MyApp : public ctk::Application {
999 public:
1000 using Application::Application;
1001 ~MyApp() { shutdown(); }
1002
1003 class MyMod : public ctk::ApplicationModule {
1004 public:
1005 using ApplicationModule::ApplicationModule;
1006 ctk::ScalarOutput<int32_t> reg2{this, "/MyModule/actuator", "", ""};
1007 void mainLoop() override {
1008 blockInitHandler.arrive_and_wait();
1009 reg2.setAndWrite(42);
1010 }
1011 } myMod{this, "MyMod", ""};
1012
1013 ctk::DeviceModule dev1{this, CDD1, "/trigger", [&](ctk::Device&) { blockInitHandler.arrive_and_wait(); }};
1014 } myApp{"MyApp"};
1015
1016 ctk::Device dev(CDD1);
1017
1018 ctk::TestFacility test(myApp);
1019
1020 test.runApplication();
1021
1022 CHECK_EQUAL_TIMEOUT(dev.read<int32_t>("/MyModule/actuator"), 42, 30000);
1023 }
1024
1025 /********************************************************************************************************************/
1026
1027} // namespace Tests::testTestFacilities
#define CHECK_EQUAL_TIMEOUT(left, right, maxMilliseconds)
Model::RootProxy getModel()
Return the root of the application model.
Definition Application.h:75
void run() override
Execute the module.
void initialise() override
void debugMakeConnections()
Enable debug output for the ConnectionMaker.
void shutdown() override
This will remove the global pointer to the instance and allows creating another instance afterwards.
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.
void writeGraphViz(const std::string &filename, Args... args) const
Implementations of RootProxy.
Definition Model.h:1789
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
void setAndWrite(UserType newValue, VersionNumber versionNumber)=delete
Convenience class for input scalar accessors with 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.
InvalidityTracer application module.
BOOST_AUTO_TEST_CASE(testNoDecorator)
constexpr std::string_view dummySdm
Convenience class for output scalar accessors (always UpdateMode::push)
Convenience class for input scalar accessors with UpdateMode::poll.
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 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 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...
#define CHECK_TIMEOUT(condition, maxMilliseconds)
void mainLoop() override