ChimeraTK-ApplicationCore 04.06.00
Loading...
Searching...
No Matches
testScriptedInitialisationHandler.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 testScriptedInitialisationHandler
4#include "Application.h"
5#include "check_timeout.h"
6#include "DeviceModule.h"
8#include "TestFacility.h"
9
10#include <boost/test/included/unit_test.hpp>
11
12#include <filesystem>
13#include <fstream>
14
16
17 using namespace ChimeraTK;
18
19 /********************************************************************************************************************/
20
21 struct TestApp : public Application {
23 ~TestApp() override { shutdown(); }
24
25 SetDMapFilePath dmap{"test.dmap"};
26
27 DeviceModule dev1{this, "Dummy0",
28 "/MyModule/actuator"}; // pick one of the writable variables to AC knows that data type for the trigger
29
30 // default name for the output variable (initScriptOutput)
31 ScriptedInitHandler initHandler1{this, "InitHander1", "description", "./deviceInitScript1.bash", dev1};
32 // change the name of the output variable in case a second script is needed. Shorten the error grace time to 1 second
34 this, "InitHander2", "description", "./deviceInitScript2.bash", dev1, "secondInitScriptOutput", 1};
35 };
36
37 /********************************************************************************************************************/
38
39 struct Fixture {
40 TestApp testApp{"ScriptedInitApp"};
43 // always clear the temporary files before each test
45 }
47 // always clear the temporary files after each test
49 }
50 static void clearTemporaryFiles() {
51 (void)std::filesystem::remove("device1Init.complete");
52 (void)std::filesystem::remove("blockDevice1Init");
53 (void)std::filesystem::remove("produceDevice2InitError");
54 }
55 };
56
57 /********************************************************************************************************************/
58 /********************************************************************************************************************/
59 // Test that the script is actually executed
61 testFacility.runApplication();
62
63 CHECK_TIMEOUT(testFacility.readScalar<int32_t>("/Devices/Dummy0/status") == 0, 30000);
64 // As soon as the device reports ready, the init script effect must be visible. This is just a smoke test that the
65 // ScriptedInitialisationHandler actually executes the specified script. It does not check the initialisation
66 // handler spec that the call is happening at the right time in the recovery process, which is tested separately.
67 BOOST_CHECK(std::filesystem::exists("device1Init.complete"));
68 }
69
70 /********************************************************************************************************************/
71 // Test that the message is published to the control system. Lines appear in the correct order.
73 testFacility.runApplication();
74
75 auto initMessage = testFacility.getScalar<std::string>("/Devices/Dummy0/initScriptOutput");
76
77 // Only test texts snippets and their order. There might be other stray output in the stuff collected from the
78 // command line, so don't test for exact string equality.
79 constexpr auto firstSnippet = "starting device1 init";
80 constexpr auto secondSnippet = "device1 init complete";
81
82 // Wait for the second message snippet.
83 size_t secondSnippetPos;
84 CHECK_TIMEOUT((initMessage.readLatest(),
85 (secondSnippetPos = std::string(initMessage).find(secondSnippet)) != std::string::npos),
86 30000);
87 // The first snippet is also there, and before the second snippet.
88 BOOST_TEST(std::string(initMessage).find(firstSnippet) < secondSnippetPos);
89 std::cout << std::string(initMessage) << std::endl;
90
91 BOOST_CHECK(std::filesystem::exists("device1Init.complete"));
92 }
93
94 /********************************************************************************************************************/
95 // A success message is appended after the script output.
97 testFacility.runApplication();
98
99 auto initMessage = testFacility.getScalar<std::string>("/Devices/Dummy0/initScriptOutput");
100
101 constexpr auto scriptOutputSnippet = "device1 init complete";
102 constexpr auto successSnippet = "SUCCESS"; // We intentionally do not test the exact line but only a snipped to be
103 // less sensitive to message refactoring.
104
105 size_t successSnippetPos;
106 CHECK_TIMEOUT((initMessage.readLatest(),
107 (successSnippetPos = std::string(initMessage).find(successSnippet)) != std::string::npos),
108 30000);
109 // Tests that the script snippet is there before the success message.
110 BOOST_TEST(std::string(initMessage).find(scriptOutputSnippet) < successSnippetPos);
111 }
112
113 /********************************************************************************************************************/
114 // The script output does not only occur at the end of the script. Lines printed by the script are already shown
115 // while the script is still running.
116 BOOST_FIXTURE_TEST_CASE(TestPartialOutput, Fixture) {
117 // Don't let the script finish, so we can do a check without race condition and know the test is sensitive.
118 std::ofstream blockFile;
119 blockFile.open("blockDevice1Init", std::ios::out);
120
121 testFacility.runApplication();
122
123 auto initMessage = testFacility.getScalar<std::string>("/Devices/Dummy0/initScriptOutput");
124
125 constexpr auto firstSnippet = "starting device1 init";
126 constexpr auto secondSnippet = "device1 init complete";
127
128 // The first snippet is in the message, the second not yet. The script has not reached the complete step yet.
129 CHECK_TIMEOUT((initMessage.readLatest(), std::string(initMessage).find(firstSnippet) != std::string::npos), 30000);
130 BOOST_TEST(std::string(initMessage).find(secondSnippet) == std::string::npos);
131 BOOST_CHECK(!std::filesystem::exists("device1Init.complete"));
132
133 // let the script finish
134 (void)std::filesystem::remove("blockDevice1Init");
135
136 // Wait for the second message snippet.
137 size_t secondSnippetPos;
138 CHECK_TIMEOUT((initMessage.readLatest(),
139 (secondSnippetPos = std::string(initMessage).find(secondSnippet)) != std::string::npos),
140 30000);
141 // The first snippet is still in there, and before the second snippet.
142 BOOST_TEST(std::string(initMessage).find(firstSnippet) < secondSnippetPos);
143
144 BOOST_CHECK(std::filesystem::exists("device1Init.complete"));
145 }
146
147 /********************************************************************************************************************/
148 // Two differennt scripts on the same device have different outputs.
150 testFacility.runApplication();
151
152 constexpr auto script1Snippet = "device1 init complete";
153 constexpr auto script2Snippet = "just a second script";
154 constexpr auto successSnippet = "SUCCESS";
155
156 // wait for the success snippet in message1. It must contain the first, but not the second snippet.
157 auto initMessage1 = testFacility.getScalar<std::string>("/Devices/Dummy0/initScriptOutput");
159 (initMessage1.readLatest(), std::string(initMessage1).find(successSnippet) != std::string::npos), 30000);
160 BOOST_TEST(std::string(initMessage1).find(script1Snippet) != std::string::npos);
161 BOOST_TEST(std::string(initMessage1).find(script2Snippet) == std::string::npos);
162
163 // same for snippets in message2.
164 auto initMessage2 = testFacility.getScalar<std::string>("/Devices/Dummy0/secondInitScriptOutput");
166 (initMessage2.readLatest(), std::string(initMessage2).find(successSnippet) != std::string::npos), 30000);
167 BOOST_TEST(std::string(initMessage2).find(script1Snippet) == std::string::npos);
168 BOOST_TEST(std::string(initMessage2).find(script2Snippet) != std::string::npos);
169 }
170
171 /********************************************************************************************************************/
172 // The error message is renewed with each run of the script, so changing error messages can be seen.
174 std::ofstream produceErrorFile; // If the file exists, the script produces an error
175 produceErrorFile.open("produceDevice2InitError", std::ios::out);
176 produceErrorFile << 0 << std::flush;
177
178 testFacility.runApplication();
179
180 auto initMessage = testFacility.getScalar<std::string>("/Devices/Dummy0/secondInitScriptOutput");
181
182 // let the script run three times, check that always the output of the last run is visible in the control system
183 for(int i = 0; i < 3; ++i) {
184 produceErrorFile.seekp(0);
185 produceErrorFile << i << std::flush;
186 std::string expectedSnippet = "Simulating error in second script: " + std::to_string(i);
188 (initMessage.readLatest(), std::string(initMessage).find(expectedSnippet) != std::string::npos), 20000);
189 // renewed means the old message is replaced
190 if(i == 1) {
191 BOOST_TEST(std::string(initMessage).find("script: 0") == std::string::npos);
192 }
193 if(i == 2) {
194 BOOST_TEST(std::string(initMessage).find("script: 1") == std::string::npos);
195 }
196 }
197
198 // recovery
199 (void)std::filesystem::remove("produceDevice2InitError");
200 CHECK_TIMEOUT(testFacility.readScalar<int32_t>("/Devices/Dummy0/status") == 0, 30000);
201 }
202
203 /********************************************************************************************************************/
204 // A failure message is appended if the script fails.
205 BOOST_FIXTURE_TEST_CASE(TestFailureMessage, Fixture) {
206 std::ofstream produceErrorFile; // If the file exists, the script produces an error
207 produceErrorFile.open("produceDevice2InitError", std::ios::out);
208
209 testFacility.runApplication();
210
211 auto initMessage = testFacility.getScalar<std::string>("/Devices/Dummy0/secondInitScriptOutput");
212
213 std::string scriptSnippet = "Simulating error in second script:";
214 std::string failSnippet = "FAILED";
215
216 size_t failSnippedPos;
218 (initMessage.readLatest(), (failSnippedPos = std::string(initMessage).find(failSnippet)) != std::string::npos),
219 20000);
220 BOOST_TEST(std::string(initMessage).find(scriptSnippet) < failSnippedPos);
221
222 // recovery
223 (void)std::filesystem::remove("produceDevice2InitError");
224 CHECK_TIMEOUT(testFacility.readScalar<int32_t>("/Devices/Dummy0/status") == 0, 30000);
225 }
226
227 /********************************************************************************************************************/
228 // A successful message replaces the previous failure message.
229 BOOST_FIXTURE_TEST_CASE(TestSuccessAfterFailure, Fixture) {
230 std::ofstream produceErrorFile; // If the file exists, the script produces an error
231 produceErrorFile.open("produceDevice2InitError", std::ios::out);
232
233 testFacility.runApplication();
234
235 auto initMessage = testFacility.getScalar<std::string>("/Devices/Dummy0/secondInitScriptOutput");
236 std::string failSnippet = "FAILED";
237 std::string successSnippet = "SUCCESS";
238
239 // wait for fail message to appear
240 CHECK_TIMEOUT((initMessage.readLatest(), std::string(initMessage).find(failSnippet) != std::string::npos), 20000);
241
242 // recovery
243 (void)std::filesystem::remove("produceDevice2InitError");
244
245 // Wait for success message to appear
247 (initMessage.readLatest(), std::string(initMessage).find(successSnippet) != std::string::npos), 20000);
248
249 // The actual test: The failure message is not part of the message any more
250 BOOST_TEST(std::string(initMessage).find(failSnippet) == std::string::npos);
251 }
252
253 /********************************************************************************************************************/
254 // There is an error grace period which limits the retry rate.
255 BOOST_FIXTURE_TEST_CASE(TestErrorGracePeriod, Fixture) {
256 std::ofstream produceErrorFile; // If the file exists, the script produces an error
257 produceErrorFile.open("produceDevice2InitError", std::ios::out);
258 produceErrorFile << 1 << std::flush;
259
260 testFacility.runApplication();
261
262 auto initMessage = testFacility.getScalar<std::string>("/Devices/Dummy0/secondInitScriptOutput");
263
264 auto startTime = std::chrono::steady_clock::now();
265 // Let the script run three times, and wait for according error message to show. This is not the
266 // actual test here, just a pre-conditon for measuring the time.
267 for(int i = 0; i < 3; ++i) {
268 produceErrorFile.seekp(0);
269 produceErrorFile << i << std::flush;
270 std::string expectedSnippet = "Simulating error in second script: " + std::to_string(i);
272 (initMessage.readLatest(), std::string(initMessage).find(expectedSnippet) != std::string::npos), 20000);
273 }
274
275 // recovery
276 (void)std::filesystem::remove("produceDevice2InitError");
277 CHECK_TIMEOUT(testFacility.readScalar<int32_t>("/Devices/Dummy0/status") == 0, 30000);
278
279 // The actual test: At least three failure grace periods have passed.
280 auto stopTime = std::chrono::steady_clock::now();
281 BOOST_CHECK(std::chrono::duration_cast<std::chrono::seconds>(stopTime - startTime).count() >= 3);
282 }
283 /********************************************************************************************************************/
284
285} // namespace Tests::testScriptedInitialisationHandler
Application(const std::string &name)
The constructor takes the application name as an argument.
void shutdown() override
This will remove the global pointer to the instance and allows creating another instance afterwards.
Helper class to set the DMAP file path.
Helper class to facilitate tests of applications based on ApplicationCore.
InvalidityTracer application module.
Initialisation handler which calls an external application (usually a script), captures its output (b...
#define CHECK_TIMEOUT(condition, maxMilliseconds)