ChimeraTK-ApplicationCore 04.06.00
Loading...
Searching...
No Matches
testAppModuleConnections.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#include "Application.h"
4#include "ApplicationModule.h"
5#include "ArrayAccessor.h"
6#include "ScalarAccessor.h"
7#include "TestFacility.h"
8
9#include <ChimeraTK/BackendFactory.h>
10
11#include <boost/mpl/list.hpp>
12
13#include <future>
14
15#define BOOST_NO_EXCEPTIONS
16#define BOOST_TEST_MODULE testAppModuleConnections
17#include <boost/test/included/unit_test.hpp>
18#undef BOOST_NO_EXCEPTIONS
19
20using namespace boost::unit_test_framework;
21namespace ctk = ChimeraTK;
22
24
25 // list of user types the accessors are tested with
26 using test_types = boost::mpl::list<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, float, double>;
27
28 /********************************************************************************************************************/
29 /* the ApplicationModule for the test is a template of the user type */
30
31 template<typename T>
33 TestModuleFeed(ctk::ModuleGroup* owner, const std::string& name, const std::string& description,
34 const std::unordered_set<std::string>& tags = {}, bool unregister = false)
35 : ApplicationModule(owner, name, description, tags), mainLoopStarted(2) {
36 if(unregister) {
37 owner->unregisterModule(this);
38 }
39 }
40
44
45 // We do not use testable mode for this test, so we need this barrier to synchronise to the beginning of the
46 // mainLoop(). This is required since the mainLoopWrapper accesses the module variables before the start of the
47 // mainLoop.
48 // execute this right after the Application::run():
49 // app.testModule.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
50 boost::barrier mainLoopStarted;
51
52 void prepare() override {
53 incrementDataFaultCounter(); // force all outputs to invalid
54 writeAll(); // write initial values
55 decrementDataFaultCounter(); // validity according to input validity
56 }
57
58 void mainLoop() override { mainLoopStarted.wait(); }
59 };
60
61 template<typename T>
63 TestModuleConsume(ctk::ModuleGroup* owner, const std::string& name, const std::string& description,
64 const std::unordered_set<std::string>& tags = {}, bool unregister = false)
65 : ApplicationModule(owner, name, description, tags), mainLoopStarted(2) {
66 if(unregister) {
67 owner->unregisterModule(this);
68 }
69 }
70
74
77
78 // We do not use testable mode for this test, so we need this barrier to synchronise to the beginning of the
79 // mainLoop(). This is required since the mainLoopWrapper accesses the module variables before the start of the
80 // mainLoop.
81 // execute this right after the Application::run():
82 // app.testModule.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
83 boost::barrier mainLoopStarted;
84
85 void prepare() override {
86 incrementDataFaultCounter(); // force all outputs to invalid
87 writeAll(); // write initial values
88 decrementDataFaultCounter(); // validity according to input validity
89 }
90
91 void mainLoop() override { mainLoopStarted.wait(); }
92 };
93
94 /********************************************************************************************************************/
95 /* dummy application */
96
97 template<typename T>
99 TestApplication() : Application("testSuite") {}
100 ~TestApplication() override { shutdown(); }
101
102 TestModuleFeed<T> testModuleFeed{this, "testModuleFeed", "The test module"};
103 TestModuleConsume<T> testModuleConsume{this, "testModuleConsume", "The other test module"};
104 };
105
106 /********************************************************************************************************************/
107 /* test case for two scalar accessors in push mode */
108
109 BOOST_AUTO_TEST_CASE_TEMPLATE(testTwoScalarPushAccessors, T, test_types) {
110 // FIXME: With the new scheme, there cannot be a 1:1 module connection any more, it will always be a network
111 // involving the ControlSystem
112 std::cout << "*** testTwoScalarPushAccessors<" << typeid(T).name() << ">" << std::endl;
113
115 app.testModuleFeed.feedingPush = {&app.testModuleFeed, "/testTwoScalarPushAccessors", "", ""};
116 app.testModuleConsume.consumingPush = {&app.testModuleConsume, "/testTwoScalarPushAccessors", "", ""};
117
118 ctk::TestFacility tf{app, false};
119 tf.runApplication();
120 app.testModuleFeed.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
121 app.testModuleConsume.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
122
123 // single threaded test
124 app.testModuleConsume.consumingPush = 0;
125 app.testModuleFeed.feedingPush = 42;
126 BOOST_CHECK(app.testModuleConsume.consumingPush == 0);
127 app.testModuleFeed.feedingPush.write();
128 BOOST_CHECK(app.testModuleConsume.consumingPush == 0);
129 app.testModuleConsume.consumingPush.read();
130 BOOST_CHECK(app.testModuleConsume.consumingPush == 42);
131
132 // launch read() on the consumer asynchronously and make sure it does not yet
133 // receive anything
134 auto futRead = std::async(std::launch::async, [&app] { app.testModuleConsume.consumingPush.read(); });
135 BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout);
136
137 BOOST_CHECK(app.testModuleConsume.consumingPush == 42);
138
139 // write to the feeder
140 app.testModuleFeed.feedingPush = 120;
141 app.testModuleFeed.feedingPush.write();
142
143 // check that the consumer now receives the just written value
144 BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(2000)) == std::future_status::ready);
145 BOOST_CHECK(app.testModuleConsume.consumingPush == 120);
146 }
147
148 /********************************************************************************************************************/
149 /* test case for four scalar accessors in push mode: one feeder and three
150 * consumers */
151
152 BOOST_AUTO_TEST_CASE_TEMPLATE(testFourScalarPushAccessors, T, test_types) {
153 std::cout << "*** testFourScalarPushAccessors<" << typeid(T).name() << ">" << std::endl;
154
156 app.testModuleConsume.consumingPush = {&app.testModuleConsume, "/testFourScalarPushAccessors", "", ""};
157 app.testModuleConsume.consumingPush2 = {&app.testModuleConsume, "/testFourScalarPushAccessors", "", ""};
158 app.testModuleFeed.feedingPush = {&app.testModuleFeed, "/testFourScalarPushAccessors", "", ""};
159 app.testModuleConsume.consumingPush3 = {&app.testModuleConsume, "/testFourScalarPushAccessors", "", ""};
160
161 ctk::TestFacility tf{app, false};
162 tf.runApplication();
163 app.testModuleFeed.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
164 app.testModuleConsume.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
165
166 // single threaded test
167 app.testModuleConsume.consumingPush = 0;
168 app.testModuleConsume.consumingPush2 = 2;
169 app.testModuleConsume.consumingPush3 = 3;
170 app.testModuleFeed.feedingPush = 42;
171 BOOST_CHECK(app.testModuleConsume.consumingPush == 0);
172 BOOST_CHECK(app.testModuleConsume.consumingPush2 == 2);
173 BOOST_CHECK(app.testModuleConsume.consumingPush3 == 3);
174 app.testModuleFeed.feedingPush.write();
175 BOOST_CHECK(app.testModuleConsume.consumingPush == 0);
176 BOOST_CHECK(app.testModuleConsume.consumingPush2 == 2);
177 BOOST_CHECK(app.testModuleConsume.consumingPush3 == 3);
178 app.testModuleConsume.consumingPush.read();
179 BOOST_CHECK(app.testModuleConsume.consumingPush == 42);
180 BOOST_CHECK(app.testModuleConsume.consumingPush2 == 2);
181 BOOST_CHECK(app.testModuleConsume.consumingPush3 == 3);
182 app.testModuleConsume.consumingPush2.read();
183 BOOST_CHECK(app.testModuleConsume.consumingPush == 42);
184 BOOST_CHECK(app.testModuleConsume.consumingPush2 == 42);
185 BOOST_CHECK(app.testModuleConsume.consumingPush3 == 3);
186 app.testModuleConsume.consumingPush3.read();
187 BOOST_CHECK(app.testModuleConsume.consumingPush == 42);
188 BOOST_CHECK(app.testModuleConsume.consumingPush2 == 42);
189 BOOST_CHECK(app.testModuleConsume.consumingPush3 == 42);
190
191 // launch read() on the consumers asynchronously and make sure it does not yet
192 // receive anything
193 auto futRead = std::async(std::launch::async, [&app] { app.testModuleConsume.consumingPush.read(); });
194 auto futRead2 = std::async(std::launch::async, [&app] { app.testModuleConsume.consumingPush2.read(); });
195 auto futRead3 = std::async(std::launch::async, [&app] { app.testModuleConsume.consumingPush3.read(); });
196 BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout);
197 BOOST_CHECK(futRead2.wait_for(std::chrono::milliseconds(1)) == std::future_status::timeout);
198 BOOST_CHECK(futRead3.wait_for(std::chrono::milliseconds(1)) == std::future_status::timeout);
199
200 BOOST_CHECK(app.testModuleConsume.consumingPush == 42);
201 BOOST_CHECK(app.testModuleConsume.consumingPush2 == 42);
202 BOOST_CHECK(app.testModuleConsume.consumingPush3 == 42);
203
204 // write to the feeder
205 app.testModuleFeed.feedingPush = 120;
206 app.testModuleFeed.feedingPush.write();
207
208 // check that the consumers now receive the just written value
209 BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(2000)) == std::future_status::ready);
210 BOOST_CHECK(futRead2.wait_for(std::chrono::milliseconds(2000)) == std::future_status::ready);
211 BOOST_CHECK(futRead3.wait_for(std::chrono::milliseconds(2000)) == std::future_status::ready);
212 BOOST_CHECK(app.testModuleConsume.consumingPush == 120);
213 BOOST_CHECK(app.testModuleConsume.consumingPush2 == 120);
214 BOOST_CHECK(app.testModuleConsume.consumingPush3 == 120);
215 }
216
217 /********************************************************************************************************************/
218 /* test case for two scalar accessors, feeder in push mode and consumer in poll
219 * mode */
220
221 BOOST_AUTO_TEST_CASE_TEMPLATE(testTwoScalarPushPollAccessors, T, test_types) {
222 std::cout << "*** testTwoScalarPushPollAccessors<" << typeid(T).name() << ">" << std::endl;
223
225
226 app.testModuleFeed.feedingPush = {&app.testModuleFeed, "/testTwoScalarPushPollAccessors", "", ""};
227 app.testModuleConsume.consumingPoll = {&app.testModuleConsume, "/testTwoScalarPushPollAccessors", "", ""};
228
229 ctk::TestFacility tf{app, false};
230 tf.runApplication();
231 app.testModuleFeed.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
232 app.testModuleConsume.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
233
234 // single threaded test only, since read() does not block in this case
235 app.testModuleConsume.consumingPoll = 0;
236 app.testModuleFeed.feedingPush = 42;
237 BOOST_CHECK(app.testModuleConsume.consumingPoll == 0);
238 app.testModuleFeed.feedingPush.write();
239 BOOST_CHECK(app.testModuleConsume.consumingPoll == 0);
240 app.testModuleConsume.consumingPoll.read();
241 BOOST_CHECK(app.testModuleConsume.consumingPoll == 42);
242 app.testModuleConsume.consumingPoll.read();
243 BOOST_CHECK(app.testModuleConsume.consumingPoll == 42);
244 app.testModuleConsume.consumingPoll.read();
245 BOOST_CHECK(app.testModuleConsume.consumingPoll == 42);
246 app.testModuleFeed.feedingPush = 120;
247 BOOST_CHECK(app.testModuleConsume.consumingPoll == 42);
248 app.testModuleFeed.feedingPush.write();
249 BOOST_CHECK(app.testModuleConsume.consumingPoll == 42);
250 app.testModuleConsume.consumingPoll.read();
251 BOOST_CHECK(app.testModuleConsume.consumingPoll == 120);
252 app.testModuleConsume.consumingPoll.read();
253 BOOST_CHECK(app.testModuleConsume.consumingPoll == 120);
254 app.testModuleConsume.consumingPoll.read();
255 BOOST_CHECK(app.testModuleConsume.consumingPoll == 120);
256 }
257
258 /********************************************************************************************************************/
259 /* test case for two array accessors in push mode */
260
261 BOOST_AUTO_TEST_CASE_TEMPLATE(testTwoArrayAccessors, T, test_types) {
262 std::cout << "*** testTwoArrayAccessors<" << typeid(T).name() << ">" << std::endl;
263
265
266 // app.testModuleFeed.feedingArray >> app.testModuleConsume.consumingPushArray;
267 app.testModuleFeed.feedingArray = {&app.testModuleFeed, "/testFourScalarPushAccessors", "", 10, ""};
268 app.testModuleConsume.consumingPushArray = {&app.testModuleConsume, "/testFourScalarPushAccessors", "", 10, ""};
269 ctk::TestFacility tf{app, false};
270 tf.runApplication();
271
272 app.testModuleFeed.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
273 app.testModuleConsume.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
274
275 BOOST_CHECK(app.testModuleFeed.feedingArray.getNElements() == 10);
276 BOOST_CHECK(app.testModuleConsume.consumingPushArray.getNElements() == 10);
277
278 // single threaded test
279 for(auto& val : app.testModuleConsume.consumingPushArray) {
280 val = 0;
281 }
282 for(unsigned int i = 0; i < 10; ++i) {
283 app.testModuleFeed.feedingArray[i] = 99 + (T)i;
284 }
285 for(auto& val : app.testModuleConsume.consumingPushArray) {
286 BOOST_CHECK(val == 0);
287 }
288 app.testModuleFeed.feedingArray.write();
289 for(auto& val : app.testModuleConsume.consumingPushArray) {
290 BOOST_CHECK(val == 0);
291 }
292 app.testModuleConsume.consumingPushArray.read();
293 for(unsigned int i = 0; i < 10; ++i) {
294 BOOST_CHECK(app.testModuleConsume.consumingPushArray[i] == 99 + (T)i);
295 }
296
297 // launch read() on the consumer asynchronously and make sure it does not yet
298 // receive anything
299 auto futRead = std::async(std::launch::async, [&app] { app.testModuleConsume.consumingPushArray.read(); });
300 BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout);
301
302 for(unsigned int i = 0; i < 10; ++i) {
303 BOOST_CHECK(app.testModuleConsume.consumingPushArray[i] == 99 + (T)i);
304 }
305
306 // write to the feeder
307 for(unsigned int i = 0; i < 10; ++i) {
308 app.testModuleFeed.feedingArray[i] = 42 - (T)i;
309 }
310 app.testModuleFeed.feedingArray.write();
311
312 // check that the consumer now receives the just written value
313 BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(2000)) == std::future_status::ready);
314 for(unsigned int i = 0; i < 10; ++i) {
315 BOOST_CHECK(app.testModuleConsume.consumingPushArray[i] == 42 - (T)i);
316 }
317 }
318
319 /********************************************************************************************************************/
320 /* test case for connecting array of length 1 with scalar */
321
323 std::cout << "*** testPseudoArray<" << typeid(T).name() << ">" << std::endl;
324
326
327 // app.testModuleFeed.feedingPseudoArray >> app.testModuleConsume.consumingPush;
328 app.testModuleFeed.feedingPseudoArray = {&app.testModuleFeed, "/testPseudoArray", "", 1, ""};
329 app.testModuleConsume.consumingPush = {&app.testModuleConsume, "/testPseudoArray", "", ""};
330
331 // run the app
332 ctk::TestFacility tf{app, false};
333 tf.runApplication();
334 app.testModuleFeed.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
335 app.testModuleConsume.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
336
337 // test data transfer
338 app.testModuleFeed.feedingPseudoArray[0] = 33;
339 app.testModuleFeed.feedingPseudoArray.write();
340 app.testModuleConsume.consumingPush.read();
341 BOOST_CHECK(app.testModuleConsume.consumingPush == 33);
342 }
343
344 /********************************************************************************************************************/
345 /* test case for EntityOwner::constant() */
346
347 template<typename T>
349 ConstantTestModule(ctk::ModuleGroup* owner, const std::string& name, const std::string& description,
350 const std::unordered_set<std::string>& tags = {})
351 : ApplicationModule(owner, name, description, tags), mainLoopStarted(2) {}
352
355 // test a second accessor of a different type but defining the constant with the same type as before
357
358 // We do not use testable mode for this test, so we need this barrier to synchronise to the beginning of the
359 // mainLoop(). This is required since the mainLoopWrapper accesses the module variables before the start of the
360 // mainLoop.
361 // execute this right after the Application::run():
362 // app.testModule.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
363 boost::barrier mainLoopStarted;
364
365 void prepare() override {
366 incrementDataFaultCounter(); // force all outputs to invalid
367 writeAll(); // write initial values
368 decrementDataFaultCounter(); // validity according to input validity
369 }
370
371 void mainLoop() override { mainLoopStarted.wait(); }
372 };
373
374 template<typename T>
378
379 ConstantTestModule<T> testModule{this, "testModule", "The test module"};
380 };
381
383 std::cout << "*** testConstants<" << typeid(T).name() << ">" << std::endl;
384
386
387 ctk::TestFacility tf{app, false};
388 tf.runApplication();
389 app.testModule.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
390
391 BOOST_TEST(app.testModule.consumingPush == 66);
392 BOOST_TEST(app.testModule.consumingPoll == 77);
393 BOOST_TEST(boost::starts_with(std::string(app.testModule.myStringConstant), "66")); // might be 66 or 66.000000
394
395 BOOST_TEST(app.testModule.consumingPush.readNonBlocking() == false);
396
397 app.testModule.consumingPoll = 0;
398 app.testModule.consumingPoll.read();
399 BOOST_TEST(app.testModule.consumingPoll == 77);
400 }
401
402 /********************************************************************************************************************/
403 /********************************************************************************************************************/
404
407 ctk::ModuleGroup* owner, const std::string& name, const std::string& description, bool unregister = false)
408 : ApplicationModule(owner, name, description) {
409 if(unregister) {
410 disable();
411 }
412 }
413
414 ctk::ScalarOutput<int> out{this, "out", "", "Some output"};
415 ctk::ScalarPushInput<int> in{this, "in", "", "Some input"};
416
417 void mainLoop() override {
418 while(true) {
419 out = 1 + in;
420 writeAll();
421 readAll();
422 }
423 }
424 };
425
426 /********************************************************************************************************************/
427
429 TestAppSelfUnregisteringModule() : Application("SelfUnregisteringModuleApp") {}
431
432 SelfUnregisteringModule a{this, "a", "First test module which stays"};
433 SelfUnregisteringModule b{this, "b", "The test module which unregisters itself", true};
434 SelfUnregisteringModule c{this, "c", "Another test module which stays"};
435 };
436
437 /********************************************************************************************************************/
438 /* test case for EntityOwner::constant() */
439
440 BOOST_AUTO_TEST_CASE(testSelfUnregisteringModule) {
441 std::cout << "*** testSelfUnregisteringModule" << std::endl;
442
445
446 ctk::TestFacility tf{app};
447
448 auto& pvm = *tf.getPvManager();
449 BOOST_TEST(pvm.hasProcessVariable("a/out"));
450 BOOST_TEST(pvm.hasProcessVariable("a/in"));
451 BOOST_TEST(!pvm.hasProcessVariable("b/out"));
452 BOOST_TEST(!pvm.hasProcessVariable("b/in"));
453 BOOST_TEST(pvm.hasProcessVariable("c/out"));
454 BOOST_TEST(pvm.hasProcessVariable("c/in"));
455
456 auto aout = tf.getScalar<int>("a/out");
457 auto ain = tf.getScalar<int>("a/in");
458 auto cout = tf.getScalar<int>("c/out");
459 auto cin = tf.getScalar<int>("c/in");
460
461 tf.setScalarDefault("a/in", 1000);
462 tf.setScalarDefault("c/in", 2000);
463
464 tf.runApplication();
465
466 BOOST_TEST(aout == 1001);
467 BOOST_TEST(cout == 2001);
468
469 ain.setAndWrite(42);
470 tf.stepApplication();
471 BOOST_TEST(aout.readNonBlocking());
472 BOOST_TEST(!cout.readNonBlocking());
473 BOOST_TEST(aout == 43);
474
475 cin.setAndWrite(120);
476 tf.stepApplication();
477 BOOST_TEST(!aout.readNonBlocking());
478 BOOST_TEST(cout.readNonBlocking());
479 BOOST_TEST(cout == 121);
480 }
481
482 /********************************************************************************************************************/
483
484} // namespace Tests::testAppModuleConnections
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.
ApplicationModule()=default
Default constructor: Allows late initialisation of modules (e.g.
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.
Convenience class for input array accessors with UpdateMode::push.
std::string constant(T value)
Create a variable name which will be automatically connected with a constant value.
void unregisterModule(Module *module) override
Unregister another module as a sub-module.
friend class Application
Definition ModuleGroup.h:47
void readAll(bool includeReturnChannels=false)
Read all readable variables in the group.
Definition Module.cc:72
void disable()
Disable the module such that it is not part of the Application.
Definition Module.cc:257
void writeAll(bool includeReturnChannels=false)
Just call write() on all writable variables in the group.
Definition Module.cc:157
Convenience class for input scalar accessors with UpdateMode::push.
Helper class to facilitate tests of applications based on ApplicationCore.
InvalidityTracer application module.
boost::mpl::list< int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, float, double > test_types
BOOST_AUTO_TEST_CASE(testSelfUnregisteringModule)
BOOST_AUTO_TEST_CASE_TEMPLATE(testTwoScalarPushAccessors, T, test_types)
Convenience class for output array accessors (always UpdateMode::push)
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.
ConstantTestModule(ctk::ModuleGroup *owner, const std::string &name, const std::string &description, const std::unordered_set< std::string > &tags={})
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...
SelfUnregisteringModule(ctk::ModuleGroup *owner, const std::string &name, const std::string &description, bool unregister=false)
void prepare() override
Prepare the execution of the module.
TestModuleConsume(ctk::ModuleGroup *owner, const std::string &name, const std::string &description, const std::unordered_set< std::string > &tags={}, bool unregister=false)
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...
TestModuleFeed(ctk::ModuleGroup *owner, const std::string &name, const std::string &description, const std::unordered_set< std::string > &tags={}, bool unregister=false)
void prepare() override
Prepare the execution of the module.