From d2dcad708c2ab63de799a9b67d0176349c4cadd3 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Mon, 6 Mar 2023 10:09:44 +0000 Subject: [PATCH] ioc: update testioc.cpp --- test/testioc.cpp | 617 ++++++++++++++++++++++++++++++----------------- 1 file changed, 397 insertions(+), 220 deletions(-) diff --git a/test/testioc.cpp b/test/testioc.cpp index ebd934e..9fd5016 100644 --- a/test/testioc.cpp +++ b/test/testioc.cpp @@ -36,25 +36,26 @@ namespace { #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"))"); + []() { + 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"); @@ -79,197 +80,335 @@ static std::initializer_list tests = { []() { 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); + 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()); - }, - []() { + 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); - }, + []() { + 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(); + }, }; /** @@ -278,30 +417,31 @@ static std::initializer_list tests = { * @return overall test status */ MAIN(testioc) { - auto testNum = 0; - testPlan((int)tests.size()); - testSetup(); - testdbPrepare(); + 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("#└──────────────────────────────────────────────────────────────────────┘"); + // 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(); + return testDone(); } //static void testDbLoadGroupOk(const char* file) { @@ -310,3 +450,40 @@ MAIN(testioc) { 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 {}; + } + } +} +