ChimeraTK-ApplicationCore 04.06.00
Loading...
Searching...
No Matches
testProcessVariableRecovery.cc
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3#define BOOST_TEST_MODULE testProcessVariableRecovery
4
5#include "Application.h"
6#include "ApplicationModule.h"
7#include "ArrayAccessor.h"
8#include "check_timeout.h"
9#include "DeviceModule.h"
10#include "ScalarAccessor.h"
11#include "TestFacility.h"
12
13#include <ChimeraTK/Device.h>
14#include <ChimeraTK/ExceptionDummyBackend.h>
15
16#include <boost/thread/barrier.hpp>
17
18#include <cstdlib>
19#include <regex>
20
21#define BOOST_NO_EXCEPTIONS
22#include <boost/test/included/unit_test.hpp>
23#undef BOOST_NO_EXCEPTIONS
24
26
27 using namespace boost::unit_test_framework;
28 namespace ctk = ChimeraTK;
29
30 static constexpr std::string_view deviceCDD{"(ExceptionDummy?map=test5.map)"};
31
32 /* The test module is writing to the device. It is the "module under test".
33 * This is the one whose variables are to be recovered. It is not the place the the
34 * application first sees the exception.
35 */
37 TestModule(ctk::ModuleGroup* owner, const std::string& name, const std::string& description,
38 const std::unordered_set<std::string>& tags = {})
39 : ApplicationModule(owner, name, description, tags), mainLoopStarted(2) {}
40
41 ctk::ScalarPushInput<int32_t> trigger{this, "trigger", "", "This is my trigger."};
42 ctk::ScalarOutput<int32_t> scalarOutput{this, "TO_DEV_SCALAR1", "", "Here I write a scalar"};
43 ctk::ArrayOutput<int32_t> arrayOutput{this, "TO_DEV_ARRAY1", "", 4, "Here I write an array"};
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 to make sure the initial value propagation is done.
47 // execute this right after the Application::run():
48 // app.testModule.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
49 boost::barrier mainLoopStarted;
50
51 void mainLoop() override {
52 mainLoopStarted.wait();
53
54 while(true) {
55 scalarOutput = int32_t(trigger);
57 for(uint i = 0; i < 4; i++) {
58 arrayOutput[i] = int32_t(trigger);
59 }
61 trigger.read(); // read the blocking variable at the end so the initial values are propagated
62 }
63 }
64 };
65
66 /* dummy application */
68 TestApplication() : Application("testSuite") {}
69 ~TestApplication() override { shutdown(); }
70
71 ctk::DeviceModule dev{this, deviceCDD.data(), "/deviceTrigger"};
72 TestModule module{this, "TEST", "The test module"};
73 };
74
75 /*
76 * Test application for the specific case of writing to a read-only accessor. Provides an input to an ApplicationModule
77 * from a read-only accessor of the device. For the test, the accessor must not be routed through the control system,
78 * the illegal write would be caught by the ControlSystemAdapter, not by the ExceptionHandlingDecorator under test here.
79 */
81 ReadOnlyTestApplication() : Application("ReadOnlytestApp") {}
83
84 ctk::DeviceModule dev{this, deviceCDD.data(), "/weNowNeedATriggerHere"};
85
87 using ctk::ApplicationModule::ApplicationModule;
88
90 this, "startTest", "", "This has to be written once, before writing to the device", {"CS"}};
92 this, "/TEST/FROM_DEV_SCALAR2", "", "Here I read from a scalar RO-register"};
93
94 void mainLoop() override {
95 // Just to have a blocking read, gives the test time to dumpConnections and explicitly trigger before terminating.
96 start.read();
97
98 scalarROInput = 42;
99 try {
101 BOOST_CHECK_MESSAGE(
102 false, "ReadOnlyTestApplication: Calling write() on input to read-only device register did not throw.");
103 }
104 catch(ChimeraTK::logic_error& e) {
105 const std::string exMsg{e.what()};
106 std::regex exceptionHandlingRegex{"^ChimeraTK::ExceptionhandlingDecorator:*"};
107 std::smatch exMatch;
108
109 std::cout << exMsg << std::endl;
110
111 BOOST_CHECK(std::regex_search(exMsg, exMatch, exceptionHandlingRegex));
112 }
113 }
114
115 } module{this, "READ_ONLY_TEST", "The test module"};
116 };
117
118 /********************************************************************************************************************/
119
120 BOOST_AUTO_TEST_CASE(testWriteToReadOnly) {
121 std::cout << "testWriteToReadOnly" << std::endl;
122
124
125 ctk::TestFacility test{app};
126 app.optimiseUnmappedVariables({"/TEST/FROM_DEV_SCALAR2"});
127
128 test.runApplication();
129
130 // Should trigger the blocking read in ReadOnlyTestApplication's ApplicationModule. It then writes to a read-only
131 // register of the device, which should throw. Check is done in the module's mainLoop. We can not check here, as the
132 // exception gets thrown in the thread of the module.
133 test.writeScalar("/READ_ONLY_TEST/startTest", 1);
134 }
135
136 /********************************************************************************************************************/
137
138 BOOST_AUTO_TEST_CASE(testProcessVariableRecovery) {
139 std::cout << "testProcessVariableRecovery" << std::endl;
140 TestApplication app;
141
142 ctk::TestFacility test{app, false};
143 // Write initial values manually since we do not use the testable mode.
144 // Otherwise the main loops never start.
145
146 // initial value for the direct CS->DEV register
147 test.writeScalar("/TEST/TO_DEV_SCALAR2", 42);
148 std::vector<int32_t> array = {99, 99, 99, 99};
149 test.writeArray("/TEST/TO_DEV_ARRAY2", array);
150
151 // initial value for the trigger
152 test.writeScalar("/TEST/trigger", 0);
153
154 app.run();
155 app.module.mainLoopStarted.wait();
156
157 ctk::Device dummy;
158 dummy.open(deviceCDD.data());
159
160 // wait for the device to be opened successfully so the access to the dummy does not throw
161 // (as they use the same backend it now throws if there has been an exception somewhere else)
162 CHECK_EQUAL_TIMEOUT(test.readScalar<int32_t>(
163 std::string("/Devices/") + ctk::Utilities::escapeName(deviceCDD.data(), false) + "/status"),
164 0, 10000);
165
166 // Check that the initial values are there.
167 CHECK_EQUAL_TIMEOUT(dummy.read<int32_t>("/TEST/TO_DEV_SCALAR2"), 42, 10000);
168 CHECK_EQUAL_TIMEOUT(dummy.read<int32_t>("/TEST/TO_DEV_ARRAY2", 1, 0)[0], 99, 10000);
169 CHECK_EQUAL_TIMEOUT(dummy.read<int32_t>("/TEST/TO_DEV_ARRAY2", 1, 1)[0], 99, 10000);
170 CHECK_EQUAL_TIMEOUT(dummy.read<int32_t>("/TEST/TO_DEV_ARRAY2", 1, 2)[0], 99, 10000);
171 CHECK_EQUAL_TIMEOUT(dummy.read<int32_t>("/TEST/TO_DEV_ARRAY2", 1, 3)[0], 99, 10000);
172
173 // Update device register via application module.
174 auto trigger = test.getScalar<int32_t>("/TEST/trigger");
175 trigger = 100;
176 trigger.write();
177 // Check if the values are updated.
178 CHECK_EQUAL_TIMEOUT(dummy.read<int32_t>("/TEST/TO_DEV_SCALAR1"), 100, 10000);
179 CHECK_EQUAL_TIMEOUT(dummy.read<int32_t>("/TEST/TO_DEV_ARRAY1", 1, 0)[0], 100, 10000);
180 CHECK_EQUAL_TIMEOUT(dummy.read<int32_t>("/TEST/TO_DEV_ARRAY1", 1, 1)[0], 100, 10000);
181 CHECK_EQUAL_TIMEOUT(dummy.read<int32_t>("/TEST/TO_DEV_ARRAY1", 1, 2)[0], 100, 10000);
182 CHECK_EQUAL_TIMEOUT(dummy.read<int32_t>("/TEST/TO_DEV_ARRAY1", 1, 3)[0], 100, 10000);
183
184 auto dummyBackend = boost::dynamic_pointer_cast<ctk::ExceptionDummy>(
185 ctk::BackendFactory::getInstance().createBackend(deviceCDD.data()));
186
187 // Set the device to throw.
188 dummyBackend->throwExceptionOpen = true;
189
190 // Set dummy registers to 0.
191 dummy.write<int32_t>("/CONSTANT/VAR32", 0);
192 dummy.write<int32_t>("/TEST/TO_DEV_SCALAR1", 0);
193 dummy.write<int32_t>("/TEST/TO_DEV_SCALAR2", 0);
194 array = {0, 0, 0, 0};
195 dummy.write("/TEST/TO_DEV_ARRAY1", array);
196 dummy.write("/TEST/TO_DEV_ARRAY2", array);
197
198 CHECK_EQUAL_TIMEOUT(dummy.read<int32_t>("/CONSTANT/VAR32"), 0, 10000);
199 dummyBackend->throwExceptionWrite = true;
200 dummyBackend->throwExceptionRead = true;
201
202 // Now we trigger the reading module. This should put the device into an error state
203 auto trigger2 = test.getVoid("/deviceTrigger");
204 trigger2.write();
205
206 // Verify that the device is in error state.
207 CHECK_EQUAL_TIMEOUT(test.readScalar<int32_t>(ctk::RegisterPath("/Devices") /
208 ctk::Utilities::escapeName(deviceCDD.data(), false) / "status"),
209 1, 10000);
210
211 // Set device back to normal.
212 dummyBackend->throwExceptionWrite = false;
213 dummyBackend->throwExceptionRead = false;
214 dummyBackend->throwExceptionOpen = false;
215 // Verify if the device is ready.
216 CHECK_EQUAL_TIMEOUT(test.readScalar<int32_t>(ctk::RegisterPath("/Devices") /
217 ctk::Utilities::escapeName(deviceCDD.data(), false) / "status"),
218 0, 10000);
219
220 // Device should have the correct values now. Notice that we did not trigger the writer module!
221 BOOST_CHECK_EQUAL(dummy.read<int32_t>("/TEST/TO_DEV_SCALAR2"), 42);
222 BOOST_CHECK((dummy.read<int32_t>("/TEST/TO_DEV_ARRAY2", 0) == std::vector<int32_t>{99, 99, 99, 99}));
223
224 BOOST_CHECK_EQUAL(dummy.read<int32_t>("/TEST/TO_DEV_SCALAR1"), 100);
225 BOOST_CHECK((dummy.read<int32_t>("/TEST/TO_DEV_ARRAY1", 0) == std::vector<int32_t>{100, 100, 100, 100}));
226 }
227
228} // namespace Tests::testProcessVariableRecovery
#define CHECK_EQUAL_TIMEOUT(left, right, maxMilliseconds)
void run() override
Execute the module.
void optimiseUnmappedVariables(const std::set< std::string > &names) override
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.
bool write(ChimeraTK::VersionNumber versionNumber)=delete
friend class Application
Definition ModuleGroup.h:47
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.
InvalidityTracer application module.
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 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...
TestModule(ctk::ModuleGroup *owner, const std::string &name, const std::string &description, const std::unordered_set< std::string > &tags={})