Newer
Older
/*
* TestFacility.h
*
* Created on: Feb 17, 2017
* Author: Martin Hierholzer
*/
#ifndef CHIMERATK_TEST_FACILITY
#define CHIMERATK_TEST_FACILITY
Martin Christoph Hierholzer
committed
#include <boost/fusion/include/at_key.hpp>
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <ChimeraTK/ControlSystemAdapter/ControlSystemPVManager.h>
#include <mtca4u/ScalarRegisterAccessor.h>
#include <mtca4u/OneDRegisterAccessor.h>
#include "Application.h"
#include "TestDecoratorRegisterAccessor.h"
namespace ChimeraTK {
/** 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. */
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();
}
/** 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>
mtca4u::ScalarRegisterAccessor<T> getScalar(const mtca4u::RegisterPath &name) const {
Martin Christoph Hierholzer
committed
// 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
Martin Christoph Hierholzer
committed
auto pv = pvManager->getProcessArray<T>(name);
if(pv == nullptr) {
throw mtca4u::DeviceException("Process variable '"+name+"' does not exist.",
mtca4u::DeviceException::REGISTER_DOES_NOT_EXIST);
}
Martin Christoph Hierholzer
committed
// decorate with TestDecoratorRegisterAccessor if variable is not poll-type and store it in cache
if(!Application::getInstance().testableMode_isPollMode[pv->getUniqueId()]) {
auto deco = boost::make_shared<TestDecoratorRegisterAccessor<T>>(pv);
Application::getInstance().testableMode_names[pv->getUniqueId()] = "ControlSystem:"+name;
Martin Christoph Hierholzer
committed
boost::fusion::at_key<T>(scalarMap.table)[name].replace(mtca4u::ScalarRegisterAccessor<T>(deco));
}
else {
Martin Christoph Hierholzer
committed
boost::fusion::at_key<T>(scalarMap.table)[name].replace(mtca4u::ScalarRegisterAccessor<T>(pv));
Martin Christoph Hierholzer
committed
// 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>
mtca4u::OneDRegisterAccessor<T> getArray(const mtca4u::RegisterPath &name) const {
Martin Christoph Hierholzer
committed
// 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
Martin Christoph Hierholzer
committed
auto pv = pvManager->getProcessArray<T>(name);
if(pv == nullptr) {
throw mtca4u::DeviceException("Process variable '"+name+"' does not exist.",
mtca4u::DeviceException::REGISTER_DOES_NOT_EXIST);
}
Martin Christoph Hierholzer
committed
// decorate with TestDecoratorRegisterAccessor if variable is not poll-type and store it in cache
if(!Application::getInstance().testableMode_isPollMode[pv->getUniqueId()]) {
auto deco = boost::make_shared<TestDecoratorRegisterAccessor<T>>(pv);
Application::getInstance().testableMode_names[pv->getUniqueId()] = "ControlSystem:"+name;
Martin Christoph Hierholzer
committed
boost::fusion::at_key<T>(arrayMap.table)[name].replace(mtca4u::OneDRegisterAccessor<T>(deco));
}
else {
Martin Christoph Hierholzer
committed
boost::fusion::at_key<T>(arrayMap.table)[name].replace(mtca4u::OneDRegisterAccessor<T>(pv));
Martin Christoph Hierholzer
committed
// return the accessor as stored in the cache
return boost::fusion::at_key<T>(arrayMap.table)[name];
}
protected:
boost::shared_ptr<ControlSystemPVManager> pvManager;
Martin Christoph Hierholzer
committed
// 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, mtca4u::ScalarRegisterAccessor<UserType>>;
mutable mtca4u::TemplateUserTypeMap<ScalarMap> scalarMap;
template<typename UserType>
using ArrayMap = std::map<std::string, mtca4u::OneDRegisterAccessor<UserType>>;
mutable mtca4u::TemplateUserTypeMap<ArrayMap> arrayMap;
};
} /* namespace ChimeraTK */
#endif /* CHIMERATK_TEST_FACILITY */