Newer
Older
#define BOOST_TEST_MODULE testExceptionHandling
#include <boost/test/included/unit_test.hpp>
#include <boost/test/test_case_template.hpp>
#include <ChimeraTK/BackendFactory.h>
Martin Killenberg
committed
#include <ChimeraTK/DummyRegisterAccessor.h>
#include "Application.h"
#include "ApplicationModule.h"
#include "ControlSystemModule.h"
#include "ScalarAccessor.h"
#include "TestFacility.h"
using namespace boost::unit_test_framework;
namespace ctk = ChimeraTK;
Martin Killenberg
committed
constexpr char ExceptionDummyCDD1[] = "(ExceptionDummy:1?map=test3.map)";
constexpr char ExceptionDummyCDD2[] = "(ExceptionDummy:2?map=test3.map)";
#define CHECK_TIMEOUT(condition, maxMilliseconds) \
{ \
std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); \
while(!(condition)) { \
bool timeout_reached = (std::chrono::steady_clock::now() - t0) > std::chrono::milliseconds(maxMilliseconds); \
BOOST_CHECK(!timeout_reached); \
if(timeout_reached) break; \
usleep(1000); \
} \
}
/* dummy application */
struct TestApplication : public ctk::Application {
TestApplication() : Application("testSuite") {}
~TestApplication() { shutdown(); }
void defineConnections() {} // the setup is done in the tests
Martin Killenberg
committed
ctk::DeviceModule dev1{this, ExceptionDummyCDD1};
ctk::DeviceModule dev2{this, ExceptionDummyCDD2};
ctk::ControlSystemModule cs;
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
BOOST_AUTO_TEST_CASE(testExceptionHandlingRead) {
std::cout << "testExceptionHandlingRead" << std::endl;
Martin Christoph Hierholzer
committed
TestApplication app;
Martin Killenberg
committed
boost::shared_ptr<ExceptionDummy> dummyBackend1 = boost::dynamic_pointer_cast<ExceptionDummy>(
Martin Killenberg
committed
ChimeraTK::BackendFactory::getInstance().createBackend(ExceptionDummyCDD1));
Martin Killenberg
committed
boost::shared_ptr<ExceptionDummy> dummyBackend2 = boost::dynamic_pointer_cast<ExceptionDummy>(
ChimeraTK::BackendFactory::getInstance().createBackend(ExceptionDummyCDD2));
Martin Killenberg
committed
Martin Killenberg
committed
ChimeraTK::DummyRegisterAccessor<int> readbackDummy1(dummyBackend1.get(), "MyModule", "readBack");
ChimeraTK::DummyRegisterAccessor<int> readbackDummy2(dummyBackend2.get(), "MyModule", "readBack");
// Connect the whole devices into the control system, and use the control system variable /trigger as trigger for
// both devices. The variable becomes a control system to application variable and writing to it through the test
// facility is generating the triggers.
app.dev1.connectTo(app.cs["Device1"], app.cs("trigger", typeid(int), 1));
app.dev2.connectTo(app.cs["Device2"], app.cs("trigger"));
Martin Christoph Hierholzer
committed
// Do not enable testable mode. The testable mode would block in the wrong place, as the trigger for reading variables
// of a device in the error state is not being processed until the error state is cleared. We want to test that the
// second device still works while the first device is in error state, which would be impossible with testable mode
// enabled. As a consequence, our test logic has to work with timeouts (CHECK_TIMEOUT) etc. instead of the
// deterministic stepApplication().
ctk::TestFacility test(false);
Martin Christoph Hierholzer
committed
test.runApplication();
auto message1 = test.getScalar<std::string>(std::string("/Devices/") + ExceptionDummyCDD1 + "/message");
auto status1 = test.getScalar<int>(std::string("/Devices/") + ExceptionDummyCDD1 + "/status");
Martin Killenberg
committed
auto readback1 = test.getScalar<int>("/Device1/MyModule/readBack");
auto message2 = test.getScalar<std::string>(std::string("/Devices/") + ExceptionDummyCDD2 + "/message");
auto status2 = test.getScalar<int>(std::string("/Devices/") + ExceptionDummyCDD2 + "/status");
Martin Killenberg
committed
auto readback2 = test.getScalar<int>("/Device2/MyModule/readBack");
Martin Killenberg
committed
auto trigger = test.getScalar<int>("trigger");
Martin Killenberg
committed
readbackDummy1 = 42;
readbackDummy2 = 52;
Martin Christoph Hierholzer
committed
// initially there should be no error set
Martin Killenberg
committed
trigger.write();
BOOST_CHECK(!message1.readLatest());
BOOST_CHECK(!status1.readLatest());
CHECK_TIMEOUT(readback1.readLatest(), 1000);
CHECK_TIMEOUT(readback2.readLatest(), 1000);
Martin Killenberg
committed
BOOST_CHECK(static_cast<std::string>(message1) == "");
Martin Killenberg
committed
BOOST_CHECK(status1 == 0);
BOOST_CHECK_EQUAL(readback1, 42);
BOOST_CHECK_EQUAL(readback2, 52);
Martin Christoph Hierholzer
committed
// repeat test a couple of times to make sure it works not only once
for(size_t i = 0; i < 3; ++i) {
// enable exception throwing in test device 1
readbackDummy1 = 10 + i;
readbackDummy2 = 20 + i;
dummyBackend1->throwExceptionRead = true;
Martin Christoph Hierholzer
committed
trigger.write();
CHECK_TIMEOUT(message1.readLatest(), 1000);
CHECK_TIMEOUT(status1.readLatest(), 1000);
Martin Killenberg
committed
BOOST_CHECK(static_cast<std::string>(message1) != "");
BOOST_CHECK_EQUAL(status1, 1);
BOOST_CHECK(readback1.readNonBlocking()); // we have been signalized new data
BOOST_CHECK(readback1.dataValidity() == ChimeraTK::DataValidity::faulty); // But the fault flag should be set
// the second device must still be functional
BOOST_CHECK(!message2.readNonBlocking());
BOOST_CHECK(!status2.readNonBlocking());
CHECK_TIMEOUT(readback2.readNonBlocking(), 1000); // device 2 still works
Martin Killenberg
committed
// even with device 1 failing the second one must process the data, so send a new trigger
// before fixing dev1
Martin Killenberg
committed
trigger.write();
BOOST_CHECK(!readback1.readNonBlocking()); // we should not have gotten any new data
BOOST_CHECK(readback1.dataValidity() == ChimeraTK::DataValidity::faulty); // But the fault flag should still be set
CHECK_TIMEOUT(readback2.readNonBlocking(), 1000); // device 2 still works
readbackDummy1 = 30 + i;
readbackDummy2 = 40 + i;
dummyBackend1->throwExceptionRead = false;
Martin Christoph Hierholzer
committed
trigger.write();
CHECK_TIMEOUT(message1.readLatest(), 1000);
CHECK_TIMEOUT(status1.readLatest(), 1000);
CHECK_TIMEOUT(readback1.readNonBlocking(), 1000);
Martin Killenberg
committed
BOOST_CHECK_EQUAL(static_cast<std::string>(message1), "");
BOOST_CHECK_EQUAL(status1, 0);
BOOST_CHECK(readback1.dataValidity() == ChimeraTK::DataValidity::ok); // The fault flag should have been cleared
// there are two more copies in the queue, since the two triggers received during the error state is still
// processed after recovery
CHECK_TIMEOUT(readback1.readNonBlocking(), 1000);
BOOST_CHECK_EQUAL(readback1, 30 + i);
CHECK_TIMEOUT(readback1.readNonBlocking(), 1000);
BOOST_CHECK(!readback1.readNonBlocking()); // now the queue should be empty
Martin Killenberg
committed
// device2
BOOST_CHECK(!message2.readNonBlocking());
BOOST_CHECK(!status2.readNonBlocking());
CHECK_TIMEOUT(readback2.readNonBlocking(), 1000); // device 2 still works
Martin Christoph Hierholzer
committed
}
}
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
/*********************************************************************************************************************/
BOOST_AUTO_TEST_CASE(testExceptionHandlingWrite) {
std::cout << "testExceptionHandlingWrite" << std::endl;
TestApplication app;
boost::shared_ptr<ExceptionDummy> dummyBackend1 = boost::dynamic_pointer_cast<ExceptionDummy>(
ChimeraTK::BackendFactory::getInstance().createBackend(ExceptionDummyCDD1));
boost::shared_ptr<ExceptionDummy> dummyBackend2 = boost::dynamic_pointer_cast<ExceptionDummy>(
ChimeraTK::BackendFactory::getInstance().createBackend(ExceptionDummyCDD2));
ChimeraTK::DummyRegisterAccessor<int> actuatorDummy1(dummyBackend1.get(), "MyModule", "actuator");
ChimeraTK::DummyRegisterAccessor<int> actuatorDummy2(dummyBackend2.get(), "MyModule", "actuator");
// Connect the whole devices into the control system, and use the control system variable /trigger as trigger for
// both devices. The variable becomes a control system to application variable and writing to it through the test
// facility is generating the triggers.
app.dev1.connectTo(app.cs["Device1"], app.cs("trigger", typeid(int), 1));
app.dev2.connectTo(app.cs["Device2"], app.cs("trigger"));
// Do not enable testable mode. The testable mode would block in the wrong place, as the trigger for reading variables
// of a device in the error state is not being processed until the error state is cleared. We want to test that the
// second device still works while the first device is in error state, which would be impossible with testable mode
// enabled. As a consequence, our test logic has to work with timeouts (CHECK_TIMEOUT) etc. instead of the
// deterministic stepApplication().
ctk::TestFacility test(false);
test.runApplication();
auto message1 = test.getScalar<std::string>(std::string("/Devices/") + ExceptionDummyCDD1 + "/message");
auto status1 = test.getScalar<int>(std::string("/Devices/") + ExceptionDummyCDD1 + "/status");
auto actuator1 = test.getScalar<int>("/Device1/MyModule/actuator");
auto message2 = test.getScalar<std::string>(std::string("/Devices/") + ExceptionDummyCDD2 + "/message");
auto status2 = test.getScalar<int>(std::string("/Devices/") + ExceptionDummyCDD2 + "/status");
auto actuator2 = test.getScalar<int>("/Device2/MyModule/actuator");
auto trigger = test.getScalar<int>("trigger");
// initially there should be no error set
actuator1 = 29;
actuator1.write();
actuator2 = 39;
actuator2.write();
BOOST_CHECK(!message1.readLatest());
BOOST_CHECK(!status1.readLatest());
CHECK_TIMEOUT(actuatorDummy1 == 29, 1000);
CHECK_TIMEOUT(actuatorDummy2 == 39, 1000);
BOOST_CHECK(static_cast<std::string>(message1) == "");
BOOST_CHECK(status1 == 0);
// repeat test a couple of times to make sure it works not only once
for(size_t i = 0; i < 3; ++i) {
// enable exception throwing in test device 1
dummyBackend1->throwExceptionWrite = true;
actuator1 = 30 + i;
actuator1.write();
actuator2 = 40 + i;
actuator2.write();
CHECK_TIMEOUT(message1.readLatest(), 1000);
CHECK_TIMEOUT(status1.readLatest(), 1000);
BOOST_CHECK(static_cast<std::string>(message1) != "");
BOOST_CHECK_EQUAL(status1, 1);
usleep(10000); // 10ms wait time so potential wrong values could have propagated
BOOST_CHECK(actuatorDummy1 == int(30 + i - 1)); // write not done for broken device
// the second device must still be functional
BOOST_CHECK(!message2.readNonBlocking());
BOOST_CHECK(!status2.readNonBlocking());
CHECK_TIMEOUT(actuatorDummy2 == int(40 + i), 1000); // device 2 still works
// even with device 1 failing the second one must process the data, so send a new data before fixing dev1
actuator2 = 120 + i;
actuator2.write();
CHECK_TIMEOUT(actuatorDummy2 == int(120 + i), 1000); // device 2 still works
// Now "cure" the device problem
dummyBackend1->throwExceptionWrite = false;
CHECK_TIMEOUT(message1.readLatest(), 1000);
CHECK_TIMEOUT(status1.readLatest(), 1000);
CHECK_TIMEOUT(actuatorDummy1 == int(30 + i), 1000); // write is now complete
BOOST_CHECK_EQUAL(static_cast<std::string>(message1), "");
BOOST_CHECK_EQUAL(status1, 0);
}
}
/*********************************************************************************************************************/
BOOST_AUTO_TEST_CASE(testExceptionHandlingOpen) {
std::cout << "testExceptionHandlingOpen" << std::endl;
TestApplication app;
boost::shared_ptr<ExceptionDummy> dummyBackend1 = boost::dynamic_pointer_cast<ExceptionDummy>(
ChimeraTK::BackendFactory::getInstance().createBackend(ExceptionDummyCDD1));
boost::shared_ptr<ExceptionDummy> dummyBackend2 = boost::dynamic_pointer_cast<ExceptionDummy>(
ChimeraTK::BackendFactory::getInstance().createBackend(ExceptionDummyCDD2));
ChimeraTK::DummyRegisterAccessor<int> readbackDummy1(dummyBackend1.get(), "MyModule", "readBack");
ChimeraTK::DummyRegisterAccessor<int> readbackDummy2(dummyBackend2.get(), "MyModule", "readBack");
// Connect the whole devices into the control system, and use the control system variable /trigger as trigger for
// both devices. The variable becomes a control system to application variable and writing to it through the test
// facility is generating the triggers.
app.dev1.connectTo(app.cs["Device1"], app.cs("trigger", typeid(int), 1));
app.dev2.connectTo(app.cs["Device2"], app.cs("trigger"));
// Do not enable testable mode. The testable mode would block in the wrong place, as the trigger for reading variables
// of a device in the error state is not being processed until the error state is cleared. We want to test that the
// second device still works while the first device is in error state, which would be impossible with testable mode
// enabled. As a consequence, our test logic has to work with timeouts (CHECK_TIMEOUT) etc. instead of the
// deterministic stepApplication().
ctk::TestFacility test(false);
dummyBackend1->throwExceptionOpen = true;
app.run(); // don't use TestFacility::runApplication() here as it blocks until all devices are open...
auto message1 = test.getScalar<std::string>(std::string("/Devices/") + ExceptionDummyCDD1 + "/message");
auto status1 = test.getScalar<int>(std::string("/Devices/") + ExceptionDummyCDD1 + "/status");
auto readback1 = test.getScalar<int>("/Device1/MyModule/readBack");
auto message2 = test.getScalar<std::string>(std::string("/Devices/") + ExceptionDummyCDD2 + "/message");
auto status2 = test.getScalar<int>(std::string("/Devices/") + ExceptionDummyCDD2 + "/status");
auto readback2 = test.getScalar<int>("/Device2/MyModule/readBack");
auto trigger = test.getScalar<int>("trigger");
readbackDummy1 = 100;
trigger.write();
//device 1 is in Error state
CHECK_TIMEOUT(message1.readLatest(), 1000);
CHECK_TIMEOUT(status1.readLatest(), 1000);
BOOST_CHECK_EQUAL(status1, 1);
BOOST_CHECK(!readback1.readNonBlocking());
CHECK_TIMEOUT(readback2.readNonBlocking(), 1000);
BOOST_CHECK_EQUAL(readback2, 110);
// even with device 1 failing the second one must process the data, so send a new trigger
// before fixing dev1
readbackDummy2 = 120;
trigger.write();
CHECK_TIMEOUT(readback2.readNonBlocking(), 1000); // device 2 still works
BOOST_CHECK_EQUAL(readback2, 120);
CHECK_TIMEOUT(!message2.readLatest(), 1000);
CHECK_TIMEOUT(!status2.readLatest(), 1000);
dummyBackend1->throwExceptionOpen = false;
//device 1 is fixed
CHECK_TIMEOUT(message1.readLatest(), 1000);
CHECK_TIMEOUT(status1.readLatest(), 1000);
BOOST_CHECK_EQUAL(status1, 0);
CHECK_TIMEOUT(readback1.readNonBlocking(), 1000);
}
Martin Killenberg
committed
BOOST_AUTO_TEST_CASE(testConstants){
Martin Killenberg
committed
std::cout << "testConstants" << std::endl;
Martin Killenberg
committed
// Constants are registered to the device to be written when opening/recovering
// Attention: This test does not test that errors when writing to constants are displayed correctly. It only checks that witing when opeing and recovering works.
TestApplication app;
ctk::VariableNetworkNode::makeConstant<int32_t>(true, 18) >> app.dev1("/MyModule/actuator");
app.cs("PleaseWriteToMe", typeid(int), 1) >> app.dev1("/Integers/signed32", typeid(int), 1);
Martin Killenberg
committed
ctk::TestFacility test;
test.runApplication();
ChimeraTK::Device dev;
dev.open(ExceptionDummyCDD1);
// after opening a device the runApplication() might return, but the initialisation might not have happened in the other thread yet. So check with timeout.
CHECK_TIMEOUT(dev.read<int32_t>("/MyModule/actuator") == 18, 3000);
// So far this is also tested by testDeviceAccessors. Now cause errors.
// Take back the value of the constant which was written to the device before making the device fail for further writes.
dev.write<int32_t>("/MyModule/actuator",0);
auto dummyBackend = boost::dynamic_pointer_cast<ExceptionDummy>( ctk::BackendFactory::getInstance().createBackend(ExceptionDummyCDD1));
dummyBackend->throwExceptionWrite=true;
auto pleaseWriteToMe = test.getScalar<int32_t>("/PleaseWriteToMe");
pleaseWriteToMe = 42;
Martin Killenberg
committed
std::cout << "here goes nothing " << std::endl;
sleep(1);
Martin Killenberg
committed
pleaseWriteToMe.write();
test.stepApplication();
// Check that the error has been seen
auto deviceStatus = test.getScalar<int32_t>(std::string("/Devices/") + ExceptionDummyCDD1 + "/status");
deviceStatus.readLatest();
BOOST_CHECK(deviceStatus == 1);
// now cure the error
dummyBackend->throwExceptionWrite=false;
// Write something so we can call stepApplication to wake up the app.
pleaseWriteToMe = 43;
pleaseWriteToMe.write();
test.stepApplication();
CHECK_TIMEOUT(dev.read<int32_t>("/MyModule/actuator") == 18, 3000);
}
/// @todo FIXME: Write test that errors during constant writing are handled correctly, incl. correct error messages to the control system
BOOST_AUTO_TEST_CASE(testConstantWitingErrors){
}