Newer
Older
Martin Christoph Hierholzer
committed
/*
* testTestFacilities.cc
*
* Created on: Feb 20, 2017
* Author: Martin Hierholzer
*/
#include <future>
#include <chrono>
#define BOOST_TEST_MODULE testTestFacilities
#include <boost/test/included/unit_test.hpp>
#include <boost/test/test_case_template.hpp>
#include <boost/mpl/list.hpp>
Martin Christoph Hierholzer
committed
#include <mtca4u/ExperimentalFeatures.h>
Martin Christoph Hierholzer
committed
#include "Application.h"
#include "ScalarAccessor.h"
#include "ApplicationModule.h"
#include "DeviceModule.h"
#include "ControlSystemModule.h"
#include "TestDecoratorRegisterAccessor.h"
Martin Christoph Hierholzer
committed
#include "VariableGroup.h"
#include "TestFacility.h"
Martin Christoph Hierholzer
committed
using namespace boost::unit_test_framework;
namespace ctk = ChimeraTK;
#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); \
} \
}
// 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;
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
/* the BlockingReadTestModule blockingly reads its input in the main loop and writes the result to its output */
template<typename T>
struct BlockingReadTestModule : public ctk::ApplicationModule {
BlockingReadTestModule(ctk::EntityOwner *owner, const std::string &name) : ctk::ApplicationModule(owner,name) {}
ctk::ScalarPushInput<T> someInput{this, "someInput", "cm", "This is just some input for testing"};
ctk::ScalarOutput<T> someOutput{this, "someOutput", "cm", "Description"};
void mainLoop() {
while(true) {
someInput.read();
T val = someInput;
someOutput = val;
usleep(10000); // wait some extra time to make sure we are really blocking the test procedure thread
Martin Christoph Hierholzer
committed
someOutput.write();
}
}
};
/*********************************************************************************************************************/
/* the AsyncReadTestModule asynchronously reads its input in the main loop and writes the result to its output */
Martin Christoph Hierholzer
committed
template<typename T>
Martin Christoph Hierholzer
committed
struct AsyncReadTestModule : public ctk::ApplicationModule {
AsyncReadTestModule(ctk::EntityOwner *owner, const std::string &name) : ctk::ApplicationModule(owner,name) {}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
ctk::ScalarPushInput<T> someInput{this, "someInput", "cm", "This is just some input for testing"};
Martin Christoph Hierholzer
committed
ctk::ScalarOutput<T> someOutput{this, "someOutput", "cm", "Description"};
Martin Christoph Hierholzer
committed
void mainLoop() {
while(true) {
auto &future = someInput.readAsync();
future.wait();
T val = someInput;
someOutput = val;
usleep(10000); // wait some extra time to make sure we are really blocking the test procedure thread
Martin Christoph Hierholzer
committed
someOutput.write();
}
}
Martin Christoph Hierholzer
committed
};
Martin Christoph Hierholzer
committed
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/*********************************************************************************************************************/
/* the ReadAnyTestModule calls readAny on a bunch of inputs and outputs some information on the received data */
template<typename T>
struct ReadAnyTestModule : public ctk::ApplicationModule {
ReadAnyTestModule(ctk::EntityOwner *owner, const std::string &name) : ctk::ApplicationModule(owner,name) {}
struct Inputs : public ctk::VariableGroup {
using ctk::VariableGroup::VariableGroup;
ctk::ScalarPushInput<T> v1{this, "v1", "cm", "Input 1 for testing"};
ctk::ScalarPushInput<T> v2{this, "v2", "cm", "Input 2 for testing"};
ctk::ScalarPushInput<T> v3{this, "v3", "cm", "Input 3 for testing"};
ctk::ScalarPushInput<T> v4{this, "v4", "cm", "Input 4 for testing"};
};
Inputs inputs{this, "inputs"};
ctk::ScalarOutput<T> value{this, "value", "cm", "The last value received from any of the inputs"};
ctk::ScalarOutput<uint32_t> index{this, "index", "", "The index (1..4) of the input where the last value was received"};
void mainLoop() {
while(true) {
auto justRead = inputs.readAny();
if(inputs.v1.isSameRegister(justRead)) {
index = 1;
value = (T)inputs.v1;
}
else if(inputs.v2.isSameRegister(justRead)) {
index = 2;
value = (T)inputs.v2;
}
else if(inputs.v3.isSameRegister(justRead)) {
index = 3;
value = (T)inputs.v3;
}
else if(inputs.v4.isSameRegister(justRead)) {
index = 4;
value = (T)inputs.v4;
}
else {
index = 0;
value = 0;
}
usleep(10000); // wait some extra time to make sure we are really blocking the test procedure thread
index.write();
value.write();
}
}
};
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
/* dummy application */
template<typename T>
struct TestApplication : public ctk::Application {
Martin Christoph Hierholzer
committed
TestApplication() : Application("test application") {
ChimeraTK::ExperimentalFeatures::enable();
}
Martin Christoph Hierholzer
committed
~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
ctk::ControlSystemModule cs{""};
Martin Christoph Hierholzer
committed
BlockingReadTestModule<T> blockingReadTestModule{this,"blockingReadTestModule"};
AsyncReadTestModule<T> asyncReadTestModule{this,"asyncReadTestModule"};
Martin Christoph Hierholzer
committed
ReadAnyTestModule<T> readAnyTestModule{this,"readAnyTestModule"};
Martin Christoph Hierholzer
committed
};
/*********************************************************************************************************************/
/* test that no TestDecoratorRegisterAccessor is used if the testable mode is not enabled */
BOOST_AUTO_TEST_CASE_TEMPLATE( testNoDecorator, T, test_types ) {
std::cout << "*********************************************************************************************************************" << std::endl;
std::cout << "==> testNoDecorator<" << typeid(T).name() << ">" << std::endl;
TestApplication<T> app;
auto pvManagers = ctk::createPVManager();
app.setPVManager(pvManagers.second);
app.blockingReadTestModule >= app.cs["blocking"];
app.asyncReadTestModule >= app.cs["async"];
app.readAnyTestModule >= app.cs["readAny"];
Martin Christoph Hierholzer
committed
app.initialise();
app.run();
// check if we got the decorator for the input
auto hlinput = app.blockingReadTestModule.someInput.getHighLevelImplElement();
Martin Christoph Hierholzer
committed
BOOST_CHECK( boost::dynamic_pointer_cast<ctk::TestDecoratorRegisterAccessor<T>>(hlinput) == nullptr );
// check that we did not get the decorator for the output
auto hloutput = app.blockingReadTestModule.someOutput.getHighLevelImplElement();
Martin Christoph Hierholzer
committed
BOOST_CHECK( boost::dynamic_pointer_cast<ctk::TestDecoratorRegisterAccessor<T>>(hloutput) == nullptr );
}
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
/* test blocking read in test mode */
BOOST_AUTO_TEST_CASE_TEMPLATE( testBlockingRead, T, test_types ) {
std::cout << "*********************************************************************************************************************" << std::endl;
std::cout << "==> testBlockingRead<" << typeid(T).name() << ">" << std::endl;
TestApplication<T> app;
app.cs("input") >> app.blockingReadTestModule.someInput;
app.blockingReadTestModule.someOutput >> app.cs("output");
app.asyncReadTestModule >= app.cs["async"]; // avoid runtime warning
app.readAnyTestModule >= app.cs["readAny"]; // avoid runtime warning
ctk::TestFacility test;
auto pvInput = test.getScalar<T>("input");
auto pvOutput = test.getScalar<T>("output");
test.runApplication();
Martin Christoph Hierholzer
committed
// test blocking read when taking control in the test thread (note: the blocking read is executed in the app module!)
Martin Christoph Hierholzer
committed
for(int i=0; i<5; ++i) {
pvInput = 120+i;
pvInput.write();
Martin Christoph Hierholzer
committed
usleep(10000);
BOOST_CHECK(pvOutput.readNonBlocking() == false);
test.stepApplication();
CHECK_TIMEOUT(pvOutput.readNonBlocking() == true, 200);
int val = pvOutput;
Martin Christoph Hierholzer
committed
BOOST_CHECK(val == 120+i);
}
}
/*********************************************************************************************************************/
/* test async read in test mode */
BOOST_AUTO_TEST_CASE_TEMPLATE( testAsyncRead, T, test_types ) {
std::cout << "*********************************************************************************************************************" << std::endl;
std::cout << "==> testAsyncRead<" << typeid(T).name() << ">" << std::endl;
TestApplication<T> app;
app.cs("input") >> app.asyncReadTestModule.someInput;
app.asyncReadTestModule.someOutput >> app.cs("output");
app.blockingReadTestModule >= app.cs["blocking"]; // avoid runtime warning
app.readAnyTestModule >= app.cs["readAny"]; // avoid runtime warning
ctk::TestFacility test;
auto pvInput = test.getScalar<T>("input");
auto pvOutput = test.getScalar<T>("output");
test.runApplication();
Martin Christoph Hierholzer
committed
// test blocking read when taking control in the test thread
for(int i=0; i<5; ++i) {
pvInput = 120+i;
pvInput.write();
Martin Christoph Hierholzer
committed
usleep(10000);
BOOST_CHECK(pvOutput.readNonBlocking() == false);
test.stepApplication();
bool ret = pvOutput.readNonBlocking();
BOOST_CHECK(ret == true);
if(!ret) {
CHECK_TIMEOUT(pvOutput.readNonBlocking() == true, 10000);
}
int val = pvOutput;
Martin Christoph Hierholzer
committed
BOOST_CHECK(val == 120+i);
}
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
/* test testReadAny in test mode */
BOOST_AUTO_TEST_CASE_TEMPLATE( testReadAny, T, test_types ) {
std::cout << "*********************************************************************************************************************" << std::endl;
std::cout << "==> testReadAny<" << typeid(T).name() << ">" << std::endl;
TestApplication<T> app;
app.readAnyTestModule.inputs >= app.cs["input"];
app.readAnyTestModule.value >> app.cs("value");
app.readAnyTestModule.index >> app.cs("index");
app.blockingReadTestModule >= app.cs["blocking"]; // avoid runtime warning
app.asyncReadTestModule >= app.cs["async"]; // avoid runtime warning
ctk::TestFacility test;
auto value = test.getScalar<T>("value");
auto index = test.getScalar<uint32_t>("index");
auto v1 = test.getScalar<T>("input/v1");
auto v2 = test.getScalar<T>("input/v2");
auto v3 = test.getScalar<T>("input/v3");
auto v4 = test.getScalar<T>("input/v4");
test.runApplication();
Martin Christoph Hierholzer
committed
// check that we don't receive anything yet
usleep(10000);
BOOST_CHECK(value.readNonBlocking() == false);
BOOST_CHECK(index.readNonBlocking() == false);
Martin Christoph Hierholzer
committed
// send something to v4
v4 = 66;
v4.write();
Martin Christoph Hierholzer
committed
// check that we still don't receive anything yet
usleep(10000);
BOOST_CHECK(value.readNonBlocking() == false);
BOOST_CHECK(index.readNonBlocking() == false);
Martin Christoph Hierholzer
committed
// run the application and check that we got the expected result
test.stepApplication();
BOOST_CHECK(value.readNonBlocking() == true);
BOOST_CHECK(index.readNonBlocking() == true);
BOOST_CHECK(value == 66);
BOOST_CHECK(index == 4);
Martin Christoph Hierholzer
committed
v1 = 33;
v1.write();
// check that we still don't receive anything yet
usleep(10000);
BOOST_CHECK(value.readNonBlocking() == false);
BOOST_CHECK(index.readNonBlocking() == false);
// run the application and check that we got the expected result
test.stepApplication();
BOOST_CHECK(value.readNonBlocking() == true);
BOOST_CHECK(index.readNonBlocking() == true);
BOOST_CHECK(value == 33);
BOOST_CHECK(index == 1);
// send something to v1 again
v1 = 34;
v1.write();
// check that we still don't receive anything yet
usleep(10000);
BOOST_CHECK(value.readNonBlocking() == false);
BOOST_CHECK(index.readNonBlocking() == false);
// run the application and check that we got the expected result
test.stepApplication();
BOOST_CHECK(value.readNonBlocking() == true);
BOOST_CHECK(index.readNonBlocking() == true);
BOOST_CHECK(value == 34);
BOOST_CHECK(index == 1);
v3 = 40;
v3.write();
// check that we still don't receive anything yet
usleep(10000);
BOOST_CHECK(value.readNonBlocking() == false);
BOOST_CHECK(index.readNonBlocking() == false);
// run the application and check that we got the expected result
test.stepApplication();
BOOST_CHECK(value.readNonBlocking() == true);
BOOST_CHECK(index.readNonBlocking() == true);
BOOST_CHECK(value == 40);
BOOST_CHECK(index == 3);
v2 = 50;
v2.write();
// check that we still don't receive anything yet
usleep(10000);
BOOST_CHECK(value.readNonBlocking() == false);
BOOST_CHECK(index.readNonBlocking() == false);
// run the application and check that we got the expected result
test.stepApplication();
BOOST_CHECK(value.readNonBlocking() == true);
BOOST_CHECK(index.readNonBlocking() == true);
BOOST_CHECK(value == 50);
BOOST_CHECK(index == 2);
// check that stepApplication() throws an exception if no input data is available
try {
test.stepApplication();
BOOST_ERROR("IllegalParameter exception expected.");
}
catch(ctk::ApplicationExceptionWithID<ctk::ApplicationExceptionID::illegalParameter>) {
}
// check that we still don't receive anything anymore
usleep(10000);
BOOST_CHECK(value.readNonBlocking() == false);
BOOST_CHECK(index.readNonBlocking() == false);
// send something to v1 a 3rd time
v1 = 35;
v1.write();
// check that we still don't receive anything yet
usleep(10000);
BOOST_CHECK(value.readNonBlocking() == false);
BOOST_CHECK(index.readNonBlocking() == false);
// run the application and check that we got the expected result
test.stepApplication();
BOOST_CHECK(value.readNonBlocking() == true);
BOOST_CHECK(index.readNonBlocking() == true);
BOOST_CHECK(value == 35);
BOOST_CHECK(index == 1);
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
/* test the interplay of multiple chained modules and their threads in test mode */
BOOST_AUTO_TEST_CASE_TEMPLATE( testChainedModules, T, test_types ) {
std::cout << "*********************************************************************************************************************" << std::endl;
std::cout << "==> testChainedModules<" << typeid(T).name() << ">" << std::endl;
TestApplication<T> app;
// put everything we got into one chain
app.readAnyTestModule.inputs >= app.cs["input"];
app.readAnyTestModule.value >> app.blockingReadTestModule.someInput;
app.blockingReadTestModule.someOutput >> app.asyncReadTestModule.someInput;
app.asyncReadTestModule.someOutput >> app.cs("value");
app.readAnyTestModule.index >> app.cs("index");
ctk::TestFacility test;
auto value = test.getScalar<T>("value");
auto index = test.getScalar<uint32_t>("index");
auto v1 = test.getScalar<T>("input/v1");
auto v2 = test.getScalar<T>("input/v2");
auto v3 = test.getScalar<T>("input/v3");
auto v4 = test.getScalar<T>("input/v4");
test.runApplication();
// check that we don't receive anything yet
usleep(10000);
BOOST_CHECK(value.readNonBlocking() == false);
BOOST_CHECK(index.readNonBlocking() == false);
// send something to v2
v2 = 11;
v2.write();
// check that we still don't receive anything yet
usleep(10000);
BOOST_CHECK(value.readNonBlocking() == false);
BOOST_CHECK(index.readNonBlocking() == false);
// run the application and check that we got the expected result
app.stepApplication();
BOOST_CHECK(value.readNonBlocking() == true);
BOOST_CHECK(index.readNonBlocking() == true);
BOOST_CHECK(value == 11);
BOOST_CHECK(index == 2);
// send something to v3
v3 = 12;
v3.write();
// check that we still don't receive anything yet
usleep(10000);
BOOST_CHECK(value.readNonBlocking() == false);
BOOST_CHECK(index.readNonBlocking() == false);
// run the application and check that we got the expected result
app.stepApplication();
BOOST_CHECK(value.readNonBlocking() == true);
BOOST_CHECK(index.readNonBlocking() == true);
BOOST_CHECK(value == 12);
BOOST_CHECK(index == 3);
// send something to v3 again
v3 = 13;
v3.write();
// check that we still don't receive anything yet
usleep(10000);
BOOST_CHECK(value.readNonBlocking() == false);
BOOST_CHECK(index.readNonBlocking() == false);
// run the application and check that we got the expected result
app.stepApplication();
BOOST_CHECK(value.readNonBlocking() == true);
BOOST_CHECK(index.readNonBlocking() == true);
BOOST_CHECK(value == 13);
BOOST_CHECK(index == 3);
// check that stepApplication() throws an exception if no input data is available
try {
test.stepApplication();
BOOST_ERROR("IllegalParameter exception expected.");
}
catch(ctk::ApplicationExceptionWithID<ctk::ApplicationExceptionID::illegalParameter>) {
}
// check that we still don't receive anything anymore
BOOST_CHECK(value.readNonBlocking() == false);
BOOST_CHECK(index.readNonBlocking() == false);