Newer
Older
Martin Christoph Hierholzer
committed
#include <future>
#define BOOST_TEST_MODULE testPropagateDataFaultFlag
#include <boost/mpl/list.hpp>
#include <boost/test/included/unit_test.hpp>
#include <ChimeraTK/BackendFactory.h>
#include <ChimeraTK/Device.h>
#include <ChimeraTK/NDRegisterAccessor.h>
#include <ChimeraTK/DummyRegisterAccessor.h>
#include "Application.h"
#include "ApplicationModule.h"
#include "ControlSystemModule.h"
#include "ScalarAccessor.h"
#include "ArrayAccessor.h"
#include "TestFacility.h"
Martin Christoph Hierholzer
committed
#include <ChimeraTK/ExceptionDummyBackend.h>
Martin Christoph Hierholzer
committed
using namespace boost::unit_test_framework;
namespace ctk = ChimeraTK;
/* dummy application */
/*********************************************************************************************************************/
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
struct TestModule1 : ctk::ApplicationModule {
using ctk::ApplicationModule::ApplicationModule;
ctk::ScalarPushInput<int> i1{this, "i1", "", ""};
ctk::ArrayPushInput<int> i2{this, "i2", "", 2, ""};
ctk::ScalarPushInputWB<int> i3{this, "i3", "", ""};
ctk::ScalarOutput<int> o1{this, "o1", "", ""};
ctk::ArrayOutput<int> o2{this, "o2", "", 2, ""};
void mainLoop() override {
auto group = readAnyGroup();
while(true) {
if(i3 > 10) {
i3 = 10;
i3.write();
}
o1 = int(i1);
o2[0] = i2[0];
o2[1] = i2[1];
o1.write();
o2.write();
Martin Killenberg
committed
group.readAny();
Martin Christoph Hierholzer
committed
}
}
};
struct TestApplication1 : ctk::Application {
TestApplication1() : Application("testSuite") {}
~TestApplication1() { shutdown(); }
void defineConnections() { t1.connectTo(cs); }
TestModule1 t1{this, "t1", ""};
ctk::ControlSystemModule cs;
};
struct TestApplication2 : ctk::Application {
TestApplication2() : Application("testSuite") {}
~TestApplication2() { shutdown(); }
void defineConnections() {
t1.connectTo(cs["A"]);
t1.connectTo(cs["B"]);
}
TestModule1 t1{this, "t1", ""};
ctk::ControlSystemModule cs;
};
Martin Christoph Hierholzer
committed
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
/*********************************************************************************************************************/
// first test without FanOuts of any kind
BOOST_AUTO_TEST_CASE(testDirectConnections) {
std::cout << "testDirectConnections" << std::endl;
TestApplication1 app;
ctk::TestFacility test;
auto i1 = test.getScalar<int>("i1");
auto i2 = test.getArray<int>("i2");
auto i3 = test.getScalar<int>("i3");
auto o1 = test.getScalar<int>("o1");
auto o2 = test.getArray<int>("o2");
test.runApplication();
// test if fault flag propagates to all outputs
i1 = 1;
i1.setDataValidity(ctk::DataValidity::faulty);
i1.write();
test.stepApplication();
o1.read();
o2.read();
BOOST_CHECK(o1.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK(o2.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK_EQUAL(int(o1), 1);
BOOST_CHECK_EQUAL(o2[0], 0);
BOOST_CHECK_EQUAL(o2[1], 0);
// write another value but keep fault flag
i1 = 42;
BOOST_CHECK(i1.dataValidity() == ctk::DataValidity::faulty);
i1.write();
test.stepApplication();
o1.read();
o2.read();
BOOST_CHECK(o1.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK(o2.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK_EQUAL(int(o1), 42);
BOOST_CHECK_EQUAL(o2[0], 0);
BOOST_CHECK_EQUAL(o2[1], 0);
// a write on the ok variable should not clear the flag
i2[0] = 10;
i2[1] = 11;
BOOST_CHECK(i2.dataValidity() == ctk::DataValidity::ok);
i2.write();
test.stepApplication();
o1.read();
o2.read();
BOOST_CHECK(o1.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK(o2.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK_EQUAL(int(o1), 42);
BOOST_CHECK_EQUAL(o2[0], 10);
BOOST_CHECK_EQUAL(o2[1], 11);
// the return channel should also receive the flag
BOOST_CHECK(i3.readNonBlocking() == false);
BOOST_CHECK(i3.dataValidity() == ctk::DataValidity::ok);
i3 = 20;
i3.write();
test.stepApplication();
o1.read();
o2.read();
i3.read();
BOOST_CHECK(o1.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK(o2.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK(i3.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK_EQUAL(int(o1), 42);
BOOST_CHECK_EQUAL(o2[0], 10);
BOOST_CHECK_EQUAL(o2[1], 11);
BOOST_CHECK_EQUAL(int(i3), 10);
// clear the flag on i1, i3 will keep it for now (we have received it there and not yet sent it out!)
i1 = 3;
i1.setDataValidity(ctk::DataValidity::ok);
i1.write();
test.stepApplication();
o1.read();
o2.read();
BOOST_CHECK(i3.readNonBlocking() == false);
BOOST_CHECK(o1.dataValidity() == ctk::DataValidity::ok);
BOOST_CHECK(o2.dataValidity() == ctk::DataValidity::ok);
BOOST_CHECK_EQUAL(int(o1), 3);
BOOST_CHECK_EQUAL(o2[0], 10);
BOOST_CHECK_EQUAL(o2[1], 11);
BOOST_CHECK(i3.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK_EQUAL(int(i3), 10);
// send two data fault flags. both need to be cleared before the outputs go back to ok
i1 = 120;
i1.setDataValidity(ctk::DataValidity::faulty);
i1.write();
i3 = 121;
i3.write();
BOOST_CHECK(i3.dataValidity() == ctk::DataValidity::faulty);
test.stepApplication();
o1.readLatest();
o2.readLatest();
i3.read();
BOOST_CHECK(o1.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK(o2.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK_EQUAL(int(o1), 120);
BOOST_CHECK_EQUAL(o2[0], 10);
BOOST_CHECK_EQUAL(o2[1], 11);
BOOST_CHECK(i3.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK_EQUAL(int(i3), 10);
// clear first flag
i1 = 122;
i1.setDataValidity(ctk::DataValidity::ok);
i1.write();
test.stepApplication();
o1.read();
o2.read();
BOOST_CHECK(i3.readNonBlocking() == false);
BOOST_CHECK(o1.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK(o2.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK_EQUAL(int(o1), 122);
BOOST_CHECK_EQUAL(o2[0], 10);
BOOST_CHECK_EQUAL(o2[1], 11);
BOOST_CHECK(i3.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK_EQUAL(int(i3), 10);
// clear second flag
i3 = 123;
i3.setDataValidity(ctk::DataValidity::ok);
i3.write();
test.stepApplication();
o1.read();
o2.read();
i3.read();
BOOST_CHECK(o1.dataValidity() == ctk::DataValidity::ok);
BOOST_CHECK(o2.dataValidity() == ctk::DataValidity::ok);
BOOST_CHECK_EQUAL(int(o1), 122);
BOOST_CHECK_EQUAL(o2[0], 10);
BOOST_CHECK_EQUAL(o2[1], 11);
BOOST_CHECK(i3.dataValidity() == ctk::DataValidity::ok);
BOOST_CHECK_EQUAL(int(i3), 10);
}
/*********************************************************************************************************************/
BOOST_AUTO_TEST_CASE(testWithFanOut) {
std::cout << "testWithFanOut" << std::endl;
TestApplication2 app;
ctk::TestFacility test;
auto Ai1 = test.getScalar<int>("A/i1");
auto Ai2 = test.getArray<int>("A/i2");
auto Ai3 = test.getScalar<int>("A/i3");
auto Ao1 = test.getScalar<int>("A/o1");
auto Ao2 = test.getArray<int>("A/o2");
auto Bi1 = test.getScalar<int>("B/i1");
auto Bi2 = test.getArray<int>("B/i2");
auto Bi3 = test.getScalar<int>("B/i3");
auto Bo1 = test.getScalar<int>("B/o1");
auto Bo2 = test.getArray<int>("B/o2");
test.runApplication();
Martin Christoph Hierholzer
committed
// test if fault flag propagates to all outputs
Ai1 = 1;
Ai1.setDataValidity(ctk::DataValidity::faulty);
Ai1.write();
test.stepApplication();
Ao1.read();
Ao2.read();
Martin Christoph Hierholzer
committed
BOOST_CHECK(Ao1.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK(Ao2.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK_EQUAL(int(Ao1), 1);
BOOST_CHECK_EQUAL(Ao2[0], 0);
BOOST_CHECK_EQUAL(Ao2[1], 0);
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
BOOST_CHECK(Bo1.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK(Bo2.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK_EQUAL(int(Bo1), 1);
BOOST_CHECK_EQUAL(Bo2[0], 0);
BOOST_CHECK_EQUAL(Bo2[1], 0);
BOOST_CHECK(Bi1.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK_EQUAL(int(Bi1), 1);
// send fault flag on a second variable
Ai2[0] = 2;
Ai2[1] = 3;
Ai2.setDataValidity(ctk::DataValidity::faulty);
Ai2.write();
test.stepApplication();
Ao1.read();
Ao2.read();
Bi2.read();
Bo1.read();
Bo2.read();
BOOST_CHECK(Ao1.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK(Ao2.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK_EQUAL(int(Ao1), 1);
BOOST_CHECK_EQUAL(Ao2[0], 2);
BOOST_CHECK_EQUAL(Ao2[1], 3);
BOOST_CHECK(Bo1.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK(Bo2.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK_EQUAL(int(Bo1), 1);
BOOST_CHECK_EQUAL(Bo2[0], 2);
BOOST_CHECK_EQUAL(Bo2[1], 3);
BOOST_CHECK(Bi2.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK_EQUAL(Bi2[0], 2);
BOOST_CHECK_EQUAL(Bi2[1], 3);
// clear fault flag on a second variable
Ai2[0] = 4;
Ai2[1] = 5;
Ai2.setDataValidity(ctk::DataValidity::ok);
Ai2.write();
test.stepApplication();
Ao1.read();
Ao2.read();
Bi2.read();
Bo1.read();
Bo2.read();
BOOST_CHECK(Ao1.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK(Ao2.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK_EQUAL(int(Ao1), 1);
BOOST_CHECK_EQUAL(Ao2[0], 4);
BOOST_CHECK_EQUAL(Ao2[1], 5);
BOOST_CHECK(Bo1.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK(Bo2.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK_EQUAL(int(Bo1), 1);
BOOST_CHECK_EQUAL(Bo2[0], 4);
BOOST_CHECK_EQUAL(Bo2[1], 5);
BOOST_CHECK(Bi2.dataValidity() == ctk::DataValidity::ok);
BOOST_CHECK_EQUAL(Bi2[0], 4);
BOOST_CHECK_EQUAL(Bi2[1], 5);
// clear fault flag on a first variable
Ai1 = 6;
Ai1.setDataValidity(ctk::DataValidity::ok);
Ai1.write();
test.stepApplication();
Ao1.read();
Ao2.read();
Bi1.read();
Bo1.read();
Bo2.read();
BOOST_CHECK(Ao1.dataValidity() == ctk::DataValidity::ok);
BOOST_CHECK(Ao2.dataValidity() == ctk::DataValidity::ok);
BOOST_CHECK_EQUAL(int(Ao1), 6);
BOOST_CHECK_EQUAL(Ao2[0], 4);
BOOST_CHECK_EQUAL(Ao2[1], 5);
BOOST_CHECK(Bo1.dataValidity() == ctk::DataValidity::ok);
BOOST_CHECK(Bo2.dataValidity() == ctk::DataValidity::ok);
BOOST_CHECK_EQUAL(int(Bo1), 6);
BOOST_CHECK_EQUAL(Bo2[0], 4);
BOOST_CHECK_EQUAL(Bo2[1], 5);
BOOST_CHECK(Bi1.dataValidity() == ctk::DataValidity::ok);
BOOST_CHECK_EQUAL(int(Bi1), 6);
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
/*
* Tests below verify data fault flag propagation on:
* - Threaded FanOut
* - Consuming FanOut
* - Triggers
*/
struct Module1 : ctk::ApplicationModule {
using ctk::ApplicationModule::ApplicationModule;
Martin Killenberg
committed
ctk::ScalarPushInput<int> fromThreadedFanout{this, "o1", "", "", {"DEVICE1", "CS"}};
// As a workaround the device side connection is done manually for
// acheiving this consumingFanout; see:
// TestApplication3::defineConnections
Martin Killenberg
committed
ctk::ScalarPollInput<int> fromConsumingFanout{this, "i1", "", "", {"CS"}};
Martin Killenberg
committed
ctk::ScalarPollInput<int> fromDevice{this, "i2", "", "", {"DEVICE2"}};
ctk::ScalarOutput<int> result{this, "Module1_result", "", "", {"CS"}};
Martin Killenberg
committed
while(true) {
result = fromConsumingFanout + fromThreadedFanout + fromDevice;
writeAll();
Martin Killenberg
committed
readAll(); // read last, so initial values are written in the first round
}
}
};
struct Module2 : ctk::ApplicationModule {
using ctk::ApplicationModule::ApplicationModule;
struct : public ctk::VariableGroup {
using ctk::VariableGroup::VariableGroup;
Martin Killenberg
committed
ctk::ScalarPushInput<int> result{this, "Module1_result", "", "", {"CS"}};
} m1VarsFromCS{this, "m1", "", ctk::HierarchyModifier::oneLevelUp}; // "m1" being in there
// not good for a general case
ctk::ScalarOutput<int> result{this, "Module2_result", "", "", {"CS"}};
Martin Killenberg
committed
while(true) {
result = static_cast<int>(m1VarsFromCS.result);
writeAll();
Martin Killenberg
committed
readAll(); // read last, so initial values are written in the first round
}
}
};
// struct TestApplication3 : ctk::ApplicationModule {
struct TestApplication3 : ctk::Application {
Martin Killenberg
committed
/*
Christoph Kampmeyer
committed
* CS +-----> threaded fanout +------------------+
* + v
* +---------+ +Device1+
* | | |
* Feeding v | |
* CS <----- fanout --+ Module1 <-----+ v |
* | ^ +Consuming |
* | +--------+ fanout |
* +------+ + + |
* v Device2 | |
* CS <-----------+ Module2 | |
* | |
* CS <-----------------------------------+ |
* |
* |
* CS <-----------+ Trigger fanout <------------------+
* ^
* |
* +
* CS
Martin Killenberg
committed
*/
constexpr static char const* ExceptionDummyCDD1 = "(ExceptionDummy:1?map=testDataValidity1.map)";
constexpr static char const* ExceptionDummyCDD2 = "(ExceptionDummy:1?map=testDataValidity2.map)";
TestApplication3() : Application("testDataFlagPropagation") {}
~TestApplication3() { shutdown(); }
Martin Killenberg
committed
Module1 m1{this, "m1", ""};
Module2 m2{this, "m2", ""};
Martin Killenberg
committed
ctk::DeviceModule device1{this, ExceptionDummyCDD1};
ctk::DeviceModule device2{this, ExceptionDummyCDD2};
Martin Killenberg
committed
device1["m1"]("i1") >> m1("i1");
Martin Killenberg
committed
findTag("DEVICE1").connectTo(device1);
findTag("DEVICE2").connectTo(device2);
device1["m1"]("i3")[cs("trigger", typeid(int), 1)] >> cs("i3", typeid(int), 1);
/*********************************************************************************************************************/
/*********************************************************************************************************************/
struct Fixture_testFacility {
Fixture_testFacility()
: device1DummyBackend(boost::dynamic_pointer_cast<ctk::ExceptionDummy>(
Martin Killenberg
committed
ChimeraTK::BackendFactory::getInstance().createBackend(TestApplication3::ExceptionDummyCDD1))),
device2DummyBackend(boost::dynamic_pointer_cast<ctk::ExceptionDummy>(
Martin Killenberg
committed
ChimeraTK::BackendFactory::getInstance().createBackend(TestApplication3::ExceptionDummyCDD2))) {
device1DummyBackend->open();
device2DummyBackend->open();
~Fixture_testFacility() {
device1DummyBackend->throwExceptionRead = false;
device2DummyBackend->throwExceptionWrite = false;
}
boost::shared_ptr<ctk::ExceptionDummy> device1DummyBackend;
boost::shared_ptr<ctk::ExceptionDummy> device2DummyBackend;
TestApplication3 app;
ctk::TestFacility test;
};
BOOST_FIXTURE_TEST_SUITE(data_validity_propagation, Fixture_testFacility)
/*********************************************************************************************************************/
BOOST_AUTO_TEST_CASE(testThreadedFanout) {
Martin Killenberg
committed
std::cout << "testThreadedFanout" << std::endl;
auto threadedFanoutInput = test.getScalar<int>("m1/o1");
auto m1_result = test.getScalar<int>("m1/Module1_result");
auto m2_result = test.getScalar<int>("m2/Module2_result");
threadedFanoutInput = 20;
threadedFanoutInput.write();
// write to register: m1.i1 linked with the consumingFanout.
Martin Killenberg
committed
auto consumingFanoutSource = app.device1.device.getScalarRegisterAccessor<int>("/m1/i1_DUMMY_WRITEABLE");
Martin Killenberg
committed
consumingFanoutSource.write();
Martin Killenberg
committed
auto pollRegister = app.device2.device.getScalarRegisterAccessor<int>("/m1/i2_DUMMY_WRITEABLE");
Martin Killenberg
committed
pollRegister.write();
test.stepApplication();
m1_result.read();
m2_result.read();
BOOST_CHECK_EQUAL(m1_result, 35);
BOOST_CHECK(m1_result.dataValidity() == ctk::DataValidity::ok);
BOOST_CHECK_EQUAL(m2_result, 35);
Martin Killenberg
committed
BOOST_CHECK(m2_result.dataValidity() == ctk::DataValidity::ok);
threadedFanoutInput = 10;
threadedFanoutInput.setDataValidity(ctk::DataValidity::faulty);
threadedFanoutInput.write();
test.stepApplication();
m1_result.read();
m2_result.read();
BOOST_CHECK_EQUAL(m1_result, 25);
BOOST_CHECK(m1_result.dataValidity() == ctk::DataValidity::faulty);
BOOST_CHECK_EQUAL(m2_result, 25);
Martin Killenberg
committed
BOOST_CHECK(m2_result.dataValidity() == ctk::DataValidity::faulty);
threadedFanoutInput = 40;
threadedFanoutInput.setDataValidity(ctk::DataValidity::ok);
threadedFanoutInput.write();
test.stepApplication();
m1_result.read();
m2_result.read();
BOOST_CHECK_EQUAL(m1_result, 55);
BOOST_CHECK(m1_result.dataValidity() == ctk::DataValidity::ok);
BOOST_CHECK_EQUAL(m2_result, 55);
Martin Killenberg
committed
BOOST_CHECK(m2_result.dataValidity() == ctk::DataValidity::ok);
/*********************************************************************************************************************/
Martin Killenberg
committed
BOOST_AUTO_TEST_CASE(testInvalidTrigger) {
Martin Killenberg
committed
std::cout << "testInvalidTrigger" << std::endl;
Martin Killenberg
committed
auto deviceRegister = app.device1.device.getScalarRegisterAccessor<int>("/m1/i3_DUMMY_WRITEABLE");
Martin Killenberg
committed
deviceRegister.write();
auto trigger = test.getScalar<int>("trigger");
auto result = test.getScalar<int>("i3"); //Cs hook into reg: m1.i3
//----------------------------------------------------------------//
// trigger works as expected
trigger = 1;
trigger.write();
test.stepApplication();
result.read();
BOOST_CHECK_EQUAL(result, 20);
BOOST_CHECK(result.dataValidity() == ctk::DataValidity::ok);
//----------------------------------------------------------------//
// faulty trigger
deviceRegister = 30;
Martin Killenberg
committed
deviceRegister.write();
trigger = 1;
trigger.setDataValidity(ctk::DataValidity::faulty);
trigger.write();
test.stepApplication();
result.read();
BOOST_CHECK_EQUAL(result, 30);
BOOST_CHECK(result.dataValidity() == ctk::DataValidity::faulty);
//----------------------------------------------------------------//
// recovery
deviceRegister = 50;
Martin Killenberg
committed
deviceRegister.write();
trigger = 1;
trigger.setDataValidity(ctk::DataValidity::ok);
trigger.write();
test.stepApplication();
result.read();
BOOST_CHECK_EQUAL(result, 50);
BOOST_CHECK(result.dataValidity() == ctk::DataValidity::ok);
/*********************************************************************************************************************/
/*********************************************************************************************************************/
Martin Killenberg
committed
struct Fixture_noTestableMode {
Fixture_noTestableMode()
: device1DummyBackend(boost::dynamic_pointer_cast<ctk::ExceptionDummy>(
Martin Killenberg
committed
ChimeraTK::BackendFactory::getInstance().createBackend(TestApplication3::ExceptionDummyCDD1))),
device2DummyBackend(boost::dynamic_pointer_cast<ctk::ExceptionDummy>(
Martin Killenberg
committed
ChimeraTK::BackendFactory::getInstance().createBackend(TestApplication3::ExceptionDummyCDD2))) {
Martin Killenberg
committed
device1Status.replace(
test.getScalar<int32_t>(ctk::RegisterPath("/Devices") / TestApplication3::ExceptionDummyCDD1 / "status"));
device1DummyBackend->open();
device2DummyBackend->open();
// the block below is a work around for a race condition; make sure
// all values are propagated to the device registers before starting
// the test.
static const int DEFAULT = 1;
test.setScalarDefault("m1/o1", DEFAULT);
test.runApplication();
CHECK_EQUAL_TIMEOUT((device1Status.readLatest(), device1Status), 0, 100000);
// Making sure the default is written to the device before proceeding.
auto m1o1 = device1DummyBackend->getRegisterAccessor<int>("m1/o1", 1, 0, false);
CHECK_EQUAL_TIMEOUT((m1o1->read(), m1o1->accessData(0)), DEFAULT, 10000);
~Fixture_noTestableMode() {
device1DummyBackend->throwExceptionRead = false;
device2DummyBackend->throwExceptionWrite = false;
}
boost::shared_ptr<ctk::ExceptionDummy> device1DummyBackend;
boost::shared_ptr<ctk::ExceptionDummy> device2DummyBackend;
Martin Killenberg
committed
ctk::TestFacility test{false};
Martin Killenberg
committed
ChimeraTK::ScalarRegisterAccessor<int> device1Status;
Martin Killenberg
committed
BOOST_FIXTURE_TEST_SUITE(data_validity_propagation_noTestFacility, Fixture_noTestableMode)
BOOST_AUTO_TEST_CASE(testDeviceReadFailure) {
Martin Killenberg
committed
std::cout << "testDeviceReadFailure" << std::endl;
Martin Killenberg
committed
auto consumingFanoutSource = app.device1.device.getScalarRegisterAccessor<int>("/m1/i1_DUMMY_WRITEABLE");
Martin Killenberg
committed
auto pollRegister = app.device2.device.getScalarRegisterAccessor<int>("/m1/i2_DUMMY_WRITEABLE");
auto threadedFanoutInput = test.getScalar<int>("m1/o1");
auto result = test.getScalar<int>("m1/Module1_result");
auto device2Status =
test.getScalar<int32_t>(ctk::RegisterPath("/Devices") / TestApplication3::ExceptionDummyCDD2 / "status");
threadedFanoutInput = 10000;
consumingFanoutSource = 1000;
Martin Killenberg
committed
consumingFanoutSource.write();
Martin Killenberg
committed
pollRegister.write();
// -------------------------------------------------------------//
// without errors
threadedFanoutInput.write();
Martin Killenberg
committed
CHECK_TIMEOUT((result.readLatest(), result == 11001), 10000);
BOOST_CHECK(result.dataValidity() == ctk::DataValidity::ok);
// -------------------------------------------------------------//
// device module exception
Martin Killenberg
committed
pollRegister.write();
// The new value from the fanout input should have been propagated,
// the new value of the poll input is not seen, because it gets skipped
BOOST_CHECK(result.dataValidity() == ctk::DataValidity::faulty);
// -------------------------------------------------------------//
threadedFanoutInput = 30000;
threadedFanoutInput.write();
// Further reads to the poll input are skipped
result.read();
BOOST_CHECK_EQUAL(result, 31001);
BOOST_CHECK(result.dataValidity() == ctk::DataValidity::faulty);
// -------------------------------------------------------------//
CHECK_EQUAL_TIMEOUT((device2Status.readLatest(), device2Status), 0, 100000);
threadedFanoutInput = 40000;
threadedFanoutInput.write();
// Now we expect also the last value written to the pollRegister being
// propagated and the DataValidity should be ok again.
BOOST_CHECK_EQUAL(result, 41000);
BOOST_CHECK(result.dataValidity() == ctk::DataValidity::ok);
Martin Killenberg
committed
BOOST_AUTO_TEST_CASE(testReadDeviceWithTrigger) {
Martin Killenberg
committed
std::cout << "testReadDeviceWithTrigger" << std::endl;
auto trigger = test.getScalar<int>("trigger");
auto fromDevice = test.getScalar<int>("i3"); // cs side display: m1.i3
//----------------------------------------------------------------//
Martin Killenberg
committed
fromDevice.read(); // there is an initial value
BOOST_CHECK_EQUAL(fromDevice, 0);
// trigger works as expected
trigger = 1;
Martin Killenberg
committed
auto deviceRegister = app.device1.device.getScalarRegisterAccessor<int>("/m1/i3_DUMMY_WRITEABLE");
Martin Killenberg
committed
deviceRegister.write();
Martin Killenberg
committed
fromDevice.read();
BOOST_CHECK_EQUAL(fromDevice, 30);
BOOST_CHECK(fromDevice.dataValidity() == ctk::DataValidity::ok);
//----------------------------------------------------------------//
// Device module exception
deviceRegister = 10;
Martin Killenberg
committed
deviceRegister.write();
trigger = 1;
trigger.write();
Martin Killenberg
committed
fromDevice.read();
BOOST_CHECK_EQUAL(fromDevice, 30);
BOOST_CHECK(fromDevice.dataValidity() == ctk::DataValidity::faulty);
//----------------------------------------------------------------//
// Recovery
Martin Killenberg
committed
// Wait until the device has recovered. Otherwise the read might be skipped and we still read the previous value with the faulty flag.
while((void)device1Status.read(), device1Status == 1) {
usleep(1000);
}
Martin Killenberg
committed
fromDevice.read();
BOOST_CHECK_EQUAL(fromDevice, 10);
BOOST_CHECK(fromDevice.dataValidity() == ctk::DataValidity::ok);
}
Martin Killenberg
committed
BOOST_AUTO_TEST_CASE(testConsumingFanout) {
Martin Killenberg
committed
std::cout << "testConsumingFanout" << std::endl;
auto threadedFanoutInput = test.getScalar<int>("m1/o1");
auto fromConsumingFanout = test.getScalar<int>("m1/i1"); // consumingfanout variable on cs side
auto result = test.getScalar<int>("m1/Module1_result");
Martin Killenberg
committed
fromConsumingFanout.read(); // initial value, don't care for this test
result.read(); // initial value, don't care for this test
Martin Killenberg
committed
auto pollRegisterSource = app.device2.device.getScalarRegisterAccessor<int>("/m1/i2_DUMMY_WRITEABLE");
Martin Killenberg
committed
pollRegisterSource = 100;
Martin Killenberg
committed
pollRegisterSource.write();
Martin Killenberg
committed
auto consumingFanoutSource = app.device1.device.getScalarRegisterAccessor<int>("/m1/i1_DUMMY_WRITEABLE");
Martin Killenberg
committed
consumingFanoutSource.write();
//----------------------------------------------------------//
// no device module exception
threadedFanoutInput.write();
Martin Killenberg
committed
result.read();
BOOST_CHECK_EQUAL(result, 111);
BOOST_CHECK(result.dataValidity() == ctk::DataValidity::ok);
Martin Killenberg
committed
fromConsumingFanout.read();
BOOST_CHECK_EQUAL(fromConsumingFanout, 1);
BOOST_CHECK(fromConsumingFanout.dataValidity() == ctk::DataValidity::ok);
// --------------------------------------------------------//
// device exception on consuming fanout source read
consumingFanoutSource = 0;
Martin Killenberg
committed
consumingFanoutSource.write();
threadedFanoutInput.write();
CHECK_TIMEOUT(result.readLatest(), 10000);
BOOST_CHECK(result.dataValidity() == ctk::DataValidity::faulty);
CHECK_TIMEOUT(fromConsumingFanout.readLatest(), 10000);
BOOST_CHECK_EQUAL(fromConsumingFanout, 1);
BOOST_CHECK(fromConsumingFanout.dataValidity() == ctk::DataValidity::faulty);
// --------------------------------------------------------//
// Recovery
device1DummyBackend->throwExceptionRead = false;
// Wait until the device has recovered. Otherwise the read might be skipped and we still read the previous value with the faulty flag.
while((void)device1Status.read(), device1Status == 1) {
usleep(1000);
}
threadedFanoutInput = 30;
threadedFanoutInput.write();
CHECK_TIMEOUT(result.readLatest(), 10000);
BOOST_CHECK(result.dataValidity() == ctk::DataValidity::ok);
CHECK_TIMEOUT(fromConsumingFanout.readLatest(), 10000);
BOOST_CHECK_EQUAL(fromConsumingFanout, 0);
BOOST_CHECK(fromConsumingFanout.dataValidity() == ctk::DataValidity::ok);
}
BOOST_AUTO_TEST_CASE(testDataFlowOnDeviceException) {
Martin Killenberg
committed
std::cout << "testDataFlowOnDeviceException" << std::endl;
auto threadedFanoutInput = test.getScalar<int>("m1/o1");
auto m1_result = test.getScalar<int>("m1/Module1_result");
auto m2_result = test.getScalar<int>("m2/Module2_result");
Martin Killenberg
committed
auto consumingFanoutSource = app.device1.device.getScalarRegisterAccessor<int>("/m1/i1_DUMMY_WRITEABLE");
Martin Killenberg
committed
consumingFanoutSource.write();
Martin Killenberg
committed
auto pollRegister = app.device2.device.getScalarRegisterAccessor<int>("/m1/i2_DUMMY_WRITEABLE");
Martin Killenberg
committed
pollRegister.write();
threadedFanoutInput = 1;
// ------------------------------------------------------------------//
// without exception
threadedFanoutInput.write();
// Read till the value we want; there is a chance of spurious values
// sneaking in due to a race condition when dealing with device
// modules. These spurious entries (with value: PV defaults) do
// not matter for a real application.
Martin Killenberg
committed
CHECK_EQUAL_TIMEOUT((m1_result.readNonBlocking(), m1_result), 1101, 10000);
BOOST_CHECK(m1_result.dataValidity() == ctk::DataValidity::ok);
CHECK_EQUAL_TIMEOUT((m2_result.readLatest(), m2_result), 1101, 10000);
BOOST_CHECK_EQUAL(m2_result, 1101);
BOOST_CHECK(m2_result.dataValidity() == ctk::DataValidity::ok);
// ------------------------------------------------------------------//
// faulty threadedFanout input
threadedFanoutInput.setDataValidity(ctk::DataValidity::faulty);
threadedFanoutInput.write();
CHECK_TIMEOUT(m1_result.readLatest(), 10000);
BOOST_CHECK_EQUAL(m1_result, 1101);
BOOST_CHECK(m1_result.dataValidity() == ctk::DataValidity::faulty);
CHECK_TIMEOUT(m2_result.readLatest(), 10000);
BOOST_CHECK_EQUAL(m2_result, 1101);
BOOST_CHECK(m2_result.dataValidity() == ctk::DataValidity::faulty);
Martin Killenberg
committed
auto deviceStatus =
test.getScalar<int32_t>(ctk::RegisterPath("/Devices") / TestApplication3::ExceptionDummyCDD2 / "status");
// the device is still OK
CHECK_EQUAL_TIMEOUT((deviceStatus.readLatest(), deviceStatus), 0, 10000);
// ---------------------------------------------------------------------//
// device module exception
Martin Killenberg
committed
pollRegister = 200;
Martin Killenberg
committed
pollRegister.write();
threadedFanoutInput = 0;
threadedFanoutInput.write();
Martin Killenberg
committed
// Now the device has to go into the error state
CHECK_EQUAL_TIMEOUT((deviceStatus.readLatest(), deviceStatus), 1, 10000);
// The new value of the threadedFanoutInput should be propagated, the
// pollRegister is skipped, see testDataValidPropagationOnException.
Martin Killenberg
committed
m1_result.read();
Martin Killenberg
committed
BOOST_CHECK(m1_result.dataValidity() == ctk::DataValidity::faulty);
m2_result.read();
// Same for m2
BOOST_CHECK_EQUAL(m2_result, 1100);
Martin Killenberg
committed
BOOST_CHECK(m2_result.dataValidity() == ctk::DataValidity::faulty);
// ---------------------------------------------------------------------//
// device exception recovery
Martin Killenberg
committed
// device error recovers. There must be exactly one new status values with the right value.
deviceStatus.read();
BOOST_CHECK(deviceStatus == 0);
// nothing else in the queue
BOOST_CHECK(deviceStatus.readNonBlocking() == false);
// ---------------------------------------------------------------------//
// Now both, threadedFanoutInput and pollRegister should propagate
pollRegister = 300;
Martin Killenberg
committed
pollRegister.write();
threadedFanoutInput = 2;
threadedFanoutInput.write();
Martin Killenberg
committed
m1_result.read();
Martin Killenberg
committed
// Data validity still faulty because the input from the fan is invalid
BOOST_CHECK(m1_result.dataValidity() == ctk::DataValidity::faulty);
Martin Killenberg
committed
// again, nothing else in the queue
BOOST_CHECK(m1_result.readNonBlocking() == false);
Martin Killenberg
committed
// same for m2
m2_result.read();
BOOST_CHECK(m2_result.dataValidity() == ctk::DataValidity::faulty);
Martin Killenberg
committed
BOOST_CHECK(m2_result.readNonBlocking() == false);
// ---------------------------------------------------------------------//
// recovery: fanout input
Martin Killenberg
committed
threadedFanoutInput = 3;
threadedFanoutInput.setDataValidity(ctk::DataValidity::ok);
threadedFanoutInput.write();
Martin Killenberg
committed
m1_result.read();
BOOST_CHECK(m1_result.dataValidity() == ctk::DataValidity::ok);
Martin Killenberg
committed
BOOST_CHECK(m1_result.readNonBlocking() == false);
Martin Killenberg
committed
m2_result.read();
BOOST_CHECK(m2_result.dataValidity() == ctk::DataValidity::ok);
Martin Killenberg
committed
BOOST_CHECK(m1_result.readNonBlocking() == false);
/*********************************************************************************************************************/
/*********************************************************************************************************************/
// Module and Application for test case "testDataValidPropagationOnException"
struct Module3 : ctk::ApplicationModule {
using ctk::ApplicationModule::ApplicationModule;
ctk::ScalarPushInput<int> pushTypeInputFromCS{this, "o1", "", "", {"CS"}};
ctk::ScalarPollInput<int> pollInputFromDevice{this, "i2", "", "", {"DEVICE2"}};
ctk::ScalarOutput<int> result{this, "Module3_result", "", "", {"CS"}};
void mainLoop() override {
while(true) {
result = pushTypeInputFromCS + pollInputFromDevice;
Martin Killenberg
committed
readAll(); // read last, so initial values are written in the first round
}
}
};
struct TestApplication4 : ctk::Application {
/*
*
*/
constexpr static char const* ExceptionDummyCDD2 = "(ExceptionDummy:1?map=testDataValidity2.map)";
TestApplication4() : Application("testDataFlagPropagation") {}
~TestApplication4() { shutdown(); }
Module3 module{this, "module", ""};
ctk::ControlSystemModule cs;
ctk::DeviceModule device2{this, ExceptionDummyCDD2};
void defineConnections() override {
findTag("CS").connectTo(cs);
findTag("DEVICE2").flatten().connectTo(device2["m1"]);
}
};
/*********************************************************************************************************************/
BOOST_AUTO_TEST_CASE(testDataValidPropagationOnException) {
std::cout << "testDataValidPropagationOnException" << std::endl;
boost::shared_ptr<ctk::ExceptionDummy> device2DummyBackend(boost::dynamic_pointer_cast<ctk::ExceptionDummy>(
ChimeraTK::BackendFactory::getInstance().createBackend(TestApplication3::ExceptionDummyCDD2)));
TestApplication4 app;
ctk::TestFacility test{false};
test.runApplication();
Martin Killenberg
committed
auto pollRegister = app.device2.device.getScalarRegisterAccessor<int>("/m1/i2_DUMMY_WRITEABLE");
auto pushInput = test.getScalar<int>("module/o1");
auto result = test.getScalar<int>("module/Module3_result");
auto deviceStatus =
test.getScalar<int32_t>(ctk::RegisterPath("/Devices") / TestApplication4::ExceptionDummyCDD2 / "status");
pollRegister = 1;
Martin Killenberg
committed
pollRegister.write();
pushInput = 10;
pushInput.write();
CHECK_TIMEOUT((result.readLatest(), result == 11), 10000);
BOOST_CHECK(result.dataValidity() == ctk::DataValidity::ok);
CHECK_EQUAL_TIMEOUT((deviceStatus.readLatest(), deviceStatus), 0, 10000);
// Set data validity to faulty and trigger excetion in the same update
pollRegister = 2;
Martin Killenberg
committed
pollRegister.write();
pushInput = 20;
pushInput.setDataValidity(ctk::DataValidity::faulty);
device2DummyBackend->throwExceptionRead = true;
pushInput.write();
CHECK_EQUAL_TIMEOUT((deviceStatus.readLatest(), deviceStatus), 1, 10000);
Martin Killenberg
committed
result.read();
BOOST_CHECK(result.readLatest() == false);
// The new data from the pushInput and the DataValidity::faulty should have been propagated to the outout,
// the pollRegister should be skipped (Exceptionhandling spec B.2.2.3), so we don't expect the latest assigned value of 2
BOOST_CHECK_EQUAL(result, 21);
BOOST_CHECK(result.dataValidity() == ctk::DataValidity::faulty);
// Writeing the pushInput should still trigger module execution and
// update the result value. Result validity should still be faulty because
// the device still has the exception
pushInput = 30;
pushInput.setDataValidity(ctk::DataValidity::ok);