ChimeraTK-ApplicationCore 04.08.00
Loading...
Searching...
No Matches
testNoInitialValue.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 "ScalarAccessor.h"
4
5#include <ChimeraTK/ExceptionDummyBackend.h>
6#include <ChimeraTK/SharedDummyBackend.h>
7
8#define BOOST_TEST_MODULE noInitialValueTest
9
10#include "Application.h"
11#include "ApplicationModule.h"
12#include "check_timeout.h"
13#include "DeviceModule.h"
14#include "TestFacility.h"
15
16#include <ChimeraTK/BackendFactory.h>
17#include <ChimeraTK/Device.h>
18
19#include <boost/test/included/unit_test.hpp>
20
21#include <atomic>
22#include <cstdint>
23#include <cstdio>
24#include <fstream>
25#include <iostream>
26namespace ctk = ChimeraTK;
27
28/**********************************************************************************************************************/
29
30BOOST_AUTO_TEST_CASE(TestNoInitialValueTagChecks) {
31 std::cout << "TestNoInitialValueTagChecks" << std::endl;
32
33 struct TestModule : public ctk::ApplicationModule {
34 using ctk::ApplicationModule::ApplicationModule;
35 ctk::ScalarPushInputNoInitialValue<int32_t> pushNoIv{this, "pushNoIv", "", ""};
36 ctk::ScalarPollInputNoInitialValue<int32_t> pollNoIv{this, "pollNoIv", "", ""};
37 ctk::ScalarPushInput<int32_t> normalPush{this, "normalPush", "", ""};
38 ctk::ScalarPollInput<int32_t> normalPoll{this, "normalPoll", "", ""};
39 void mainLoop() override {}
40 };
41
42 struct TagCheckApp : ctk::Application {
43 TagCheckApp() : ctk::Application("tagCheckApp") {}
44 ~TagCheckApp() override { shutdown(); }
45 TestModule mod{this, "Module", ""};
46 } app;
47
48 auto accessorList = app.mod.getAccessorListRecursive();
49
50 for(auto& var : accessorList) {
51 if(var.getName() == "pushNoIv" || var.getName() == "pollNoIv") {
52 BOOST_TEST(var.getTags().contains(ctk::noInitialValueReadTag));
53 }
54 if(var.getName() == "normalPush" || var.getName() == "normalPoll") {
55 BOOST_TEST(!var.getTags().contains(ctk::noInitialValueReadTag));
56 }
57 }
58}
59
60/**********************************************************************************************************************/
61
62BOOST_AUTO_TEST_CASE(TestNoInitialValueModuleEntersMainLoop) {
63 std::cout << "TestNoInitialValueModuleEntersMainLoop" << std::endl;
64
65 struct TestModule : public ctk::ApplicationModule {
66 using ctk::ApplicationModule::ApplicationModule;
67
68 ctk::ScalarPushInputNoInitialValue<int32_t> pushNoIv{this, "pushNoIv", "", ""};
69 ctk::ScalarPollInputNoInitialValue<int32_t> pollNoIv{this, "pollNoIv", "", ""};
70 ctk::ScalarPushInput<int32_t> normalPush{this, "normalPush", "", ""};
71 ctk::ScalarPollInput<int32_t> normalPoll{this, "normalPoll", "", ""};
72
73 std::atomic<bool> mainLoopEntered{false};
74
75 void mainLoop() override {
76 mainLoopEntered = true;
77 mainLoopEntered.notify_one();
78 }
79 };
80
81 struct NoInitApp : ctk::Application {
82 NoInitApp() : ctk::Application("noInitApp") {}
83 ~NoInitApp() override { shutdown(); }
84 TestModule mod{this, "Module", ""};
85 } app;
86
87 ctk::TestFacility test(app, false);
88
89 test.setScalarDefault<int32_t>("/Module/pushNoIv", 42);
90 test.setScalarDefault<int32_t>("/Module/pollNoIv", 120);
91 test.setScalarDefault<int32_t>("/Module/normalPush", 666);
92 test.setScalarDefault<int32_t>("/Module/normalPoll", 777);
93
94 test.runApplication();
95
96 CHECK_EQUAL_TIMEOUT(app.mod.mainLoopEntered, true, 2000);
97
98 BOOST_TEST(app.mod.pushNoIv.getVersionNumber() == ctk::VersionNumber{nullptr});
99 BOOST_TEST(app.mod.pollNoIv.getVersionNumber() == ctk::VersionNumber{nullptr});
100 BOOST_TEST(app.mod.normalPush.getVersionNumber() != ctk::VersionNumber{nullptr});
101 BOOST_TEST(app.mod.normalPoll.getVersionNumber() != ctk::VersionNumber{nullptr});
102
103 BOOST_TEST(app.mod.pushNoIv == 0);
104 BOOST_TEST(app.mod.pollNoIv == 0);
105 BOOST_TEST(app.mod.normalPush == 666);
106 BOOST_TEST(app.mod.normalPoll == 777);
107
108 app.mod.pushNoIv.read();
109 app.mod.pollNoIv.read();
110
111 BOOST_TEST(app.mod.pushNoIv == 42);
112 BOOST_TEST(app.mod.pollNoIv == 120);
113
114 app.shutdown();
115}
116
117/**********************************************************************************************************************/
118
119static constexpr const char* testMap = "testNoInitVal.map";
120static constexpr const char* testDmap = "testNoInitVal.dmap";
121static constexpr const char* backendCdd = "(ExceptionDummy:1?map=testNoInitVal.map)";
122
123static void setupDeviceMap() {
124 std::ofstream dmapFile(testDmap);
125 dmapFile << "testDevice " << backendCdd << std::endl;
126
127 std::ofstream mapFile(testMap);
128 mapFile << "Module.noInit 0x00000001 0x00000000 0x00000004 1 32 0 1 RO" << std::endl;
129 mapFile << "Module.noReco 0x00000001 0x00000004 0x00000004 1 32 0 1 RW" << std::endl;
130}
131
132static void cleanupDeviceMap() {
133 std::remove(testDmap);
134 std::remove(testMap);
135}
136
137/**********************************************************************************************************************/
138
139BOOST_AUTO_TEST_CASE(TestNoInitialValueOnlyEntersWithBrokenDevice) {
140 std::cout << "TestNoInitialValueOnlyEntersWithBrokenDevice" << std::endl;
141
142 setupDeviceMap();
143 ctk::BackendFactory::getInstance().setDMapFilePath(testDmap);
144
145 // Place backend into error state BEFORE the application starts
146 auto backend =
147 boost::dynamic_pointer_cast<ctk::ExceptionDummy>(ctk::BackendFactory::getInstance().createBackend("testDevice"));
148 BOOST_REQUIRE(backend);
149 backend->throwExceptionOpen = true;
150 struct NoInitOnlyModule : public ctk::ApplicationModule {
151 using ctk::ApplicationModule::ApplicationModule;
152
153 ctk::ScalarPollInputNoInitialValue<int32_t> noInit{this, "/Module/noInit", "", ""};
154
155 // Reverse Recovery paired with noInitialValueRead effectively disables recovery entirely. Writes only go through
156 // when the device is online during the write operation and are otherwise discarded.
158 this, "/Module/noReco", "", "", {ChimeraTK::noInitialValueReadTag}};
159
160 std::atomic<bool> mainLoopEntered{false};
161
162 void mainLoop() override {
163 // Now read the NoInitialValue input — this should not block even though the device is broken, because we skip
164 // DeviceManager::waitForInitialValues() in ExceptionHandlingDecorator::doPreRead()
165 noInit.read();
166
167 noReco.write();
168
169 mainLoopEntered = true;
170 mainLoopEntered.notify_one();
171 }
172 };
173
174 struct OnlyNoInitApp : ctk::Application {
175 OnlyNoInitApp() : ctk::Application("onlyNoInitApp") {}
176 ~OnlyNoInitApp() override { shutdown(); }
177 ctk::DeviceModule devMod{this, "testDevice", "/fakeTrigger"};
178 NoInitOnlyModule mod{this, "Module", ""};
179 } app;
180
181 ctk::TestFacility test(app, false);
182 test.runApplication();
183
184 // Module with only NoInitialValue inputs should enter mainLoop
185 // despite broken device, because all its inputs skip the initial read
186 // AND the ExceptionHandlingDecorator::doPreRead skips waitForInitialValues()
187 CHECK_EQUAL_TIMEOUT(app.mod.mainLoopEntered, true, 5000);
188
189 app.shutdown();
190 cleanupDeviceMap();
191}
192
193/**********************************************************************************************************************/
194
195BOOST_AUTO_TEST_CASE(TestWithOptimiseUnmappedVariables) {
196 std::cout << "TestWithOptimiseUnmappedVariables" << std::endl;
197
198 setupDeviceMap();
199 ctk::BackendFactory::getInstance().setDMapFilePath(testDmap);
200
201 // Place backend into error state BEFORE the application starts
202 auto backend =
203 boost::dynamic_pointer_cast<ctk::ExceptionDummy>(ctk::BackendFactory::getInstance().createBackend("testDevice"));
204 BOOST_REQUIRE(backend);
205 backend->throwExceptionOpen = true;
206 struct NoInitOnlyModule : public ctk::ApplicationModule {
207 using ctk::ApplicationModule::ApplicationModule;
208
209 ctk::ScalarPollInputNoInitialValue<int32_t> noInit{this, "/Module/noInit", "", ""};
210
211 // Reverse Recovery paired with noInitialValueRead effectively disables recovery entirely. Writes only go through
212 // when the device is online during the write operation and are otherwise discarded.
214 this, "/Module/noReco", "", "", {ChimeraTK::noInitialValueReadTag}};
215
216 std::atomic<bool> mainLoopEntered{false};
217
218 void mainLoop() override {
219 // Now read the NoInitialValue input — this should not block even though the device is broken and the CS
220 // connection has been optimised out (direct Device→App connection), because we skip
221 // DeviceManager::waitForInitialValues() in ExceptionHandlingDecorator::doPreRead()
222 noInit.read();
223
224 noReco.write();
225
226 mainLoopEntered = true;
227 mainLoopEntered.notify_one();
228 }
229 };
230
231 struct OnlyNoInitApp : ctk::Application {
232 OnlyNoInitApp() : ctk::Application("onlyNoInitOptApp") {}
233 ~OnlyNoInitApp() override { shutdown(); }
234 ctk::DeviceModule devMod{this, "testDevice", "/fakeTrigger"};
235 NoInitOnlyModule mod{this, "Module", ""};
236 } app;
237
238 ctk::TestFacility test(app, false);
239
240 // Optimise out the control system connection, making it a direct Device→App connection (1:1).
241 // This exercises makeDirectConnectionForFeederWithImplementation instead of makeFanOutConnectionForFeederWithImplementation.
242 app.optimiseUnmappedVariables({"/Module/noInit", "/Module/noReco"});
243
244 test.runApplication();
245
246 // Module with only NoInitialValue inputs should enter mainLoop
247 // despite broken device, because all its inputs skip the initial read
248 // AND the ExceptionHandlingDecorator::doPreRead skips waitForInitialValues()
249 CHECK_EQUAL_TIMEOUT(app.mod.mainLoopEntered, true, 5000);
250
251 app.shutdown();
252 cleanupDeviceMap();
253}
#define CHECK_EQUAL_TIMEOUT(left, right, maxMilliseconds)
void shutdown() override
This will remove the global pointer to the instance and allows creating another instance afterwards.
virtual void mainLoop()=0
To be implemented by the user: function called in a separate thread executing the main loop of the mo...
bool write(ChimeraTK::VersionNumber versionNumber)=delete
Convenience class for output scalar accessors with return channel ("read back") (always UpdateMode::p...
Convenience class for input scalar accessors with UpdateMode::poll.
Convenience class for input scalar accessors with UpdateMode::poll that skip initial value reading.
Convenience class for input scalar accessors with UpdateMode::push.
Convenience class for input scalar accessors with UpdateMode::push that skip initial value reading.
Helper class to facilitate tests of applications based on ApplicationCore.
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.
constexpr char noInitialValueReadTag[]
System tag to mark an accessor which should be excluded from the initial value read during applicatio...
BOOST_AUTO_TEST_CASE(TestNoInitialValueTagChecks)