6d1216daad
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
919 lines
34 KiB
C++
919 lines
34 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 <atomic>
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <testMain.h>
|
|
#include <asDbLib.h>
|
|
#include <dbAccess.h>
|
|
#include <dbLock.h>
|
|
#include <epicsTime.h>
|
|
#include <epicsExit.h>
|
|
#include <asTrapWrite.h>
|
|
#include <generalTimeSup.h>
|
|
|
|
#include "testioc.h"
|
|
#include "utilpvt.h"
|
|
|
|
#if EPICS_VERSION_INT >= VERSION_INT(3, 15, 0, 2)
|
|
# define HAVE_lsi
|
|
#endif
|
|
|
|
extern "C" {
|
|
extern int testioc_registerRecordDeviceDriver(struct dbBase*);
|
|
}
|
|
|
|
using namespace pvxs;
|
|
|
|
namespace {
|
|
|
|
std::atomic<bool> timeSim{true};
|
|
std::atomic<uint32_t> testTimeSec{12345678};
|
|
|
|
int testTimeCurrent(epicsTimeStamp *pDest)
|
|
{
|
|
if(timeSim) {
|
|
pDest->secPastEpoch = testTimeSec;
|
|
pDest->nsec = 102030;
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
void forceUTAG(const char *rec)
|
|
{
|
|
dbCommon *prec = testdbRecordPtr(rec);
|
|
dbScanLock(prec);
|
|
#ifdef DBR_UTAG
|
|
prec->utag = 42;
|
|
#endif
|
|
dbScanUnlock(prec);
|
|
}
|
|
|
|
void checkUTAG(Value& v, int32_t expect=42)
|
|
{
|
|
#ifdef DBR_UTAG
|
|
auto utag = v["timeStamp.userTag"];
|
|
if(!utag.isMarked() || utag.as<int32_t>()!=expect)
|
|
testFail("userTag not set");
|
|
utag = 0;
|
|
utag.unmark();
|
|
#endif
|
|
}
|
|
|
|
void testGetScalar()
|
|
{
|
|
testDiag("%s", __func__);
|
|
TestClient ctxt;
|
|
|
|
testdbPutFieldOk("test:ai.PROC", DBF_LONG, 0);
|
|
forceUTAG("test:ai");
|
|
|
|
auto val(ctxt.get("test:ai").exec()->wait(5.0));
|
|
checkUTAG(val);
|
|
testFldEq<uint32_t>(val, "timeStamp.secondsPastEpoch", testTimeSec + POSIX_TIME_AT_EPICS_EPOCH);
|
|
testStrEq(std::string(SB()<<val.format()),
|
|
"struct \"epics:nt/NTScalar:1.0\" {\n"
|
|
" double value = 42.2\n"
|
|
" struct \"alarm_t\" {\n"
|
|
" int32_t severity = 2\n"
|
|
" int32_t status = 1\n"
|
|
" string message = \"HIGH\"\n"
|
|
" } alarm\n"
|
|
" struct \"time_t\" {\n"
|
|
" int64_t secondsPastEpoch = 643497678\n"
|
|
" int32_t nanoseconds = 102030\n"
|
|
" int32_t userTag = 0\n"
|
|
" } timeStamp\n"
|
|
" struct {\n"
|
|
" double limitLow = 0\n"
|
|
" double limitHigh = 100\n"
|
|
" string description = \"Analog input\"\n"
|
|
" string units = \"arb\"\n"
|
|
" int32_t precision = 1\n"
|
|
" struct \"enum_t\" {\n"
|
|
" int32_t index = 6\n"
|
|
" string[] choices = {7}[\"Default\", \"String\", \"Binary\", \"Decimal\", \"Hex\", \"Exponential\", \"Engineering\"]\n"
|
|
" } form\n"
|
|
" } display\n"
|
|
" struct {\n"
|
|
" double limitLow = 0\n"
|
|
" double limitHigh = 100\n"
|
|
" double minStep = 0\n"
|
|
" } control\n"
|
|
" struct {\n"
|
|
" bool active = false\n"
|
|
" double lowAlarmLimit = 0\n"
|
|
" double lowWarningLimit = 4\n"
|
|
" double highWarningLimit = 6\n"
|
|
" double highAlarmLimit = 100\n"
|
|
" int32_t lowAlarmSeverity = 0\n"
|
|
" int32_t lowWarningSeverity = 0\n"
|
|
" int32_t highWarningSeverity = 0\n"
|
|
" int32_t highAlarmSeverity = 0\n"
|
|
" double hysteresis = 0\n"
|
|
" } valueAlarm\n"
|
|
"}\n")<<" fetch VAL w/ meta-data";
|
|
testStrEq(std::string(SB()<<val.format().delta()),
|
|
"value double = 42.2\n"
|
|
"alarm.severity int32_t = 2\n"
|
|
"alarm.status int32_t = 1\n"
|
|
"alarm.message string = \"HIGH\"\n"
|
|
"timeStamp.secondsPastEpoch int64_t = 643497678\n"
|
|
"timeStamp.nanoseconds int32_t = 102030\n"
|
|
"display.limitLow double = 0\n"
|
|
"display.limitHigh double = 100\n"
|
|
"display.description string = \"Analog input\"\n"
|
|
"display.units string = \"arb\"\n"
|
|
"display.precision int32_t = 1\n"
|
|
"display.form.index int32_t = 6\n"
|
|
"display.form.choices string[] = {7}[\"Default\", \"String\", \"Binary\", \"Decimal\", \"Hex\", \"Exponential\", \"Engineering\"]\n"
|
|
"control.limitLow double = 0\n"
|
|
"control.limitHigh double = 100\n"
|
|
"valueAlarm.lowAlarmLimit double = 0\n"
|
|
"valueAlarm.lowWarningLimit double = 4\n"
|
|
"valueAlarm.highWarningLimit double = 6\n"
|
|
"valueAlarm.highAlarmLimit double = 100\n"
|
|
)<<" fetch VAL w/ meta-data. delta output";
|
|
|
|
val = ctxt.get("test:ai.DESC").exec()->wait(5.0);
|
|
checkUTAG(val);
|
|
testStrEq(std::string(SB()<<val.format()),
|
|
"struct \"epics:nt/NTScalar:1.0\" {\n"
|
|
" string value = \"Analog input\"\n"
|
|
" struct \"alarm_t\" {\n"
|
|
" int32_t severity = 2\n"
|
|
" int32_t status = 1\n"
|
|
" string message = \"HIGH\"\n"
|
|
" } alarm\n"
|
|
" struct \"time_t\" {\n"
|
|
" int64_t secondsPastEpoch = 643497678\n"
|
|
" int32_t nanoseconds = 102030\n"
|
|
" int32_t userTag = 0\n"
|
|
" } timeStamp\n"
|
|
" struct {\n"
|
|
" string description = \"Analog input\"\n"
|
|
" string units = \"\"\n"
|
|
" } display\n"
|
|
"}\n");
|
|
|
|
val = ctxt.get("test:ai.SCAN").exec()->wait(5.0);
|
|
checkUTAG(val);
|
|
testStrEq(std::string(SB()<<val.format()),
|
|
"struct \"epics:nt/NTEnum:1.0\" {\n"
|
|
" struct \"enum_t\" {\n"
|
|
" int32_t index = 0\n"
|
|
" string[] choices = {10}[\"Passive\", \"Event\", \"I/O Intr\", \"10 second\", \"5 second\", \"2 second\", \"1 second\", \".5 second\", \".2 second\", \".1 second\"]\n"
|
|
" } value\n"
|
|
" struct \"alarm_t\" {\n"
|
|
" int32_t severity = 2\n"
|
|
" int32_t status = 1\n"
|
|
" string message = \"HIGH\"\n"
|
|
" } alarm\n"
|
|
" struct \"time_t\" {\n"
|
|
" int64_t secondsPastEpoch = 643497678\n"
|
|
" int32_t nanoseconds = 102030\n"
|
|
" int32_t userTag = 0\n"
|
|
" } timeStamp\n"
|
|
" struct {\n"
|
|
" string description = \"Analog input\"\n"
|
|
" } display\n"
|
|
"}\n");
|
|
|
|
val = ctxt.get("test:ai.RVAL").exec()->wait(5.0);
|
|
checkUTAG(val);
|
|
testStrEq(std::string(SB()<<val.format()),
|
|
"struct \"epics:nt/NTScalar:1.0\" {\n"
|
|
" int32_t value = 123\n"
|
|
" struct \"alarm_t\" {\n"
|
|
" int32_t severity = 2\n"
|
|
" int32_t status = 1\n"
|
|
" string message = \"HIGH\"\n"
|
|
" } alarm\n"
|
|
" struct \"time_t\" {\n"
|
|
" int64_t secondsPastEpoch = 643497678\n"
|
|
" int32_t nanoseconds = 102030\n"
|
|
" int32_t userTag = 0\n"
|
|
" } timeStamp\n"
|
|
" struct {\n"
|
|
" int32_t limitLow = -2147483648\n"
|
|
" int32_t limitHigh = 2147483647\n"
|
|
" string description = \"Analog input\"\n"
|
|
" string units = \"\"\n"
|
|
" int32_t precision = 0\n"
|
|
" struct \"enum_t\" {\n"
|
|
" int32_t index = 0\n"
|
|
" string[] choices = {7}[\"Default\", \"String\", \"Binary\", \"Decimal\", \"Hex\", \"Exponential\", \"Engineering\"]\n"
|
|
" } form\n"
|
|
" } display\n"
|
|
" struct {\n"
|
|
" int32_t limitLow = -2147483648\n"
|
|
" int32_t limitHigh = 2147483647\n"
|
|
" int32_t minStep = 0\n"
|
|
" } control\n"
|
|
" struct {\n"
|
|
" bool active = false\n"
|
|
" int32_t lowAlarmLimit = 0\n"
|
|
" int32_t lowWarningLimit = 0\n"
|
|
" int32_t highWarningLimit = 0\n"
|
|
" int32_t highAlarmLimit = 0\n"
|
|
" int32_t lowAlarmSeverity = 0\n"
|
|
" int32_t lowWarningSeverity = 0\n"
|
|
" int32_t highWarningSeverity = 0\n"
|
|
" int32_t highAlarmSeverity = 0\n"
|
|
" double hysteresis = 0\n"
|
|
" } valueAlarm\n"
|
|
"}\n");
|
|
|
|
val = ctxt.get("test:ai.FLNK").exec()->wait(5.0);
|
|
checkUTAG(val);
|
|
#if EPICS_VERSION_INT < VERSION_INT(3, 16, 0, 0)
|
|
if(val["value"].as<std::string>()=="0")
|
|
val["value"] = "";
|
|
#endif
|
|
testStrEq(std::string(SB()<<val.format()),
|
|
"struct \"epics:nt/NTScalar:1.0\" {\n"
|
|
" string value = \"\"\n"
|
|
" struct \"alarm_t\" {\n"
|
|
" int32_t severity = 2\n"
|
|
" int32_t status = 1\n"
|
|
" string message = \"HIGH\"\n"
|
|
" } alarm\n"
|
|
" struct \"time_t\" {\n"
|
|
" int64_t secondsPastEpoch = 643497678\n"
|
|
" int32_t nanoseconds = 102030\n"
|
|
" int32_t userTag = 0\n"
|
|
" } timeStamp\n"
|
|
" struct {\n"
|
|
" string description = \"Analog input\"\n"
|
|
" string units = \"\"\n"
|
|
" } display\n"
|
|
"}\n");
|
|
|
|
testFldEq<std::string>(ctxt.get("test:this:is:a:really:really:long:record:name.NAME").exec()->wait(5000.0),
|
|
"value", "test:this:is:a:really:really:long:record:name");
|
|
|
|
testFldEq<std::string>(ctxt.get("test:this:is:a:really:really:long:record:name.NAME$").exec()->wait(5.0),
|
|
"value", "test:this:is:a:really:really:long:record:name");
|
|
|
|
testFldEq<std::string>(ctxt.get("test:src.INP").exec()->wait(5.0),
|
|
"value", "test:this:is:a:really:really:long:record:name NPP NMS");
|
|
|
|
testFldEq<std::string>(ctxt.get("test:src.INP$").exec()->wait(5.0),
|
|
"value", "test:this:is:a:really:really:long:record:name NPP NMS");
|
|
|
|
testdbPutFieldOk("test:nsec.PROC", DBF_LONG, 0);
|
|
forceUTAG("test:nsec"); // forced value should be ignored
|
|
|
|
val = ctxt.get("test:nsec").exec()->wait(5.0);
|
|
#ifdef DBR_UTAG
|
|
testFldEq(val, "timeStamp.userTag", 142);
|
|
val["timeStamp.userTag"].unmark();
|
|
#else
|
|
testSkip(1, "not UTAG");
|
|
#endif
|
|
testStrEq(std::string(SB()<<val.format().delta()),
|
|
"value int32_t = 100\n"
|
|
"alarm.severity int32_t = 0\n"
|
|
"alarm.status int32_t = 0\n"
|
|
"alarm.message string = \"\"\n"
|
|
"timeStamp.secondsPastEpoch int64_t = 643497678\n"
|
|
"timeStamp.nanoseconds int32_t = 101888\n"
|
|
"display.limitLow int32_t = 0\n"
|
|
"display.limitHigh int32_t = 0\n"
|
|
"display.description string = \"\"\n"
|
|
"display.units string = \"\"\n"
|
|
"display.form.index int32_t = 0\n"
|
|
"display.form.choices string[] = {7}[\"Default\", \"String\", \"Binary\", \"Decimal\", \"Hex\", \"Exponential\", \"Engineering\"]\n"
|
|
"control.limitLow int32_t = 0\n"
|
|
"control.limitHigh int32_t = 0\n"
|
|
"valueAlarm.lowAlarmLimit int32_t = 0\n"
|
|
"valueAlarm.lowWarningLimit int32_t = 0\n"
|
|
"valueAlarm.highWarningLimit int32_t = 0\n"
|
|
"valueAlarm.highAlarmLimit int32_t = 0\n");
|
|
}
|
|
|
|
void testLongString()
|
|
{
|
|
testDiag("%s", __func__);
|
|
TestClient ctxt;
|
|
|
|
ctxt.put("test:long:str:wf")
|
|
.set("value", "test:this:is:a:really:really:long:string:value")
|
|
.exec()->wait(5.0);
|
|
|
|
auto val(ctxt.get("test:long:str:wf").exec()->wait(5.0));
|
|
testEq(val["value"].as<std::string>(), "test:this:is:a:really:really:long:string:value");
|
|
|
|
#ifdef HAVE_lsi
|
|
ctxt.put("test:long:str:lsi.VAL")
|
|
.set("value", "test:this:is:a:really:really:long:string:value")
|
|
.exec()->wait(5.0);
|
|
|
|
val = ctxt.get("test:long:str:lsi.VAL").exec()->wait(5.0);
|
|
testEq(val["value"].as<std::string>(), "test:this:is:a:really:really:long:string:value");
|
|
|
|
#else
|
|
testSkip(1, "No lsiRecord");
|
|
#endif
|
|
}
|
|
|
|
void testGetArray()
|
|
{
|
|
testDiag("%s", __func__);
|
|
TestClient ctxt;
|
|
|
|
{
|
|
const double dbl[] = {1.0, 2.2, 3.0};
|
|
testdbPutArrFieldOk("test:wf:f64", DBF_DOUBLE, NELEMENTS(dbl), dbl);
|
|
const epicsInt32 lng[] = {4, 5, 6, 7};
|
|
testdbPutArrFieldOk("test:wf:i32", DBF_LONG, NELEMENTS(lng), lng);
|
|
char str[MAX_STRING_SIZE*3u] = {};
|
|
strcpy(&str[MAX_STRING_SIZE*0u], "one");
|
|
strcpy(&str[MAX_STRING_SIZE*1u], "two");
|
|
strcpy(&str[MAX_STRING_SIZE*2u], "three");
|
|
testdbPutArrFieldOk("test:wf:s", DBF_STRING, 3u, str);
|
|
}
|
|
|
|
forceUTAG("test:wf:f64");
|
|
forceUTAG("test:wf:i32");
|
|
forceUTAG("test:wf:s");
|
|
|
|
auto val(ctxt.get("test:wf:f64").exec()->wait(5.0));
|
|
checkUTAG(val);
|
|
testStrEq(std::string(SB()<<val.format()),
|
|
"struct \"epics:nt/NTScalarArray:1.0\" {\n"
|
|
" double[] value = {3}[1, 2.2, 3]\n"
|
|
" struct \"alarm_t\" {\n"
|
|
" int32_t severity = 0\n"
|
|
" int32_t status = 0\n"
|
|
" string message = \"\"\n"
|
|
" } alarm\n"
|
|
" struct \"time_t\" {\n"
|
|
" int64_t secondsPastEpoch = 643497678\n"
|
|
" int32_t nanoseconds = 102030\n"
|
|
" int32_t userTag = 0\n"
|
|
" } timeStamp\n"
|
|
" struct {\n"
|
|
" double limitLow = 0\n"
|
|
" double limitHigh = 0\n"
|
|
" string description = \"\"\n"
|
|
" string units = \"\"\n"
|
|
" int32_t precision = 0\n"
|
|
" struct \"enum_t\" {\n"
|
|
" int32_t index = 0\n"
|
|
" string[] choices = {7}[\"Default\", \"String\", \"Binary\", \"Decimal\", \"Hex\", \"Exponential\", \"Engineering\"]\n"
|
|
" } form\n"
|
|
" } display\n"
|
|
" struct {\n"
|
|
" double limitLow = 0\n"
|
|
" double limitHigh = 0\n"
|
|
" double minStep = 0\n"
|
|
" } control\n"
|
|
" struct {\n"
|
|
" bool active = false\n"
|
|
" double lowAlarmLimit = 0\n"
|
|
" double lowWarningLimit = 0\n"
|
|
" double highWarningLimit = 0\n"
|
|
" double highAlarmLimit = 0\n"
|
|
" int32_t lowAlarmSeverity = 0\n"
|
|
" int32_t lowWarningSeverity = 0\n"
|
|
" int32_t highWarningSeverity = 0\n"
|
|
" int32_t highAlarmSeverity = 0\n"
|
|
" double hysteresis = 0\n"
|
|
" } valueAlarm\n"
|
|
"}\n");
|
|
|
|
val = ctxt.get("test:wf:i32").exec()->wait(5.0);
|
|
checkUTAG(val);
|
|
testStrEq(std::string(SB()<<val.format().delta()),
|
|
"value int32_t[] = {4}[4, 5, 6, 7]\n"
|
|
"alarm.severity int32_t = 0\n"
|
|
"alarm.status int32_t = 0\n"
|
|
"alarm.message string = \"\"\n"
|
|
"timeStamp.secondsPastEpoch int64_t = 643497678\n"
|
|
"timeStamp.nanoseconds int32_t = 102030\n"
|
|
"display.limitLow int32_t = 0\n"
|
|
"display.limitHigh int32_t = 0\n"
|
|
"display.description string = \"\"\n"
|
|
"display.units string = \"\"\n"
|
|
"display.form.index int32_t = 0\n"
|
|
"display.form.choices string[] = {7}[\"Default\", \"String\", \"Binary\", \"Decimal\", \"Hex\", \"Exponential\", \"Engineering\"]\n"
|
|
"control.limitLow int32_t = 0\n"
|
|
"control.limitHigh int32_t = 0\n");
|
|
|
|
val = ctxt.get("test:wf:s").exec()->wait(5.0);
|
|
checkUTAG(val);
|
|
testStrEq(std::string(SB()<<val.format().delta()),
|
|
"value string[] = {3}[\"one\", \"two\", \"three\"]\n"
|
|
"alarm.severity int32_t = 0\n"
|
|
"alarm.status int32_t = 0\n"
|
|
"alarm.message string = \"\"\n"
|
|
"timeStamp.secondsPastEpoch int64_t = 643497678\n"
|
|
"timeStamp.nanoseconds int32_t = 102030\n"
|
|
"display.description string = \"\"\n"
|
|
"display.units string = \"\"\n");
|
|
|
|
val = ctxt.get("test:wf:i32.[1:2]").exec()->wait(5.0);
|
|
testStrEq(std::string(SB()<<val["value"].format()),
|
|
"int32_t[] = {2}[5, 6]\n");
|
|
}
|
|
|
|
void testPut()
|
|
{
|
|
testDiag("%s", __func__);
|
|
TestClient ctxt;
|
|
|
|
ctxt.put("test:ai").set("value", 53.2).exec()->wait(5.0);
|
|
testdbGetFieldEqual("test:ai", DBF_DOUBLE, 53.2);
|
|
|
|
ctxt.put("test:ai.DESC").set("value", "testing").exec()->wait(5.0);
|
|
testdbGetFieldEqual("test:ai.DESC", DBF_STRING, "testing");
|
|
|
|
ctxt.put("test:bo").set("value.index", 1).exec()->wait(5.0);
|
|
testdbGetFieldEqual("test:bo", DBF_STRING, "One");
|
|
|
|
{
|
|
shared_array<const int32_t> arr({1, -3, 5, -7});
|
|
ctxt.put("test:wf:i32").set("value", arr).exec()->wait(5.0);
|
|
testdbGetArrFieldEqual("test:wf:i32", DBR_LONG, arr.size(), arr.size(), arr.data());
|
|
}
|
|
{
|
|
shared_array<const double> arr({2.0, 3.2, 5.6});
|
|
ctxt.put("test:wf:f64").set("value", arr).exec()->wait(5.0);
|
|
testdbGetArrFieldEqual("test:wf:f64", DBR_DOUBLE, arr.size(), arr.size(), arr.data());
|
|
}
|
|
{
|
|
shared_array<const std::string> arr({"x", "why", "that last one"});
|
|
ctxt.put("test:wf:s").set("value", arr).exec()->wait(5.0);
|
|
|
|
char str[MAX_STRING_SIZE*3u] = {};
|
|
strcpy(&str[MAX_STRING_SIZE*0u], "x");
|
|
strcpy(&str[MAX_STRING_SIZE*1u], "why");
|
|
strcpy(&str[MAX_STRING_SIZE*2u], "that last one");
|
|
testdbGetArrFieldEqual("test:wf:s", DBR_STRING, 3, 3, str);
|
|
}
|
|
|
|
testdbGetFieldEqual("test:ai2.INP", DBF_STRING, "test:ai NPP NMS");
|
|
|
|
ctxt.put("test:ai2.INP").set("value", "").exec()->wait(5.0);
|
|
testdbGetFieldEqual("test:ai2.INP", DBF_STRING, "");
|
|
|
|
ctxt.put("test:ai2.INP").set("value", "test:ai").exec()->wait(5.0);
|
|
testdbGetFieldEqual("test:ai2.INP", DBF_STRING, "test:ai NPP NMS");
|
|
|
|
ctxt.put("test:ai2.INP$").set("value", "test:this:is:a:really:really:long:record:name").exec()->wait(5.0);
|
|
testdbGetFieldEqual("test:ai2.INP", DBF_STRING, "test:this:is:a:really:really:long:recor");
|
|
{
|
|
const char expect[] = "test:this:is:a:really:really:long:record:name NPP NMS";
|
|
testdbGetArrFieldEqual("test:ai2.INP$", DBR_CHAR, NELEMENTS(expect), NELEMENTS(expect), expect);
|
|
}
|
|
|
|
ctxt.put("test:ai2.INP$").set("value", "").exec()->wait(5.0);
|
|
testdbGetFieldEqual("test:ai2.INP", DBF_STRING, "");
|
|
|
|
try{
|
|
ctxt.put("test:ai.STAT").set("value.index", 1).exec()->wait(5.0);
|
|
testFail("test:ai.STAT was writable");
|
|
}catch(pvxs::client::RemoteError& e){
|
|
std::string msg(e.what());
|
|
testTrue(msg.find("noMod")!=msg.npos || msg.find("511")!=msg.npos)
|
|
<<" expected RemoteError: "<<msg;
|
|
}
|
|
|
|
try{
|
|
ctxt.put("test:ro").set("value", 42).exec()->wait(5.0);
|
|
testFail("test:ro was writable");
|
|
}catch(pvxs::client::RemoteError& e){
|
|
testStrEq(e.what(), "Put not permitted");
|
|
}
|
|
|
|
try{
|
|
ctxt.put("test:disp").set("value", 42).exec()->wait(5.0);
|
|
testFail("test:disp was writable");
|
|
}catch(pvxs::client::RemoteError& e){
|
|
testStrMatch(".*Field Disabled.*", e.what());
|
|
}
|
|
}
|
|
|
|
void testGetPut64()
|
|
{
|
|
#ifdef DBR_UINT64
|
|
testDiag("%s", __func__);
|
|
TestClient ctxt;
|
|
|
|
{
|
|
const epicsUInt64 dbl[] = {11111111111111111, 222222222222222, 3333333333333};
|
|
testdbPutArrFieldOk("test:wf:u64", DBF_UINT64, NELEMENTS(dbl), dbl);
|
|
}
|
|
|
|
testdbPutFieldOk("test:i64.PROC", DBF_LONG, 0);
|
|
forceUTAG("test:i64");
|
|
forceUTAG("test:wf:u64");
|
|
|
|
auto val(ctxt.get("test:i64").exec()->wait(5.0));
|
|
checkUTAG(val);
|
|
testFldEq<uint64_t>(val, "value", 12345678901234);
|
|
|
|
val = ctxt.get("test:wf:u64").exec()->wait(5.0);
|
|
checkUTAG(val);
|
|
testStrEq(std::string(SB()<<val.format().delta()),
|
|
"value uint64_t[] = {3}[11111111111111111, 222222222222222, 3333333333333]\n"
|
|
"alarm.severity int32_t = 0\n"
|
|
"alarm.status int32_t = 0\n"
|
|
"alarm.message string = \"\"\n"
|
|
"timeStamp.secondsPastEpoch int64_t = 643497678\n"
|
|
"timeStamp.nanoseconds int32_t = 102030\n"
|
|
"display.limitLow uint64_t = 0\n"
|
|
"display.limitHigh uint64_t = 0\n"
|
|
"display.description string = \"\"\n"
|
|
"display.units string = \"\"\n"
|
|
"display.form.index int32_t = 0\n"
|
|
"display.form.choices string[] = {7}[\"Default\", \"String\", \"Binary\", \"Decimal\", \"Hex\", \"Exponential\", \"Engineering\"]\n"
|
|
"control.limitLow uint64_t = 0\n"
|
|
"control.limitHigh uint64_t = 0\n");
|
|
|
|
|
|
ctxt.put("test:i64").set<uint64_t>("value", 12345678901230).exec()->wait(5.0);
|
|
testdbGetFieldEqual("test:i64", DBF_INT64, 12345678901230ll);
|
|
|
|
{
|
|
shared_array<const uint64_t> arr({5555555555555555, 66666666666666});
|
|
ctxt.put("test:wf:u64").set("value", arr).exec()->wait(5.0);
|
|
testdbGetArrFieldEqual("test:wf:u64", DBR_UINT64, arr.size(), arr.size(), arr.data());
|
|
}
|
|
|
|
#else // DBR_UINT64
|
|
testSkip(6, "No INT64");
|
|
#endif // DBR_UINT64
|
|
}
|
|
|
|
void testPutProc()
|
|
{
|
|
testDiag("%s", __func__);
|
|
TestClient ctxt;
|
|
|
|
testdbGetFieldEqual("test:counter", DBF_LONG, 0);
|
|
|
|
// assume calcRecord has HIGH marked pp(TRUE), and LOPR marked pp(FALSE)
|
|
|
|
ctxt.put("test:counter.LOPR").set("value", 0).exec()->wait(5.0);
|
|
testdbGetFieldEqual("test:counter", DBF_LONG, 0);
|
|
|
|
ctxt.put("test:counter.LOPR").set("value", 0).pvRequest("record[process=passive]").exec()->wait(5.0);
|
|
testdbGetFieldEqual("test:counter", DBF_LONG, 0);
|
|
|
|
ctxt.put("test:counter.LOPR").set("value", 0).pvRequest("record[process=false]").exec()->wait(5.0);
|
|
testdbGetFieldEqual("test:counter", DBF_LONG, 0);
|
|
|
|
ctxt.put("test:counter.LOPR").set("value", 0).pvRequest("record[process=true]").exec()->wait(5.0);
|
|
testdbGetFieldEqual("test:counter", DBF_LONG, 1);
|
|
}
|
|
|
|
void testPutLog()
|
|
{
|
|
testDiag("%s", __func__);
|
|
TestClient ctxt;
|
|
|
|
static std::ostringstream messages;
|
|
struct AsLog {
|
|
const asTrapWriteId id;
|
|
static
|
|
void message(asTrapWriteMessage *pmessage,int after)
|
|
{
|
|
// caPutLog assumes "serverSpecific" is always available, and a dbChannel*
|
|
// (or dbAddr* with 3.14)
|
|
auto *pchan = (dbChannel*)pmessage->serverSpecific;
|
|
|
|
char val[MAX_STRING_SIZE+1u];
|
|
|
|
dbChannelGetField(pchan, DBR_STRING, val, nullptr, nullptr, nullptr);
|
|
val[MAX_STRING_SIZE] = '\0';
|
|
|
|
if(!after) {
|
|
// for repeatability, don't include host and user
|
|
messages<<"host:user "<<dbChannelName(pchan)<<" "<<val;
|
|
|
|
} else {
|
|
messages<<" -> "<<val<<"\n";
|
|
}
|
|
}
|
|
AsLog()
|
|
:id(asTrapWriteRegisterListener(&message))
|
|
{}
|
|
~AsLog() {
|
|
asTrapWriteUnregisterListener(id);
|
|
}
|
|
} asLog;
|
|
|
|
ctxt.put("test:log").set("value.index", 1).exec()->wait(5.0);
|
|
ctxt.put("test:log").set("value.index", 0).exec()->wait(5.0);
|
|
ctxt.put("test:log").set("value.index", 0).exec()->wait(5.0);
|
|
|
|
auto log(messages.str());
|
|
|
|
testStrEq(log,
|
|
"host:user test:log Something -> Else\n"
|
|
"host:user test:log Else -> Something\n"
|
|
"host:user test:log Something -> Something\n");
|
|
}
|
|
|
|
void testPutBlock()
|
|
{
|
|
#if EPICS_VERSION_INT >= VERSION_INT(3, 16, 0, 1)
|
|
testDiag("%s", __func__);
|
|
TestClient ctxt;
|
|
|
|
testdbGetFieldEqual("test:slowmo", DBR_DOUBLE, 0.0);
|
|
|
|
auto start(epicsTime::getCurrent());
|
|
ctxt.put("test:slowmo.PROC").set("value", 0).pvRequest("record[block=true]").exec()->wait(5.0);
|
|
auto elapsed(epicsTime::getCurrent()-start);
|
|
|
|
testdbGetFieldEqual("test:slowmo", DBR_DOUBLE, 1.0);
|
|
testTrue(elapsed>=0.75)<<"time elapsed "<<elapsed<<" s";
|
|
|
|
// so long it should not complete
|
|
testdbPutFieldOk("test:slowmo.ODLY", DBR_LONG, 30);
|
|
|
|
auto op(ctxt.put("test:slowmo.PROC").set("value", 5).pvRequest("record[block=true]").exec());
|
|
|
|
// wait until processing has started
|
|
{
|
|
auto prec = testdbRecordPtr("test:slowmo");
|
|
while(1) {
|
|
dbScanLock(prec);
|
|
auto pact(prec->pact);
|
|
dbScanUnlock(prec);
|
|
if(pact)
|
|
break;
|
|
testDiag("waiting for slowmo");
|
|
epicsThreadSleep(1.0);
|
|
}
|
|
}
|
|
|
|
testTrue(op->cancel());
|
|
op.reset();
|
|
|
|
// ensure processing still in progress
|
|
{
|
|
auto prec = testdbRecordPtr("test:slowmo");
|
|
dbScanLock(prec);
|
|
auto pact(prec->pact);
|
|
dbScanUnlock(prec);
|
|
testTrue(pact)<<" still busy";
|
|
}
|
|
|
|
testdbGetFieldEqual("test:slowmo", DBR_DOUBLE, 2.0);
|
|
|
|
testdbPutFieldOk("test:bo", DBR_LONG, 0);
|
|
ctxt.put("test:bo").set("value.index", 1).pvRequest("record[block=true]").exec()->wait(5.0);
|
|
|
|
testdbGetFieldEqual("test:bo", DBR_LONG, 1);
|
|
|
|
#else
|
|
testSkip(9, "dbNotify testing broken on 3.15");
|
|
/* epics-base 3.15 circa a249561677de73e3f174ec8e4478937a7a55a9b2
|
|
* contains ddaa6e4eb6647545db7a43c9b83ca7e2c497f3b8
|
|
* but not a7a87372aab2c086f7ac60db4a5d9e39f08b9f05
|
|
*/
|
|
#endif
|
|
}
|
|
|
|
void testMonitorAI(TestClient& ctxt)
|
|
{
|
|
testDiag("%s", __func__);
|
|
|
|
TestSubscription sub(ctxt.monitor("test:ai")
|
|
.maskConnected(true)
|
|
.maskDisconnected(true));
|
|
|
|
auto val(sub.waitForUpdate());
|
|
checkUTAG(val);
|
|
testStrEq(std::string(SB()<<val.format().delta()),
|
|
"value double = 53.2\n"
|
|
"alarm.severity int32_t = 2\n"
|
|
"alarm.status int32_t = 1\n"
|
|
"alarm.message string = \"HIGH\"\n"
|
|
"timeStamp.secondsPastEpoch int64_t = 643497678\n"
|
|
"timeStamp.nanoseconds int32_t = 102030\n"
|
|
"display.limitLow double = 0\n"
|
|
"display.limitHigh double = 100\n"
|
|
"display.description string = \"testing\"\n"
|
|
"display.units string = \"arb\"\n"
|
|
"display.precision int32_t = 1\n"
|
|
"display.form.index int32_t = 6\n"
|
|
"display.form.choices string[] = {7}[\"Default\", \"String\", \"Binary\", \"Decimal\", \"Hex\", \"Exponential\", \"Engineering\"]\n"
|
|
"control.limitLow double = 0\n"
|
|
"control.limitHigh double = 100\n"
|
|
"valueAlarm.lowAlarmLimit double = 0\n"
|
|
"valueAlarm.lowWarningLimit double = 4\n"
|
|
"valueAlarm.highWarningLimit double = 6\n"
|
|
"valueAlarm.highAlarmLimit double = 100\n"
|
|
)<<" initial VAL w/ meta-data. delta output";
|
|
|
|
testTimeSec++;
|
|
testdbPutFieldOk("test:ai", DBR_DOUBLE, 66.5); // triggers only DBE_VALUE
|
|
|
|
val = sub.waitForUpdate();
|
|
checkUTAG(val);
|
|
#if EPICS_VERSION_INT < VERSION_INT(7, 0, 6, 0)
|
|
// lacks db_field_log::mask
|
|
val["alarm"].unmark();
|
|
#endif
|
|
testStrEq(std::string(SB()<<val.format().delta()),
|
|
"value double = 66.5\n"
|
|
"timeStamp.secondsPastEpoch int64_t = 643497679\n"
|
|
"timeStamp.nanoseconds int32_t = 102030\n"
|
|
)<<" fetch VAL w/ meta-data. delta output";
|
|
|
|
testTimeSec++;
|
|
testdbPutFieldOk("test:ai", DBR_DOUBLE, 5.0); // triggers DBE_VALUE | DBE_ALARM
|
|
|
|
val = sub.waitForUpdate();
|
|
checkUTAG(val);
|
|
testStrEq(std::string(SB()<<val.format().delta()),
|
|
"value double = 5\n"
|
|
"alarm.severity int32_t = 0\n"
|
|
"alarm.status int32_t = 0\n"
|
|
"alarm.message string = \"\"\n"
|
|
"timeStamp.secondsPastEpoch int64_t = 643497680\n"
|
|
"timeStamp.nanoseconds int32_t = 102030\n"
|
|
)<<" fetch VAL w/ meta-data. delta output";
|
|
|
|
testTimeSec++;
|
|
testdbPutFieldOk("test:ai.HIGH", DBR_DOUBLE, 7.0); // triggers only DBE_PROPERTY
|
|
|
|
val = sub.waitForUpdate();
|
|
testStrEq(std::string(SB()<<val.format().delta()),
|
|
"display.limitLow double = 0\n"
|
|
"display.limitHigh double = 100\n"
|
|
"display.description string = \"testing\"\n"
|
|
"display.units string = \"arb\"\n"
|
|
"display.precision int32_t = 1\n"
|
|
"control.limitLow double = 0\n"
|
|
"control.limitHigh double = 100\n"
|
|
"valueAlarm.lowAlarmLimit double = 0\n"
|
|
"valueAlarm.lowWarningLimit double = 4\n"
|
|
"valueAlarm.highWarningLimit double = 7\n"
|
|
"valueAlarm.highAlarmLimit double = 100\n"
|
|
)<<" fetch VAL w/ meta-data. delta output";
|
|
|
|
sub.testEmpty();
|
|
}
|
|
|
|
void testMonitorBO(TestClient& ctxt)
|
|
{
|
|
testDiag("%s", __func__);
|
|
|
|
testdbPutFieldOk("test:bo", DBR_STRING, "One");
|
|
|
|
TestSubscription sub(ctxt.monitor("test:bo")
|
|
.maskConnected(true)
|
|
.maskDisconnected(true));
|
|
|
|
auto val(sub.waitForUpdate());
|
|
checkUTAG(val, 0);
|
|
testStrEq(std::string(SB()<<val.format().delta()),
|
|
"value.index int32_t = 1\n"
|
|
"value.choices string[] = {2}[\"Zero\", \"One\"]\n"
|
|
"alarm.severity int32_t = 0\n"
|
|
"alarm.status int32_t = 0\n"
|
|
"alarm.message string = \"\"\n"
|
|
"timeStamp.secondsPastEpoch int64_t = 643497681\n"
|
|
"timeStamp.nanoseconds int32_t = 102030\n"
|
|
"display.description string = \"\"\n");
|
|
|
|
testTimeSec++;
|
|
testdbPutFieldOk("test:bo", DBR_STRING, "Zero");
|
|
|
|
val = sub.waitForUpdate();
|
|
checkUTAG(val, 0);
|
|
#if EPICS_VERSION_INT < VERSION_INT(7, 0, 6, 0)
|
|
// lacks db_field_log::mask
|
|
val["alarm"].unmark();
|
|
#endif
|
|
testStrEq(std::string(SB()<<val.format().delta()),
|
|
"value.index int32_t = 0\n"
|
|
"timeStamp.secondsPastEpoch int64_t = 643497682\n"
|
|
"timeStamp.nanoseconds int32_t = 102030\n");
|
|
|
|
testTimeSec++;
|
|
testdbPutFieldOk("test:bo.ZNAM", DBR_STRING, "Off");
|
|
|
|
val = sub.waitForUpdate();
|
|
testStrEq(std::string(SB()<<val.format().delta()),
|
|
"value.choices string[] = {2}[\"Off\", \"One\"]\n"
|
|
"display.description string = \"\"\n");
|
|
|
|
sub.testEmpty();
|
|
}
|
|
|
|
void testMonitorAIFilt(TestClient& ctxt)
|
|
{
|
|
testDiag("%s", __func__);
|
|
|
|
TestSubscription sub1(ctxt.monitor("test:ai.VAL{\"dbnd\":{\"d\":0.0}}")
|
|
.maskConnected(true)
|
|
.maskDisconnected(true));
|
|
TestSubscription sub2(ctxt.monitor("test:ai.VAL{\"dbnd\":{\"d\":2.0}}")
|
|
.maskConnected(true)
|
|
.maskDisconnected(true));
|
|
|
|
Value val;
|
|
{
|
|
auto expect = "value double = 5\n"
|
|
"alarm.severity int32_t = 0\n"
|
|
"alarm.status int32_t = 0\n"
|
|
"alarm.message string = \"\"\n"
|
|
"timeStamp.secondsPastEpoch int64_t = 643497681\n"
|
|
"timeStamp.nanoseconds int32_t = 102030\n"
|
|
"display.limitLow double = 0\n"
|
|
"display.limitHigh double = 100\n"
|
|
"display.description string = \"testing\"\n"
|
|
"display.units string = \"arb\"\n"
|
|
"display.precision int32_t = 1\n"
|
|
"display.form.index int32_t = 6\n"
|
|
"display.form.choices string[] = {7}[\"Default\", \"String\", \"Binary\", \"Decimal\", \"Hex\", \"Exponential\", \"Engineering\"]\n"
|
|
"control.limitLow double = 0\n"
|
|
"control.limitHigh double = 100\n"
|
|
"valueAlarm.lowAlarmLimit double = 0\n"
|
|
"valueAlarm.lowWarningLimit double = 4\n"
|
|
"valueAlarm.highWarningLimit double = 7\n"
|
|
"valueAlarm.highAlarmLimit double = 100\n";
|
|
val = sub1.waitForUpdate();
|
|
checkUTAG(val);
|
|
testStrEq(std::string(SB()<<val.format().delta()), expect)<<" initial dbnd 0";
|
|
val = sub2.waitForUpdate();
|
|
checkUTAG(val);
|
|
testStrEq(std::string(SB()<<val.format().delta()), expect)<<" initial dbnd 2";
|
|
}
|
|
|
|
testdbPutFieldOk("test:ai", DBR_DOUBLE, 6.0);
|
|
|
|
val = sub1.waitForUpdate();
|
|
testFldEq(val, "value", 6.0);
|
|
testFalse(val["display"].isMarked(true, true));
|
|
sub2.testEmpty();
|
|
|
|
testdbPutFieldOk("test:ai", DBR_DOUBLE, 8.0);
|
|
|
|
val = sub1.waitForUpdate();
|
|
testFldEq(val, "value", 8.0);
|
|
val = sub2.waitForUpdate();
|
|
testFldEq(val, "value", 8.0);
|
|
|
|
sub1.testEmpty();
|
|
sub2.testEmpty();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
MAIN(testqsingle)
|
|
{
|
|
testPlan(87);
|
|
testSetup();
|
|
pvxs::logger_config_env();
|
|
{
|
|
TestIOC ioc;
|
|
asSetFilename("../testioc.acf");
|
|
generalTimeRegisterCurrentProvider("test", 1, &testTimeCurrent);
|
|
testdbReadDatabase("testioc.dbd", nullptr, nullptr);
|
|
testOk1(!testioc_registerRecordDeviceDriver(pdbbase));
|
|
testdbReadDatabase("testqsingle.db", nullptr, nullptr);
|
|
#ifdef HAVE_lsi
|
|
testdbReadDatabase("testqsinglelsi.db", nullptr, nullptr);
|
|
#endif
|
|
#ifdef DBR_UINT64
|
|
testdbReadDatabase("testqsingle64.db", nullptr, nullptr);
|
|
#endif
|
|
ioc.init();
|
|
testGetScalar();
|
|
testLongString();
|
|
testGetArray();
|
|
testPut();
|
|
testGetPut64();
|
|
testPutProc();
|
|
testPutLog();
|
|
{
|
|
TestClient mctxt;
|
|
testMonitorAI(mctxt);
|
|
testMonitorBO(mctxt);
|
|
testMonitorAIFilt(mctxt);
|
|
}
|
|
timeSim = false;
|
|
testPutBlock();
|
|
}
|
|
// call epics atexits explicitly to handle older base w/o de-init hooks
|
|
epicsExitCallAtExits();
|
|
cleanup_for_valgrind();
|
|
return testDone();
|
|
}
|