Skip to content
Snippets Groups Projects
testDeviceInitialisationHandler.cc 8.49 KiB
Newer Older
//#include <future>

#define BOOST_TEST_MODULE testDeviceInitialisationHandler

//#include <boost/mpl/list.hpp>
#include <boost/test/included/unit_test.hpp>

#include "Application.h"
#include "ControlSystemModule.h"
#include "DeviceModule.h"
//#include "ScalarAccessor.h"
#include "TestFacility.h"

#include <ChimeraTK/Device.h>
#include <ChimeraTK/ExceptionDummyBackend.h>
#include <stdlib.h>
#include "check_timeout.h"

using namespace boost::unit_test_framework;
namespace ctk = ChimeraTK;

static constexpr char deviceCDD[] = "(ExceptionDummy?map=test.map)";
static constexpr char exceptionMessage[] = "DEBUG: runtime error intentionally cased in device initialisation";

static std::atomic<bool> throwInInitialisation{false};
static std::atomic<int32_t> var1{0};
static std::atomic<int32_t> var2{0};
static std::atomic<int32_t> var3{0};
Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed

void initialiseReg1(ctk::DeviceModule*) {
  var1 = 42;
  if(throwInInitialisation) {
    throw ctk::runtime_error(exceptionMessage);
  }
Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
void initialiseReg2(ctk::DeviceModule*) {
  // the initialisation of reg 2 must happen after the initialisation of reg1
Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
  var2 = var1 + 5;
Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
void initialiseReg3(ctk::DeviceModule*) {
  // the initialisation of reg 3 must happen after the initialisation of reg2
Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
  var3 = var2 + 5;
/* dummy application */
struct TestApplication : public ctk::Application {
  TestApplication() : Application("testSuite") {}
  ~TestApplication() { shutdown(); }

  void defineConnections() {} // the setup is done in the tests
  ctk::ControlSystemModule cs;
  ctk::DeviceModule dev{this, deviceCDD, &initialiseReg1};
};

/*********************************************************************************************************************/

BOOST_AUTO_TEST_CASE(testBasicInitialisation) {
  std::cout << "testBasicInitialisation" << std::endl;
  TestApplication app;

Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
  var1 = 0;
  var2 = 0;
  var3 = 0;

  app.dev.connectTo(app.cs);
  ctk::TestFacility test;
  test.runApplication();
  //app.dumpConnections();
  ctk::Device dummy;

  // ********************************************************
  // REQUIRED TEST 1: After opening the device is initialised
  // ********************************************************
Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
  BOOST_CHECK_EQUAL(var1, 42);
Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
  var1 = 0;

  // check that accessing an exception triggers a reconnection with re-initialisation
  auto dummyBackend =
      boost::dynamic_pointer_cast<ctk::ExceptionDummy>(ctk::BackendFactory::getInstance().createBackend(deviceCDD));
  dummyBackend->throwExceptionWrite = true;
  auto reg2_cs = test.getScalar<int32_t>("/REG2");
  reg2_cs.write();
Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
  BOOST_CHECK_EQUAL(var2, 0);
  BOOST_CHECK_EQUAL(var1, 0);
  dummyBackend->throwExceptionWrite = false; // now the device should work again and be re-initialised
  reg2_cs.write();
  test.stepApplication();

Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
  BOOST_CHECK_EQUAL(dummy.read<int32_t>("/REG2"), 20);

  // ****************************************************************
  // REQUIRED TEST 2: After an exception the device is re-initialised
  // ****************************************************************
Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
  BOOST_CHECK_EQUAL(var1, 42);
BOOST_AUTO_TEST_CASE(testMultipleInitialisationHandlers) {
  std::cout << "testMultipleInitialisationHandlers" << std::endl;
  TestApplication app;

Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
  var1 = 0;
  var2 = 0;
  var3 = 0;

  app.dev.addInitialisationHandler(&initialiseReg2);
  app.dev.addInitialisationHandler(&initialiseReg3);
  app.dev.connectTo(app.cs);
  ctk::TestFacility test;
  test.runApplication();
  //app.dumpConnections();

  auto deviceStatus = test.getScalar<int32_t>(ctk::RegisterPath("/Devices") / deviceCDD / "status");
  // *********************************************************
  // REQUIRED TEST 4: Handlers are executed in the right order
  // *********************************************************
Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
  BOOST_CHECK_EQUAL(var1, 42);
  BOOST_CHECK_EQUAL(var2, 47); // the initialiser used reg1+5, so order matters
  BOOST_CHECK_EQUAL(var3, 52); // the initialiser used reg2+5, so order matters

  // check that after an exception the re-initialisation is OK
Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
  var1 = 0;
  var2 = 0;
  var3 = 0;

  // cause an exception
  auto dummyBackend =
      boost::dynamic_pointer_cast<ctk::ExceptionDummy>(ctk::BackendFactory::getInstance().createBackend(deviceCDD));
  dummyBackend->throwExceptionWrite = true;
  auto reg4_cs = test.getScalar<int32_t>("/REG4");
  reg4_cs.write();
  dummyBackend->throwExceptionWrite = false;
  reg4_cs.write();
  test.stepApplication();

Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
  BOOST_CHECK_EQUAL(var1, 42);
  BOOST_CHECK_EQUAL(var2, 47); // the initialiser used reg1+5, so order matters
  BOOST_CHECK_EQUAL(var3, 52); // the initialiser used reg2+5, so order matters
}

BOOST_AUTO_TEST_CASE(testInitialisationException) {
  std::cout << "testInitialisationException" << std::endl;

Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
  var1 = 0;
  var2 = 0;
  var3 = 0;

  throwInInitialisation = true;
  TestApplication app;

  app.dev.addInitialisationHandler(&initialiseReg2);
  app.dev.addInitialisationHandler(&initialiseReg3);
  app.dev.connectTo(app.cs);
  ctk::TestFacility test(false); // test facility without testable mode
Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
  ctk::Device dummy;
  dummy.open(deviceCDD);

  // We cannot use runApplication because the DeviceModule leaves the testable mode without variables in the queue, but has not finished error handling yet.
  // In this special case we cannot make the programme continue, because stepApplication only works if the queues are not empty.
  // We have to work with timeouts here (until someone comes up with a better idea)
  app.run();
  CHECK_EQUAL_TIMEOUT(test.readScalar<int32_t>(ctk::RegisterPath("/Devices") / deviceCDD / "status"), 1, 30000);
      test.readScalar<std::string>(ctk::RegisterPath("/Devices") / deviceCDD / "message"), exceptionMessage, 10000);

  // Check that the execution of init handlers was stopped after the exception:
  // initialiseReg2 and initialiseReg3 were not executed. As we already checked with timeout that the
  // initialisation error has been reported, we know that the data was written to the device and don't need the timeout here.
Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
  BOOST_CHECK_EQUAL(var1, 42);
  BOOST_CHECK_EQUAL(var2, 0);
  BOOST_CHECK_EQUAL(var3, 0);
  // recover the error
  throwInInitialisation = false;

  // wait until the device is reported to be OK again (chech with timeout),
  // then check the initialisation (again, no extra timeout needed because of the logic:
  // success is only reported after successful init).
  CHECK_EQUAL_TIMEOUT(test.readScalar<int32_t>(ctk::RegisterPath("/Devices") / deviceCDD / "status"), 0, 10000);
  CHECK_EQUAL_TIMEOUT(test.readScalar<std::string>(ctk::RegisterPath("/Devices") / deviceCDD / "message"), "", 10000);

  // initialisation should be correct now
Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
  BOOST_CHECK_EQUAL(var1, 42);
  BOOST_CHECK_EQUAL(var2, 47);
  BOOST_CHECK_EQUAL(var3, 52);
  // now check that the initialisation error is also reportet when recovering
  // Prepare registers to be initialised
Martin Christoph Hierholzer's avatar
Martin Christoph Hierholzer committed
  var1 = 12;
  var2 = 13;
  var3 = 14;

  // Make initialisation fail when executed, and then cause an error condition
  throwInInitialisation = true;
  auto dummyBackend =
      boost::dynamic_pointer_cast<ctk::ExceptionDummy>(ctk::BackendFactory::getInstance().createBackend(deviceCDD));
  dummyBackend->throwExceptionWrite = true;

  auto reg4_cs = test.getScalar<int32_t>("/REG4");
  CHECK_EQUAL_TIMEOUT(test.readScalar<int32_t>(ctk::RegisterPath("/Devices") / deviceCDD / "status"), 1, 10000);
  // First we see the message from the failing write
  CHECK_EQUAL_TIMEOUT(test.readScalar<std::string>(ctk::RegisterPath("/Devices") / deviceCDD / "message"),
      "DummyException: write throws by request", 10000);
  dummyBackend->throwExceptionWrite = false;
  // Afterwards we see a message from the failing initialisation (which we can now distinguish from the original write exception because write does not throw any more)
  CHECK_EQUAL_TIMEOUT(
      test.readScalar<std::string>(ctk::RegisterPath("/Devices") / deviceCDD / "message"), exceptionMessage, 10000);

  // Now fix the initialisation error and check that the device comes up.
  throwInInitialisation = false;
  // Wait until the device is OK again
  CHECK_EQUAL_TIMEOUT(test.readScalar<int32_t>(ctk::RegisterPath("/Devices") / deviceCDD / "status"), 0, 10000);
  CHECK_EQUAL_TIMEOUT(test.readScalar<std::string>(ctk::RegisterPath("/Devices") / deviceCDD / "message"), "", 10000);
  // Finally check that the 20 arrives on the device
  CHECK_EQUAL_TIMEOUT(dummy.read<int32_t>("/REG4"), 20, 10000);