diff --git a/src/ExceptionHandlingDecorator.cc b/src/ExceptionHandlingDecorator.cc index d127e93d90a2f26c60d414395efa4ee96d39e590..cc09ec7a29bd6566e0eb2b15c2cd19fed3f62744 100644 --- a/src/ExceptionHandlingDecorator.cc +++ b/src/ExceptionHandlingDecorator.cc @@ -101,7 +101,7 @@ namespace ChimeraTK { template<typename UserType> void ExceptionHandlingDecorator<UserType>::doPreWrite() { - /* Copy data to the recoveryAcessor before perfroming the write. + /* For writable accessors, copy data to the recoveryAcessor before perfroming the write. * Otherwise, the decorated accessor may have swapped the data out of the user buffer already. * This obtains a shared lock from the DeviceModule, hence, the regular writing happeniin here * can be performed in shared mode of the mutex and accessors are not blocking each other. @@ -111,9 +111,14 @@ namespace ChimeraTK { { auto lock{dm.getRecoverySharedLock()}; - // Access to _recoveryAccessor is only possible channel-wise - for(unsigned int ch=0; ch<_recoveryAccessor->getNumberOfChannels(); ++ch){ - _recoveryAccessor->accessChannel(ch) = buffer_2D[ch]; + if(_recoveryAccessor != nullptr){ + // Access to _recoveryAccessor is only possible channel-wise + for(unsigned int ch=0; ch<_recoveryAccessor->getNumberOfChannels(); ++ch){ + _recoveryAccessor->accessChannel(ch) = buffer_2D[ch]; + } + } + else{ + throw ChimeraTK::logic_error("ChimeraTK::ExceptionhandlingDecorator: Calling write() on a non-writeable accessor is not supported "); } } // Now delegate call to the generic decorator, which swaps the buffer diff --git a/tests/executables_src/testProcessVariableRecovery.cc b/tests/executables_src/testProcessVariableRecovery.cc index be606c19f23a471fb4b24b4f2a5706573e59ccf1..1b5c5612d8daf649cb2b2434835c22f21f901fa8 100644 --- a/tests/executables_src/testProcessVariableRecovery.cc +++ b/tests/executables_src/testProcessVariableRecovery.cc @@ -12,6 +12,8 @@ #include "ApplicationModule.h" #include "ArrayAccessor.h" +#include <regex> + using namespace boost::unit_test_framework; namespace ctk = ChimeraTK; @@ -52,8 +54,76 @@ struct TestApplication : public ctk::Application { TestModule module{this, "TEST", "The test module"}; }; +/* Test application for the specific case of wrinting to a read-only + * accessor. Provides an input to an ApplicationModule from a read-only + * accessor of the device. For the test, the accessor must not be routed + * through the control system, the illegal write would be catched by the + * ControlSystemAdapter, not by the ExceptionHandlingDecorator under test here. + */ +struct ReadOnlyTestApplication : public ctk::Application { + ReadOnlyTestApplication() : Application("ReadOnlytestApp") {} + ~ReadOnlyTestApplication(){ shutdown(); } + + void defineConnections() { + dev["TEST"]("FROM_DEV_SCALAR2") >> module("FROM_DEV_SCALAR2"); + findTag("CS").connectTo(cs); + } + + ctk::ControlSystemModule cs; + ctk::DeviceModule dev{this, deviceCDD}; + + struct TestModule : public ctk::ApplicationModule { + using ctk::ApplicationModule::ApplicationModule; + + ctk::ScalarPushInput<int> start{this, "startTest", "", "This has to be written once, before writing to the device", {"CS"}}; + ctk::ScalarPollInput<int32_t> scalarROInput{this, "FROM_DEV_SCALAR2", "", "Here I read from a scalar RO-register"}; + + void mainLoop() override { + + // Just to have a blocking read, gives the test + // time to dumpConnections and explicitly trigger + // before terminating. + start.read(); + + scalarROInput = 42; + try{ + scalarROInput.write(); + BOOST_CHECK_MESSAGE(false, + "ReadOnlyTestApplication: Calling write() on input to read-only device register did not throw."); + } + catch(ChimeraTK::logic_error &e){ + + const std::string exMsg{e.what()}; + std::regex exceptionHandlingRegex{"^ChimeraTK::ExceptionhandlingDecorator:*"}; + std::smatch exMatch; + + BOOST_CHECK(std::regex_search(exMsg, exMatch, exceptionHandlingRegex)); + } + } + + } module{this, "READ_ONLY_TEST", "The test module"}; +}; + /*********************************************************************************************************************/ +BOOST_AUTO_TEST_CASE(testWriteToReadOnly){ + std::cout << "testWriteToReadOnly" << std::endl; + + ReadOnlyTestApplication app; + + ctk::TestFacility test; + + app.run(); + app.dumpConnections(); + + // Should trigger the blocking read in ReadOnlyTestApplication's + // ApplicationModule. It then writes to a read-only register of the device, + // which should throw. Check is done in the module's mainLoop. We can not check + // here, as the exception gets thrown in the thread of the module. + test.writeScalar("/READ_ONLY_TEST/startTest", 1); +} + + BOOST_AUTO_TEST_CASE(testProcessVariableRecovery) { std::cout << "testProcessVariableRecovery" << std::endl; TestApplication app;