Skip to content
Snippets Groups Projects
Commit 59ad2613 authored by Jens Georg's avatar Jens Georg Committed by Martin Christoph Hierholzer
Browse files

Fix testTrigger

parent b7237dcd
No related branches found
No related tags found
No related merge requests found
......@@ -54,7 +54,7 @@ namespace ChimeraTK {
if(not neededFeeder) {
// Only add CS consumer if we did not previously add CS feeder, we will add one or the other, but never both
debug(" No CS feeder in network, creating additional ControlSystem consumer");
debug(" Network has a non-CS feeder, can create additional ControlSystem consumer");
net.consumers.push_back(VariableNetworkNode(
proxy.getFullyQualifiedPath(), {VariableDirection::consuming, false}, *net.valueType, net.valueLength));
}
......@@ -94,12 +94,12 @@ namespace ChimeraTK {
debug(" Creating fixed implementation for feeder '", net.feeder.getName(), "'...");
if(net.consumers.size() == 1 && !net.useExternalTrigger) {
debug(" One consumer, setting up direct connection without external trigger.");
debug(" One consumer without external trigger, creating direct connection");
makeDirectConnectionForFeederWithImplementation(net);
}
else {
// More than one consuming node
debug(" More than one consuming node, setting up FanOut");
debug(" More than one consuming node or having external trigger, setting up FanOut");
makeFanOutConnectionForFeederWithImplementation(net, device, trigger);
}
}
......@@ -623,8 +623,8 @@ namespace ChimeraTK {
});
break;
case NodeType::TriggerReceiver:
// This cannot happen. In a network Application -> TriggerReceiver, the trigger
// collection code will always add a CS consumer, so there is never a 1:1 connection
// This cannot happen. In a network Application -> TriggerReceiver, the connectNetwork()
// code will always add a CS consumer, so there is never a 1:1 connection
debug(" Node type is TriggerReceiver");
assert(false);
break;
......
......@@ -176,8 +176,3 @@ struct TestApplication6 : public ctk::Application {
void mainLoop() override {}
} testModule2{this, ".", ""};
};
BOOST_AUTO_TEST_CASE_TEMPLATE(testNoElements, T, TestTypes) {
TestApplication6<T> app;
BOOST_CHECK_THROW(ctk::TestFacility tf(app, false), ctk::logic_error);
}
// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "VoidAccessor.h"
#include <chrono>
#include <future>
......@@ -26,340 +28,503 @@
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;
// Application that has one polling consumer for a polling provider
// It should work without any trigger
struct TestApp1 : ctk::Application {
TestApp1() : ctk::Application("testApp1") {}
~TestApp1() override { shutdown(); }
/**********************************************************************************************************************/
struct : ctk::ApplicationModule {
using ctk::ApplicationModule::ApplicationModule;
class TestTransferGroupDummy : public ChimeraTK::DummyBackend {
public:
TestTransferGroupDummy(std::string mapFileName) : DummyBackend(mapFileName) {}
ctk::ScalarPollInput<int> readBack{this, "/MyModule/readBack", "unit", "description"};
// This is just here so that we do not need a trigger - otherwise it would be connected to a pushing CS consumer
// automatically which would require a trigger
ctk::ScalarPollInput<int> tests{this, "/Deeper/hierarchies/need/tests", "unit", "description"};
ctk::VoidInput finger{this, "/finger", "", ""};
void mainLoop() override {
while(true) {
readAll();
}
}
} someModule{this, ".", ""};
ctk::SetDMapFilePath path{"test.dmap"};
ctk::DeviceModule dev;
};
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()));
BOOST_AUTO_TEST_CASE(testDev2AppWithPollTrigger) {
// TestApp1 should work without specifying any trigger
{
TestApp1 app;
app.dev = {&app, "Dummy0"};
ChimeraTK::TestFacility tf{app};
auto finger = tf.getVoid("/finger");
auto rb = tf.getScalar<int>("/MyModule/readBack");
tf.runApplication();
ctk::Device dev("Dummy0");
dev.open();
dev.write("MyModule/actuator", 1);
BOOST_TEST(rb.readNonBlocking() == false);
finger.write();
tf.stepApplication();
BOOST_TEST(rb.readNonBlocking() == true);
BOOST_TEST(rb == 1);
dev.write("MyModule/actuator", 10);
finger.write();
tf.stepApplication();
BOOST_TEST(rb.readNonBlocking() == true);
BOOST_TEST(rb == 10);
}
void read(uint64_t bar, uint64_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);
// TestApp1 should also work with any trigger, but the trigger should be ignored
{
TestApp1 app;
app.dev = {&app, "Dummy0", "/cs/tick"};
ChimeraTK::TestFacility tf{app};
auto tick = tf.getVoid("/cs/tick");
auto finger = tf.getVoid("/finger");
auto rb = tf.getScalar<int>("/MyModule/readBack");
tf.runApplication();
ctk::Device dev("Dummy0");
dev.open();
dev.write("MyModule/actuator", 2);
BOOST_TEST(rb.readNonBlocking() == false);
finger.write();
tf.stepApplication();
BOOST_TEST(rb.readNonBlocking() == true);
BOOST_TEST(rb == 2);
// Trigger device trigger - values should not change
tick.write();
tf.stepApplication();
BOOST_TEST(rb.readNonBlocking() == false);
BOOST_TEST(rb == 2);
dev.write("MyModule/actuator", 20);
// Trigger read-out of poll variables in main loop
finger.write();
tf.stepApplication();
BOOST_TEST(rb.readNonBlocking() == true);
BOOST_TEST(rb == 20);
// Trigger device trigger - values should not change
tick.write();
tf.stepApplication();
BOOST_TEST(rb.readNonBlocking() == false);
BOOST_TEST(rb == 20);
}
}
std::atomic<size_t> numberOfTransfers{0};
std::atomic<uint64_t> last_bar;
std::atomic<uint64_t> last_address;
std::atomic<size_t> last_sizeInBytes;
/**********************************************************************************************************************/
struct TestApp2 : ctk::Application {
TestApp2() : ctk::Application("testApp2") {}
~TestApp2() override { shutdown(); }
struct : ctk::ApplicationModule {
using ctk::ApplicationModule::ApplicationModule;
ctk::ScalarPushInput<int> readBack{this, "/MyModule/readBack", "unit", "description"};
void mainLoop() override {
while(true) {
readAll();
}
}
} someModule{this, ".", ""};
ctk::SetDMapFilePath path{"test.dmap"};
ctk::DeviceModule dev;
};
/*********************************************************************************************************************/
/* the ApplicationModule for the test is a template of the user type */
// Device that requires trigger, the trigger is 1:1 put into the CS
BOOST_AUTO_TEST_CASE(testDev2AppWithCsDirectTrigger) {
// TestApp2 should not work without specifying any trigger
{
TestApp2 app;
app.dev = {&app, "Dummy0"};
BOOST_CHECK_THROW(ChimeraTK::TestFacility(app, true), ChimeraTK::logic_error);
}
template<typename T>
struct TestModule : public ctk::ApplicationModule {
TestModule(ctk::ModuleGroup* owner, const std::string& name, const std::string& description,
const std::unordered_set<std::string>& tags = {})
: ApplicationModule(owner, name, description, tags), mainLoopStarted(2) {}
// TestApp2 also works with a trigger. If the trigger is triggered, no data transfer should happen
{
TestApp2 app;
app.dev = {&app, "Dummy0", "/cs/trigger"};
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"};
ChimeraTK::TestFacility tf{app, true};
auto tick = tf.getVoid("/cs/trigger");
auto rb = tf.getScalar<int>("/MyModule/readBack");
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"};
tf.runApplication();
ctk::ScalarOutput<T> theTrigger{this, "theTrigger", "MV/m", "Description"};
ctk::ScalarOutput<T> feedingToDevice{this, "feedingToDevice", "MV/m", "Description"};
ctk::Device dev("Dummy0");
dev.open();
dev.write("MyModule/actuator", 1);
// We do not use testable mode for this test, so we need this barrier to synchronise to the beginning of the
// mainLoop(). This is required since the mainLoopWrapper accesses the module variables before the start of the
// mainLoop.
// execute this right after the Application::run():
// app.testModule.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
boost::barrier mainLoopStarted;
tick.write();
tf.stepApplication();
void prepare() override {
incrementDataFaultCounter(); // force data to be flagged as faulty
feedingToDevice = 13; // the initial value
writeAll();
decrementDataFaultCounter(); // data validity depends on inputs
BOOST_TEST(rb.readNonBlocking() == true);
BOOST_TEST(rb == 1);
dev.write("MyModule/actuator", 12);
BOOST_TEST(rb.readNonBlocking() == false);
BOOST_TEST(rb == 1);
tick.write();
tf.stepApplication();
BOOST_TEST(rb.readNonBlocking() == true);
BOOST_TEST(rb == 12);
}
}
void mainLoop() override { mainLoopStarted.wait(); }
/**********************************************************************************************************************/
struct TestApp3 : ctk::Application {
TestApp3() : ctk::Application("testApp3") {}
~TestApp3() override { shutdown(); }
struct : ctk::ApplicationModule {
using ctk::ApplicationModule::ApplicationModule;
ctk::VoidInput tick{this, "/cs/trigger", "unit", "description"};
ctk::ScalarOutput<int> tock{this, "/tock", "", ""};
void mainLoop() override {
tock = 0;
while(true) {
tock.write();
tock++;
readAll();
}
}
} tock{this, ".", ""};
struct : ctk::ApplicationModule {
using ctk::ApplicationModule::ApplicationModule;
ctk::ScalarPushInput<int> readBack{this, "/MyModule/readBack", "unit", "description"};
ctk::ScalarPollInput<int> tests{this, "/Deeper/hierarchies/need/tests", "unit", "description"};
void mainLoop() override {
while(true) {
readAll();
}
}
} someModule{this, ".", ""};
ctk::SetDMapFilePath path{"test.dmap"};
ctk::DeviceModule dev{this, "Dummy0", "/cs/trigger"};
};
/*********************************************************************************************************************/
/* dummy application */
// Device that requires trigger, the trigger is distributed in the Application as well
BOOST_AUTO_TEST_CASE(testDev2AppWithCsDistributedTrigger) {
TestApp3 app;
template<typename T>
struct TestApplication : public ctk::Application {
TestApplication() : Application("testSuite") {
ChimeraTK::BackendFactory::getInstance().registerBackendType(
"TestTransferGroupDummy", "", &TestTransferGroupDummy::createInstance, CHIMERATK_DEVICEACCESS_VERSION);
}
~TestApplication() { shutdown(); }
ChimeraTK::TestFacility tf{app, true};
auto tick = tf.getVoid("/cs/trigger");
auto tock = tf.getScalar<int>("/tock");
auto rb = tf.getScalar<int>("/MyModule/readBack");
TestModule<T> testModule{this, "testModule", "The test module"};
ctk::DeviceModule dev{this, "Dummy0"};
ctk::DeviceModule dev2{this, dummySdm};
};
tf.runApplication();
/*********************************************************************************************************************/
/* test trigger by app variable when connecting a polled device register to an
* app variable */
ctk::Device dev("Dummy0");
dev.open();
dev.write("MyModule/actuator", 1);
tick.write();
tf.stepApplication();
BOOST_TEST(tock.readNonBlocking() == true);
BOOST_TEST(tock == 1);
BOOST_TEST(rb.readNonBlocking() == true);
BOOST_TEST(rb == 1);
dev.write("MyModule/actuator", 12);
BOOST_TEST(tock.readNonBlocking() == false);
BOOST_TEST(tock == 1);
BOOST_TEST(rb.readNonBlocking() == false);
BOOST_TEST(rb == 1);
tick.write();
tf.stepApplication();
BOOST_TEST(tock.readNonBlocking() == true);
BOOST_TEST(tock == 2);
BOOST_TEST(rb.readNonBlocking() == true);
BOOST_TEST(rb == 12);
}
BOOST_AUTO_TEST_CASE_TEMPLATE(testTriggerDevToApp, T, test_types) {
std::cout << "***************************************************************"
"******************************************************"
<< std::endl;
std::cout << "==> testTriggerDevToApp<" << typeid(T).name() << ">" << std::endl;
/**********************************************************************************************************************/
ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap");
struct TestApp4 : ctk::Application {
TestApp4() : ctk::Application("testApp4") {}
~TestApp4() override { shutdown(); }
TestApplication<T> app;
auto pvManagers = ctk::createPVManager();
app.setPVManager(pvManagers.second);
struct : ctk::ApplicationModule {
using ctk::ApplicationModule::ApplicationModule;
// app.testModule.feedingToDevice >> app.dev["MyModule"]("actuator");
ctk::ScalarPushInput<float> singed32{this, "/Device/signed32", "unit", "description"};
// app.dev["MyModule"]("readBack")[app.testModule.theTrigger] >> app.testModule.consumingPush;
app.initialise();
void mainLoop() override {
while(true) {
readAll();
}
}
} someOtherModule{this, ".", ""};
app.run();
app.testModule.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
struct : ctk::ApplicationModule {
using ctk::ApplicationModule::ApplicationModule;
// check for initial value. Should be there when entering the main loop.
BOOST_CHECK_EQUAL(static_cast<T>(app.testModule.consumingPush), 13);
ctk::ScalarPushInput<int> readBack{this, "/MyModule/readBack", "unit", "description"};
ctk::ScalarPollInput<int> tests{this, "/Deeper/hierarchies/need/tests", "unit", "description"};
// single theaded test
app.testModule.feedingToDevice = 42;
app.testModule.feedingToDevice.write();
BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false);
BOOST_CHECK_EQUAL(static_cast<T>(app.testModule.consumingPush), 13);
app.testModule.theTrigger.write();
app.testModule.consumingPush.read();
BOOST_CHECK(app.testModule.consumingPush == 42);
void mainLoop() override {
while(true) {
readAll();
}
}
} someModule{this, ".", ""};
ctk::SetDMapFilePath path{"test.dmap"};
ctk::DeviceModule dev{this, "Dummy0", "/cs/trigger"};
ctk::DeviceModule dev2{this, "Dummy1Mapped", "/cs/trigger"};
};
// Two devices using the same trigger
BOOST_AUTO_TEST_CASE(testDev2App1Trigger2Devices) {
TestApp4 app;
ChimeraTK::TestFacility tf{app, true};
auto tick = tf.getVoid("/cs/trigger");
auto f = tf.getScalar<float>("/Device/signed32");
auto rb = tf.getScalar<int>("/MyModule/readBack");
// 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);
ctk::Device dev("Dummy0");
dev.open();
BOOST_CHECK(app.testModule.consumingPush == 42);
ctk::Device dev2("Dummy1");
dev2.open();
dev2.write("FixedPoint/value", 12.4);
// 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);
tf.runApplication();
// send trigger
app.testModule.theTrigger.write();
dev.write("MyModule/actuator", 1);
// 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);
BOOST_TEST(f.readNonBlocking() == false);
BOOST_TEST(rb.readNonBlocking() == false);
tick.write();
tf.stepApplication();
BOOST_TEST(f.readNonBlocking() == true);
BOOST_TEST(rb.readNonBlocking() == true);
BOOST_TEST((f - 12.4) < 0.01);
BOOST_TEST(rb == 1);
dev.write("MyModule/actuator", 2);
dev2.write("FixedPoint/value", 24.8);
BOOST_TEST(f.readNonBlocking() == false);
BOOST_TEST(rb.readNonBlocking() == false);
tick.write();
tf.stepApplication();
BOOST_TEST(f.readNonBlocking() == true);
BOOST_TEST(rb.readNonBlocking() == true);
BOOST_TEST((f - 24.8) < 0.001);
BOOST_TEST(rb == 2);
}
/*********************************************************************************************************************/
/* test trigger by app variable when connecting a polled device register to
* control system variable */
#if 0
BOOST_AUTO_TEST_CASE_TEMPLATE(testTriggerDevToCS, T, test_types) {
std::cout << "***************************************************************"
"******************************************************"
<< std::endl;
std::cout << "==> testTriggerDevToCS<" << typeid(T).name() << ">" << std::endl;
/**********************************************************************************************************************/
ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap");
struct TestApp5 : ctk::Application {
TestApp5() : ctk::Application("testApp5") { }
~TestApp5() override { shutdown(); }
TestApplication<T> app;
struct : ctk::ApplicationModule {
using ctk::ApplicationModule::ApplicationModule;
auto pvManagers = ctk::createPVManager();
app.setPVManager(pvManagers.second);
ctk::VoidInput finger{this, "/finger", "", ""};
ctk::VoidOutput trigger{this, "/trigger", "", ""};
app.dev("/MyModule/readBack", typeid(T), 1)[app.testModule.theTrigger] >> app.cs("myCSVar");
void mainLoop() override {
while(true) {
readAll();
trigger.write();
}
}
} someModule{this, ".", ""};
ctk::Device dev("Dummy0");
dev.open();
dev.write("MyModule/actuator", 1); // write initial value
ctk::SetDMapFilePath path{"test.dmap"};
ctk::DeviceModule dev;
};
app.initialise();
app.run();
app.testModule.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
BOOST_AUTO_TEST_CASE(testDev2CSCsTrigger) {
TestApp5 app;
app.dev = {&app, "Dummy0", "/cs/trigger"};
auto myCSVar = pvManagers.first->getProcessArray<T>("/myCSVar");
ChimeraTK::TestFacility tf{app, true};
auto tick = tf.getVoid("/cs/trigger");
auto rb = tf.getScalar<int>("/MyModule/readBack");
// single theaded test only, since the receiving process scalar does not support blocking
myCSVar->read(); // read initial value
BOOST_CHECK_EQUAL(myCSVar->accessData(0), 1);
BOOST_CHECK(myCSVar->readNonBlocking() == false);
dev.write("MyModule/actuator", 42);
usleep(10000);
BOOST_CHECK(myCSVar->readNonBlocking() == false);
app.testModule.setCurrentVersionNumber({});
app.testModule.theTrigger.write();
myCSVar->read();
BOOST_CHECK_EQUAL(myCSVar->accessData(0), 42);
tf.runApplication();
BOOST_CHECK(myCSVar->readNonBlocking() == false);
dev.write("MyModule/actuator", 120);
usleep(10000);
BOOST_CHECK(myCSVar->readNonBlocking() == false);
app.testModule.theTrigger.write();
myCSVar->read();
BOOST_CHECK_EQUAL(myCSVar->accessData(0), 120);
ctk::Device dev("Dummy0");
dev.open();
dev.write("MyModule/actuator", 1);
BOOST_CHECK(myCSVar->readNonBlocking() == false);
}
tick.write();
tf.stepApplication();
/*********************************************************************************************************************/
/* test trigger by app variable when connecting a polled device register to
* control system variable */
BOOST_TEST(rb.readNonBlocking() == true);
BOOST_TEST(rb == 1);
BOOST_AUTO_TEST_CASE_TEMPLATE(testTriggerByCS, T, test_types) {
std::cout << "***************************************************************"
"******************************************************"
<< std::endl;
std::cout << "==> testTriggerByCS<" << typeid(T).name() << ">" << std::endl;
dev.write("MyModule/actuator", 12);
BOOST_TEST(rb.readNonBlocking() == false);
BOOST_TEST(rb == 1);
ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap");
tick.write();
tf.stepApplication();
BOOST_TEST(rb.readNonBlocking() == true);
BOOST_TEST(rb == 12);
}
TestApplication<T> app;
BOOST_AUTO_TEST_CASE(testDev2CSAppTrigger) {
TestApp5 app;
app.dev = {&app, "Dummy0", "/trigger"};
auto pvManagers = ctk::createPVManager();
app.setPVManager(pvManagers.second);
ChimeraTK::TestFacility tf{app, true};
auto tick = tf.getVoid("/finger");
auto rb = tf.getScalar<int>("/MyModule/readBack");
app.dev("/MyModule/readBack", typeid(T), 1)[app.cs("theTrigger", typeid(T), 1)] >> app.cs("myCSVar");
tf.runApplication();
ctk::Device dev("Dummy0");
dev.open();
dev.write("MyModule/actuator", 1); // write initial value
dev.write("MyModule/actuator", 1);
app.initialise();
app.run();
app.testModule.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
tick.write();
tf.stepApplication();
BOOST_TEST(rb.readNonBlocking() == true);
BOOST_TEST(rb == 1);
dev.write("MyModule/actuator", 12);
BOOST_TEST(rb.readNonBlocking() == false);
BOOST_TEST(rb == 1);
auto myCSVar = pvManagers.first->getProcessArray<T>("/myCSVar");
auto theTrigger = pvManagers.first->getProcessArray<T>("/theTrigger");
// Need to send the trigger once, since ApplicationCore expects all CS variables to be written once by the
// ControlSystemAdapter. We do not use the TestFacility here, so we have to do it ourself.
theTrigger->write();
// single theaded test only, since the receiving process scalar does not support blocking
myCSVar->read(); // read initial value
BOOST_CHECK_EQUAL(myCSVar->accessData(0), 1);
BOOST_CHECK(myCSVar->readNonBlocking() == false);
dev.write("MyModule/actuator", 42);
usleep(10000);
BOOST_CHECK(myCSVar->readNonBlocking() == false);
myCSVar->accessData(0) = 0;
theTrigger->write();
myCSVar->read();
BOOST_CHECK_EQUAL(myCSVar->accessData(0), 42);
BOOST_CHECK(myCSVar->readNonBlocking() == false);
dev.write("MyModule/actuator", 120);
usleep(10000);
BOOST_CHECK(myCSVar->readNonBlocking() == false);
myCSVar->accessData(0) = 0;
theTrigger->write();
myCSVar->read();
BOOST_CHECK_EQUAL(myCSVar->accessData(0), 120);
BOOST_CHECK(myCSVar->readNonBlocking() == false);
tick.write();
tf.stepApplication();
BOOST_TEST(rb.readNonBlocking() == true);
BOOST_TEST(rb == 12);
}
constexpr char dummySdm[] = "(TestTransferGroupDummy?map=test_readonly.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;
/**********************************************************************************************************************/
class TestTransferGroupDummy : public ChimeraTK::DummyBackend {
public:
TestTransferGroupDummy(std::string mapFileName) : DummyBackend(mapFileName) {}
static boost::shared_ptr<DeviceBackend> createInstance(std::string, std::map<std::string, std::string> parameters) {
return boost::shared_ptr<DeviceBackend>(new TestTransferGroupDummy(parameters["map"]));
}
void read(uint64_t bar, uint64_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<uint64_t> last_bar;
std::atomic<uint64_t> last_address;
std::atomic<size_t> last_sizeInBytes;
};
/*********************************************************************************************************************/
/* test trigger by app variable through FanOut (i.e. trigger variable is also used else where) */
/* the ApplicationModule for the test is a template of the user type */
BOOST_AUTO_TEST_CASE_TEMPLATE(testTriggerByCSFanOut, T, test_types) {
std::cout << "***************************************************************"
"******************************************************"
<< std::endl;
std::cout << "==> testTriggerByCS<" << typeid(T).name() << ">" << std::endl;
struct TestModule : public ctk::ApplicationModule {
TestModule(ctk::ModuleGroup* owner, const std::string& name, const std::string& description,
const std::unordered_set<std::string>& tags = {})
: ApplicationModule(owner, name, description, tags), mainLoopStarted(2) {}
ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap");
ctk::ScalarPushInput<int> consumingPush{this, "/REG1", "MV/m", "Description"};
ctk::ScalarPushInput<int> consumingPush2{this, "/REG2", "MV/m", "Description"};
ctk::ScalarPushInput<int> consumingPush3{this, "/REG3", "MV/m", "Description"};
TestApplication<T> app;
ctk::ScalarOutput<int> theTrigger{this, "theTrigger", "MV/m", "Description"};
auto pvManagers = ctk::createPVManager();
app.setPVManager(pvManagers.second);
// We do not use testable mode for this test, so we need this barrier to synchronise to the beginning of the
// mainLoop(). This is required since the mainLoopWrapper accesses the module variables before the start of the
// mainLoop.
// execute this right after the Application::run():
// app.testModule.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
boost::barrier mainLoopStarted;
auto trigger = app.cs("theTrigger", typeid(T), 1);
trigger >> app.cs("theTriggerCopied");
app.dev("/MyModule/readBack", typeid(T), 1)[trigger] >> app.cs("myCSVar");
void prepare() override {
incrementDataFaultCounter(); // force data to be flagged as faulty
writeAll();
decrementDataFaultCounter(); // data validity depends on inputs
}
ctk::Device dev("Dummy0");
dev.open();
dev.write("MyModule/actuator", 1); // write initial value
void mainLoop() override {
std::cout << "Start of main loop" << std::endl;
mainLoopStarted.wait();
std::cout << "End of main loop" << std::endl;
}
};
app.initialise();
/*********************************************************************************************************************/
/* dummy application */
app.run();
app.testModule.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
struct TestApplication : public ctk::Application {
TestApplication() : Application("testSuite") {
ChimeraTK::BackendFactory::getInstance().registerBackendType(
"TestTransferGroupDummy", &TestTransferGroupDummy::createInstance);
dev2 = {this, dummySdm, "/testModule/theTrigger"};
}
~TestApplication() override { shutdown(); }
TestModule testModule{this, "testModule", "The test module"};
ctk::DeviceModule dev2;
};
auto myCSVar = pvManagers.first->getProcessArray<T>("/myCSVar");
auto theTrigger = pvManagers.first->getProcessArray<T>("/theTrigger");
auto theTriggerCopied = pvManagers.first->getProcessArray<T>("/theTriggerCopied");
// Need to send the trigger once, since ApplicationCore expects all CS variables to be written once by the
// ControlSystemAdapter. We do not use the TestFacility here, so we have to do it ourself.
theTrigger->accessData(0) = 2;
theTrigger->write();
theTriggerCopied->read();
BOOST_CHECK_EQUAL(theTriggerCopied->accessData(0), 2);
// single theaded test only, since the receiving process scalar does not support blocking
myCSVar->read(); // read initial value
BOOST_CHECK_EQUAL(myCSVar->accessData(0), 1);
BOOST_CHECK(myCSVar->readNonBlocking() == false);
dev.write("MyModule/actuator", 42);
usleep(10000);
BOOST_CHECK(myCSVar->readNonBlocking() == false);
myCSVar->accessData(0) = 0;
theTrigger->accessData(0) = 3;
theTrigger->write();
myCSVar->read();
BOOST_CHECK_EQUAL(myCSVar->accessData(0), 42);
theTriggerCopied->read();
BOOST_CHECK_EQUAL(theTriggerCopied->accessData(0), 3);
BOOST_CHECK(myCSVar->readNonBlocking() == false);
dev.write("MyModule/actuator", 120);
usleep(10000);
BOOST_CHECK(myCSVar->readNonBlocking() == false);
myCSVar->accessData(0) = 0;
theTrigger->accessData(0) = 4;
theTrigger->write();
myCSVar->read();
BOOST_CHECK_EQUAL(myCSVar->accessData(0), 120);
theTriggerCopied->read();
BOOST_CHECK_EQUAL(theTriggerCopied->accessData(0), 4);
BOOST_CHECK(myCSVar->readNonBlocking() == false);
BOOST_CHECK(theTriggerCopied->readNonBlocking() == false);
}
#endif
/*********************************************************************************************************************/
/* test that multiple variables triggered by the same source are put into the
* same TransferGroup */
BOOST_AUTO_TEST_CASE_TEMPLATE(testTriggerTransferGroup, T, test_types) {
BOOST_AUTO_TEST_CASE(testTriggerTransferGroup) {
std::cout << "***************************************************************"
"******************************************************"
<< std::endl;
std::cout << "==> testTriggerTransferGroup<" << typeid(T).name() << ">" << std::endl;
std::cout << "==> testTriggerTransferGroup" << std::endl;
ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap");
TestApplication<T> app;
TestApplication app;
auto pvManagers = ctk::createPVManager();
app.setPVManager(pvManagers.second);
......@@ -369,9 +534,6 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(testTriggerTransferGroup, T, test_types) {
ChimeraTK::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();
app.testModule.mainLoopStarted.wait(); // make sure the module's mainLoop() is entered
......@@ -380,19 +542,22 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(testTriggerTransferGroup, T, test_types) {
app.testModule.consumingPush = 0;
app.testModule.consumingPush2 = 0;
app.testModule.consumingPush3 = 0;
dev.write("/REG1", 11);
dev.write("/REG2", 22);
dev.write("/REG3", 33);
dev.write("/REG1.DUMMY_WRITEABLE", 11);
dev.write("/REG2.DUMMY_WRITEABLE", 22);
dev.write("/REG3.DUMMY_WRITEABLE", 33);
// from the inital value transfer
// from the initial value transfer
CHECK_TIMEOUT(backend->numberOfTransfers == 1, 10000);
// trigger the transfer
app.testModule.theTrigger.write();
CHECK_TIMEOUT(backend->numberOfTransfers == 2, 10000);
BOOST_CHECK(backend->last_bar == 0);
BOOST_CHECK(backend->last_address == 0);
BOOST_CHECK(backend->last_sizeInBytes == 12);
BOOST_TEST(backend->last_bar == 0);
BOOST_TEST(backend->last_address == 0);
// We only explicitly connect the three registers in the app, but the connection code will also connect the other
// registers into the CS, hence we need to check for the full size
BOOST_TEST(backend->last_sizeInBytes == 32);
// check result
app.testModule.consumingPush.read();
......@@ -403,16 +568,19 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(testTriggerTransferGroup, T, test_types) {
BOOST_CHECK_EQUAL(app.testModule.consumingPush3, 33);
// prepare a second transfer
dev.write("/REG1", 12);
dev.write("/REG2", 23);
dev.write("/REG3", 34);
dev.write("/REG1.DUMMY_WRITEABLE", 12);
dev.write("/REG2.DUMMY_WRITEABLE", 23);
dev.write("/REG3.DUMMY_WRITEABLE", 34);
// trigger the transfer
app.testModule.theTrigger.write();
CHECK_TIMEOUT(backend->numberOfTransfers == 3, 10000);
BOOST_CHECK(backend->last_bar == 0);
BOOST_CHECK(backend->last_address == 0);
BOOST_CHECK(backend->last_sizeInBytes == 12);
BOOST_TEST(backend->last_bar == 0);
BOOST_TEST(backend->last_address == 0);
// We only explicitly connect the three registers in the app, but the connection code will also connect the other
// registers into the CS, hence we need to check for the full size
BOOST_TEST(backend->last_sizeInBytes == 32);
// check result
app.testModule.consumingPush.read();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment