Skip to content
Snippets Groups Projects
TestFacility.h 7.73 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/OneDRegisterAccessor.h>
#include <ChimeraTK/ScalarRegisterAccessor.h>
#include "DeviceModule.h"
  /** Helper class to facilitate tests of applications based on ApplicationCore */
  class TestFacility {
   public:
    /** 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. */
    explicit TestFacility(bool enableTestableMode = true) {
      auto pvManagers = createPVManager();
      pvManager = pvManagers.first;
      Application::getInstance().setPVManager(pvManagers.second);
      if(enableTestableMode) 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");
      Application::testableModeUnlock("waitDevicesToOpen");
      while(true) {
        boost::this_thread::yield();
        bool allOpened = true;
        for(auto dm : Application::getInstance().deviceModuleList) {
          if(!dm->device.isOpened()) allOpened = false;
        }
        if(allOpened) break;
      }
      Application::testableModeLock("waitDevicesToOpen");
    /** 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, and store it in cache
      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));
      }
      else {
        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, and store it in cache
      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));
      }
      else {
        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;
   protected:
    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;

    template<typename UserType>
    using ArrayMap = std::map<std::string, ChimeraTK::OneDRegisterAccessor<UserType>>;
    mutable ChimeraTK::TemplateUserTypeMap<ArrayMap> arrayMap;
  };
} /* namespace ChimeraTK */

#endif /* CHIMERATK_TEST_FACILITY */