ChimeraTK-ApplicationCore 04.06.00
Loading...
Searching...
No Matches
testDeviceAccessors.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 <future>
4
5#define BOOST_TEST_MODULE testDeviceAccessors
6
7#include "Application.h"
8#include "ApplicationModule.h"
9#include "DeviceModule.h"
10#include "ScalarAccessor.h"
11#include "TestFacility.h"
12
13#include <ChimeraTK/BackendFactory.h>
14#include <ChimeraTK/Device.h>
15#include <ChimeraTK/NDRegisterAccessor.h>
16
17#include <boost/mpl/list.hpp>
18#include <boost/test/included/unit_test.hpp>
19
20using namespace boost::unit_test_framework;
21namespace ctk = ChimeraTK;
22
24
25 /********************************************************************************************************************/
26
27#define CHECK_TIMEOUT(condition, maxMilliseconds) \
28 { \
29 std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); \
30 while(!(condition)) { \
31 bool timeout_reached = (std::chrono::steady_clock::now() - t0) > std::chrono::milliseconds(maxMilliseconds); \
32 BOOST_CHECK(!timeout_reached); \
33 if(timeout_reached) break; \
34 usleep(1000); \
35 } \
36 }
37
38 /********************************************************************************************************************/
39
41 TestModule(ctk::ModuleGroup* owner, const std::string& name, const std::string& description,
42 const std::unordered_set<std::string>& tags = {})
43 : ApplicationModule(owner, name, description, tags) {}
44
45 ctk::ScalarPollInput<int> consumingPoll{this, "consumingPoll", "MV/m", "Description"};
46
47 ctk::ScalarPushInput<int> consumingPush{this, "consumingPush", "MV/m", "Description"};
48 ctk::ScalarPushInput<int> consumingPush2{this, "consumingPush2", "MV/m", "Description"};
49
50 ctk::ScalarOutput<int> feedingToDevice{this, "feedingToDevice", "MV/m", "Description"};
51
52 void prepare() override {
53 incrementDataFaultCounter(); // force data to be flagged as faulty
54 writeAll();
55 decrementDataFaultCounter(); // data validity depends on inputs
56 }
57
58 void mainLoop() override {}
59 };
60
61 /********************************************************************************************************************/
62 /* dummy application */
63
65 TestApplication() : Application("testSuite") {}
66 ~TestApplication() override { shutdown(); }
67
68 TestModule testModule{this, "testModule", "The test module"};
69 ctk::DeviceModule dev{this, "Dummy0", "/dummyTriggerForUnusedVariables"};
71
72 // note: direct device-to-controlsystem connections are tested in testControlSystemAccessors!
73 };
74
75 /********************************************************************************************************************/
76 /* test feeding a scalar to a device */
77
78 BOOST_AUTO_TEST_CASE(testFeedToDevice) {
79 std::cout << "testFeedToDevice" << std::endl;
80
81 ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap");
82
84
85 app.testModule.feedingToDevice = ctk::ScalarOutput<int>{&app.testModule, "/MyModule/actuator", "MV/m", ""};
86
87 ctk::TestFacility test{app};
88 test.runApplication();
90 dev.open("Dummy0");
91 auto regacc = dev.getScalarRegisterAccessor<int>("/MyModule/actuator");
92
93 regacc = 0;
96 regacc.read();
97 BOOST_CHECK(regacc == 42);
99 regacc.read();
100 BOOST_CHECK(regacc == 42);
102 regacc.read();
103 BOOST_CHECK(regacc == 120);
104 }
105
106 /********************************************************************************************************************/
107 /* test feeding a scalar to two different device registers */
108
109 BOOST_AUTO_TEST_CASE(testFeedToDeviceFanOut) {
110 std::cout << "testFeedToDeviceFanOut" << std::endl;
111
112 ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap");
113
114 TestApplication app;
115
116 app.testModule.feedingToDevice = {&app.testModule, "/MyModule/actuator", "MV/m", ""};
117 app.dev2 = {&app, "Dummy0wo"};
118
119 app.getModel().writeGraphViz("testFeedToDeviceFanOut.dot");
120
121 ctk::TestFacility test{app};
122 test.runApplication();
123
124 ChimeraTK::Device dev("Dummy0");
125 ChimeraTK::Device dev2("Dummy0wo");
126
127 auto regac = dev.getScalarRegisterAccessor<int>("/MyModule/actuator");
128 auto regrb = dev2.getScalarRegisterAccessor<int>("/MyModule/actuator");
129
130 regac = 0;
131 regrb = 0;
134 regac.read();
135 BOOST_CHECK(regac == 42);
136 regrb.read();
137 BOOST_CHECK(regrb == 42);
138 app.testModule.feedingToDevice = 120;
139 regac.read();
140 BOOST_CHECK(regac == 42);
141 regrb.read();
142 BOOST_CHECK(regrb == 42);
144 regac.read();
145 BOOST_CHECK(regac == 120);
146 regrb.read();
147 BOOST_CHECK(regrb == 120);
148 }
149
150 /********************************************************************************************************************/
151 /* test consuming a scalar from a device */
152
153 BOOST_AUTO_TEST_CASE(testConsumeFromDevice) {
154 std::cout << "testConsumeFromDevice" << std::endl;
155
156 ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap");
157
158 TestApplication app;
159
160 app.testModule.consumingPoll = {&app.testModule, "/MyModule/readBack", "MV/m", ""};
161
162 ctk::TestFacility test{app};
163
164 // Set the default value through the CS. The actuator and readBack map to the same register in the map file
165 // Not setting a default will overwrite whatever is put into the device before the TestFacility::runApplication()
166 // So we feed the default for the register through the IV mechanism of TestFacility.
167 test.setScalarDefault<int>("/MyModule/actuator", 1);
168 test.runApplication();
169
171 dev.open("Dummy0");
172 auto regacc = dev.getScalarRegisterAccessor<int>("/MyModule/readBack.DUMMY_WRITEABLE");
173
174 BOOST_REQUIRE(app.testModule.hasReachedTestableMode());
175
176 BOOST_CHECK(app.testModule.consumingPoll == 1);
177 regacc = 42;
178 regacc.write();
179 BOOST_CHECK(app.testModule.consumingPoll == 1);
181 BOOST_CHECK(app.testModule.consumingPoll == 42);
183 BOOST_CHECK(app.testModule.consumingPoll == 42);
185 BOOST_CHECK(app.testModule.consumingPoll == 42);
186 regacc = 120;
187 regacc.write();
188 BOOST_CHECK(app.testModule.consumingPoll == 42);
190 BOOST_CHECK(app.testModule.consumingPoll == 120);
192 BOOST_CHECK(app.testModule.consumingPoll == 120);
194 BOOST_CHECK(app.testModule.consumingPoll == 120);
195 }
196
197 /********************************************************************************************************************/
198 /* test consuming a scalar from a device with a ConsumingFanOut (i.e. one
199 * poll-type consumer and several push-type consumers). */
200
201 BOOST_AUTO_TEST_CASE(testConsumingFanOut) {
202 std::cout << "testConsumingFanOut" << std::endl;
203
204 ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap");
205
206 TestApplication app;
207
208 app.testModule.consumingPoll = {&app.testModule, "/MyModule/readBack", "MV/m", ""};
209 app.testModule.consumingPush = {&app.testModule, "/MyModule/readBack", "MV/m", ""};
210 app.testModule.consumingPush2 = {&app.testModule, "/MyModule/readBack", "MV/m", ""};
211
212 ctk::TestFacility test{app};
213
214 // Set the default value through the CS. The actuator and readBack map to the same register in the map file
215 // Not setting a default will overwrite whatever is put into the device before the TestFacility::runApplication()
216 // So we feed the default for the register through the IV mechanism of TestFacility.
217 test.setScalarDefault<int>("/MyModule/actuator", 1);
219 dev.open("Dummy0");
220 auto regacc = dev.getScalarRegisterAccessor<int>("/MyModule/readBack.DUMMY_WRITEABLE");
221 test.runApplication();
222
223 // single threaded test only, since read() does not block in this case
224 BOOST_CHECK(app.testModule.consumingPoll == 1);
225 BOOST_CHECK(app.testModule.consumingPush2 == 1);
226 regacc = 42;
227 regacc.write();
228
229 BOOST_CHECK(app.testModule.consumingPoll == 1);
230 BOOST_CHECK(app.testModule.consumingPush2 == 1);
231 BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false);
232 BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == false);
233 BOOST_CHECK(app.testModule.consumingPush == 1);
234 BOOST_CHECK(app.testModule.consumingPush2 == 1);
236 BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == true);
237 BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == true);
238 BOOST_CHECK(app.testModule.consumingPoll == 42);
239 BOOST_CHECK(app.testModule.consumingPush == 42);
240 BOOST_CHECK(app.testModule.consumingPush2 == 42);
241 BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false);
242 BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == false);
244 BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == true);
245 BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == true);
246 BOOST_CHECK(app.testModule.consumingPoll == 42);
247 BOOST_CHECK(app.testModule.consumingPush == 42);
248 BOOST_CHECK(app.testModule.consumingPush2 == 42);
249 BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false);
250 BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == false);
252 BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == true);
253 BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == true);
254 BOOST_CHECK(app.testModule.consumingPoll == 42);
255 BOOST_CHECK(app.testModule.consumingPush == 42);
256 BOOST_CHECK(app.testModule.consumingPush2 == 42);
257 BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false);
258 BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == false);
259 regacc = 120;
260 regacc.write();
261 BOOST_CHECK(app.testModule.consumingPoll == 42);
262 BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false);
263 BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == false);
264 BOOST_CHECK(app.testModule.consumingPush == 42);
265 BOOST_CHECK(app.testModule.consumingPush2 == 42);
267 BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == true);
268 BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == true);
269 BOOST_CHECK(app.testModule.consumingPoll == 120);
270 BOOST_CHECK(app.testModule.consumingPush == 120);
271 BOOST_CHECK(app.testModule.consumingPush2 == 120);
272 BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false);
273 BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == false);
275 BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == true);
276 BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == true);
277 BOOST_CHECK(app.testModule.consumingPoll == 120);
278 BOOST_CHECK(app.testModule.consumingPush == 120);
279 BOOST_CHECK(app.testModule.consumingPush2 == 120);
280 BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false);
281 BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == false);
283 BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == true);
284 BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == true);
285 BOOST_CHECK(app.testModule.consumingPoll == 120);
286 BOOST_CHECK(app.testModule.consumingPush == 120);
287 BOOST_CHECK(app.testModule.consumingPush2 == 120);
288 BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false);
289 BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == false);
290 }
291
292 /********************************************************************************************************************/
293 /* Application for tests of DeviceModule */
294
296 using ctk::ApplicationModule::ApplicationModule;
297
298 ctk::ScalarOutput<int> actuator{this, "actuator", "MV/m", "Description"};
299 ctk::ScalarPollInput<int> readback{this, "readBack", "MV/m", "Description"};
300
301 void mainLoop() override {}
302 };
303
305 using ctk::ApplicationModule::ApplicationModule;
306
311 ctk::ScalarPollInput<int> tests{this, "tests", "MV/m", "Description"};
312 } need{this, "need", ""};
313 ctk::ScalarOutput<int> also{this, "also", "MV/m", "Description", {"ALSO"}};
314 } hierarchies{this, "hierarchies", ""};
315
316 void mainLoop() override {}
317 };
318
320 using ctk::ModuleGroup::ModuleGroup;
321
322 ctk::DeviceModule dev{this, "Dummy1", "/Deeper/hierarchies/trigger", nullptr, "/MyModule"};
323
325 using ctk::ApplicationModule::ApplicationModule;
326
329 ctk::ScalarPollInput<int> tests{this, "tests", "MV/m", "Description"};
330 } need{this, "need", ""};
331 ctk::ScalarOutput<int> also{this, "also", "MV/m", "Description", {"ALSO"}};
332 ctk::ScalarOutput<int> trigger{this, "trigger", "MV/m", "Description", {"ALSO"}};
333
334 void mainLoop() override {}
335 } hierarchies{this, "hierarchies", ""};
336 };
337
339 TestApplication3() : Application("testSuite") {}
340 ~TestApplication3() override { shutdown(); }
341
342 TestModule2 testModule{this, "MyModule", "The test module"};
343 Deeper2 deeper{this, "Deeper", ""};
344
345 std::atomic<size_t> initHandlerCallCount{0};
346 ctk::DeviceModule dev{this, "Dummy0", "", [this](ChimeraTK::Device&) { initHandlerCallCount++; }};
347 };
348
349 /********************************************************************************************************************/
350
351 BOOST_AUTO_TEST_CASE(testDeviceModuleExceptions) {
352 std::cout << "testDeviceModuleExceptions" << std::endl;
353
354 ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap");
356
357 // non-absolute trigger path
358 BOOST_CHECK_THROW(
359 ctk::DeviceModule(&app.deeper, "Dummy0", "unqualifiedName", nullptr, "/MyModule"), ctk::logic_error);
360 BOOST_CHECK_THROW(
361 ctk::DeviceModule(&app.deeper, "Dummy0", "relative/name", nullptr, "/MyModule"), ctk::logic_error);
362 BOOST_CHECK_THROW(
363 ctk::DeviceModule(&app.deeper, "Dummy0", "./also/relative", nullptr, "/MyModule"), ctk::logic_error);
364 BOOST_CHECK_THROW(
365 ctk::DeviceModule(&app.deeper, "Dummy0", "../another/relative/name", nullptr, "/MyModule"), ctk::logic_error);
366 }
367
368 /********************************************************************************************************************/
369
370 BOOST_AUTO_TEST_CASE(testDeviceModule) {
371 std::cout << "testDeviceModule" << std::endl;
372
373 ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap");
375
376 ctk::TestFacility test{app};
377 test.runApplication();
378 BOOST_CHECK_EQUAL(app.initHandlerCallCount, 1);
379
380 ctk::Device dev;
381 dev.open("Dummy0");
382 auto actuator = dev.getScalarRegisterAccessor<int>("MyModule/actuator");
383 auto& readback = actuator; // same address in map file
384 auto tests = dev.getScalarRegisterAccessor<int>("Deeper/hierarchies/need/tests/DUMMY_WRITEABLE");
385 auto also = dev.getScalarRegisterAccessor<int>("Deeper/hierarchies/also");
386
387 app.testModule.actuator = 42;
389 actuator.read();
390 BOOST_CHECK_EQUAL(actuator, 42);
391
392 app.testModule.actuator = 12;
394 actuator.read();
395 BOOST_CHECK_EQUAL(actuator, 12);
396
397 readback = 120;
398 readback.write();
400 BOOST_CHECK_EQUAL(app.testModule.readback, 120);
401
402 readback = 66;
403 readback.write();
405 BOOST_CHECK_EQUAL(app.testModule.readback, 66);
406
407 tests = 120;
408 tests.write();
410 BOOST_CHECK_EQUAL(app.deeper.hierarchies.need.tests, 120);
411
412 tests = 66;
413 tests.write();
415 BOOST_CHECK_EQUAL(app.deeper.hierarchies.need.tests, 66);
416
417 app.deeper.hierarchies.also = 42;
419 also.read();
420 BOOST_CHECK_EQUAL(also, 42);
421
422 app.deeper.hierarchies.also = 12;
424 also.read();
425 BOOST_CHECK_EQUAL(also, 12);
426
427 // test the second DeviceModule with the trigger
428 ctk::Device dev2;
429 dev2.open("Dummy1");
430 auto readback2 = dev2.getScalarRegisterAccessor<int>("/MyModule/readBack/DUMMY_WRITEABLE");
431 readback2 = 543;
432 readback2.write();
433
435 test.stepApplication();
436 BOOST_CHECK_EQUAL(test.readScalar<int>("/Deeper/readBack"), 543);
437
438 // make sure init handler is not called somehow a second time
439 BOOST_CHECK_EQUAL(app.initHandlerCallCount, 1);
440 }
441
442 /********************************************************************************************************************/
443 /* Application for tests of DeviceModule move constructor/assignment */
444
446 TestApplication4() : Application("testSuite") {}
447 ~TestApplication4() override { shutdown(); }
448
449 ctk::DeviceModule dev{this, "Dummy1"};
450 ctk::DeviceModule dev2{this, "Dummy0"};
451
452 std::vector<ctk::DeviceModule> cdevs;
453
454 TestModule2 m{this, "MyModule", ""};
455 Deeper deeper{this, "Deeper", ""};
456 };
457
458 /********************************************************************************************************************/
459
460 BOOST_AUTO_TEST_CASE(testDeviceModuleMove) {
461 std::cout << "testDeviceModuleMove" << std::endl;
462 ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap");
463
465 app.dev = std::move(app.dev2); // test move-assign
466 app.cdevs.push_back(std::move(app.dev)); // test move-construct
467
468 app.getModel().writeGraphViz("testDeviceModuleMove.dot");
469
470 ctk::TestFacility test{app};
471
472 test.runApplication();
473 ctk::Device dummy0("Dummy0");
474 auto readBack = dummy0.getScalarRegisterAccessor<int>("MyModule/readBack/DUMMY_WRITEABLE");
475 readBack = 432;
476 readBack.write();
477 app.m.readback.read();
478 BOOST_CHECK_EQUAL(app.m.readback, 432);
479 }
480 /********************************************************************************************************************/
481
482} // namespace Tests::testDeviceAccessors
Model::RootProxy getModel()
Return the root of the application model.
Definition Application.h:75
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.
bool hasReachedTestableMode()
Check whether this module has declared that it reached the testable mode.
void writeGraphViz(const std::string &filename, Args... args) const
Implementations of RootProxy.
Definition Model.h:1789
friend class Application
Definition ModuleGroup.h:47
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.
VariableGroup()=default
Default constructor: Allows late initialisation of VariableGroups (e.g.
InvalidityTracer application module.
BOOST_AUTO_TEST_CASE(testFeedToDevice)
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...
Tests::testDeviceAccessors::Deeper2::Hierarchies::Need need
Tests::testDeviceAccessors::Deeper2::Hierarchies hierarchies
Tests::testDeviceAccessors::Deeper::Hierarchies::Need need
Tests::testDeviceAccessors::Deeper::Hierarchies hierarchies
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...
ctk::ScalarPollInput< int > consumingPoll
void mainLoop() override
To be implemented by the user: function called in a separate thread executing the main loop of the mo...
TestModule(ctk::ModuleGroup *owner, const std::string &name, const std::string &description, const std::unordered_set< std::string > &tags={})
void prepare() override
Prepare the execution of the module.
ctk::ScalarPushInput< int > consumingPush
ctk::ScalarPushInput< int > consumingPush2