pvalink: porting part 3

add pvalink json schema
avoid JSON5 in testpvalink for portability.
fixup build with pvalink
trap bad_weak_ptr open during dtor
  Not sure why this is happening, but need not be CRIT.
c++11, cleanup, and notes
fix pvalink test sync
fix test cleanup on exit
pvalink disconnected link is always INVALID
pvalink logging
pvalink capture Disconnect time
pvalink eliminate providerName
  restrict local to dbChannelTest()
  aka. no qsrv groups
pvalink onTypeChange when attaching link to existing channel
pvalink eliminate unused Connecting state
pvalink add InstCounter
pvalink AfterPut can be const
pvalink add atomic jlif flag
include epicsStdio.h later
  avoid #define printf troubles
assert cleanup state on exit
pvalink add newer lset functions
test link disconnect
testpvalink redo testPutAsync()
pvalink fill out meta-data fetch
pvalink fix FLNK
pvalink cache putReq
pvalink test atomic monitor
pvalink test enum handling
pvalink handle scalar read of empty array
  make it well defined anyway...
pvalink test array of strings
handle db_add_event() failure
handle record._options.DBE
This commit is contained in:
Michael Davidsaver
2023-08-23 10:50:33 +02:00
parent 5f48325890
commit 6d1216daad
17 changed files with 992 additions and 398 deletions
+318 -60
View File
@@ -1,14 +1,31 @@
/*
* 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 <aiRecord.h>
#include <aaoRecord.h>
#include <aaiRecord.h>
#include <calcRecord.h>
#include <calcoutRecord.h>
#include <longinRecord.h>
#include <longoutRecord.h>
#include <stringoutRecord.h>
#define PVXS_ENABLE_EXPERT_API
//#include <pv/qsrv.h>
//#include "utilities.h"
#include "dblocker.h"
#include <pvxs/client.h>
#include "pvxs/iochooks.h"
#include <pvxs/nt.h>
#include <pvxs/sharedpv.h>
#include "pvalink.h"
#include "testioc.h"
//#include "pv/qsrv.h"
@@ -16,16 +33,24 @@
using namespace pvxs::ioc;
using namespace pvxs;
namespace
{
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");
while (!dbIsLinkConnected(&i1->inp))
testqsrvWaitForLinkEvent(&i1->inp);
testqsrvWaitForLinkConnected(&i1->inp);
testdbGetFieldEqual("target:i.VAL", DBF_LONG, 42L);
@@ -39,8 +64,7 @@ namespace
testdbPutFieldOk("src:i1.INP", DBF_STRING, "{\"pva\":\"target:ai\"}");
while (!dbIsLinkConnected(&i1->inp))
testqsrvWaitForLinkEvent(&i1->inp);
testqsrvWaitForLinkConnected(&i1->inp);
testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 42L); // changing link doesn't automatically process
@@ -58,8 +82,7 @@ namespace
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);
testqsrvWaitForLinkConnected(&i1->inp);
testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 4L); // changing link doesn't automatically process
@@ -76,20 +99,24 @@ namespace
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\"}}";
testdbPutArrFieldOk("src:i1.INP$", DBF_CHAR, pv_name.length()+1, pv_name.c_str());
{
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();
}
while (!dbIsLinkConnected(&i1->inp))
testqsrvWaitForLinkEvent(&i1->inp);
// Link should read old value again
// Link should read current value of target.
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);
{
QSrvWaitForLinkUpdate C(&i1->inp);
testdbPutFieldOk("target:ai", DBF_FLOAT, 5.0);
}
// now it's changed
testdbGetFieldEqual("src:i1.VAL", DBF_LONG, 5L);
@@ -104,8 +131,7 @@ namespace
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);
testqsrvWaitForLinkConnected(&i1->inp);
testdbPutFieldOk("target:ai.LOLO", DBF_FLOAT, 5.0);
testdbPutFieldOk("target:ai.LLSV", DBF_STRING, "MAJOR");
@@ -117,8 +143,7 @@ namespace
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);
testqsrvWaitForLinkConnected(&i1->inp);
testdbPutFieldOk("src:i1.PROC", DBF_LONG, 1L);
testdbGetFieldEqual("src:i1.SEVR", DBF_SHORT, epicsSevMajor);
@@ -126,14 +151,16 @@ namespace
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);
testqsrvWaitForLinkConnected(&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);
{
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);
}
@@ -144,17 +171,16 @@ namespace
longoutRecord *o2 = (longoutRecord *)testdbRecordPtr("src:o2");
while (!dbIsLinkConnected(&o2->out))
testqsrvWaitForLinkEvent(&o2->out);
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\"}");
testdbPutFieldOk("src:o2.VAL", DBF_LONG, 14L);
testqsrvWaitForLinkEvent(&o2->out);
testqsrvWaitForLinkEvent(&o2->out);
{
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);
@@ -170,71 +196,294 @@ namespace
testdbPutFieldOk("target:str1.PROC", DBF_LONG, 1L);
testdbGetFieldEqual("target:str1", DBF_STRING, "bar");
testdbPutFieldOk("src:str.OUT", DBF_STRING, "{pva : \"target:str2\"}");
testdbPutFieldOk("src:str.OUT", DBF_STRING, R"({"pva" : "target:str2"})");
while (!dbIsLinkConnected(&so->out))
testqsrvWaitForLinkEvent(&so->out);
testqsrvWaitForLinkConnected(&so->out);
testdbPutFieldOk("src:str.PROC", DBF_LONG, 1L);
testqsrvWaitForLinkEvent(&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()
{
aaoRecord *aao = (aaoRecord *)testdbRecordPtr("source:aao");
auto aai_inp = (aaiRecord *)testdbRecordPtr("target:aai_inp");
testDiag("==== testArrays ====");
static const epicsFloat32 input_arr[] = {1, 2, -1, 1.2, 0};
testdbPutArrFieldOk("source:aao", DBR_FLOAT, 5, input_arr);
{
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);
testqsrvWaitForLinkEvent(&aao->out);
testqsrvWaitForLinkEvent(&aao->out);
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()
{
#ifdef USE_MULTILOCK
testDiag("==== testPutAsync ====");
longoutRecord *trig = (longoutRecord *)testdbRecordPtr("async:trig");
auto trig = (longoutRecord *)testdbRecordPtr("async:trig");
auto seq = (calcRecord *)testdbRecordPtr("async:seq");
while (!dbIsLinkConnected(&trig->out))
testqsrvWaitForLinkEvent(&trig->out);
testqsrvWaitForLinkConnected(&trig->out);
testMonitor *done = testMonitorCreate("async:after", DBE_VALUE, 0);
TestMonitor done("async:seq", DBE_VALUE, 0);
testdbPutFieldOk("async:trig.PROC", DBF_LONG, 1);
testMonitorWait(done);
dbScanLock((dbCommon*)seq);
while(seq->val < 2) {
dbScanUnlock((dbCommon*)seq);
done.wait();
dbScanLock((dbCommon*)seq);
}
dbScanUnlock((dbCommon*)seq);
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
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;
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;
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;
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);
}
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 implimented");
testdbGetFieldEqual("enum:tgt:b", DBR_STRING, "one");
testTodoEnd();
}
} // namespace
extern "C" void testioc_registerRecordDeviceDriver(struct dbBase *);
MAIN(testpvalink)
{
testPlan(49);
testPlan(92);
testSetup();
pvxs::logger_config_env();
try
{
@@ -251,8 +500,15 @@ MAIN(testpvalink)
testSevr();
testPut();
testStrings();
testToFromString();
testArrays();
(void)testPutAsync;
testStringArray();
testPutAsync();
testDisconnect();
testMeta();
testFwd();
testAtomic();
testEnum();
testqsrvShutdownOk();
IOC.shutdown();
testqsrvCleanup();
@@ -261,6 +517,8 @@ MAIN(testpvalink)
{
testFail("Unexpected exception: %s", e.what());
}
// call epics atexits explicitly as workaround for c++ static dtor issues...
epicsExit(testDone());
// call epics atexits explicitly to handle older base w/o de-init hooks
epicsExitCallAtExits();
cleanup_for_valgrind();
return testDone();
}