Skip to content

Commit

Permalink
Add tests for pvalink properties
Browse files Browse the repository at this point in the history
  • Loading branch information
simon-ess committed Sep 14, 2023
1 parent 87e8bdb commit 7d80472
Show file tree
Hide file tree
Showing 6 changed files with 305 additions and 3 deletions.
10 changes: 7 additions & 3 deletions ioc/pvalink.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@
#include <epicsUnitTest.h>
#include <epicsString.h>

#include <epicsStdio.h> /* redirects stdout/stderr */

#include <pvxs/server.h>

#include "pvalink.h"
#include "dblocker.h"
#include "dbentry.h"
#include "iocshcommand.h"
#include "utilpvt.h"

#include <epicsStdio.h> /* redirects stdout/stderr; include after util.h from libevent */
#include <epicsExport.h> /* defines epicsExportSharedSymbols */

#if EPICS_VERSION_INT>=VERSION_INT(7,0,6,0)
Expand Down Expand Up @@ -126,7 +126,11 @@ void initPVALink(initHookState state)

} else if(state==initHookAfterInitDatabase) {
// TODO "local" provider
pvaGlobal->provider_remote = client::Config().build();
if (inUnitTest()) {
pvaGlobal->provider_remote = ioc::server().clientConfig().build();
} else {
pvaGlobal->provider_remote = client::Config().build();
}

} else if(state==initHookAfterIocBuilt) {
// after epicsExit(exitDatabase)
Expand Down
9 changes: 9 additions & 0 deletions ioc/pvxs/iochooks.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,14 @@ void testPrepare();
PVXS_IOC_API
void testShutdown();

PVXS_IOC_API
void testqsrvWaitForLinkEvent(struct link *plink);

PVXS_IOC_API
void testqsrvShutdownOk(void);

PVXS_IOC_API
void testqsrvCleanup(void);

}} // namespace pvxs::ioc
#endif // PVXS_IOCHOOKS_H
7 changes: 7 additions & 0 deletions test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ include $(TOP)/configure/CONFIG_PVXS_VERSION

# access to private headers
USR_CPPFLAGS += -I$(TOP)/src
USR_CPPFLAGS += -I$(TOP)/ioc

PROD_LIBS = pvxs Com

Expand Down Expand Up @@ -120,6 +121,12 @@ ifdef BASE_7_0
TESTPROD_HOST += benchdata
benchdata_SRCS += benchdata.cpp

TESTPROD_HOST += testpvalink
testpvalink_SRCS += testpvalink.cpp
testpvalink_SRCS += testioc_registerRecordDeviceDriver.cpp
testpvalink_LIBS += pvxsIoc pvxs $(EPICS_BASE_IOC_LIBS)
TESTS += testpvalink

endif

ifdef BASE_3_15
Expand Down
1 change: 1 addition & 0 deletions test/testioc.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class TestIOC {
if(running) {
pvxs::ioc::testShutdown();
testIocShutdownOk();
running = false;
}
}
~TestIOC() {
Expand Down
218 changes: 218 additions & 0 deletions test/testpvalink.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@

#include <testMain.h>
#include <longinRecord.h>
#include <longoutRecord.h>

//#include <pv/qsrv.h>
//#include "utilities.h"
#include "dblocker.h"
#include "pvxs/iochooks.h"
#include "pvalink.h"
#include "testioc.h"
//#include "pv/qsrv.h"

using namespace pvxs::ioc;
using namespace pvxs;

namespace
{
void testGet()
{
testDiag("==== testGet ====");

longinRecord *i1 = (longinRecord *)testdbRecordPtr("src:i1");

while (!dbIsLinkConnected(&i1->inp))
testqsrvWaitForLinkEvent(&i1->inp);

testdbGetFieldEqual("target:i.VAL", DBF_LONG, 42L);

testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 0L); // value before first process

testdbGetFieldEqual("src:i1.INP", DBF_STRING, "{\"pva\":\"target:i\"}");

testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L);

testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 42L);

