/** * 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 #include #include #include #include #include #include #include #include #include #include "testioc.h" #include "utilpvt.h" extern "C" { extern int testioc_registerRecordDeviceDriver(struct dbBase*); } using namespace pvxs; namespace { std::atomic timeSim{true}; std::atomic 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()!=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(val, "timeStamp.secondsPastEpoch", testTimeSec + POSIX_TIME_AT_EPICS_EPOCH); testStrEq(std::string(SB()<wait(5.0); checkUTAG(val); testStrEq(std::string(SB()<wait(5.0); checkUTAG(val); testStrEq(std::string(SB()<wait(5.0); checkUTAG(val); testStrEq(std::string(SB()<wait(5.0); checkUTAG(val); #if EPICS_VERSION_INT < VERSION_INT(3, 16, 0, 0) if(val["value"].as()=="0") val["value"] = ""; #endif testStrEq(std::string(SB()<(ctxt.get("test:this:is:a:really:really:long:record:name.NAME").exec()->wait(5.0), "value", "test:this:is:a:really:really:long:recor"); testFldEq(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(ctxt.get("test:src.INP").exec()->wait(5.0), "value", "test:this:is:a:really:really:long:recor"); testFldEq(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()<wait(5.0)); checkUTAG(val); testStrEq(std::string(SB()<wait(5.0); checkUTAG(val); testStrEq(std::string(SB()<wait(5.0); checkUTAG(val); testStrEq(std::string(SB()<wait(5.0); testStrEq(std::string(SB()<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 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 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 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: "<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(val, "value", 12345678901234); val = ctxt.get("test:wf:u64").exec()->wait(5.0); checkUTAG(val); testStrEq(std::string(SB()<("value", 12345678901230).exec()->wait(5.0); testdbGetFieldEqual("test:i64", DBF_INT64, 12345678901230ll); { shared_array 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 "< "<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 "<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); #else testSkip(7, "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()<