/** * 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 #include #include #include #include #include #include #include #include #include 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(__lambda) static void boxLeft(); static std::shared_ptr subscribe(epicsEvent& event, const char* pvName); static Value waitForUpdate(const std::shared_ptr& subscription, epicsEvent& event); static pvxs::client::Context clientContext; // List of all tests to be run in order static std::initializer_list 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 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 expected({ 10, 20, 30, 40, 50 }); testdbGetArrFieldEqualB("test:vectorExampleD1", DBR_DOUBLE, 5, expected.size(), expected.data()); }, []() { shared_array 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(); auto expected = 42.2; testEqB(aiExample, expected); }, []() { auto val = clientContext.get("test:stringExample").exec()->wait(5.0); auto stringExample = val["value"].as(); auto expected = "Some random value"; testStrEqB(stringExample, expected); }, []() { auto val = clientContext.get("test:arrayExample").exec()->wait(5.0); shared_array expected({ 1.0, 2.0, 3.0 }); auto arrayExample = val["value"].as>(); testArrEqB(arrayExample, expected); }, []() { auto val = clientContext.get("test:arrayExample.[1:2]").exec()->wait(5.0); shared_array expected({ 2.0, 3.0 }); auto arrayExample = val["value"].as>(); testArrEqB(arrayExample, expected); }, []() { shared_array array({}); testdbPutArrFieldOkB("test:arrayExample", DBR_DOUBLE, array.size(), array.data()); }, []() { auto val = clientContext.get("test:arrayExample").exec()->wait(5.0); shared_array expected({}); auto arrayExample = val["value"].as>(); testArrEqB(arrayExample, expected); }, []() { shared_array 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 expected({ 1.0, 2.0, 3.0, 4.0, 5.0 }); auto arrayExample = val["value"].as>(); testArrEqB(arrayExample, expected); }, []() { auto val = clientContext.get("test:longExample").exec()->wait(5.0); auto longValue = val["value"].as(); auto expected = 102042; testEqB(longValue, expected); }, []() { auto val = clientContext.get("test:enumExample").exec()->wait(5.0); auto enumExample = val["value.index"].as(); auto expected = 2; testEqB(enumExample, expected); }, []() { auto val = clientContext.get("test:enumExample").exec()->wait(5.0); shared_array expected({ "zero", "one", "two" }); auto enumExampleChoices = val["value.choices"].as>(); testArrEqB(enumExampleChoices, expected); }, []() { auto val = clientContext.get("test:tableExample").exec()->wait(5.0); shared_array expected({ "Column A", "Column B" }); auto tableExampleLabels = val["labels"].as>(); testArrEqB(tableExampleLabels, expected); }, []() { auto val = clientContext.get("test:tableExample").exec()->wait(5.0); shared_array expected({ 10, 20, 30, 40, 50 }); auto tableExampleValueA = val["value.A"].as>(); testArrEqB(tableExampleValueA, expected); }, []() { auto val = clientContext.get("test:structExample").exec()->wait(5.0); auto structExampleStringValue = val["string.value"].as(); auto expected = "Some random value"; testStrEqB(structExampleStringValue, expected); }, []() { auto val = clientContext.get("test:structExample").exec()->wait(5.0); auto structExampleAiValue = val["ai.value"].as(); auto expected = 42.2; testEqB(structExampleAiValue, expected); }, []() { auto val = clientContext.get("test:structExample").exec()->wait(5.0); shared_array expected({ 1, 2, 3, 4, 5 }); auto structExampleArrayValue = val["array.value"].as>(); testArrEqB(structExampleArrayValue, expected); }, []() { auto val = clientContext.get("test:structExample").exec()->wait(5.0); auto structExampleSa_0_LongValue = val["sa[0].long.value"].as(); 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(); 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 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(); 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 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 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 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([&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(); 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([&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"<(); 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([&subscription, &event]() { waitForUpdate(subscription, event); }); auto pvName = "test:vectorExampleD2"; shared_array 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>(); 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([&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(); 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([&subscription, &event]() { waitForUpdate(subscription, event); }); auto pvName = "test:vectorExampleD2"; shared_array 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>(); 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 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& 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 {}; } } }