From 5f5ada9d6bde5df7dd019e7e55ee5745e432133d Mon Sep 17 00:00:00 2001
From: Martin Hierholzer <martin.hierholzer@desy.de>
Date: Wed, 28 Sep 2022 15:04:57 +0200
Subject: [PATCH] fix testVersionPropagation

---
 tests/CMakeLists.txt                          |   1 +
 .../executables_src/testExceptionHandling.cc  |   4 +-
 .../executables_src/testVersionPropagation.cc |  68 ++--
 tests/include/fixtures.h                      | 294 +++++++++++-------
 tests/test_with_push.map                      |   6 +
 5 files changed, 238 insertions(+), 135 deletions(-)
 create mode 100644 tests/test_with_push.map

diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index e549c3ea..2ecc1f46 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -15,6 +15,7 @@ endforeach( testExecutableSrcFile )
 
 # copy config files
 FILE( COPY ${CMAKE_CURRENT_SOURCE_DIR}/test.map DESTINATION ${PROJECT_BINARY_DIR}/tests)
+FILE( COPY ${CMAKE_CURRENT_SOURCE_DIR}/test_with_push.map DESTINATION ${PROJECT_BINARY_DIR}/tests)
 FILE( COPY ${CMAKE_CURRENT_SOURCE_DIR}/test.xlmap DESTINATION ${PROJECT_BINARY_DIR}/tests)
 FILE( COPY ${CMAKE_CURRENT_SOURCE_DIR}/test2.map DESTINATION ${PROJECT_BINARY_DIR}/tests)
 FILE( COPY ${CMAKE_CURRENT_SOURCE_DIR}/test3.map DESTINATION ${PROJECT_BINARY_DIR}/tests)
