Skip to content
Snippets Groups Projects
testDeviceAccessors.cc 16.4 KiB
Newer Older
/*
 * testDeviceAccessors.cc
 *
 *  Created on: Jun 22, 2016
 *      Author: Martin Hierholzer
 */

#include <future>

#define BOOST_TEST_MODULE testDeviceAccessors

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

#include <mtca4u/BackendFactory.h>
#include <mtca4u/NDRegisterAccessor.h>

#include "ScalarAccessor.h"
#include "ApplicationModule.h"
#include "DeviceModule.h"

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

// list of user types the accessors are tested with
typedef boost::mpl::list<int8_t,uint8_t,
                         int16_t,uint16_t,
                         int32_t,uint32_t,
                         float,double>        test_types;

#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);                                                                                               \
      }                                                                                                             \
    }

/*********************************************************************************************************************/
/* the ApplicationModule for the test is a template of the user type */

template<typename T>
struct TestModule : public ctk::ApplicationModule {
    using ctk::ApplicationModule::ApplicationModule;
    ctk::ScalarPollInput<T> consumingPoll{this, "consumingPoll", "MV/m", "Description"};
    ctk::ScalarPushInput<T> consumingPush{this, "consumingPush", "MV/m", "Description"};
    ctk::ScalarPushInput<T> consumingPush2{this, "consumingPush2", "MV/m", "Description"};
    ctk::ScalarOutput<T> feedingToDevice{this, "feedingToDevice", "MV/m", "Description"};

    void mainLoop() {}
};

/*********************************************************************************************************************/
/* dummy application */

struct TestApplication : public ctk::Application {
    TestApplication() : Application("testSuite") {}
    using Application::makeConnections;     // we call makeConnections() manually in the tests to catch exceptions etc.
    using Application::deviceMap;           // expose the device map for the tests
    using Application::networkList;         // expose network list to check merging networks
    void defineConnections() {}             // the setup is done in the tests
    TestModule<T> testModule{this,"testModule", "The test module"};
    ctk::DeviceModule devMymodule{"Dummy0","MyModule"};
    ctk::DeviceModule dev{"Dummy0"};
    // note: direct device-to-controlsystem connections are tested in testControlSystemAccessors!
};

/*********************************************************************************************************************/
/* test feeding a scalar to a device */

BOOST_AUTO_TEST_CASE_TEMPLATE( testFeedToDevice, T, test_types ) {
  std::cout << "testFeedToDevice" << std::endl;
  mtca4u::BackendFactory::getInstance().setDMapFilePath("test.dmap");
  app.testModule.feedingToDevice >> app.devMymodule("actuator");
  mtca4u::Device dev;
  dev.open("Dummy0");
  auto regacc = dev.getScalarRegisterAccessor<int>("/MyModule/actuator");
  app.testModule.feedingToDevice = 42;
  app.testModule.feedingToDevice.write();
  regacc.read();
  BOOST_CHECK(regacc == 42);
  regacc.read();
  BOOST_CHECK(regacc == 42);
  regacc.read();
  BOOST_CHECK(regacc == 120);
/*********************************************************************************************************************/
/* test feeding a scalar to two different device registers */

BOOST_AUTO_TEST_CASE_TEMPLATE( testFeedToDeviceFanOut, T, test_types ) {
  std::cout << "testFeedToDeviceFanOut" << std::endl;

  mtca4u::BackendFactory::getInstance().setDMapFilePath("test.dmap");

  TestApplication<T> app;

  app.testModule.feedingToDevice >> app.devMymodule("actuator")
                                 >> app.devMymodule("readBack");
  app.initialise();

  mtca4u::Device dev;
  dev.open("Dummy0");
  auto regac = dev.getScalarRegisterAccessor<int>("/MyModule/actuator");
  auto regrb = dev.getScalarRegisterAccessor<int>("/MyModule/readBack");
  app.testModule.feedingToDevice = 42;
  app.testModule.feedingToDevice.write();
  regac.read();
  BOOST_CHECK(regac == 42);
  regrb.read();
  BOOST_CHECK(regrb == 42);
  regac.read();
  BOOST_CHECK(regac == 42);
  regrb.read();
  BOOST_CHECK(regrb == 42);
  app.testModule.feedingToDevice.write();
  regac.read();
  BOOST_CHECK(regac == 120);
  regrb.read();
  BOOST_CHECK(regrb == 120);
/*********************************************************************************************************************/
/* test consuming a scalar from a device */

BOOST_AUTO_TEST_CASE_TEMPLATE( testConsumeFromDevice, T, test_types ) {
  std::cout << "testConsumeFromDevice" << std::endl;
  mtca4u::BackendFactory::getInstance().setDMapFilePath("test.dmap");
  app.dev("/MyModule/actuator") >> app.testModule.consumingPoll;
  mtca4u::Device dev;
  dev.open("Dummy0");
  auto regacc = dev.getScalarRegisterAccessor<int>("/MyModule/actuator");

  // single theaded test only, since read() does not block in this case
  regacc = 42;
  regacc.write();
  BOOST_CHECK(app.testModule.consumingPoll == 0);
  app.testModule.consumingPoll.read();
  BOOST_CHECK(app.testModule.consumingPoll == 42);
  app.testModule.consumingPoll.read();
  BOOST_CHECK(app.testModule.consumingPoll == 42);
  app.testModule.consumingPoll.read();
  BOOST_CHECK(app.testModule.consumingPoll == 42);
  regacc = 120;
  regacc.write();
  BOOST_CHECK(app.testModule.consumingPoll == 42);
  app.testModule.consumingPoll.read();
  BOOST_CHECK( app.testModule.consumingPoll == 120 );
  app.testModule.consumingPoll.read();
  BOOST_CHECK( app.testModule.consumingPoll == 120 );
  app.testModule.consumingPoll.read();
  BOOST_CHECK( app.testModule.consumingPoll == 120 );
/*********************************************************************************************************************/
/* test consuming a scalar from a device with a ConsumingFanOut (i.e. one poll-type consumer and several push-type
 * consumers). */

BOOST_AUTO_TEST_CASE_TEMPLATE( testConsumingFanOut, T, test_types ) {
  std::cout << "testConsumingFanOut" << std::endl;

  mtca4u::BackendFactory::getInstance().setDMapFilePath("test.dmap");

  TestApplication<T> app;

  app.dev("/MyModule/actuator") >> app.testModule.consumingPoll
                                >> app.testModule.consumingPush
                                >> app.testModule.consumingPush2;
  app.initialise();

  mtca4u::Device dev;
  dev.open("Dummy0");
  auto regacc = dev.getScalarRegisterAccessor<int>("/MyModule/actuator");

  // single theaded test only, since read() does not block in this case
  app.testModule.consumingPoll = 0;
  BOOST_CHECK(app.testModule.consumingPoll == 0);
  BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false);
  BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == false);
  BOOST_CHECK(app.testModule.consumingPush == 0);
  BOOST_CHECK(app.testModule.consumingPush2 == 0);
  app.testModule.consumingPoll.read();
  BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == true);
  BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == true);
  BOOST_CHECK(app.testModule.consumingPoll == 42);
  BOOST_CHECK(app.testModule.consumingPush == 42);
  BOOST_CHECK(app.testModule.consumingPush2 == 42);
  BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false);
  BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == false);
  app.testModule.consumingPoll.read();
  BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == true);
  BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == true);
  BOOST_CHECK(app.testModule.consumingPoll == 42);
  BOOST_CHECK(app.testModule.consumingPush == 42);
  BOOST_CHECK(app.testModule.consumingPush2 == 42);
  BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false);
  BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == false);
  app.testModule.consumingPoll.read();
  BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == true);
  BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == true);
  BOOST_CHECK(app.testModule.consumingPoll == 42);
  BOOST_CHECK(app.testModule.consumingPush == 42);
  BOOST_CHECK(app.testModule.consumingPush2 == 42);
  BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false);
  BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == false);
  regacc = 120;
  regacc.write();
  BOOST_CHECK(app.testModule.consumingPoll == 42);
  BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false);
  BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == false);
  BOOST_CHECK(app.testModule.consumingPush == 42);
  BOOST_CHECK(app.testModule.consumingPush2 == 42);
  app.testModule.consumingPoll.read();
  BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == true);
  BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == true);
  BOOST_CHECK( app.testModule.consumingPoll == 120 );
  BOOST_CHECK(app.testModule.consumingPush == 120);
  BOOST_CHECK(app.testModule.consumingPush2 == 120);
  BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false);
  BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == false);
  app.testModule.consumingPoll.read();
  BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == true);
  BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == true);
  BOOST_CHECK(app.testModule.consumingPoll == 120);
  BOOST_CHECK(app.testModule.consumingPush == 120);
  BOOST_CHECK(app.testModule.consumingPush2 == 120);
  BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false);
  BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == false);
  app.testModule.consumingPoll.read();
  BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == true);
  BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == true);
  BOOST_CHECK(app.testModule.consumingPoll == 120);
  BOOST_CHECK(app.testModule.consumingPush == 120);
  BOOST_CHECK(app.testModule.consumingPush2 == 120);
  BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false);
  BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == false);

}

/*********************************************************************************************************************/
/* test merged networks (optimisation done in Application::optimiseConnections()) */

BOOST_AUTO_TEST_CASE_TEMPLATE( testMergedNetworks, T, test_types ) {
  std::cout << "testMergedNetworks" << std::endl;

  mtca4u::BackendFactory::getInstance().setDMapFilePath("test.dmap");

  TestApplication<T> app;

  // we abuse "feedingToDevice" as trigger here...
  app.dev("/MyModule/actuator") [ app.testModule.feedingToDevice ] >> app.testModule.consumingPush;
  app.dev("/MyModule/actuator") [ app.testModule.feedingToDevice ] >> app.testModule.consumingPush2;
  // check that we have two separate networks for both connections
  size_t nDeviceFeeders = 0;
  for(auto &net : app.networkList) {
    if( net.getFeedingNode().getType() == ctk::NodeType::Device ) nDeviceFeeders++;
  }
  BOOST_CHECK_EQUAL( nDeviceFeeders, 2 );
  // the optimisation to test takes place here
  app.initialise();

  // check we are left with just one network fed by the device
  nDeviceFeeders = 0;
  for(auto &net : app.networkList) {
    if( net.getFeedingNode().getType() == ctk::NodeType::Device ) nDeviceFeeders++;
  }
  BOOST_CHECK_EQUAL( nDeviceFeeders, 1 );

  // run the application to see if everything still behaves as expected
  app.run();

  mtca4u::Device dev;
  dev.open("Dummy0");
  auto regacc = dev.getScalarRegisterAccessor<int>("/MyModule/actuator");

  // single theaded test only, since read() does not block in this case
  app.testModule.consumingPush = 0;
  app.testModule.consumingPush2 = 0;
  regacc = 42;
  regacc.write();
  BOOST_CHECK(app.testModule.consumingPush == 0);
  BOOST_CHECK(app.testModule.consumingPush2 == 0);
  app.testModule.feedingToDevice.write();
  app.testModule.consumingPush.read();
  app.testModule.consumingPush2.read();
  BOOST_CHECK(app.testModule.consumingPush == 42);
  BOOST_CHECK(app.testModule.consumingPush2 == 42);
  regacc = 120;
  regacc.write();
  BOOST_CHECK(app.testModule.consumingPush == 42);
  BOOST_CHECK(app.testModule.consumingPush2 == 42);
  app.testModule.feedingToDevice.write();
  app.testModule.consumingPush.read();
  app.testModule.consumingPush2.read();
  BOOST_CHECK( app.testModule.consumingPush == 120 );
  BOOST_CHECK( app.testModule.consumingPush2 == 120 );

}

/*********************************************************************************************************************/
/* test feeding a constant to a device register. */

BOOST_AUTO_TEST_CASE_TEMPLATE( testConstantToDevice, T, test_types ) {
  std::cout << "testConstantToDevice" << std::endl;

  mtca4u::BackendFactory::getInstance().setDMapFilePath("test.dmap");

  TestApplication<T> app;

  ctk::VariableNetworkNode::makeConstant<T>(true, 18) >> app.dev("/MyModule/actuator");
  app.initialise();
  app.run();
  mtca4u::Device dev;
  dev.open("Dummy0");
  CHECK_TIMEOUT( dev.read<T>("/MyModule/actuator") == 18, 3000 );
}

/*********************************************************************************************************************/
/* test feeding a constant to a device register with a fan out. */

BOOST_AUTO_TEST_CASE_TEMPLATE( testConstantToDeviceFanOut, T, test_types ) {
  std::cout << "testConstantToDeviceFanOut" << std::endl;

  mtca4u::BackendFactory::getInstance().setDMapFilePath("test.dmap");

  TestApplication<T> app;

  ctk::VariableNetworkNode::makeConstant<T>(true, 20) >> app.dev("/MyModule/actuator")
                                                      >> app.dev("/MyModule/readBack");
  app.initialise();
  app.run();
  mtca4u::Device dev;
  dev.open("Dummy0");
  CHECK_TIMEOUT( dev.read<T>("/MyModule/actuator") == 20, 3000 );
  CHECK_TIMEOUT( dev.read<T>("/MyModule/readBack") == 20, 3000 );

/*********************************************************************************************************************/
/* test subscript operator of DeviceModule */

BOOST_AUTO_TEST_CASE_TEMPLATE( testDeviceModuleSubscriptOp, T, test_types ) {
  std::cout << "testDeviceModuleSubscriptOp" << std::endl;

  mtca4u::BackendFactory::getInstance().setDMapFilePath("test.dmap");

  TestApplication<T> app;

  app.testModule.feedingToDevice >> app.dev["MyModule"]("actuator");
  app.initialise();

  mtca4u::Device dev;
  dev.open("Dummy0");
  auto regacc = dev.getScalarRegisterAccessor<int>("/MyModule/actuator");
  app.testModule.feedingToDevice = 42;
  app.testModule.feedingToDevice.write();
  regacc.read();
  BOOST_CHECK(regacc == 42);
  regacc.read();
  BOOST_CHECK(regacc == 42);
  regacc.read();
  BOOST_CHECK(regacc == 120);

}

/*********************************************************************************************************************/
/* test DeviceModule::virtualise()  (trivial implementation) */

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

  mtca4u::BackendFactory::getInstance().setDMapFilePath("test.dmap");

  TestApplication<int> app;

  app.testModule.feedingToDevice >> app.dev.virtualise()["MyModule"]("actuator");
  app.initialise();

  BOOST_CHECK( &(app.dev.virtualise()) == &(app.dev) );