testdbPutFieldOk("src:i1.INP", DBF_STRING, "{\"pva\":\"target:ai\"}");

while (!dbIsLinkConnected(&i1->inp))
testqsrvWaitForLinkEvent(&i1->inp);

testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 42L); // changing link doesn't automatically process

testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L);

testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 4L); // now it's changed
}

void testFieldLinks() {

longinRecord *i1 = (longinRecord *)testdbRecordPtr("src:i1");

testDiag("==== Test field links ====");

std::string pv_name = "{\"pva\":{\"pv\":\"target:ai\",\"field\":\"display.precision\"}}";
testdbPutArrFieldOk("src:i1.INP$", DBF_CHAR, pv_name.length()+1, pv_name.c_str());

while (!dbIsLinkConnected(&i1->inp))
testqsrvWaitForLinkEvent(&i1->inp);

testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 4L); // changing link doesn't automatically process

testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L);

testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 2L); // changing link doesn't automatically process

}

void testProc()
{

longinRecord *i1 = (longinRecord *)testdbRecordPtr("src:i1");

testDiag("==== Test proc settings ====");

// Set it to CPP
std::string pv_name = "{\"pva\":{\"pv\":\"target:ai\",\"proc\":\"CPP\"}}";
testdbPutArrFieldOk("src:i1.INP$", DBF_CHAR, pv_name.length()+1, pv_name.c_str());

while (!dbIsLinkConnected(&i1->inp))
testqsrvWaitForLinkEvent(&i1->inp);

// Link should read old value again
testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 4L);

testdbPutFieldOk("target:ai", DBF_FLOAT, 5.0);

// We are already connected at this point, wait for the update.
testqsrvWaitForLinkEvent(&i1->inp);

// now it's changed
testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 5L);
}

void testSevr()
{
longinRecord *i1 = (longinRecord *)testdbRecordPtr("src:i1");

testDiag("==== Test severity forwarding (NMS, MS, MSI) ====");

std::string pv_name = "{\"pva\":{\"pv\":\"target:ai\",\"sevr\":\"NMS\"}}";
testdbPutArrFieldOk("src:i1.INP$", DBF_CHAR, pv_name.length() + 1, pv_name.c_str());

while (!dbIsLinkConnected(&i1->inp))
testqsrvWaitForLinkEvent(&i1->inp);

testdbPutFieldOk("target:ai.LOLO", DBF_FLOAT, 5.0);
testdbPutFieldOk("target:ai.LLSV", DBF_STRING, "MAJOR");
testdbPutFieldOk("target:ai", DBF_FLOAT, 0.0);

testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L);
testdbGetFieldEqual("src:i1.SEVR", DBF_SHORT, epicsSevNone);

pv_name = "{\"pva\":{\"pv\":\"target:ai\",\"sevr\":\"MS\"}}";
testdbPutArrFieldOk("src:i1.INP$", DBF_CHAR, pv_name.length() + 1, pv_name.c_str());

while (!dbIsLinkConnected(&i1->inp))
testqsrvWaitForLinkEvent(&i1->inp);

testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L);
testdbGetFieldEqual("src:i1.SEVR", DBF_SHORT, epicsSevMajor);

pv_name = "{\"pva\":{\"pv\":\"target:mbbi\",\"sevr\":\"MSI\"}}";
testdbPutArrFieldOk("src:i1.INP$", DBF_CHAR, pv_name.length() + 1, pv_name.c_str());

while (!dbIsLinkConnected(&i1->inp))
testqsrvWaitForLinkEvent(&i1->inp);

testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L);
testdbGetFieldEqual("src:i1.SEVR", DBF_SHORT, epicsSevNone);

testdbPutFieldOk("target:ai", DBF_FLOAT, 1.0);
testqsrvWaitForLinkEvent(&i1->inp);
testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L);
testdbGetFieldEqual("src:i1.SEVR", DBF_SHORT, epicsSevInvalid);
}

void testPut()
{
testDiag("==== testPut ====");

longoutRecord *o2 = (longoutRecord *)testdbRecordPtr("src:o2");

while (!dbIsLinkConnected(&o2->out))
testqsrvWaitForLinkEvent(&o2->out);

testdbGetFieldEqual("target:i2.VAL", DBF_LONG, 43L);
testdbGetFieldEqual("src:o2.VAL", DBF_LONG, 0L);
testdbGetFieldEqual("src:o2.OUT", DBF_STRING, "{\"pva\":\"target:i2\"}");

testdbPutFieldOk("src:o2.VAL", DBF_LONG, 14L);

// TODO: This test will only be implemented after the pva link puts work.
//testdbGetFieldEqual("target:i2.VAL", DBF_LONG, 14L);
testdbGetFieldEqual("src:o2.VAL", DBF_LONG, 14L);
}

void testPutAsync()
{
#ifdef USE_MULTILOCK
testDiag("==== testPutAsync ====");

longoutRecord *trig = (longoutRecord *)testdbRecordPtr("async:trig");

while (!dbIsLinkConnected(&trig->out))
testqsrvWaitForLinkEvent(&trig->out);

testMonitor *done = testMonitorCreate("async:after", DBE_VALUE, 0);

testdbPutFieldOk("async:trig.PROC", DBF_LONG, 1);
testMonitorWait(done);

testdbGetFieldEqual("async:trig", DBF_LONG, 1);
testdbGetFieldEqual("async:slow", DBF_LONG, 1); // pushed from async:trig
testdbGetFieldEqual("async:slow2", DBF_LONG, 2);
testdbGetFieldEqual("async:after", DBF_LONG, 3);

#else
testSkip(5, "Not USE_MULTILOCK");
#endif
}

} // namespace

extern "C" void testioc_registerRecordDeviceDriver(struct dbBase *);

MAIN(testpvalink)
{
testPlan(37);
testSetup();

try
{
TestIOC IOC;

testdbReadDatabase("testioc.dbd", NULL, NULL);
testioc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("testpvalink.db", NULL, NULL);

IOC.init();
testGet();
testFieldLinks();
testProc();
testSevr();
testPut();
(void)testPutAsync;
testqsrvShutdownOk();
IOC.shutdown();
testqsrvCleanup();
}
catch (std::exception &e)
{
testFail("Unexpected exception: %s", e.what());
}
// call epics atexits explicitly as workaround for c++ static dtor issues...
epicsExit(testDone());
}
63 changes: 63 additions & 0 deletions test/testpvalink.db
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@

# used by testGet(), testFieldLinks, testProc, testSevr
record(longin, "target:i") {
field(VAL, "42")
}
record(ai, "target:ai") {
field(VAL, "4.0")
field(FLNK, "target:mbbi")
field(PREC, "2")
}

record(longin, "src:i1") {
field(INP, {"pva":"target:i"})
}

record(mbbi, "target:mbbi") {
field(INP, "target:ai")
field(ZRSV, "NO_ALARM")
field(ONSV, "INVALID")
}

# used by testPut()
record(longin, "target:i2") {
field(VAL, "43")
}

record(longout, "src:o2") {
field(OUT, {"pva":"target:i2"})
}

# used by testPutAsync()
record(calc, "async:seq") {
field(CALC, "VAL+1")
field(VAL , "0")
field(TPRO, "1")
}

record(longout, "async:trig") {
field(OMSL, "closed_loop")
field(DOL , "async:seq PP")
field(DTYP, "Async Soft Channel")
field(OUT , { "pva":{"pv":"async:slow.A", "proc":true} })
field(FLNK, "async:after")
field(TPRO, "1")
}

record(calcout, "async:slow") {
field(ODLY, "1")
field(CALC, "A")
field(FLNK, "async:slow2")
field(TPRO, "1")
}

record(longin, "async:slow2") {
field(INP , "async:seq PP")
field(TPRO, "1")
}

record(longin, "async:after") {
field(INP , "async:seq PP")
field(MDEL, "-1")
field(TPRO, "1")
}

0 comments on commit 7d80472

Please sign in to comment.