Skip to content
Snippets Groups Projects
TestFacility.h 7.42 KiB
Newer Older
/*
 * TestFacility.h
 *
 *  Created on: Feb 17, 2017
 *      Author: Martin Hierholzer
 */

#ifndef CHIMERATK_TEST_FACILITY
#define CHIMERATK_TEST_FACILITY

#include <ChimeraTK/ControlSystemAdapter/ControlSystemPVManager.h>
#include <ChimeraTK/ScalarRegisterAccessor.h>
#include <ChimeraTK/OneDRegisterAccessor.h>

namespace ChimeraTK {

  /** Helper class to facilitate tests of applications based on ApplicationCore */
  class TestFacility {
      /** The constructor will internally obtain the instance of the application, so the instance of the TestFacility
       *  must not be created before the application (i.e. usually not before the main() routine). The application will
       *  automatically be put into the testable mode and initialised. */
      TestFacility() {
        auto pvManagers = createPVManager();
        pvManager = pvManagers.first;
        Application::getInstance().setPVManager(pvManagers.second);
        Application::getInstance().enableTestableMode();
        Application::getInstance().initialise();
      }
      /** Start the application. This simply calls Application::run(). Since the application is in testable mode, it
       *  will be ??? TODO define precisely what happens on start up */
      void runApplication() const {
        Application::getInstance().run();
        Application::registerThread("TestThread");
      /** Perform a "step" of the application. This runs the application until all input provided to it has been
       *  processed and all application modules wait for new data in blocking read calls. This function returns only
       *  after the application has reached that stated and was paused again. After returning from this function,
       *  the result can be checked and new data can be provided to the application. The new data will not be
       *  processed until the next call to step(). */
      void stepApplication() const {
        Application::getInstance().stepApplication();
      }
      /** Obtain a scalar process variable from the application, which is published to the control system. */
      template<typename T>
      ChimeraTK::ScalarRegisterAccessor<T> getScalar(const ChimeraTK::RegisterPath &name) const {

        // check for existing accessor in cache
        if(boost::fusion::at_key<T>(scalarMap.table).count(name) > 0) {
          return boost::fusion::at_key<T>(scalarMap.table)[name];
        }

        // obtain accessor from ControlSystemPVManager
        auto pv = pvManager->getProcessArray<T>(name);
        if(pv == nullptr) {
          throw ChimeraTK::logic_error("Process variable '"+name+"' does not exist.");
        // obtain variable id from pvIdMap and transfer it to idMap (required by the TestableModeAccessorDecorator)
        size_t varId = Application::getInstance().pvIdMap[pv->getUniqueId()];
        // decorate with TestableModeAccessorDecorator if variable is sender and receiver is not poll-type,
        if(pv->isWriteable() && !Application::getInstance().testableMode_isPollMode[varId]) {
          auto deco = boost::make_shared<TestableModeAccessorDecorator<T>>(pv, false, true, varId, varId);
          Application::getInstance().testableMode_names[varId] = "ControlSystem:"+name;
          boost::fusion::at_key<T>(scalarMap.table)[name].replace(ChimeraTK::ScalarRegisterAccessor<T>(deco));
          boost::fusion::at_key<T>(scalarMap.table)[name].replace(ChimeraTK::ScalarRegisterAccessor<T>(pv));

        // return the accessor as stored in the cache
        return boost::fusion::at_key<T>(scalarMap.table)[name];
      /** Obtain an array-type process variable from the application, which is published to the control system. */
      template<typename T>
      ChimeraTK::OneDRegisterAccessor<T> getArray(const ChimeraTK::RegisterPath &name) const {

        // check for existing accessor in cache
        if(boost::fusion::at_key<T>(arrayMap.table).count(name) > 0) {
          return boost::fusion::at_key<T>(arrayMap.table)[name];
        }

        // obtain accessor from ControlSystemPVManager
        auto pv = pvManager->getProcessArray<T>(name);
        if(pv == nullptr) {
          throw ChimeraTK::logic_error("Process variable '"+name+"' does not exist.");
        // obtain variable id from pvIdMap and transfer it to idMap (required by the TestableModeAccessorDecorator)
        size_t varId = Application::getInstance().pvIdMap[pv->getUniqueId()];
        // decorate with TestableModeAccessorDecorator if variable is sender and receiver is not poll-type,
        if(pv->isWriteable() && !Application::getInstance().testableMode_isPollMode[varId]) {
          auto deco = boost::make_shared<TestableModeAccessorDecorator<T>>(pv, false, true, varId, varId);
          Application::getInstance().testableMode_names[varId] = "ControlSystem:"+name;
          boost::fusion::at_key<T>(arrayMap.table)[name].replace(ChimeraTK::OneDRegisterAccessor<T>(deco));
          boost::fusion::at_key<T>(arrayMap.table)[name].replace(ChimeraTK::OneDRegisterAccessor<T>(pv));

        // return the accessor as stored in the cache
        return boost::fusion::at_key<T>(arrayMap.table)[name];
      /** Convenience function to write a scalar process variable in a single call */
      template<typename TYPE>
      void writeScalar( const std::string &name, const TYPE value ) {
        auto acc = getScalar<TYPE>(name);
        acc = value;
        acc.write();
      }
      /** Convenience function to write an array process variable in a single call */
      template<typename TYPE>
      void writeArray( const std::string &name, const std::vector<TYPE> &value ) {
        auto acc = getArray<TYPE>(name);
        acc = value;
        acc.write();
      }
      /** Convenience function to read the latest value of a scalar process variable in a single call */
      template<typename TYPE>
      TYPE readScalar( const std::string &name ) {
        auto acc = getScalar<TYPE>(name);
        acc.readLatest();
        return acc;
      }
      /** Convenience function to read the latest value of an array process variable in a single call */
      template<typename TYPE>
      std::vector<TYPE> readArray( const std::string &name ) {
        auto acc = getArray<TYPE>(name);
        acc.readLatest();
        return acc;
      }

      boost::shared_ptr<ControlSystemPVManager> pvManager;
      // Cache (possible decorated) accessors to avoid the need to create accessors multiple times. This would not work
      // if the accessor is decorated, since the buffer would be lost and thus the current value could no longer be
      // obtained. This has to be done separately for scalar and array accessors and in dependence of the user type.
      // Since this is a cache and does not change the logical behaviour of the class, the maps are defined mutable.
      template<typename UserType>
      using ScalarMap = std::map<std::string, ChimeraTK::ScalarRegisterAccessor<UserType>>;
      mutable ChimeraTK::TemplateUserTypeMap<ScalarMap> scalarMap;
      using ArrayMap = std::map<std::string, ChimeraTK::OneDRegisterAccessor<UserType>>;
      mutable ChimeraTK::TemplateUserTypeMap<ArrayMap> arrayMap;
} /* namespace ChimeraTK */

#endif /* CHIMERATK_TEST_FACILITY */