Files
pvxs/test/testioc.cpp
T
Michael Davidsaver d2dcad708c ioc: update testioc.cpp
2023-05-09 10:17:32 -07:00

490 lines
20 KiB
C++

/**
* Copyright - See the COPYRIGHT that is included with this distribution.
* pvxs is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
*/
#include <iostream>
#include <dbAccess.h>
#include <dbUnitTest.h>
#include <iocInit.h>
#include <iocsh.h>
#include <testMain.h>
#include <pvxs/client.h>
#include <pvxs/iochooks.h>
#include <pvxs/server.h>
#include <pvxs/unittest.h>
extern "C" {
extern int testioc_registerRecordDeviceDriver(struct dbBase*);
}
using namespace pvxs;
namespace {
} // namespace
#define testOkB(__pass, __fmt, ...) boxLeft(); testOk(__pass, __fmt, ##__VA_ARGS__)
#define testEqB(__lhs, __rhs) boxLeft(); testEq(__lhs, __rhs)
#define testStrEqB(__lhs, __rhs) boxLeft(); testStrEq(__lhs, __rhs)
#define testArrEqB(__lhs, __rhs) boxLeft(); testArrEq(__lhs, __rhs)
#define testdbPutArrFieldOkB(__pv, __dbrType, __count, __pbuf) boxLeft(); testdbPutArrFieldOk(__pv, __dbrType, __count, __pbuf)
#define testdbGetFieldEqualB(__pv, __dbrType, ...) boxLeft(); testdbGetFieldEqual(__pv, __dbrType, ##__VA_ARGS__)
#define testdbGetArrFieldEqualB(__pv, __dbfType, __nRequest, __pbufcnt, __pbuf) boxLeft(); testdbGetArrFieldEqual(__pv, __dbfType, __nRequest, __pbufcnt, __pbuf)
#define testThrowsB(__lambda) boxLeft(); testThrows<std::logic_error>(__lambda)
static void boxLeft();
static std::shared_ptr<pvxs::client::Subscription> subscribe(epicsEvent& event, const char* pvName);
static Value waitForUpdate(const std::shared_ptr<client::Subscription>& subscription, epicsEvent& event);
static pvxs::client::Context clientContext;
// List of all tests to be run in order
static std::initializer_list<void (*)()> tests = {
[]() {
testThrowsB([] { ioc::server(); });
},
[]() {
testdbReadDatabase("testioc.dbd", nullptr, nullptr);
testOkB(true, R"("testioc.dbd" loaded)");
},
[]() {
testOkB(!testioc_registerRecordDeviceDriver(pdbbase), "testioc_registerRecordDeviceDriver(pdbbase)");
},
[]() { testOkB((bool)ioc::server(), "ioc::server()"); },
[]() {
testdbReadDatabase("testioc.db", nullptr, "user=test");
testOkB(true, R"(testdbReadDatabase("testioc.db", nullptr, "user=test"))");
},
[]() {
testdbReadDatabase("testiocg.db", nullptr, "user=test");
testOkB(true, R"(testdbReadDatabase("testiocg.db", nullptr, "user=test"))");
},
[]() {
testdbReadDatabase("image.db", nullptr, "N=tst");
testOkB(true, R"(testdbReadDatabase("testiocg.db", nullptr, "user=test"))");
},
[]() { testOkB(!pvxs::ioc::dbLoadGroup("../testioc.json"), R"(dbLoadGroup("testioc.json"))"); },
[]() {
testIocInitOk();
testPass("testIocInitOk()");
},
[]() { testdbGetFieldEqualB("test:aiExample", DBR_DOUBLE, 42.2); },
[]() { testdbGetFieldEqualB("test:stringExample", DBR_STRING, "Some random value"); },
[]() {
shared_array<double> expected({ 1.0, 2.0, 3.0 });
testdbGetArrFieldEqualB("test:arrayExample", DBR_DOUBLE, 3, expected.size(), expected.data());
},
[]() { testdbGetFieldEqualB("test:longExample", DBR_LONG, 102042); },
[]() { testdbGetFieldEqualB("test:enumExample", DBR_ENUM, 2); },
[]() {
char expected[MAX_STRING_SIZE * 2]{ 0 };
std::string first("Column A");
std::string second("Column B");
first.copy(&expected[0], MAX_STRING_SIZE - 1);
second.copy(&expected[MAX_STRING_SIZE], MAX_STRING_SIZE - 1);
testdbGetArrFieldEqualB("test:groupExampleAS", DBR_STRING, 2, 2, &expected);
},
[]() {
shared_array<double> expected({ 10, 20, 30, 40, 50 });
testdbGetArrFieldEqualB("test:vectorExampleD1", DBR_DOUBLE, 5, expected.size(), expected.data());
},
[]() {
shared_array<double> expected({ 1.1, 2.2, 3.3, 4.4, 5.5 });
testdbGetArrFieldEqualB("test:vectorExampleD2", DBR_DOUBLE, 5, expected.size(), expected.data());
},
[]() {
clientContext = ioc::server().clientConfig().build();
testShow() << clientContext.config();
testOkB(true, "cli = ioc::server().clientConfig().build()");
},
[]() {
auto val = clientContext.get("test:aiExample").exec()->wait(5.0);
auto aiExample = val["value"].as<double>();
auto expected = 42.2;
testEqB(aiExample, expected);
},
[]() {
auto val = clientContext.get("test:stringExample").exec()->wait(5.0);
auto stringExample = val["value"].as<std::string>();
auto expected = "Some random value";
testStrEqB(stringExample, expected);
},
[]() {
auto val = clientContext.get("test:arrayExample").exec()->wait(5.0);
shared_array<double> expected({ 1.0, 2.0, 3.0 });
auto arrayExample = val["value"].as<shared_array<const double>>();
testArrEqB(arrayExample, expected);
},
[]() {
auto val = clientContext.get("test:arrayExample.[1:2]").exec()->wait(5.0);
shared_array<double> expected({ 2.0, 3.0 });
auto arrayExample = val["value"].as<shared_array<const double>>();
testArrEqB(arrayExample, expected);
},
[]() {
shared_array<double> array({});
testdbPutArrFieldOkB("test:arrayExample", DBR_DOUBLE, array.size(), array.data());
},
[]() {
auto val = clientContext.get("test:arrayExample").exec()->wait(5.0);
shared_array<double> expected({});
auto arrayExample = val["value"].as<shared_array<const double>>();
testArrEqB(arrayExample, expected);
},
[]() {
shared_array<double> array({ 1.0, 2.0, 3.0, 4.0, 5.0 });
testdbPutArrFieldOkB("test:arrayExample", DBR_DOUBLE, array.size(), array.data());
},
[]() {
auto val = clientContext.get("test:arrayExample").exec()->wait(5.0);
shared_array<double> expected({ 1.0, 2.0, 3.0, 4.0, 5.0 });
auto arrayExample = val["value"].as<shared_array<const double>>();
testArrEqB(arrayExample, expected);
},
[]() {
auto val = clientContext.get("test:longExample").exec()->wait(5.0);
auto longValue = val["value"].as<long>();
auto expected = 102042;
testEqB(longValue, expected);
},
[]() {
auto val = clientContext.get("test:enumExample").exec()->wait(5.0);
auto enumExample = val["value.index"].as<short>();
auto expected = 2;
testEqB(enumExample, expected);
},
[]() {
auto val = clientContext.get("test:enumExample").exec()->wait(5.0);
shared_array<const std::string> expected({ "zero", "one", "two" });
auto enumExampleChoices = val["value.choices"].as<shared_array<const std::string>>();
testArrEqB(enumExampleChoices, expected);
},
[]() {
auto val = clientContext.get("test:tableExample").exec()->wait(5.0);
shared_array<const std::string> expected({ "Column A", "Column B" });
auto tableExampleLabels = val["labels"].as<shared_array<const std::string>>();
testArrEqB(tableExampleLabels, expected);
},
[]() {
auto val = clientContext.get("test:tableExample").exec()->wait(5.0);
shared_array<const double> expected({ 10, 20, 30, 40, 50 });
auto tableExampleValueA = val["value.A"].as<shared_array<const double>>();
testArrEqB(tableExampleValueA, expected);
},
[]() {
auto val = clientContext.get("test:structExample").exec()->wait(5.0);
auto structExampleStringValue = val["string.value"].as<std::string>();
auto expected = "Some random value";
testStrEqB(structExampleStringValue, expected);
},
[]() {
auto val = clientContext.get("test:structExample").exec()->wait(5.0);
auto structExampleAiValue = val["ai.value"].as<double>();
auto expected = 42.2;
testEqB(structExampleAiValue, expected);
},
[]() {
auto val = clientContext.get("test:structExample").exec()->wait(5.0);
shared_array<const double> expected({ 1, 2, 3, 4, 5 });
auto structExampleArrayValue = val["array.value"].as<shared_array<const double>>();
testArrEqB(structExampleArrayValue, expected);
},
[]() {
auto val = clientContext.get("test:structExample").exec()->wait(5.0);
auto structExampleSa_0_LongValue = val["sa[0].long.value"].as<long>();
auto expected = 102042;
testEqB(structExampleSa_0_LongValue, expected);
},
[]() {
auto val = clientContext.get("test:structExample").exec()->wait(5.0);
auto structExampleSa_0_EnumValueIndex = val["sa[0].enum.value.index"].as<short>();
auto expected = 2;
testEqB(structExampleSa_0_EnumValueIndex, expected);
},
[]() {
auto val = clientContext.get("test:structExample").exec()->wait(5.0);
auto structExampleSa_0_EnumValueChoices = val["sa[0].enum.value.choices"]
.as<shared_array<const std::string>>();
shared_array<const std::string> expected({ "zero", "one", "two" });
testArrEqB(structExampleSa_0_EnumValueChoices, expected);
},
[]() {
auto val = clientContext.get("test:structExample2").exec()->wait(5.0);
auto structExample2Sa_0_AnyValue = val["sa[0].any"].as<long>();
auto expected = 102042;
testEqB(structExample2Sa_0_AnyValue, expected);
},
[]() {
clientContext.put("test:calcExample.FLNK").set("value", "").exec()->wait(5.0);
testdbGetFieldEqualB("test:calcExample.FLNK", DBR_STRING, "");
},
[]() {
clientContext.put("test:calcExample.FLNK").set("value", "test:stringExample").exec()->wait(5.0);
testdbGetFieldEqualB("test:calcExample.FLNK", DBR_STRING, "test:stringExample");
},
[]() {
// TODO check whether long strings need to be null terminated
shared_array<const int8_t> arrayLinkVal(
{ 't', 'e', 's', 't', ':', 'a', 'i', 'E', 'x', 'a', 'm', 'p', 'l', 'e', '\0' });
clientContext.put("test:calcExample.FLNK$").set("value", arrayLinkVal).exec()->wait(5.0);
testdbGetFieldEqualB("test:calcExample.FLNK", DBR_STRING, "test:aiExample");
},
[]() {
shared_array<const uint16_t> expected({});
clientContext.put("tst:Array").build([&expected](Value&& prototype) -> Value {
auto putval = prototype.cloneEmpty();
putval["value"] = expected;
return putval;
})
.exec()->wait(5.0);
testdbGetArrFieldEqualB("tst:ArrayData", DBR_USHORT, 0, expected.size(), expected.data());
},
[]() {
shared_array<const uint16_t> expected({ 1, 2, 3, 4, 5 });
clientContext.put("tst:Array").build([&expected](Value&& prototype) -> Value {
auto putval = prototype.cloneEmpty();
putval["value"] = expected;
return putval;
})
.exec()->wait(5.0);
testdbGetArrFieldEqualB("tst:ArrayData", DBR_USHORT, 5, expected.size(), expected.data());
},
[]() {
clientContext.put("test:slowmo.PROC").set("value", 0).pvRequest("record[block=true]").exec()->wait(5.0);
testdbGetFieldEqualB("test:slowmo", DBR_DOUBLE, 1.0);
},
[]() {
clientContext.put("test:procCounter.HIGH").set("value", 0).pvRequest("record[process=true]").exec()
->wait(5.0);
testdbGetFieldEqualB("test:procCounter", DBR_DOUBLE, 1.0);
},
[]() {
clientContext.put("test:procCounter.HIGH").set("value", 0).pvRequest("record[process=false]").exec()
->wait(5.0);
testdbGetFieldEqualB("test:procCounter", DBR_DOUBLE, 1.0);
},
[]() {
clientContext.put("test:procCounter.HIGH").set("value", 0).pvRequest("record[process=passive]").exec()
->wait(5.0);
testdbGetFieldEqualB("test:procCounter", DBR_DOUBLE, 2.0);
},
[]() {
epicsEvent event;
// Subscribe for changes to PV
auto pvName = "test:longExample";
auto subscription = subscribe(event, pvName);
clientContext.hurryUp();
testThrows<client::Connected>([&subscription, &event]() {
waitForUpdate(subscription, event);
});
uint64_t expectedValue = 10L;
clientContext.put(pvName)
.set("value", expectedValue)
.exec()
->wait(5.0);
auto updatedValue = waitForUpdate(subscription, event);
if (updatedValue) {
auto actualValue = updatedValue["value"].as<uint64_t>();
testEqB(expectedValue, actualValue);
}
subscription->cancel();
},
[]() {
epicsEvent event;
// Subscribe for changes to Group PV
auto pvGroupName = "test:structExample";
auto subscription = subscribe(event, pvGroupName);
clientContext.hurryUp();
testThrows<client::Connected>([&subscription, &event]() {
waitForUpdate(subscription, event);
});
auto pvName = "test:longExample";
uint64_t expectedValue = 12L;
clientContext.put(pvName)
.set("value", expectedValue)
.exec()
->wait(5.0);
auto subFieldName = "sa[0].long.value";
auto updatedValue = waitForUpdate(subscription, event);
testShow()<<"Event!!\n"<<updatedValue.format().delta();
if (updatedValue) {
auto actualValue = updatedValue[subFieldName].as<uint64_t>();
testEqB(expectedValue, actualValue);
}
subscription->cancel();
},
[]() {
epicsEvent event;
// Subscribe for changes to Group PV
auto pvGroupName = "test:tableExample";
auto subscription = subscribe(event, pvGroupName);
clientContext.hurryUp();
testThrows<client::Connected>([&subscription, &event]() {
waitForUpdate(subscription, event);
});
auto pvName = "test:vectorExampleD2";
shared_array<const double> expectedValue({ 3.1, 3.2, 3.3, 3.4, 3.5 });
clientContext.put(pvName).build([&expectedValue](Value&& prototype) -> Value {
auto putVal = prototype.cloneEmpty();
putVal["value"] = expectedValue;
return putVal;
})
.exec()->wait(5.0);
auto subFieldName = "value.B";
auto updatedValue = waitForUpdate(subscription, event);
if (updatedValue) {
auto actualValue = updatedValue[subFieldName].as<shared_array<const double>>();
testArrEq(expectedValue, actualValue);
}
subscription->cancel();
},
[]() {
epicsEvent event;
// Subscribe for changes to Group PV
auto pvGroupName = "test:structExample2";
auto subscription = subscribe(event, pvGroupName);
clientContext.hurryUp();
testThrows<client::Connected>([&subscription, &event]() {
waitForUpdate(subscription, event);
});
auto pvName = "test:longExample";
uint64_t expectedValue = 12L;
clientContext.put(pvName)
.set("value", expectedValue)
.exec()
->wait(5.0);
auto subFieldName = "sa[0].long.value";
auto updatedValue = waitForUpdate(subscription, event);
if (updatedValue) {
auto actualValue = updatedValue[subFieldName].as<uint64_t>();
testEqB(expectedValue, actualValue);
}
subscription->cancel();
},
[]() {
epicsEvent event;
// Subscribe for changes to Group PV
auto pvGroupName = "test:tableExample2";
auto subscription = subscribe(event, pvGroupName);
clientContext.hurryUp();
testThrows<client::Connected>([&subscription, &event]() {
waitForUpdate(subscription, event);
});
auto pvName = "test:vectorExampleD2";
shared_array<const double> expectedValue({ 3.1, 3.2, 3.3, 3.4, 3.5 });
clientContext.put(pvName).build([&expectedValue](Value&& prototype) -> Value {
auto putVal = prototype.cloneEmpty();
putVal["value"] = expectedValue;
return putVal;
})
.exec()->wait(5.0);
auto subFieldName = "value.jB";
auto updatedValue = waitForUpdate(subscription, event);
if (updatedValue) {
auto actualValue = updatedValue[subFieldName].as<shared_array<const double>>();
testArrEq(expectedValue, actualValue);
}
subscription->cancel();
},
};
/**
* Test runner
*
* @return overall test status
*/
MAIN(testioc) {
auto testNum = 0;
auto nMonitorTests = 5;
testPlan((int)tests.size() + nMonitorTests);
testSetup();
testdbPrepare();
// Run tests
for (auto& test: tests) {
if (testNum++) {
printf("#├──────────────────────────────────────────────────────────────────────┤\n");
} else {
printf("#┌──────────────────────────────────────────────────────────────────────┐\n");
}
try {
test();
} catch (const std::exception& e) {
testFail("Test failed with unexpected exception: %s\n", e.what());
}
}
printf("#└──────────────────────────────────────────────────────────────────────┘");
testIocShutdownOk();
testdbCleanup();
return testDone();
}
//static void testDbLoadGroupOk(const char* file) {
// testOk(!pvxs::ioc::dbLoadGroup(file), "%s scheduled to be loaded during IocInit()", file);
//}
static void boxLeft() {
}
/**
* Subscribe to a given pv using the specified epics event for synchronization
*
* @param event epics event
* @param pvName the pvName to subscribe to
* @return the subscription
*/
static std::shared_ptr<pvxs::client::Subscription> subscribe(epicsEvent& event, const char* pvName) {
return clientContext.monitor(pvName)
.maskConnected(false)
.maskDisconnected(false)
.event([&event, pvName](client::Subscription& subscription) {
testDiag("%s update event occurred", pvName);
event.signal();
})
.exec();
}
/**
* Wait for up to 5 seconds for an update to the specified subscription on the epics event
*
* @param subscription the subscription
* @param event the epics event
* @return the updated value or an empty value if the subscription timed out
*/
static Value waitForUpdate(const std::shared_ptr<client::Subscription>& subscription, epicsEvent& event) {
while (true) {
if (auto value = subscription->pop()) {
return value;
} else if (!event.wait(5.0)) {
testFail("timeout waiting for event for %s", subscription->name().c_str());
return {};
}
}
}