656 lines
20 KiB
C++
656 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 <testMain.h>
|
|
#include <epicsExit.h>
|
|
#include <dbLock.h>
|
|
#include <dbLink.h>
|
|
#include <recGbl.h>
|
|
#include <dbUnitTest.h>
|
|
#include <aiRecord.h>
|
|
#include <aaoRecord.h>
|
|
#include <aaiRecord.h>
|
|
#include <calcRecord.h>
|
|
#include <calcoutRecord.h>
|
|
#include <longinRecord.h>
|
|
#include <longoutRecord.h>
|
|
#include <stringoutRecord.h>
|
|
#include <registryFunction.h>
|
|
|
|
#define PVXS_ENABLE_EXPERT_API
|
|
|
|
#include <pvxs/log.h>
|
|
#include <pvxs/client.h>
|
|
#include <pvxs/server.h>
|
|
#include <pvxs/iochooks.h>
|
|
#include <pvxs/unittest.h>
|
|
#include <pvxs/nt.h>
|
|
#include <pvxs/sharedpv.h>
|
|
|
|
#include "dblocker.h"
|
|
#include "qsrvpvt.h"
|
|
#include "pvalink.h"
|
|
|
|
using namespace pvxs::ioc;
|
|
using namespace pvxs;
|
|
|
|
namespace {
|
|
struct TestMonitor {
|
|
testMonitor * const mon;
|
|
TestMonitor(const char* pvname, unsigned dbe_mask, unsigned opt=0)
|
|
:mon(testMonitorCreate(pvname, dbe_mask, opt))
|
|
{}
|
|
~TestMonitor() { testMonitorDestroy(mon); }
|
|
void wait() { testMonitorWait(mon); }
|
|
unsigned count(bool reset=true) { return testMonitorCount(mon, reset); }
|
|
};
|
|
|
|
void testGet()
|
|
{
|
|
testDiag("==== testGet ====");
|
|
|
|
longinRecord *i1 = (longinRecord *)testdbRecordPtr("src:i1");
|
|
|
|
testqsrvWaitForLinkConnected(&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\"}");
|
|
|
|
testqsrvWaitForLinkConnected(&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());
|
|
|
|
testqsrvWaitForLinkConnected(&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 ====");
|
|
|
|
testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 2L);
|
|
|
|
// Set it to CPP
|
|
std::string pv_name = "{\"pva\":{\"pv\":\"target:ai\",\"proc\":\"CPP\"}}";
|
|
{
|
|
TestMonitor m("src:i1", DBE_VALUE);
|
|
testdbPutArrFieldOk("src:i1.INP$", DBF_CHAR, pv_name.length()+1, pv_name.c_str());
|
|
// wait for initial scan
|
|
m.wait();
|
|
}
|
|
|
|
// Link should read current value of target.
|
|
testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 4L);
|
|
|
|
{
|
|
QSrvWaitForLinkUpdate C(&i1->inp);
|
|
testdbPutFieldOk("target:ai", DBF_FLOAT, 5.0);
|
|
}
|
|
|
|
// 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());
|
|
|
|
testqsrvWaitForLinkConnected(&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());
|
|
|
|
testqsrvWaitForLinkConnected(&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());
|
|
|
|
testqsrvWaitForLinkConnected(&i1->inp);
|
|
|
|
testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L);
|
|
testdbGetFieldEqual("src:i1.SEVR", DBF_SHORT, epicsSevNone);
|
|
|
|
{
|
|
QSrvWaitForLinkUpdate C(&i1->inp);
|
|
testdbPutFieldOk("target:ai", DBF_FLOAT, 1.0);
|
|
}
|
|
|
|
testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L);
|
|
testdbGetFieldEqual("src:i1.SEVR", DBF_SHORT, epicsSevInvalid);
|
|
}
|
|
|
|
void testPut()
|
|
{
|
|
testDiag("==== testPut ====");
|
|
|
|
longoutRecord *o2 = (longoutRecord *)testdbRecordPtr("src:o2");
|
|
|
|
testqsrvWaitForLinkConnected(&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\"}");
|
|
|
|
{
|
|
QSrvWaitForLinkUpdate C(&o2->out);
|
|
testdbPutFieldOk("src:o2.VAL", DBF_LONG, 14L);
|
|
}
|
|
|
|
testdbGetFieldEqual("target:i2.VAL", DBF_LONG, 14L);
|
|
testdbGetFieldEqual("src:o2.VAL", DBF_LONG, 14L);
|
|
}
|
|
|
|
void testStrings()
|
|
{
|
|
testDiag("==== testStrings ====");
|
|
|
|
stringoutRecord *so = (stringoutRecord *)testdbRecordPtr("src:str");
|
|
|
|
testdbGetFieldEqual("target:str1", DBF_STRING, "foo");
|
|
testdbPutFieldOk("target:str1.PROC", DBF_LONG, 1L);
|
|
testdbGetFieldEqual("target:str1", DBF_STRING, "bar");
|
|
|
|
testdbPutFieldOk("src:str.OUT", DBF_STRING, R"({"pva" : "target:str2"})");
|
|
|
|
testqsrvWaitForLinkConnected(&so->out);
|
|
|
|
{
|
|
QSrvWaitForLinkUpdate C(&so->out);
|
|
testdbPutFieldOk("src:str.PROC", DBF_LONG, 1L);
|
|
}
|
|
|
|
testdbGetFieldEqual("target:str2", DBF_STRING, "bar");
|
|
}
|
|
|
|
void testToFromString()
|
|
{
|
|
testDiag("==== %s ====", __func__);
|
|
|
|
testqsrvWaitForLinkConnected("testToFromString:src.OUT");
|
|
testqsrvWaitForLinkConnected("testToFromString:str2.INP");
|
|
testqsrvWaitForLinkConnected("testToFromString:out.INP");
|
|
|
|
{
|
|
QSrvWaitForLinkUpdate C("testToFromString:out.INP");
|
|
testdbPutFieldOk("testToFromString:src", DBR_LONG, 43);
|
|
}
|
|
|
|
testdbGetFieldEqual("testToFromString:str1", DBR_STRING, "43");
|
|
testdbGetFieldEqual("testToFromString:str2", DBR_STRING, "43");
|
|
testdbGetFieldEqual("testToFromString:out", DBR_LONG, 43);
|
|
}
|
|
|
|
void testArrays()
|
|
{
|
|
auto aai_inp = (aaiRecord *)testdbRecordPtr("target:aai_inp");
|
|
testDiag("==== testArrays ====");
|
|
static const epicsFloat32 input_arr[] = {1, 2, -1, 1.2, 0};
|
|
{
|
|
QSrvWaitForLinkUpdate C(&aai_inp->inp);
|
|
testdbPutArrFieldOk("source:aao", DBR_FLOAT, 5, input_arr);
|
|
}
|
|
|
|
// underlying channel cache updated, but record has not be re-processed
|
|
testdbGetArrFieldEqual("target:aai_inp", DBF_CHAR, 10, 0, NULL);
|
|
|
|
static const epicsInt8 expected_char[] = {1, 2, -1, 1, 0};
|
|
testdbPutFieldOk("target:aai_inp.PROC", DBF_LONG, 1L);
|
|
testdbGetArrFieldEqual("target:aai_inp", DBF_CHAR, 10, 5, expected_char);
|
|
|
|
static const epicsUInt32 expected_ulong[] = {1L, 2L, 4294967295L, 1L, 0};
|
|
testdbGetArrFieldEqual("target:aai_inp", DBF_ULONG, 10, 5, expected_ulong);
|
|
|
|
testqsrvWaitForLinkConnected("target:aai_inp_first.INP");
|
|
testdbPutFieldOk("target:aai_inp_first.PROC", DBF_LONG, 1L);
|
|
testdbGetFieldEqual("target:aai_inp_first", DBR_DOUBLE, 1.0);
|
|
}
|
|
|
|
void testStringArray()
|
|
{
|
|
testDiag("==== %s ====", __func__);
|
|
|
|
testqsrvWaitForLinkConnected("sarr:inp.INP");
|
|
|
|
const char expect[3][MAX_STRING_SIZE] = {"one", "two", "three"};
|
|
{
|
|
QSrvWaitForLinkUpdate U("sarr:inp.INP");
|
|
|
|
testdbPutArrFieldOk("sarr:src", DBR_STRING, 3, expect);
|
|
}
|
|
|
|
testdbPutFieldOk("sarr:inp.PROC", DBR_LONG, 0);
|
|
|
|
testdbGetArrFieldEqual("sarr:inp", DBR_STRING, 4, 3, expect);
|
|
}
|
|
|
|
void testPutAsync()
|
|
{
|
|
testDiag("==== testPutAsync ====");
|
|
|
|
auto trig = (longoutRecord *)testdbRecordPtr("async:trig");
|
|
auto seq = (calcRecord *)testdbRecordPtr("async:seq");
|
|
|
|
testqsrvWaitForLinkConnected(&trig->out);
|
|
|
|
TestMonitor done("async:seq", DBE_VALUE, 0);
|
|
|
|
testdbPutFieldOk("async:trig.PROC", DBF_LONG, 1);
|
|
dbScanLock((dbCommon*)seq);
|
|
while(seq->val < 2) {
|
|
dbScanUnlock((dbCommon*)seq);
|
|
done.wait();
|
|
dbScanLock((dbCommon*)seq);
|
|
}
|
|
dbScanUnlock((dbCommon*)seq);
|
|
|
|
testdbGetFieldEqual("async:target", DBF_LONG, 1);
|
|
testdbGetFieldEqual("async:next", DBF_LONG, 2);
|
|
testdbGetFieldEqual("async:seq", DBF_LONG, 2);
|
|
}
|
|
|
|
void testDisconnect()
|
|
{
|
|
testDiag("==== %s ====", __func__);
|
|
auto serv(ioc::server());
|
|
|
|
testdbPutFieldFail(-1, "disconnected.PROC", DBF_LONG, 1);
|
|
testdbGetFieldEqual("disconnected.SEVR", DBF_SHORT, epicsSevInvalid);
|
|
|
|
auto special(server::SharedPV::buildReadonly());
|
|
special.open(nt::NTScalar{TypeCode::Int32}.create()
|
|
.update("value", 43));
|
|
serv.addPV("special:pv", special);
|
|
|
|
testqsrvWaitForLinkConnected("disconnected.INP");
|
|
|
|
testdbPutFieldOk("disconnected.PROC", DBF_LONG, 1);
|
|
testdbGetFieldEqual("disconnected.SEVR", DBF_SHORT, epicsSevNone);
|
|
|
|
serv.removePV("special:pv");
|
|
special.close();
|
|
|
|
testqsrvWaitForLinkConnected("disconnected.INP", false);
|
|
|
|
testdbPutFieldFail(-1, "disconnected.PROC", DBF_LONG, 1);
|
|
testdbGetFieldEqual("disconnected.SEVR", DBF_SHORT, epicsSevInvalid);
|
|
|
|
testdbPutFieldOk("disconnected.INP", DBR_STRING, ""); // avoid further log messages
|
|
}
|
|
|
|
void testMeta()
|
|
{
|
|
testDiag("==== %s ====", __func__);
|
|
|
|
testqsrvWaitForLinkConnected("meta:inp.INP");
|
|
|
|
{
|
|
auto src = (aiRecord*)testdbRecordPtr("meta:src");
|
|
QSrvWaitForLinkUpdate U("meta:inp.INP");
|
|
dbScanLock((dbCommon*)src);
|
|
src->tse = epicsTimeEventDeviceTime;
|
|
src->time.secPastEpoch = 0x12345678;
|
|
src->time.nsec = 0x10203040;
|
|
src->val = 7;
|
|
#ifdef HAS_ALARM_MESSAGE
|
|
strcpy(src->amsg, "Test");
|
|
#endif
|
|
dbProcess((dbCommon*)src);
|
|
dbScanUnlock((dbCommon*)src);
|
|
}
|
|
auto inp = (aiRecord*)testdbRecordPtr("meta:inp");
|
|
|
|
long ret, nelem;
|
|
epicsEnum16 stat, sevr;
|
|
epicsTimeStamp time;
|
|
char egu[10] = "";
|
|
short prec;
|
|
double val, lolo, low, high, hihi;
|
|
|
|
dbScanLock((dbCommon*)inp);
|
|
|
|
testTrue(dbIsLinkConnected(&inp->inp)!=0);
|
|
|
|
testEq(dbGetLinkDBFtype(&inp->inp), DBF_DOUBLE);
|
|
|
|
// alarm and time meta-data will be "latched" by a call to dbGetLink.
|
|
// until then, the initial values are used
|
|
|
|
testTrue((ret=dbGetAlarm(&inp->inp, &stat, &sevr))==0
|
|
&& stat==LINK_ALARM && sevr==INVALID_ALARM)
|
|
<<" ret="<<ret<<" stat="<<stat<<" sevr="<<sevr;
|
|
|
|
#ifdef HAS_ALARM_MESSAGE
|
|
{
|
|
char amsg[DB_AMSG_SIZE];
|
|
amsg[sizeof(amsg)-1] = '\0';
|
|
testTrue((ret=dbGetAlarmMsg(&inp->inp, &stat, &sevr, amsg, sizeof(amsg)-1))==0
|
|
&& stat==LINK_ALARM && sevr==INVALID_ALARM
|
|
&& strncmp(amsg, "", sizeof(amsg))==0)
|
|
<<" ret="<<ret<<" stat="<<stat<<" sevr="<<sevr<<" amsg="<<amsg;
|
|
}
|
|
#else
|
|
testSkip(1, "No AMSG");
|
|
#endif
|
|
|
|
testTrue((ret=dbGetTimeStamp(&inp->inp, &time))==0
|
|
&& time.secPastEpoch==0 && time.nsec==0)
|
|
<<" ret="<<ret<<" sec="<<time.secPastEpoch<<" ns="<<time.nsec;
|
|
|
|
testTrue((ret=dbGetLink(&inp->inp, DBR_DOUBLE, &val, nullptr, nullptr))==0
|
|
&& val==7.0)<<" ret="<<ret<<" val="<<val;
|
|
|
|
// now latched...
|
|
testTrue((ret=dbGetAlarm(&inp->inp, &stat, &sevr))==0
|
|
&& stat==LINK_ALARM && sevr==MINOR_ALARM)
|
|
<<" ret="<<ret<<" stat="<<stat<<" sevr="<<sevr;
|
|
|
|
#ifdef HAS_ALARM_MESSAGE
|
|
{
|
|
char amsg[DB_AMSG_SIZE];
|
|
amsg[sizeof(amsg)-1] = '\0';
|
|
testTrue((ret=dbGetAlarmMsg(&inp->inp, &stat, &sevr, amsg, sizeof(amsg)-1))==0
|
|
&& stat==LINK_ALARM && sevr==MINOR_ALARM
|
|
&& strncmp(amsg, "HIGH", sizeof(amsg))==0)
|
|
<<" ret="<<ret<<" stat="<<stat<<" sevr="<<sevr<<" amsg="<<amsg;
|
|
}
|
|
#else
|
|
testSkip(1, "No AMSG");
|
|
#endif
|
|
|
|
testTrue((ret=dbGetTimeStamp(&inp->inp, &time))==0
|
|
&& time.secPastEpoch==0x12345678 && time.nsec==0x10203040)
|
|
<<" ret="<<ret<<" sec="<<time.secPastEpoch<<" ns="<<time.nsec;
|
|
|
|
testTrue((ret=dbGetGraphicLimits(&inp->inp, &low, &high))==0 && low==-9 && high==9)
|
|
<<" ret="<<ret<<" low="<<low<<" high="<<high;
|
|
|
|
testTrue((ret=dbGetControlLimits(&inp->inp, &low, &high))==0 && low==-10 && high==10)
|
|
<<" ret="<<ret<<" low="<<low<<" high="<<high;
|
|
|
|
testTrue((ret=dbGetAlarmLimits(&inp->inp, &lolo, &low, &high, &hihi))==0
|
|
&& lolo==-8 && low==-7 && high==7 && hihi==8)
|
|
<<" ret="<<ret<<" lolo="<<lolo<<" low="<<low<<" high="<<high<<" hihi="<<hihi;
|
|
|
|
testTrue((ret=dbGetPrecision(&inp->inp, &prec))==0 && prec==2)
|
|
<<" ret="<<ret<<" prec="<<prec;
|
|
|
|
testTrue((ret=dbGetUnits(&inp->inp, egu, sizeof(egu)))==0 && strcmp(egu, "arb")==0)
|
|
<<" ret="<<ret<<" egu='"<<egu<<"'";
|
|
|
|
testTrue((ret=dbGetNelements(&inp->inp, &nelem))==0 && nelem==1)
|
|
<<" ret="<<ret<<" nelem='"<<nelem<<"'";
|
|
|
|
dbScanUnlock((dbCommon*)inp);
|
|
}
|
|
|
|
long setAMSG(dbCommon *prec)
|
|
{
|
|
(void)recGblSetSevrMsg(prec, READ_ALARM, MAJOR_ALARM, "%s", __func__);
|
|
prec->time.secPastEpoch = 0x12345678;
|
|
prec->time.nsec = 0x10203041;
|
|
return 0;
|
|
}
|
|
|
|
void testMetaMS()
|
|
{
|
|
testDiag("==== %s ====", __func__);
|
|
|
|
testqsrvWaitForLinkConnected("meta:inp:ms.INP");
|
|
|
|
{
|
|
QSrvWaitForLinkUpdate U("meta:inp:ms.INP");
|
|
testdbPutFieldOk("meta:src:ms.PROC", DBR_LONG, 0);
|
|
}
|
|
auto inp = (aiRecord*)testdbRecordPtr("meta:inp:ms");
|
|
|
|
dbScanLock((dbCommon*)inp);
|
|
|
|
testTrue(dbIsLinkConnected(&inp->inp)!=0);
|
|
|
|
testEq(dbGetLinkDBFtype(&inp->inp), DBF_DOUBLE);
|
|
|
|
testOk(inp->nsev==0 && inp->nsta==0
|
|
#ifdef HAS_ALARM_MESSAGE
|
|
&& inp->namsg[0]==0
|
|
#endif
|
|
,
|
|
"NSEVR=%u NSTAT=%u NAMSG=\"%s\"", inp->nsev, inp->nsta,
|
|
#ifdef HAS_ALARM_MESSAGE
|
|
inp->namsg
|
|
#else
|
|
"N/A"
|
|
#endif
|
|
);
|
|
|
|
long ret;
|
|
double val;
|
|
testTrue((ret=dbGetLink(&inp->inp, DBR_DOUBLE, &val, nullptr, nullptr))==0
|
|
&& val==0.0)<<" ret="<<ret<<" val="<<val;
|
|
|
|
testOk(inp->nsev==MAJOR_ALARM && inp->nsta==LINK_ALARM
|
|
#ifdef HAS_ALARM_MESSAGE
|
|
&& strcmp(inp->namsg, "setAMSG")==0
|
|
#endif
|
|
,
|
|
"NSEVR=%u NSTAT=%u NAMSG=\"%s\"", inp->nsev, inp->nsta,
|
|
#ifdef HAS_ALARM_MESSAGE
|
|
inp->namsg
|
|
#else
|
|
"N/A"
|
|
#endif
|
|
);
|
|
|
|
dbScanUnlock((dbCommon*)inp);
|
|
}
|
|
|
|
void testFwd()
|
|
{
|
|
testDiag("==== %s ====", __func__);
|
|
|
|
testqsrvWaitForLinkConnected("flnk:src.FLNK");
|
|
|
|
testdbGetFieldEqual("flnk:tgt", DBF_LONG, 0);
|
|
|
|
testdbPutFieldOk("flnk:src.PROC", DBF_LONG, 1);
|
|
|
|
{
|
|
auto prec = testdbRecordPtr("flnk:tgt");
|
|
TestMonitor mon("flnk:tgt", DBE_VALUE);
|
|
|
|
dbScanLock(prec);
|
|
while(((calcRecord*)prec)->val==0) {
|
|
dbScanUnlock(prec);
|
|
mon.wait();
|
|
dbScanLock(prec);
|
|
}
|
|
dbScanUnlock(prec);
|
|
}
|
|
|
|
testdbGetFieldEqual("flnk:tgt", DBF_LONG, 1);
|
|
}
|
|
|
|
void testAtomic()
|
|
{
|
|
testDiag("==== %s ====", __func__);
|
|
|
|
testqsrvWaitForLinkConnected("atomic:lnk:1.INP");
|
|
testqsrvWaitForLinkConnected("atomic:lnk:2.INP");
|
|
|
|
{
|
|
QSrvWaitForLinkUpdate A("atomic:lnk:1.INP");
|
|
QSrvWaitForLinkUpdate B("atomic:lnk:2.INP");
|
|
|
|
testdbPutFieldOk("atomic:src:1.PROC", DBR_LONG, 0);
|
|
}
|
|
|
|
epicsUInt32 expect;
|
|
{
|
|
auto src1(testdbRecordPtr("atomic:src:1"));
|
|
dbScanLock(src1);
|
|
expect = ((calcoutRecord*)src1)->val;
|
|
testEq(expect & ~0xff, 0u);
|
|
expect |= expect<<8u;
|
|
dbScanUnlock(src1);
|
|
}
|
|
|
|
testdbGetFieldEqual("atomic:lnk:out", DBF_ULONG, expect);
|
|
}
|
|
|
|
void testEnum()
|
|
{
|
|
testDiag("==== %s ====", __func__);
|
|
|
|
testqsrvWaitForLinkConnected("enum:src:b.OUT");
|
|
testqsrvWaitForLinkConnected("enum:src:s.OUT");
|
|
testqsrvWaitForLinkConnected("enum:tgt:s.INP");
|
|
testqsrvWaitForLinkConnected("enum:tgt:b.INP");
|
|
|
|
{
|
|
QSrvWaitForLinkUpdate A("enum:tgt:b.INP"); // last in chain...
|
|
|
|
testdbPutFieldOk("enum:src:b", DBR_STRING, "one");
|
|
}
|
|
|
|
testdbGetFieldEqual("enum:tgt:s", DBR_STRING, "one");
|
|
// not clear how to handle this case, where a string is
|
|
// read as DBR_USHORT, which is actually as DBF_ENUM
|
|
testTodoBegin("Not yet implemented");
|
|
testdbGetFieldEqual("enum:tgt:b", DBR_STRING, "one");
|
|
testTodoEnd();
|
|
}
|
|
|
|
void testNTNDArray()
|
|
{
|
|
testDiag("==== %s ====", __func__);
|
|
auto serv(ioc::server());
|
|
|
|
auto ntndarray(server::SharedPV::buildReadonly());
|
|
auto top = nt::NTNDArray{}.create();
|
|
top["value->floatValue"] = shared_array<const float>({0.0});
|
|
ntndarray.open(top);
|
|
serv.addPV("source:ntndarray", ntndarray);
|
|
|
|
testqsrvWaitForLinkConnected("target:ntndarray_inp.INP");
|
|
|
|
{
|
|
QSrvWaitForLinkUpdate A("target:ntndarray_inp.INP");
|
|
auto update = ntndarray.fetch().cloneEmpty();
|
|
update["value->floatValue"] = shared_array<const float>({0.1, 0.2, 0.3, 0.4, 0.5});
|
|
ntndarray.post(update);
|
|
}
|
|
static const epicsFloat32 expected_float_A[] = {0.1, 0.2, 0.3, 0.4, 0.5};
|
|
testdbGetArrFieldEqual("target:ntndarray_inp", DBF_FLOAT, 5, 5, expected_float_A);
|
|
|
|
{
|
|
QSrvWaitForLinkUpdate B("target:ntndarray_inp.INP");
|
|
auto update = ntndarray.fetch().cloneEmpty();
|
|
update["value->floatValue"] = shared_array<const float>({0.6, 0.7, 0.8, 0.9, 0.91});
|
|
ntndarray.post(update);
|
|
}
|
|
static const epicsFloat32 expected_float_B[] = {0.6, 0.7, 0.8, 0.9, 0.91};
|
|
testdbGetArrFieldEqual("target:ntndarray_inp", DBF_FLOAT, 5, 5, expected_float_B);
|
|
|
|
serv.removePV("source:ntndarray");
|
|
ntndarray.close();
|
|
}
|
|
|
|
|
|
} // namespace
|
|
|
|
extern "C" void testioc_registerRecordDeviceDriver(struct dbBase *);
|
|
|
|
MAIN(testpvalink)
|
|
{
|
|
testPlan(102);
|
|
testSetup();
|
|
pvxs::logger_config_env();
|
|
|
|
try
|
|
{
|
|
TestIOC IOC;
|
|
|
|
testdbReadDatabase("testioc.dbd", NULL, NULL);
|
|
testioc_registerRecordDeviceDriver(pdbbase);
|
|
registryFunctionAdd("setAMSG", (REGISTRYFUNCTION)&setAMSG);
|
|
testdbReadDatabase("testpvalink.db", NULL, NULL);
|
|
|
|
IOC.init();
|
|
|
|
testGet();
|
|
testFieldLinks();
|
|
testProc();
|
|
testSevr();
|
|
testPut();
|
|
testStrings();
|
|
testToFromString();
|
|
testArrays();
|
|
testStringArray();
|
|
testPutAsync();
|
|
testDisconnect();
|
|
testMeta();
|
|
testMetaMS();
|
|
testFwd();
|
|
testAtomic();
|
|
testEnum();
|
|
testNTNDArray();
|
|
}
|
|
catch (std::exception &e)
|
|
{
|
|
testFail("Unexpected exception: %s", e.what());
|
|
}
|
|
// call epics atexits explicitly to handle older base w/o de-init hooks
|
|
epicsExitCallAtExits();
|
|
cleanup_for_valgrind();
|
|
return testDone();
|
|
}
|