diff --git a/tests/executables_src/testExceptionHandling.cc b/tests/executables_src/testExceptionHandling.cc
index 3a1dd582..6a888f5a 100644
--- a/tests/executables_src/testExceptionHandling.cc
+++ b/tests/executables_src/testExceptionHandling.cc
@@ -56,7 +56,7 @@ BOOST_FIXTURE_TEST_CASE(B_2_1, Fixture) {
 
   deviceBackend->throwExceptionOpen = true;
   deviceBackend->throwExceptionRead = true;
-  application.pollModule.pollInput.read(); // causes device exception
+  application.group1.pollModule.pollInput.read(); // causes device exception
 
   CHECK_TIMEOUT(status.readNonBlocking() == true, 10000);
   CHECK_TIMEOUT(message.readNonBlocking() == true, 10000);
@@ -518,7 +518,7 @@ BOOST_FIXTURE_TEST_CASE(B_2_2_5, Fixture) {
   // Go to exception state, report it explicitly
   ctk::VersionNumber someVersionBeforeReporting = {};
   deviceBackend->throwExceptionOpen = true; // required to make sure device stays down
-  application.device.reportException("explicit report by test");
+  application.group1.device.reportException("explicit report by test");
   deviceBackend->setException(); // FIXME: should this be called by reportException()??
   ctk::VersionNumber someVersionAfterReporting = {};
 
diff --git a/tests/executables_src/testVersionPropagation.cc b/tests/executables_src/testVersionPropagation.cc
index 4f74dd48..891273de 100644
--- a/tests/executables_src/testVersionPropagation.cc
+++ b/tests/executables_src/testVersionPropagation.cc
@@ -19,51 +19,67 @@ using Fixture = fixture_with_poll_and_push_input<false>;
 
 BOOST_FIXTURE_TEST_SUITE(versionPropagation, Fixture)
 
+/*********************************************************************************************************************/
+
 BOOST_AUTO_TEST_CASE(versionPropagation_testPolledRead) {
   std::cout << "versionPropagation_testPolledRead" << std::endl;
-  auto moduleVersion = application.pollModule.getCurrentVersionNumber();
+  auto moduleVersion = application.group1.pollModule.getCurrentVersionNumber();
   auto pollVariableVersion = pollVariable.getVersionNumber();
 
+  application.group1.outputModule.setCurrentVersionNumber({});
+  outputVariable.write();
   pollVariable.read();
 
-  BOOST_CHECK(pollVariable.getVersionNumber() > pollVariableVersion);
-  BOOST_CHECK(moduleVersion == application.pollModule.getCurrentVersionNumber());
+  assert(pollVariable.getVersionNumber() > pollVariableVersion);
+  BOOST_CHECK(moduleVersion == application.group1.pollModule.getCurrentVersionNumber());
 }
 
+/*********************************************************************************************************************/
+
 BOOST_AUTO_TEST_CASE(versionPropagation_testPolledReadNonBlocking) {
   std::cout << "versionPropagation_testPolledReadNonBlocking" << std::endl;
-  auto moduleVersion = application.pollModule.getCurrentVersionNumber();
+  auto moduleVersion = application.group1.pollModule.getCurrentVersionNumber();
   auto pollVariableVersion = pollVariable.getVersionNumber();
 
+  application.group1.outputModule.setCurrentVersionNumber({});
+  outputVariable.write();
   pollVariable.readNonBlocking();
 
-  BOOST_CHECK(pollVariable.getVersionNumber() > pollVariableVersion);
-  BOOST_CHECK(moduleVersion == application.pollModule.getCurrentVersionNumber());
+  assert(pollVariable.getVersionNumber() > pollVariableVersion);
+  BOOST_CHECK(moduleVersion == application.group1.pollModule.getCurrentVersionNumber());
 }
 
+/*********************************************************************************************************************/
+
 BOOST_AUTO_TEST_CASE(versionPropagation_testPolledReadLatest) {
   std::cout << "versionPropagation_testPolledReadLatest" << std::endl;
-  auto moduleVersion = application.pollModule.getCurrentVersionNumber();
+  auto moduleVersion = application.group1.pollModule.getCurrentVersionNumber();
   auto pollVariableVersion = pollVariable.getVersionNumber();
 
+  application.group1.outputModule.setCurrentVersionNumber({});
+  outputVariable.write();
   pollVariable.readLatest();
 
-  BOOST_CHECK(pollVariable.getVersionNumber() > pollVariableVersion);
-  BOOST_CHECK(moduleVersion == application.pollModule.getCurrentVersionNumber());
+  assert(pollVariable.getVersionNumber() > pollVariableVersion);
+  BOOST_CHECK(moduleVersion == application.group1.pollModule.getCurrentVersionNumber());
 }
 
+/*********************************************************************************************************************/
+
 BOOST_AUTO_TEST_CASE(versionPropagation_testPushTypeRead) {
   std::cout << "versionPropagation_testPushTypeRead" << std::endl;
   // Make sure we pop out any stray values in the pushInput before test start:
   CHECK_TIMEOUT(pushVariable.readLatest() == false, 10000);
 
   ctk::VersionNumber nextVersionNumber = {};
-  deviceBackend->triggerPush(ctk::RegisterPath("REG1/PUSH_READ"), nextVersionNumber);
+  interrupt.write();
   pushVariable.read();
-  BOOST_CHECK(pushVariable.getVersionNumber() == nextVersionNumber);
-  BOOST_CHECK(application.pushModule.getCurrentVersionNumber() == nextVersionNumber);
+  assert(pushVariable.getVersionNumber() > nextVersionNumber);
+  BOOST_CHECK(application.group1.pushModule.getCurrentVersionNumber() == pushVariable.getVersionNumber());
 }
 
+/*********************************************************************************************************************/
+
 BOOST_AUTO_TEST_CASE(versionPropagation_testPushTypeReadNonBlocking) {
   std::cout << "versionPropagation_testPushTypeReadNonBlocking" << std::endl;
   CHECK_TIMEOUT(pushVariable.readLatest() == false, 10000);
@@ -75,15 +91,18 @@ BOOST_AUTO_TEST_CASE(versionPropagation_testPushTypeReadNonBlocking) {
   BOOST_CHECK(pushInputVersionNumber == pushVariable.getVersionNumber());
 
   ctk::VersionNumber nextVersionNumber = {};
-  auto moduleVersion = application.pushModule.getCurrentVersionNumber();
-  deviceBackend->triggerPush(ctk::RegisterPath("REG1/PUSH_READ"), nextVersionNumber);
-  BOOST_CHECK_EQUAL(pushVariable.readNonBlocking(), true);
-  BOOST_CHECK(nextVersionNumber == pushVariable.getVersionNumber());
+  auto moduleVersion = application.group1.pushModule.getCurrentVersionNumber();
+
+  interrupt.write();
+  CHECK_TIMEOUT(pushVariable.readNonBlocking() == true, 10000);
+  BOOST_CHECK(pushVariable.getVersionNumber() > nextVersionNumber);
 
   // readNonBlocking will not propagete the version to the module
-  BOOST_CHECK(moduleVersion == application.pushModule.getCurrentVersionNumber());
+  BOOST_CHECK(application.group1.pushModule.getCurrentVersionNumber() == moduleVersion);
 }
 
+/*********************************************************************************************************************/
+
 BOOST_AUTO_TEST_CASE(versionPropagation_testPushTypeReadLatest) {
   std::cout << "versionPropagation_testPushTypeReadLatest" << std::endl;
   // Make sure we pop out any stray values in the pushInput before test start:
@@ -96,13 +115,18 @@ BOOST_AUTO_TEST_CASE(versionPropagation_testPushTypeReadLatest) {
   BOOST_CHECK(pushInputVersionNumber == pushVariable.getVersionNumber());
 
   ctk::VersionNumber nextVersionNumber = {};
-  deviceBackend->triggerPush(ctk::RegisterPath("REG1/PUSH_READ"), nextVersionNumber);
-  auto moduleVersion = application.pushModule.getCurrentVersionNumber();
-  BOOST_CHECK_EQUAL(pushVariable.readLatest(), true);
-  BOOST_CHECK(nextVersionNumber == pushVariable.getVersionNumber());
+  auto moduleVersion = application.group1.pushModule.getCurrentVersionNumber();
+
+  interrupt.write();
+  CHECK_TIMEOUT(pushVariable.readLatest() == true, 10000);
+  BOOST_CHECK(pushVariable.getVersionNumber() > nextVersionNumber);
 
   // readLatest will not propagete the version to the module
-  BOOST_CHECK(moduleVersion == application.pushModule.getCurrentVersionNumber());
+  BOOST_CHECK(application.group1.pushModule.getCurrentVersionNumber() == moduleVersion);
 }
 
+/*********************************************************************************************************************/
+
 BOOST_AUTO_TEST_SUITE_END()
+
+/*********************************************************************************************************************/
diff --git a/tests/include/fixtures.h b/tests/include/fixtures.h
index 564b29e0..69b8413b 100644
--- a/tests/include/fixtures.h
+++ b/tests/include/fixtures.h
@@ -4,7 +4,6 @@
 
 #include "Application.h"
 #include "ApplicationModule.h"
-#include "check_timeout.h"
 #include "DeviceModule.h"
 #include "ScalarAccessor.h"
 #include "TestFacility.h"
@@ -17,6 +16,7 @@
 
 #include <future>
 
+/**********************************************************************************************************************/
 /**********************************************************************************************************************/
 
 struct PollModule : ChimeraTK::ApplicationModule {
@@ -32,7 +32,8 @@ struct PushModule : ChimeraTK::ApplicationModule {
   using ChimeraTK::ApplicationModule::ApplicationModule;
   struct : ChimeraTK::VariableGroup {
     using ChimeraTK::VariableGroup::VariableGroup;
-    ChimeraTK::ScalarPushInput<int> pushInput{this, "PUSH_READ", "", ""};
+    ChimeraTK::ScalarPushInput<int> pushInput{this, "../REG1_PUSHED", "", ""};
+    ChimeraTK::ScalarPushInput<int> pushInputCopy{this, "../REG1_PUSHED", "", ""};
   } reg1{this, "REG1", ""};
 
   std::promise<void> p;
@@ -48,31 +49,44 @@ struct OutputModule : ChimeraTK::ApplicationModule {
   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 prepare() override { deviceRegister.write(); }
   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)";
+  constexpr static const char* ExceptionDummyCDD1 = "(ExceptionDummy:1?map=test_with_push.map)";
+  constexpr static const char* ExceptionDummyCDD2 = "(ExceptionDummy:2?map=test_with_push.map)";
+  constexpr static const char* ExceptionDummyCDD3 = "(ExceptionDummy:3?map=test_with_push.map)";
 
-  DummyApplication() : Application("DummyApplication") {}
+  DummyApplication() : Application("DummyApplication") { // debugMakeConnections();
+  }
   ~DummyApplication() override { shutdown(); }
 
-  PushModule pushModule{this, "pushModule", "", ChimeraTK::TAGS{"DEV"}};
-  PollModule pollModule{this, "pollModule", "", ChimeraTK::TAGS{"DEV"}};
-  OutputModule outputModule{this, "outputModule", "", ChimeraTK::TAGS{"DEV"}};
-  PushModule pushModule2{this, "pushModule2", "With TriggerFanOut", ChimeraTK::TAGS{"DEV2"}};
-  PushModule pushModule3{this, "pushModule3", "With ThreadedFanOut", ChimeraTK::TAGS{"DEV2"}};
-  PollModule pollModule2{this, "pollModule2", "", ChimeraTK::TAGS{"DEV2"}};
-  OutputModule outputModule2{this, "outputModule2", "", ChimeraTK::TAGS{"DEV2"}};
-  PollModule pollModule3{this, "pollModule3", "", ChimeraTK::TAGS{"DEV3"}};
-
-  ChimeraTK::DeviceModule device{this, ExceptionDummyCDD1};
-  ChimeraTK::DeviceModule device2{this, ExceptionDummyCDD2};
-  ChimeraTK::DeviceModule device3{this, ExceptionDummyCDD3};
+  struct Group1 : ChimeraTK::ModuleGroup {
+    using ChimeraTK::ModuleGroup::ModuleGroup;
+    ChimeraTK::DeviceModule device{this, ExceptionDummyCDD1};
+    PushModule pushModule{this, ".", ""};
+    PollModule pollModule{this, ".", ""};
+    OutputModule outputModule{this, ".", ""};
+  } group1{this, "Group1", ""};
+
+  struct Group2 : ChimeraTK::ModuleGroup {
+    using ChimeraTK::ModuleGroup::ModuleGroup;
+    ChimeraTK::DeviceModule device2{this, ExceptionDummyCDD2};
+    PushModule pushModule2{this, ".", "With TriggerFanOut"};
+    PushModule pushModule3{this, ".", "With ThreadedFanOut"};
+    PollModule pollModule2{this, ".", ""};
+    OutputModule outputModule2{this, ".", ""};
+  } group2{this, "Group2", ""};
+
+  struct Group3 : ChimeraTK::ModuleGroup {
+    using ChimeraTK::ModuleGroup::ModuleGroup;
+    ChimeraTK::DeviceModule device3{this, ExceptionDummyCDD3};
+    PollModule pollModule3{this, ".", ""};
+  } group3{this, "Group3", ""};
+
   /*
     ChimeraTK::ControlSystemModule cs;
 
@@ -91,26 +105,85 @@ struct DummyApplication : ChimeraTK::Application {
     }*/
 };
 
+/**********************************************************************************************************************/
 /**********************************************************************************************************************/
 
 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))),
-    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")) {
+  fixture_with_poll_and_push_input();
+
+  ~fixture_with_poll_and_push_input();
+
+  template<typename T>
+  auto read(ChimeraTK::DummyRegisterRawAccessor& accessor);
+
+  template<typename T>
+  auto read(ChimeraTK::DummyRegisterRawAccessor&& accessor);
+
+  template<typename T>
+  void write(ChimeraTK::DummyRegisterRawAccessor& accessor, T value);
+
+  template<typename T>
+  void write(ChimeraTK::DummyRegisterRawAccessor&& accessor, T value);
+
+  bool isDeviceInError();
+
+  boost::shared_ptr<ChimeraTK::ExceptionDummy> deviceBackend;
+  boost::shared_ptr<ChimeraTK::ExceptionDummy> deviceBackend2;
+  boost::shared_ptr<ChimeraTK::ExceptionDummy> deviceBackend3;
+  DummyApplication application;
+  ChimeraTK::TestFacility testFacitiy{application, enableTestFacility};
+
+  ChimeraTK::ScalarRegisterAccessor<int> status, status2;
+  ChimeraTK::VoidRegisterAccessor deviceBecameFunctional;
+  ChimeraTK::ScalarRegisterAccessor<std::string> message;
+  ChimeraTK::DummyRegisterRawAccessor exceptionDummyRegister;
+  ChimeraTK::DummyRegisterRawAccessor exceptionDummyRegister2;
+  ChimeraTK::DummyRegisterRawAccessor exceptionDummyRegister3;
+  ChimeraTK::DummyRegisterRawAccessor exceptionDummy2Register;
+
+  ChimeraTK::ScalarPushInput<int>& pushVariable{application.group1.pushModule.reg1.pushInput};
+  ChimeraTK::ScalarPollInput<int>& pollVariable{application.group1.pollModule.pollInput};
+  ChimeraTK::ScalarOutput<int>& outputVariable{application.group1.outputModule.deviceRegister};
+  ChimeraTK::ScalarOutput<int>& outputVariable2{application.group1.outputModule.deviceRegister2};
+  ChimeraTK::ScalarOutput<int>& outputVariable3{application.group1.outputModule.deviceRegister3};
+
+  ChimeraTK::ScalarPushInput<int>& triggeredInput{application.group2.pushModule2.reg1.pushInput};
+  ChimeraTK::ScalarPollInput<int>& pollVariable2{application.group2.pollModule2.pollInput};
+
+  ChimeraTK::ScalarPushInput<int>& pushVariable3{application.group2.pushModule3.reg1.pushInput};
+  ChimeraTK::ScalarPushInput<int>& pushVariable3copy{application.group2.pushModule3.reg1.pushInputCopy};
+  ChimeraTK::ScalarPollInput<int>& pollVariable3{application.group3.pollModule3.pollInput};
+
+  ChimeraTK::VoidRegisterAccessor interrupt;
+
+  std::atomic<bool> initHandler1Throws{false};
+  std::atomic<bool> initHandler2Throws{false};
+  std::atomic<bool> initHandler1Called{false};
+  std::atomic<bool> initHandler2Called{false};
+};
+
+/**********************************************************************************************************************/
+
+template<bool enableTestFacility, bool addInitHandlers, bool breakSecondDeviceAtStart>
+fixture_with_poll_and_push_input<enableTestFacility, addInitHandlers,
+    breakSecondDeviceAtStart>::fixture_with_poll_and_push_input()
+: deviceBackend(boost::dynamic_pointer_cast<ChimeraTK::ExceptionDummy>(
+      ChimeraTK::BackendFactory::getInstance().createBackend(DummyApplication::ExceptionDummyCDD1))),
+  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")) {
+  try {
     deviceBackend2->throwExceptionOpen = breakSecondDeviceAtStart;
 
     if constexpr(addInitHandlers) {
       auto initHandler1 = [this](ChimeraTK::DeviceManager* dm) {
-        if(dm == &application.device.getDeviceManager()) {
+        if(dm == &application.group1.device.getDeviceManager()) {
           initHandler1Called = true;
           if(initHandler1Throws) {
             throw ChimeraTK::runtime_error("Init handler 1 throws by request");
@@ -118,113 +191,112 @@ struct fixture_with_poll_and_push_input {
         }
       };
       auto initHandler2 = [this](ChimeraTK::DeviceManager* dm) {
-        if(dm == &application.device.getDeviceManager()) {
+        if(dm == &application.group1.device.getDeviceManager()) {
           initHandler2Called = true;
           if(initHandler2Throws) {
             throw ChimeraTK::runtime_error("Init handler 2 throws by request");
           }
         }
       };
-      application.device.addInitialisationHandler(initHandler1);
-      application.device.addInitialisationHandler(initHandler2);
+      application.group1.device.addInitialisationHandler(initHandler1);
+      application.group1.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 / "status_message"));
-    deviceBecameFunctional.replace(testFacitiy.getScalar<int>(
-        ChimeraTK::RegisterPath("/Devices") / DummyApplication::ExceptionDummyCDD1 / "deviceBecameFunctional"));
+    auto dm1 = ChimeraTK::Utilities::stripName(DummyApplication::ExceptionDummyCDD1, false);
+    auto dm2 = ChimeraTK::Utilities::stripName(DummyApplication::ExceptionDummyCDD2, false);
+    status.replace(testFacitiy.getScalar<int>(ChimeraTK::RegisterPath("/Devices") / dm1 / "status"));
+    message.replace(testFacitiy.getScalar<std::string>(ChimeraTK::RegisterPath("/Devices") / dm1 / "status_message"));
+    deviceBecameFunctional.replace(
+        testFacitiy.getVoid(ChimeraTK::RegisterPath("/Devices") / dm1 / "deviceBecameFunctional"));
 
-    status2.replace(testFacitiy.getScalar<int>(
-        ChimeraTK::RegisterPath("/Devices") / DummyApplication::ExceptionDummyCDD2 / "status"));
+    status2.replace(testFacitiy.getScalar<int>(ChimeraTK::RegisterPath("/Devices") / dm2 / "status"));
 
-    pushVariable3copy.replace(testFacitiy.getScalar<int>("dev2_reg1_push_read"));
+    ChimeraTK::Device dev(DummyApplication::ExceptionDummyCDD1);
+    interrupt.replace(dev.getVoidRegisterAccessor("DUMMY_INTERRUPT_1_0"));
 
-    //  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.outputModule.p.get_future().wait();
+    // wait until all modules have been properly started, to ensure the initial value propagation is complete
+    application.group1.pollModule.p.get_future().wait();
+    application.group1.pushModule.p.get_future().wait();
+    application.group1.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();
+      application.group2.outputModule2.p.get_future().wait();
+      application.group2.pollModule2.p.get_future().wait();
+      application.group2.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;
+  catch(std::exception& e) {
+    std::cout << "Exception caught in constructor: " << e.what() << std::endl;
+    throw;
   }
+}
 
-  template<typename T>
-  auto read(ChimeraTK::DummyRegisterRawAccessor& accessor) {
-    auto lock = accessor.getBufferLock();
-    return static_cast<T>(accessor);
-  }
-  template<typename T>
-  auto read(ChimeraTK::DummyRegisterRawAccessor&& accessor) {
-    read<T>(accessor);
-  }
+/**********************************************************************************************************************/
 
-  template<typename T>
-  void write(ChimeraTK::DummyRegisterRawAccessor& accessor, T value) {
-    auto lock = accessor.getBufferLock();
-    accessor = static_cast<int32_t>(value);
-  }
-  template<typename T>
-  void write(ChimeraTK::DummyRegisterRawAccessor&& accessor, T value) {
-    write(accessor, value);
-  }
+template<bool enableTestFacility, bool addInitHandlers, bool breakSecondDeviceAtStart>
+fixture_with_poll_and_push_input<enableTestFacility, addInitHandlers,
+    breakSecondDeviceAtStart>::~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;
+}
 
-  bool isDeviceInError() {
-    // By definition, the DeviceModule has finished the recovery procedure when the status is 0 again.
-    status.readLatest();
-    return static_cast<int>(status);
-  }
+/**********************************************************************************************************************/
 
-  boost::shared_ptr<ChimeraTK::ExceptionDummy> deviceBackend;
-  boost::shared_ptr<ChimeraTK::ExceptionDummy> deviceBackend2;
-  boost::shared_ptr<ChimeraTK::ExceptionDummy> deviceBackend3;
-  DummyApplication application;
-  ChimeraTK::TestFacility testFacitiy{application, enableTestFacility};
+template<bool enableTestFacility, bool addInitHandlers, bool breakSecondDeviceAtStart>
+bool fixture_with_poll_and_push_input<enableTestFacility, addInitHandlers,
+    breakSecondDeviceAtStart>::isDeviceInError() {
+  // By definition, the DeviceModule has finished the recovery procedure when the status is 0 again.
+  status.readLatest();
+  return static_cast<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.outputModule.deviceRegister};
-  ChimeraTK::ScalarOutput<int>& outputVariable2{application.outputModule.deviceRegister2};
-  ChimeraTK::ScalarOutput<int>& outputVariable3{application.outputModule.deviceRegister3};
+template<bool enableTestFacility, bool addInitHandlers, bool breakSecondDeviceAtStart>
+template<typename T>
+auto fixture_with_poll_and_push_input<enableTestFacility, addInitHandlers, breakSecondDeviceAtStart>::read(
+    ChimeraTK::DummyRegisterRawAccessor& accessor) {
+  auto lock = accessor.getBufferLock();
+  return static_cast<T>(accessor);
+}
 
-  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};
+template<bool enableTestFacility, bool addInitHandlers, bool breakSecondDeviceAtStart>
+template<typename T>
+auto fixture_with_poll_and_push_input<enableTestFacility, addInitHandlers, breakSecondDeviceAtStart>::read(
+    ChimeraTK::DummyRegisterRawAccessor&& accessor) {
+  read<T>(accessor);
+}
 
-  std::atomic<bool> initHandler1Throws{false};
-  std::atomic<bool> initHandler2Throws{false};
-  std::atomic<bool> initHandler1Called{false};
-  std::atomic<bool> initHandler2Called{false};
-};
+/**********************************************************************************************************************/
+
+template<bool enableTestFacility, bool addInitHandlers, bool breakSecondDeviceAtStart>
+template<typename T>
+void fixture_with_poll_and_push_input<enableTestFacility, addInitHandlers, breakSecondDeviceAtStart>::write(
+    ChimeraTK::DummyRegisterRawAccessor& accessor, T value) {
+  auto lock = accessor.getBufferLock();
+  accessor = static_cast<int32_t>(value);
+}
 
 /**********************************************************************************************************************/
+
+template<bool enableTestFacility, bool addInitHandlers, bool breakSecondDeviceAtStart>
+template<typename T>
+void fixture_with_poll_and_push_input<enableTestFacility, addInitHandlers, breakSecondDeviceAtStart>::write(
+    ChimeraTK::DummyRegisterRawAccessor&& accessor, T value) {
+  write(accessor, value);
+}
+
+/**********************************************************************************************************************/
+/**********************************************************************************************************************/
diff --git a/tests/test_with_push.map b/tests/test_with_push.map
new file mode 100644
index 00000000..e3786af0
--- /dev/null
+++ b/tests/test_with_push.map
@@ -0,0 +1,6 @@
+REG1                0x00000001    0x00000000    0x00000004    0    32    0     1     RW
+REG2                0x00000001    0x00000004    0x00000004    0    32    0     1     RW
+REG3                0x00000001    0x00000008    0x00000004    0    32    0     1     RW
+REG4                0x00000001    0x0000000C    0x00000004    0    32    0     1     RW
+AREA1               0x00000004    0x00000010    0x00000010    0    32    0     1     RW
+REG1_PUSHED         0x00000001    0x00000000    0x00000004    0    32    0     1     INTERRUPT1:0
-- 
GitLab