Skip to content
Snippets Groups Projects
Unverified Commit a95fb9a6 authored by Martin Christoph Hierholzer's avatar Martin Christoph Hierholzer Committed by GitHub
Browse files

Mhier/wip 8374 exception handling tests (#213)

Implement systematic tests for exception handling along specification.

This fixes redmin issues #8374, #8520 and #8364.
parent 63980f3d
No related branches found
No related tags found
No related merge requests found
......@@ -47,41 +47,38 @@ namespace ChimeraTK {
- 2.1.1 The variable \c Devices/\<alias\>/status contains a boolean flag whether the device is in an error state.
- 2.1.2 The variable \c Devices/\<alias\>/message contains an error message, if the device is in an error state, or an empty string otherwise.
- \anchor b_2_2 2.2 Read operations will propagate the DataValidity::faulty flag to the owning module / fan out:
- 2.2.1 The normal module algorithm code will be continued, to allow this flag to propagate to the outputs in the same way as if it had been received through the process variable itself (cf. \ref b_1_2 "1.2").
- 2.2.2 The DataValidity::faulty flag resulting from the fault state is propagated once, even if the variable had the a DataValidity::faulty flag already set previously for another reason.
- \anchor exceptionHandling_b_2_2_3 2.2.3 Read operations without AccessMode::wait_for_new_data are _skipped_ until the device is fully recovered again (cf. \ref b_3_1 "3.1"). The first skipped read operation will have a new VersionNumber. [\ref testExceptionHandling_b_2_2_3 "T"]
- \anchor exceptionHandling_b_2_2_4 2.2.4 Read operations with AccessMode::wait_for_new_data will be _skipped_ once for each accessor to propagate the DataValidity::faulty flag (which counts as new data, i.e. readNonBlocking()/readLatest() will return true (= hasNewData), and a new VersionNumber is obtained). In the following:
- non-blocking read operations (readNonBlocking() and readLatest()) are _skipped_ and return false (= no new data), until the device is recovered[\ref testExceptionHandling_b_2_2_4_b "T1"] [\ref testExceptionHandling_b_2_2_4_c "T2"]
, and
- blocking read operations (read()) will be _frozen_ until the device is recovered. [\ref testExceptionHandling_b_2_2_4_a "T"]
- After the device is fully recovered (cf. \ref b_3_1 "3.1"), the current value is (synchronously) read from the device. This is the first value received by the accessor after an exception.
- \anchor b_2_2_5 2.2.5 The VersionNumbers returned in case of an exception are the same for the same exception, even across variables and modules. It will be generated in the moment the exception is reported. \ref comment_b_2_2_5 "(*)"
- \anchor exceptionHandling_b_2_2_6 2.2.6 The data buffer is not updated. This guarantees that the data buffer stays on the last known value if the user code has not modified it since the last read.
- 2.2.6.1 This is different to a working device or an implementation without exception handling, where a returning read() has overwritten the data content of the buffer. If an application requires the last read value in the data buffer, it must not change it in the user code. This is the only exception to the *golden rule* \ref b_1_2 "1.2".
- 2.2.1 The normal module algorithm code will be continued, to allow this flag to propagate to the outputs in the same way as if it had been received through the process variable itself (cf. \ref b_1_2 "1.2"). [no test, just an intro]
- \anchor exceptionHandling_b_2_2_2 2.2.2 The DataValidity::faulty flag resulting from the fault state is propagated once, even if the variable had the a DataValidity::faulty flag already set previously for another reason. [\ref testExceptionHandling_b_2_2_2_poll "T", \ref testExceptionHandling_b_2_2_2_push "T"]
- \anchor exceptionHandling_b_2_2_3 2.2.3 Read operations without AccessMode::wait_for_new_data are _skipped_ until the device is fully recovered again (cf. \ref b_3_1 "3.1"). The first skipped read operation will have a new VersionNumber. [\ref testExceptionHandling_b_2_2_3 "T", \ref testExceptionHandling_b_2_2_3_TrFO "T"]
- \anchor exceptionHandling_b_2_2_4 2.2.4 Read operations with AccessMode::wait_for_new_data will be _skipped_ once for each accessor to propagate the DataValidity::faulty flag (which counts as new data, i.e. readNonBlocking()/readLatest() will return true (= hasNewData), and a new VersionNumber is obtained) [\ref testExceptionHandling_b_2_2_4_blocking "T", \ref testExceptionHandling_b_2_2_4_nonBlocking "T", \ref testExceptionHandling_b_2_2_4_latest "T", \ref testExceptionHandling_b_2_2_4_ThFO "T", \ref testExceptionHandling_b_2_2_4_TrFO "T"]. Subsequently:
- \anchor exceptionHandling_b_2_2_4_1 2.2.4.1 non-blocking read operations (readNonBlocking() and readLatest()) are _skipped_ and return false (= no new data), until the device is recovered [\ref testExceptionHandling_b_2_2_4_1_nonBlocking "T", \ref testExceptionHandling_b_2_2_4_1_latest "T"], and
- \anchor exceptionHandling_b_2_2_4_2 2.2.4.2 blocking read operations (read()) will be _frozen_ until the device is recovered. [\ref testExceptionHandling_b_2_2_4_2 "T"]
- \anchor exceptionHandling_b_2_2_4_3 2.2.4.3 After the device is fully recovered (cf. \ref b_3_1 "3.1"), the current value is (synchronously) read from the device. This is the first value received by the accessor after an exception. [\ref testExceptionHandling_b_2_2_4_3 "T"]
- \anchor exceptionHandling_b_2_2_5 2.2.5 The VersionNumbers returned in case of an exception are the same for the same exception, even across variables and modules. It will be generated in the moment the exception is reported. \ref comment_b_2_2_5 "(*)" [\ref testExceptionHandling_b_2_2_5 "T"]
- \anchor exceptionHandling_b_2_2_6 2.2.6 The data buffer is not updated. This guarantees that the data buffer stays on the last known value if the user code has not modified it since the last read. [\ref testExceptionHandling_b_2_2_6 "T"]
- 2.2.6.1 This is different to a working device or an implementation without exception handling, where a returning read() has overwritten the data content of the buffer. If an application requires the last read value in the data buffer, it must not change it in the user code. This is the only exception to the *golden rule* \ref b_1_2 "1.2". [No test, as out of scope]
- \anchor b_2_3 2.3 Write operations will be _delayed_ until the device is fully recovered again (cf. \ref b_3_1 "3.1").
- \anchor exceptionHandling_b_2_3_1 2.3.1 In case of a fault state (new or persisting), the actual write operation will take place asynchronously when the device is recovering. [\ref testExceptionHandling_b_2_3_1 "T"]
- \anchor b_2_3_2 2.3.2 The same mechanism as used for \ref b_3_1_2 "3.1.2" is used here, hence the order of write operations is guaranteed across accessors, but only the latest written value of each accessor prevails. \ref comment_b_2_3_2 "(*)"
- \anchor exceptionHandling_b_2_3_1 2.3.1 In case of a fault state (new or persisting), the actual write operation will take place asynchronously when the device is recovering. [tested by \ref exceptionHandling_b_3_1_2 "3.1.2"]
- \anchor exceptionHandling_b_2_3_2 2.3.2 The same mechanism as used for \ref exceptionHandling_b_3_1_2 "3.1.2" is used here, hence the order of write operations is guaranteed across accessors, but only the latest written value of each accessor prevails. \ref comment_b_2_3_2 "(*)" [tested by \ref exceptionHandling_b_3_1_2 "3.1.2"]
- \anchor exceptionHandling_b_2_3_3 2.3.3 The return value of write() indicates whether data was lost in the transfer. If the write has to be delayed due to an exception, the return value will be true (= data lost) if a previously delayed and not-yet written value is discarded in the process, false (= no data lost) otherwise. [\ref testExceptionHandling_b_2_3_3 "T"]
- \anchor b_2_3_4 2.3.4 When the delayed value is finally written to the device during the recovery procedure, the return value of the write() is ignored. \ref comment_b_2_3_4 "(*)"
- 2.3.5 It is guaranteed that the write takes place before the device is considered fully recovered again and other transfers are allowed (cf. \ref b_3_1 "3.1").
- \anchor b_2_4 2.4 In case of exceptions, there is no guaranteed realtime behaviour, not even for "non-blocking" transfers. \ref comment_b_2_4 "(*)"
- \anchor exceptionHandling_b_2_5 2.5 TransferElement::isReadable(), TransferElement::isWriteable() and TransferElement::isReadonly() return with values as if reading and writing would be allowed. \ref exceptionHandling_comment_b_2_5 "(*)"
- \anchor b_2_3_4 2.3.4 When the delayed value is finally written to the device during the recovery procedure, the return value of the write() is ignored. \ref comment_b_2_3_4 "(*)" [not testable]
- \anchor exceptionHandling_b_2_3_5 2.3.5 It is guaranteed that the write takes place before the device is considered fully recovered again and other transfers are allowed (cf. \ref b_3_1 "3.1"). [\ref testExceptionHandling_b_2_3_5 "T"]
- \anchor b_2_4 2.4 In case of exceptions, there is no guaranteed realtime behaviour, not even for "non-blocking" transfers. \ref comment_b_2_4 "(*)" [not testable]
- \anchor exceptionHandling_b_2_5 2.5 TransferElement::isReadable(), TransferElement::isWriteable() and TransferElement::isReadonly() return with values as if reading and writing would be allowed. \ref exceptionHandling_comment_b_2_5 "(*)" [\ref testExceptionHandling_b_2_5 "T"]
\subsection spec_execptionHandling_behaviour_recovery Recovery
- 3. The framework tries to resolve an exception state by periodically re-opening the faulty device.
- \anchor b_3_1 3.1 After successfully re-opening the device, a recovery procedure is executed before allowing any read/write operations from the ApplicationModules and FanOuts again. This recovery procedure involves:
- 3.1.1 the execution of so-called initialisation handlers (see \ref b_3_2 "3.2"), and
- \anchor b_3_1_2 3.1.2 restoring all registers that have been written since the start of the application with their latest values. The register values are restored in the same order they were written. \ref comment_b_3_1_2 "(*)"
- 3.1.3 The asynchronous read transfers of the device are (re-)activated by calling Device::activateAsyncReads().
- \anchor b_3_1_4 3.1.4 Finally, \c Devices/\<alias\>/deviceBecameFunctional is written to inform any module subscribing to this variable about the finished recovery. \ref comment_b_3_1_4 "(*)"
- \anchor b_3_2 3.2 Any number of initialisation handlers can be added to the DeviceModule in the user code. Initialisation handlers are callback functions which will be executed when a device is opened for the first time and after a device recovers from an exception, before any application-initiated transfers are executed (including delayed write transfers). See DeviceModule::addInitialisationHandler().
- \anchor exceptionHandling_b_3_1_1 3.1.1 the execution of so-called initialisation handlers (see \ref exceptionHandling_b_3_2 "3.2") [\ref testExceptionHandling_b_3_1_1 "T"], and
- \anchor exceptionHandling_b_3_1_2 3.1.2 restoring all registers that have been written since the start of the application with their latest values. The register values are restored in the same order they were written. \ref comment_b_3_1_2 "(*)" [\ref testExceptionHandling_b_3_1_2 "T"]
- \anchor exceptionHandling_b_3_1_3 3.1.3 The asynchronous read transfers of the device are (re-)activated by calling Device::activateAsyncReads(). [\ref testExceptionHandling_b_3_1_3 "T"]
- \anchor exceptionHandling_b_3_1_4 3.1.4 Finally, \c Devices/\<alias\>/deviceBecameFunctional is written to inform any module subscribing to this variable about the finished recovery. \ref comment_b_3_1_4 "(*)" [\ref testExceptionHandling_b_3_1_4 "T"]
- \anchor exceptionHandling_b_3_2 3.2 Any number of initialisation handlers can be added to the DeviceModule in the user code. Initialisation handlers are callback functions which will be executed when a device is opened for the first time and after a device recovers from an exception, before any application-initiated transfers are executed (including delayed write transfers). See DeviceModule::addInitialisationHandler(). [\ref testExceptionHandling_b_3_2 "T"]
\subsection spec_execptionHandling_behaviour_startup Startup
- 4. The behaviour at application start (at which all devices are still closed at first) is similar to the case of a later received exception. The only differences are mentioned in \ref b_4_2 "4.2".
- 4.1 Even if some devices are initially in a persisting error state, the part of the application which does not interact with the faulty devices starts and works normally.
- \anchor b_4_2 4.2 Initial values are correctly propagated after a device is opened. See the \link spec_initialValuePropagation Technical specification: propagation of initial values\endlink. Especially, all read operations (even readNonBlocking/readLatest or without AccessMode::wait_for_new_data) will be _frozen_ until an initial value has been successfully read. \ref comment_b_4_2 "(*)"
- \anchor exceptionHandling_b_4_1 4.1 Even if some devices are initially in a persisting error state, the part of the application which does not interact with the faulty devices starts and works normally. [\ref testExceptionHandling_b_4_1 "T"]
- \anchor b_4_2 4.2 Initial values are correctly propagated after a device is opened. See the \link spec_initialValuePropagation Technical specification: propagation of initial values\endlink. Especially, all read operations (even readNonBlocking/readLatest or without AccessMode::wait_for_new_data) will be _frozen_ until an initial value has been successfully read. \ref comment_b_4_2 "(*)" [test in other spec]
\subsection spec_execptionHandling_behaviour_forced_recovery Forced Recovery
- \anchor b_5 5. Any ApplicationModule can explicitly report a problem with the device by calling DeviceModule::reportException(). This allows the reinitialisation of a device e.g. after a reboot of the device which didn't result in an exception (e.g. because it was too quick to be noticed, or rebooting the device takes place without interrupting the communication).
......@@ -90,9 +87,9 @@ namespace ChimeraTK {
- \anchor comment_b_1_1 \ref b_1_1 "1.1" In future, maybe logic_errors are also handled, so configuration errors can nicely be presented to the control system. This may be important especially since logic_errors may depend also on the configuration of external components (devices). If e.g. a device is changed (e.g. device is another control system application which has been modified), logic_errors may be thrown in the recovery phase, despite the device had been successfully initialsed previously.
- \anchor comment_b_2_2_5 \ref b_2_2_5 "2.2.5" Without changing the VersionNumber, the faulty-marked data might get correlated with good data (e.g. a trigger number which is also used as a trigger to read data from the device), resulting in marking the originally good data as faulty, just because an exception has been received _after_ the good data was processed. Using a VersionNumber generated when reporting the exception ensures that the VersionNumber is older than any data read from the device after recovery. There might still be a race condition if a trigger is delayed for some reason for the entire time of detecting and reporting an exception and recovering the device, in which case the trigger number is older than the exception, but the data is still newer and shouldn't really be correlated with the trigger any more. Since ApplicationModules will always use the newest VersionNumber of its inputs, in this case the VersionNumber from the exception will still be used, which is not ideal but should merely prevent the correlation of the data with other data.
- \anchor comment_b_2_2_5 \ref exceptionHandling_b_2_2_5 "2.2.5" Without changing the VersionNumber, the faulty-marked data might get correlated with good data (e.g. a trigger number which is also used as a trigger to read data from the device), resulting in marking the originally good data as faulty, just because an exception has been received _after_ the good data was processed. Using a VersionNumber generated when reporting the exception ensures that the VersionNumber is older than any data read from the device after recovery. There might still be a race condition if a trigger is delayed for some reason for the entire time of detecting and reporting an exception and recovering the device, in which case the trigger number is older than the exception, but the data is still newer and shouldn't really be correlated with the trigger any more. Since ApplicationModules will always use the newest VersionNumber of its inputs, in this case the VersionNumber from the exception will still be used, which is not ideal but should merely prevent the correlation of the data with other data.
- \anchor comment_b_2_3_2 \anchor comment_b_3_1_4 \ref b_2_3_2 "2.3.2" / \ref b_3_1_4 "3.1.4" If timing is important for write operations (e.g. must not write a sequence of registers too fast), or if multiple values need to be written to the same register in sequence, the application cannot fully rely on the framework's recovery procedure. The framework hence provides the process variable \c Devices/\<alias\>/deviceBecameFunctional for each device, which will be written each time the recovery procedure is completed (cf. \ref b_3_1_4 "3.1.4"). ApplicationModules which implement such timed sequence need to receive this variable and restart the entire sequence after the recovery.
- \anchor comment_b_2_3_2 \anchor comment_b_3_1_4 \ref exceptionHandling_b_2_3_2 "2.3.2" / \ref exceptionHandling_b_3_1_4 "3.1.4" If timing is important for write operations (e.g. must not write a sequence of registers too fast), or if multiple values need to be written to the same register in sequence, the application cannot fully rely on the framework's recovery procedure. The framework hence provides the process variable \c Devices/\<alias\>/deviceBecameFunctional for each device, which will be written each time the recovery procedure is completed (cf. \ref exceptionHandling_b_3_1_4 "3.1.4"). ApplicationModules which implement such timed sequence need to receive this variable and restart the entire sequence after the recovery.
- \anchor comment_b_2_3_4 \ref b_2_3_4 "2.3.4" The TransferElement specification B.7.2 guarantees that only old data may be lost in a write transfer, hence the latest data is guaranteed to be written to the device during recovery.
......@@ -101,7 +98,7 @@ namespace ChimeraTK {
- \anchor exceptionHandling_comment_b_2_5 \ref exceptionHandling_b_2_5 "2.5" These functions can throw runtime errors if the behaviour has to be determined from the running device. In this case readability and writeability can change on the device (cf. <a href="https://chimeratk.github.io/DeviceAccess/master/spec__transfer_element.html">TransferElement specification</a> C.5.3). Suppressing the exception and allowing the operation does not pose the risk of getting a ChimeraTK::logic_error in the preXxx() phase of the
operation because all transfer elements are tested for this during device recovery (cf. \ref exceptionHandling_c_3_3_3 "C.3.3.3").
- \anchor comment_b_3_1_2 \ref b_3_1_2 "3.1.2" For some applications, the order of writes may be important, e.g. if firmware expects this. Please note that the VersionNumber is insufficient as a sorting criteria, since many writes may have been done with the same VersionNumber (in an ApplicationModule, the VersionNumber used for the writes is determined by the largest VersionNumber of the inputs).
- \anchor comment_b_3_1_2 \ref exceptionHandling_b_3_1_2 "3.1.2" For some applications, the order of writes may be important, e.g. if firmware expects this. Please note that the VersionNumber is insufficient as a sorting criteria, since many writes may have been done with the same VersionNumber (in an ApplicationModule, the VersionNumber used for the writes is determined by the largest VersionNumber of the inputs).
- \anchor comment_b_4_2 \ref b_4_2 "4.2" DataValidity::faulty is initially set by default, so there is no need to propagate this flag initially. To prevent race conditions and undefined behaviour (especially in automated tests), it even needs to be made sure that the flag is not propagated unnecessarily. The behaviour of non-blocking reads presents a slight asymmetry between the initial device opening and a later recovery. This will in particular be visible when restarting a server while a device is offline. If a module only uses readLatest()/readNonBlocking() (= read() for poll-type inputs) for the offline device, the module was still running before the server restart using the last known values for the dysfunctional registers (and flagging all outputs as faulty). After the restart, the module has to wait for the initial value and hence will not run until the device becomes functional again. To make this behaviour symmetric, one would need to persist the values of device inputs. Since this only affects a corner case in which likely no usable output is produced anyway, this slight inconsistency is considered acceptable.
......
This diff is collapsed.
......@@ -16,6 +16,8 @@
#include <future>
/**********************************************************************************************************************/
struct PollModule : ChimeraTK::ApplicationModule {
using ChimeraTK::ApplicationModule::ApplicationModule;
ChimeraTK::ScalarPollInput<int> pollInput{this, "REG1", "", "", {"DEVICE"}};
......@@ -23,6 +25,8 @@ struct PollModule : ChimeraTK::ApplicationModule {
void mainLoop() override { p.set_value(); }
};
/**********************************************************************************************************************/
struct PushModule : ChimeraTK::ApplicationModule {
using ChimeraTK::ApplicationModule::ApplicationModule;
struct : ChimeraTK::VariableGroup {
......@@ -34,59 +38,133 @@ struct PushModule : ChimeraTK::ApplicationModule {
void mainLoop() override { p.set_value(); }
};
struct UpdateModule : ChimeraTK::ApplicationModule {
/**********************************************************************************************************************/
struct OutputModule : ChimeraTK::ApplicationModule {
using ChimeraTK::ApplicationModule::ApplicationModule;
ChimeraTK::ScalarOutput<int> deviceRegister{this, "REG1", "", "", {"DEVICE"}};
ChimeraTK::ScalarOutput<int> deviceRegister2{this, "REG2", "", "", {"DEVICE"}};
ChimeraTK::ScalarOutput<int> deviceRegister3{this, "REG3", "", "", {"DEVICE"}};
ChimeraTK::ScalarOutput<int> trigger{this, "trigger", "", ""}; // must not be connected to any device
std::promise<void> p;
void mainLoop() override { p.set_value(); }
};
/**********************************************************************************************************************/
struct DummyApplication : ChimeraTK::Application {
constexpr static const char* ExceptionDummyCDD1 = "(ExceptionDummy:1?map=test.map)";
constexpr static const char* ExceptionDummyCDD2 = "(ExceptionDummy:2?map=test.map)";
constexpr static const char* ExceptionDummyCDD3 = "(ExceptionDummy:3?map=test.map)";
DummyApplication() : Application("DummyApplication") {}
~DummyApplication() { shutdown(); }
PushModule pushModule{this, "", ""};
PollModule pollModule{this, "", ""};
UpdateModule updateModule{this, "", ""};
PushModule pushModule{this, "pushModule", "", ChimeraTK::HierarchyModifier::none, {"DEV"}};
PollModule pollModule{this, "pollModule", "", ChimeraTK::HierarchyModifier::none, {"DEV"}};
OutputModule outputModule{this, "outputModule", "", ChimeraTK::HierarchyModifier::none, {"DEV"}};
PushModule pushModule2{this, "pushModule2", "With TriggerFanOut", ChimeraTK::HierarchyModifier::none, {"DEV2"}};
PushModule pushModule3{this, "pushModule3", "With ThreadedFanOut", ChimeraTK::HierarchyModifier::none, {"DEV2"}};
PollModule pollModule2{this, "pollModule2", "", ChimeraTK::HierarchyModifier::none, {"DEV2"}};
OutputModule outputModule2{this, "outputModule2", "", ChimeraTK::HierarchyModifier::none, {"DEV2"}};
PollModule pollModule3{this, "pollModule3", "", ChimeraTK::HierarchyModifier::none, {"DEV3"}};
ChimeraTK::ControlSystemModule cs;
ChimeraTK::DeviceModule device{this, ExceptionDummyCDD1};
ChimeraTK::DeviceModule device2{this, ExceptionDummyCDD2};
ChimeraTK::DeviceModule device3{this, ExceptionDummyCDD3};
void defineConnections() override {
findTag("CS").connectTo(cs);
findTag("DEVICE").connectTo(device);
ChimeraTK::ControlSystemModule cs;
auto push_input = device("REG1/PUSH_READ", typeid(int), 1, ChimeraTK::UpdateMode::push);
push_input >> pushModule.reg1.pushInput;
void defineConnections() override {
findTag("DEVICE").excludeTag("DEV2").excludeTag("DEV3").flatten().connectTo(device);
findTag("DEVICE").excludeTag("DEV").excludeTag("DEV3").flatten().connectTo(device2);
findTag("DEVICE").excludeTag("DEV").excludeTag("DEV2").flatten().connectTo(device3);
device("REG1/PUSH_READ", typeid(int), 1, ChimeraTK::UpdateMode::push) >> pushModule.reg1.pushInput;
device2("REG1")[device3("REG1/PUSH_READ", typeid(int), 1, ChimeraTK::UpdateMode::push)] >>
pushModule2.reg1.pushInput;
device2("REG1/PUSH_READ", typeid(int), 1, ChimeraTK::UpdateMode::push) >> pushModule3.reg1.pushInput >>
cs("dev2_reg1_push_read");
findTag("DEVICE").excludeTag("DEV").connectTo(cs["Device2"]);
//dumpConnections();
}
};
template<bool enableTestFacility>
/**********************************************************************************************************************/
template<bool enableTestFacility, bool addInitHandlers = false, bool breakSecondDeviceAtStart = false>
struct fixture_with_poll_and_push_input {
fixture_with_poll_and_push_input()
: deviceBackend(boost::dynamic_pointer_cast<ChimeraTK::ExceptionDummy>(
ChimeraTK::BackendFactory::getInstance().createBackend(DummyApplication::ExceptionDummyCDD1))),
exceptionDummyRegister(deviceBackend->getRawAccessor("", "REG1")) {
deviceBackend->open();
deviceBackend2(boost::dynamic_pointer_cast<ChimeraTK::ExceptionDummy>(
ChimeraTK::BackendFactory::getInstance().createBackend(DummyApplication::ExceptionDummyCDD2))),
deviceBackend3(boost::dynamic_pointer_cast<ChimeraTK::ExceptionDummy>(
ChimeraTK::BackendFactory::getInstance().createBackend(DummyApplication::ExceptionDummyCDD3))),
exceptionDummyRegister(deviceBackend->getRawAccessor("", "REG1")),
exceptionDummyRegister2(deviceBackend->getRawAccessor("", "REG2")),
exceptionDummyRegister3(deviceBackend->getRawAccessor("", "REG3")),
exceptionDummy2Register(deviceBackend2->getRawAccessor("", "REG1")) {
deviceBackend2->throwExceptionOpen = breakSecondDeviceAtStart;
if constexpr(addInitHandlers) {
auto initHandler1 = [this](ChimeraTK::DeviceModule* dm) {
if(dm == &application.device) {
initHandler1Called = true;
if(initHandler1Throws) {
throw ChimeraTK::runtime_error("Init handler 1 throws by request");
}
}
};
auto initHandler2 = [this](ChimeraTK::DeviceModule* dm) {
if(dm == &application.device) {
initHandler2Called = true;
if(initHandler2Throws) {
throw ChimeraTK::runtime_error("Init handler 2 throws by request");
}
}
};
application.device.addInitialisationHandler(initHandler1);
application.device.addInitialisationHandler(initHandler2);
}
testFacitiy.runApplication();
status.replace(testFacitiy.getScalar<int>(
ChimeraTK::RegisterPath("/Devices") / DummyApplication::ExceptionDummyCDD1 / "status"));
message.replace(testFacitiy.getScalar<std::string>(
ChimeraTK::RegisterPath("/Devices") / DummyApplication::ExceptionDummyCDD1 / "message"));
deviceBecameFunctional.replace(testFacitiy.getScalar<int>(
ChimeraTK::RegisterPath("/Devices") / DummyApplication::ExceptionDummyCDD1 / "deviceBecameFunctional"));
status2.replace(testFacitiy.getScalar<int>(
ChimeraTK::RegisterPath("/Devices") / DummyApplication::ExceptionDummyCDD2 / "status"));
pushVariable3copy.replace(testFacitiy.getScalar<int>("dev2_reg1_push_read"));
// wait until all modules have been properly started, to ensure the initial value propagation is complete
/************************************************************************************************/
application.pollModule.p.get_future().wait();
application.pushModule.p.get_future().wait();
application.updateModule.p.get_future().wait();
/************************************************************************************************/
application.outputModule.p.get_future().wait();
if(!breakSecondDeviceAtStart) {
application.outputModule2.p.get_future().wait();
application.pollModule2.p.get_future().wait();
application.pushModule2.p.get_future().wait();
}
deviceBecameFunctional.read();
}
~fixture_with_poll_and_push_input() {
// make sure no exception throwing is still enabled from previous test
deviceBackend->throwExceptionOpen = false;
deviceBackend->throwExceptionRead = false;
deviceBackend->throwExceptionWrite = false;
deviceBackend2->throwExceptionOpen = false;
deviceBackend2->throwExceptionRead = false;
deviceBackend2->throwExceptionWrite = false;
deviceBackend3->throwExceptionOpen = false;
deviceBackend3->throwExceptionRead = false;
deviceBackend3->throwExceptionWrite = false;
}
template<typename T>
......@@ -116,14 +194,36 @@ struct fixture_with_poll_and_push_input {
}
boost::shared_ptr<ChimeraTK::ExceptionDummy> deviceBackend;
boost::shared_ptr<ChimeraTK::ExceptionDummy> deviceBackend2;
boost::shared_ptr<ChimeraTK::ExceptionDummy> deviceBackend3;
DummyApplication application;
ChimeraTK::TestFacility testFacitiy{enableTestFacility};
ChimeraTK::ScalarRegisterAccessor<int> status;
ChimeraTK::ScalarRegisterAccessor<int> status, status2;
ChimeraTK::ScalarRegisterAccessor<int> deviceBecameFunctional;
ChimeraTK::ScalarRegisterAccessor<std::string> message;
ChimeraTK::DummyRegisterRawAccessor exceptionDummyRegister;
ChimeraTK::DummyRegisterRawAccessor exceptionDummyRegister2;
ChimeraTK::DummyRegisterRawAccessor exceptionDummyRegister3;
ChimeraTK::DummyRegisterRawAccessor exceptionDummy2Register;
ChimeraTK::ScalarPushInput<int>& pushVariable{application.pushModule.reg1.pushInput};
ChimeraTK::ScalarPollInput<int>& pollVariable{application.pollModule.pollInput};
ChimeraTK::ScalarOutput<int>& outputVariable{application.updateModule.deviceRegister};
ChimeraTK::ScalarOutput<int>& outputVariable{application.outputModule.deviceRegister};
ChimeraTK::ScalarOutput<int>& outputVariable2{application.outputModule.deviceRegister2};
ChimeraTK::ScalarOutput<int>& outputVariable3{application.outputModule.deviceRegister3};
ChimeraTK::ScalarPushInput<int>& triggeredInput{application.pushModule2.reg1.pushInput};
ChimeraTK::ScalarPollInput<int>& pollVariable2{application.pollModule2.pollInput};
ChimeraTK::ScalarPushInput<int>& pushVariable3{application.pushModule3.reg1.pushInput};
ChimeraTK::ScalarRegisterAccessor<int> pushVariable3copy;
ChimeraTK::ScalarPollInput<int>& pollVariable3{application.pollModule3.pollInput};
std::atomic<bool> initHandler1Throws{false};
std::atomic<bool> initHandler2Throws{false};
std::atomic<bool> initHandler1Called{false};
std::atomic<bool> initHandler2Called{false};
};
/**********************************************************************************************************************/
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment