Skip to content
Snippets Groups Projects
testTrigger.cc 13.48 KiB
/*
 * testTrigger.cc
 *
 *  Created on: Jun 22, 2016
 *      Author: Martin Hierholzer
 */

#include <future>
#include <chrono>

#define BOOST_TEST_MODULE testTrigger

#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/DummyBackend.h>
#include <mtca4u/Device.h>
#include <mtca4u/DeviceAccessVersion.h>

#include <ChimeraTK/ControlSystemAdapter/PVManager.h>
#include <ChimeraTK/ControlSystemAdapter/ControlSystemPVManager.h>
#include <ChimeraTK/ControlSystemAdapter/DevicePVManager.h>

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

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

constexpr char dummySdm[] = "sdm://./TestTransferGroupDummy=test.map";

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

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

class TestTransferGroupDummy : public mtca4u::DummyBackend {
  public:
    TestTransferGroupDummy(std::string mapFileName) : DummyBackend(mapFileName) {}

    static boost::shared_ptr<DeviceBackend> createInstance(std::string, std::string, std::list<std::string> parameters, std::string) {
      return boost::shared_ptr<DeviceBackend>(new TestTransferGroupDummy(parameters.front()));
    }
    
    void read(uint8_t bar, uint32_t address, int32_t* data,  size_t sizeInBytes) override {
      last_bar = bar;
      last_address = address;
      last_sizeInBytes = sizeInBytes;
      numberOfTransfers++;
      DummyBackend::read(bar,address,data,sizeInBytes);
    }
    
    std::atomic<size_t> numberOfTransfers{0};
    std::atomic<uint8_t> last_bar;
    std::atomic<uint32_t> last_address;
    std::atomic<size_t> last_sizeInBytes;
};

/*********************************************************************************************************************/
/* 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::ScalarPushInput<T> consumingPush{this, "consumingPush", "MV/m", "Description"};
    ctk::ScalarPushInput<T> consumingPush2{this, "consumingPush2", "MV/m", "Description"};
    ctk::ScalarPushInput<T> consumingPush3{this, "consumingPush3", "MV/m", "Description"};

    ctk::ScalarPollInput<T> consumingPoll{this, "consumingPoll", "MV/m", "Description"};
    ctk::ScalarPollInput<T> consumingPoll2{this, "consumingPoll2", "MV/m", "Description"};
    ctk::ScalarPollInput<T> consumingPoll3{this, "consumingPoll3", "MV/m", "Description"};

    ctk::ScalarOutput<T> theTrigger{this, "theTrigger", "MV/m", "Description"};
    ctk::ScalarOutput<T> feedingToDevice{this, "feedingToDevice", "MV/m", "Description"};

    void mainLoop() {}
};

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

template<typename T>
struct TestApplication : public ctk::Application {
    TestApplication() : Application("testSuite") {
      mtca4u::BackendFactory::getInstance().registerBackendType("TestTransferGroupDummy", "",
        &TestTransferGroupDummy::createInstance, CHIMERATK_DEVICEACCESS_VERSION);
    }
    ~TestApplication() { shutdown(); }

    using Application::makeConnections;     // we call makeConnections() manually in the tests to catch exceptions etc.
    void defineConnections() {}             // the setup is done in the tests

    TestModule<T> testModule{this,"testModule", "The test module"};
    ctk::DeviceModule dev{"Dummy0"};
    ctk::DeviceModule dev2{dummySdm};
    ctk::ControlSystemModule cs;
};

/*********************************************************************************************************************/
/* test trigger by app variable when connecting a polled device register to an app variable */

BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerDevToApp, T, test_types ) {
  std::cout << "*********************************************************************************************************************" << std::endl;
  std::cout << "==> testTriggerDevToApp<" << typeid(T).name() << ">" << std::endl;

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

  TestApplication<T> app;

  app.testModule.feedingToDevice >> app.dev("/MyModule/actuator");

  app.dev("/MyModule/readBack") [ app.testModule.theTrigger ] >> app.testModule.consumingPush;
  app.initialise();
  app.run();

  // single theaded test
  app.testModule.consumingPush = 0;
  app.testModule.feedingToDevice = 42;
  BOOST_CHECK(app.testModule.consumingPush == 0);
  app.testModule.feedingToDevice.write();
  BOOST_CHECK(app.testModule.consumingPush == 0);
  app.testModule.theTrigger.write();
  BOOST_CHECK(app.testModule.consumingPush == 0);
  app.testModule.consumingPush.read();
  BOOST_CHECK(app.testModule.consumingPush == 42);

  // launch read() on the consumer asynchronously and make sure it does not yet receive anything
  auto futRead = std::async(std::launch::async, [&app]{ app.testModule.consumingPush.read(); });
  BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout);

  BOOST_CHECK(app.testModule.consumingPush == 42);

  // write to the feeder
  app.testModule.feedingToDevice = 120;
  app.testModule.feedingToDevice.write();
  BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout);
  BOOST_CHECK(app.testModule.consumingPush == 42);

  // send trigger
  app.testModule.theTrigger.write();

  // check that the consumer now receives the just written value
  BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(2000)) == std::future_status::ready);
  BOOST_CHECK( app.testModule.consumingPush == 120 );
}

/*********************************************************************************************************************/
/* test trigger by app variable when connecting a polled device register to control system variable */

BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerDevToCS, T, test_types ) {
  std::cout << "*********************************************************************************************************************" << std::endl;
  std::cout << "==> testTriggerDevToCS<" << typeid(T).name() << ">" << std::endl;

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

  TestApplication<T> app;

  auto pvManagers = ctk::createPVManager();
  app.setPVManager(pvManagers.second);

  app.testModule.feedingToDevice >> app.dev("/MyModule/actuator");

  app.dev("/MyModule/readBack", typeid(T), 1) [ app.testModule.theTrigger ] >> app.cs("myCSVar");
  
  app.initialise();
  app.run();

  BOOST_CHECK_EQUAL(pvManagers.first->getAllProcessVariables().size(), 1);
  auto myCSVar = pvManagers.first->getProcessArray<T>("/myCSVar");

  // single theaded test only, since the receiving process scalar does not support blocking
  BOOST_CHECK(myCSVar->readNonBlocking() == false);
  app.testModule.feedingToDevice = 42;
  BOOST_CHECK(myCSVar->readNonBlocking() == false);
  app.testModule.feedingToDevice.write();
  BOOST_CHECK(myCSVar->readNonBlocking() == false);
  app.testModule.theTrigger.write();
  CHECK_TIMEOUT(myCSVar->readNonBlocking() == true, 30000);
  BOOST_CHECK(myCSVar->accessData(0) == 42);

  BOOST_CHECK(myCSVar->readNonBlocking() == false);
  app.testModule.feedingToDevice = 120;
  BOOST_CHECK(myCSVar->readNonBlocking() == false);
  app.testModule.feedingToDevice.write();
  BOOST_CHECK(myCSVar->readNonBlocking() == false);
  app.testModule.theTrigger.write();
  CHECK_TIMEOUT(myCSVar->readNonBlocking() == true, 30000);
  BOOST_CHECK(myCSVar->accessData(0) == 120);

  BOOST_CHECK(myCSVar->readNonBlocking() == false);
}

/*********************************************************************************************************************/
/* test trigger by app variable when connecting a polled device register to control system variable */

BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerByCS, T, test_types ) {
  std::cout << "*********************************************************************************************************************" << std::endl;
  std::cout << "==> testTriggerByCS<" << typeid(T).name() << ">" << std::endl;

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

  TestApplication<T> app;

  auto pvManagers = ctk::createPVManager();
  app.setPVManager(pvManagers.second);

  app.testModule.feedingToDevice >> app.dev("/MyModule/actuator");

  app.dev("/MyModule/readBack", typeid(T), 1) [ app.cs("theTrigger", typeid(T), 1) ] >> app.cs("myCSVar");
  
  app.initialise();
  app.run();

  BOOST_CHECK_EQUAL(pvManagers.first->getAllProcessVariables().size(), 2);
  auto myCSVar = pvManagers.first->getProcessArray<T>("/myCSVar");
  auto theTrigger = pvManagers.first->getProcessArray<T>("/theTrigger");
  
  // single theaded test only, since the receiving process scalar does not support blocking
  BOOST_CHECK(myCSVar->readNonBlocking() == false);
  app.testModule.feedingToDevice = 42;
  BOOST_CHECK(myCSVar->readNonBlocking() == false);
  app.testModule.feedingToDevice.write();
  BOOST_CHECK(myCSVar->readNonBlocking() == false);
  myCSVar->accessData(0) = 0;
  theTrigger->write();
  CHECK_TIMEOUT(myCSVar->readNonBlocking() == true, 30000);
  BOOST_CHECK(myCSVar->accessData(0) == 42);

  BOOST_CHECK(myCSVar->readNonBlocking() == false);
  app.testModule.feedingToDevice = 120;
  BOOST_CHECK(myCSVar->readNonBlocking() == false);
  app.testModule.feedingToDevice.write();
  BOOST_CHECK(myCSVar->readNonBlocking() == false);
  myCSVar->accessData(0) = 0;
  theTrigger->write();
  CHECK_TIMEOUT(myCSVar->readNonBlocking() == true, 30000);
  BOOST_CHECK(myCSVar->accessData(0) == 120);

  BOOST_CHECK(myCSVar->readNonBlocking() == false);
}

/*********************************************************************************************************************/
/* test that multiple variables triggered by the same source are put into the same TransferGroup */

BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerTransferGroup, T, test_types ) {
  std::cout << "*********************************************************************************************************************" << std::endl;
  std::cout << "==> testTriggerTransferGroup<" << typeid(T).name() << ">" << std::endl;

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

  TestApplication<T> app;

  mtca4u::Device dev;
  dev.open(dummySdm);
  auto backend = boost::dynamic_pointer_cast<TestTransferGroupDummy>(mtca4u::BackendFactory::getInstance().createBackend(dummySdm));
  BOOST_CHECK( backend != NULL );

  app.dev2("/REG1") [ app.testModule.theTrigger ] >> app.testModule.consumingPush;
  app.dev2("/REG2") [ app.testModule.theTrigger ] >> app.testModule.consumingPush2;
  app.dev2("/REG3") [ app.testModule.theTrigger ] >> app.testModule.consumingPush3;
  app.initialise();
  app.run();

  // initialise values
  app.testModule.consumingPush = 0;
  app.testModule.consumingPush2 = 0;
  app.testModule.consumingPush3 = 0;
  dev.write("/REG1", 11);
  dev.write("/REG2", 22);
  dev.write("/REG3", 33);
  
  // trigger the transfer
  app.testModule.theTrigger.write();
  CHECK_TIMEOUT(backend->numberOfTransfers == 1, 200);
  BOOST_CHECK(backend->last_bar == 0);
  BOOST_CHECK(backend->last_address == 0);
  BOOST_CHECK(backend->last_sizeInBytes == 12);
  
  // check result
  app.testModule.consumingPush.read();
  app.testModule.consumingPush2.read();
  app.testModule.consumingPush3.read();
  BOOST_CHECK(app.testModule.consumingPush  == 11);
  BOOST_CHECK(app.testModule.consumingPush2 == 22);
  BOOST_CHECK(app.testModule.consumingPush3 == 33);
  
  // prepare a second transfer
  dev.write("/REG1", 12);
  dev.write("/REG2", 23);
  dev.write("/REG3", 34);
  
  // trigger the transfer
  app.testModule.theTrigger.write();
  CHECK_TIMEOUT(backend->numberOfTransfers == 2, 200);
  BOOST_CHECK(backend->last_bar == 0);
  BOOST_CHECK(backend->last_address == 0);
  BOOST_CHECK(backend->last_sizeInBytes == 12);

  // check result
  app.testModule.consumingPush.read();
  app.testModule.consumingPush2.read();
  app.testModule.consumingPush3.read();
  BOOST_CHECK(app.testModule.consumingPush  == 12);
  BOOST_CHECK(app.testModule.consumingPush2 == 23);
  BOOST_CHECK(app.testModule.consumingPush3 == 34);
  
  dev.close();

}