diff --git a/src/ioc/db/test/Makefile b/src/ioc/db/test/Makefile index 4299a6ba5..2a7c15272 100644 --- a/src/ioc/db/test/Makefile +++ b/src/ioc/db/test/Makefile @@ -10,6 +10,9 @@ TOP=../../../.. include $(TOP)/configure/CONFIG +# Find private headers +USR_CFLAGS += -I../.. + TESTLIBRARY = dbTestIoc dbTestIoc_SRCS += xRecord.c @@ -85,6 +88,13 @@ testHarness_SRCS += dbCaStatsTest.c TESTS += dbCaStatsTest TESTFILES += ../dbCaStatsTest.db +TESTPROD_HOST += dbCaLinkTest +dbCaLinkTest_SRCS += dbCaLinkTest.c +dbCaLinkTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += dbCaLinkTest.c +TESTS += dbCaLinkTest +TESTFILES += ../dbCaLinkTest1.db + scanIoTest_DBD += menuScan.dbd TESTPROD_HOST += scanIoTest scanIoTest_SRCS += scanIoTest.c diff --git a/src/ioc/db/test/dbCaLinkTest.c b/src/ioc/db/test/dbCaLinkTest.c new file mode 100644 index 000000000..ff01b3ad4 --- /dev/null +++ b/src/ioc/db/test/dbCaLinkTest.c @@ -0,0 +1,553 @@ +/*************************************************************************\ +* Copyright (c) 2015 Brookhaven Science Assoc. as operator of Brookhaven +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Author: Michael Davidsaver + */ + +#include +#include +#include + +#include "epicsString.h" +#include "dbUnitTest.h" +#include "epicsThread.h" +#include "epicsEvent.h" +#include "iocInit.h" +#include "dbBase.h" +#include "link.h" +#include "dbAccess.h" +#include "epicsStdio.h" +#include "dbEvent.h" +/* hackish duplication since we can't include db_access.h here */ +typedef void* chid; +#define MAX_UNITS_SIZE 8 +#include "dbCaPvt.h" +#include "errlog.h" + +#include "xRecord.h" +#include "arrRecord.h" + +#include "testMain.h" + +#define testOp(FMT,A,OP,B) testOk((A)OP(B), #A " ("FMT") " #OP " " #B " ("FMT")", A,B) + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static epicsEventId waitEvent; +static unsigned waitCounter; + +static +void waitCB(void *unused) +{ + if(waitEvent) + epicsEventMustTrigger(waitEvent); + waitCounter++; //TODO: atomic +} + +static +void startWait(DBLINK *plink) +{ + caLink *pca = plink->value.pv_link.pvt; + + assert(!waitEvent); + waitEvent = epicsEventMustCreate(epicsEventEmpty); + + assert(pca); + epicsMutexMustLock(pca->lock); + assert(!pca->monitor && !pca->userPvt); + pca->monitor = &waitCB; + epicsMutexUnlock(pca->lock); + testDiag("Preparing to wait on pca=%p", pca); +} + +static +void waitForUpdate(DBLINK *plink) +{ + caLink *pca = plink->value.pv_link.pvt; + + assert(pca); + + testDiag("Waiting on pca=%p", pca); + epicsEventMustWait(waitEvent); + + epicsMutexMustLock(pca->lock); + pca->monitor = NULL; + pca->userPvt = NULL; + epicsMutexUnlock(pca->lock); + + epicsEventDestroy(waitEvent); + waitEvent = NULL; +} + +static +void putLink(DBLINK *plink, short dbr, const void*buf, long nReq) +{ + long ret; + waitEvent = epicsEventMustCreate(epicsEventEmpty); + + ret = dbCaPutLinkCallback(plink, dbr, buf, nReq, + &waitCB, NULL); + if(ret) { + testFail("putLink fails %ld\n", ret); + } else { + epicsEventMustWait(waitEvent); + testPass("putLink ok\n"); + } + epicsEventDestroy(waitEvent); + waitEvent = NULL; +} + +static void testNativeLink(void) +{ + xRecord *psrc, *ptarg; + DBLINK *psrclnk; + epicsInt32 temp; + long nReq; + testDiag("Link to a scalar numeric field"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbCaLinkTest1.db", NULL, "TARGET=target CA"); + + eltc(0); + testIocInitOk(); + eltc(1); + + psrc = (xRecord*)testdbRecordPtr("source"); + ptarg= (xRecord*)testdbRecordPtr("target"); + psrclnk = &psrc->lnk; + + // ensure this is really a CA link + testOk1(dbLockGetLockId((dbCommon*)psrc)!=dbLockGetLockId((dbCommon*)ptarg)); + + testOk1(psrclnk->type==CA_LINK); + + startWait(psrclnk); + + dbScanLock((dbCommon*)ptarg); + ptarg->val = 42; + db_post_events(ptarg, &ptarg->val, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE); + dbScanUnlock((dbCommon*)ptarg); + + waitForUpdate(psrclnk); + + dbScanLock((dbCommon*)psrc); + // local CA_LINK connects immediately + testOk1(dbCaIsLinkConnected(psrclnk)); + nReq = 422; + testOk1(dbCaGetNelements(psrclnk, &nReq)==0); + testOp("%ld",nReq,==,1l); + + nReq = 1; + temp = 0x0f0f0f0f; + testOk1(dbGetLink(psrclnk, DBR_LONG, (void*)&temp, NULL, &nReq)==0); + testOp("%d",temp,==,42); + dbScanUnlock((dbCommon*)psrc); + + temp = 1010; + nReq = 1; + putLink(psrclnk, DBR_LONG, (void*)&temp, nReq); + + dbScanLock((dbCommon*)ptarg); + testOk1(ptarg->val==1010); + dbScanUnlock((dbCommon*)ptarg); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void testStringLink(void) +{ + xRecord *psrc, *ptarg; + DBLINK *psrclnk; + char temp[MAX_STRING_SIZE]; + long nReq; + testDiag("Link to a string field"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbCaLinkTest1.db", NULL, "TARGET=target.DESC CA"); + + eltc(0); + testIocInitOk(); + eltc(1); + + psrc = (xRecord*)testdbRecordPtr("source"); + ptarg= (xRecord*)testdbRecordPtr("target"); + psrclnk = &psrc->lnk; + + // ensure this is really a CA link + testOk1(dbLockGetLockId((dbCommon*)psrc)!=dbLockGetLockId((dbCommon*)ptarg)); + + testOk1(psrclnk->type==CA_LINK); + + startWait(psrclnk); + + dbScanLock((dbCommon*)ptarg); + strcpy(ptarg->desc, "hello"); + db_post_events(ptarg, &ptarg->desc, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE); + dbScanUnlock((dbCommon*)ptarg); + + waitForUpdate(psrclnk); + + dbScanLock((dbCommon*)psrc); + // local CA_LINK connects immediately + testOk1(dbCaIsLinkConnected(psrclnk)); + + nReq = 1; + memset(temp, '!', sizeof(temp)); + testOk1(dbGetLink(psrclnk, DBR_STRING, (void*)&temp, NULL, &nReq)==0); + testOk(strcmp(temp, "hello")==0, "%s == hello", temp); + dbScanUnlock((dbCommon*)psrc); + + strcpy(temp, "world"); + putLink(psrclnk, DBR_STRING, (void*)&temp, nReq); + + dbScanLock((dbCommon*)ptarg); + testOk(strcmp(ptarg->desc, "world")==0, "%s == world", ptarg->desc); + dbScanUnlock((dbCommon*)ptarg); + + testIocShutdownOk(); + + testdbCleanup(); +} + +static void wasproc(xRecord *prec) +{ + waitCB(NULL); +} + +static void testCP(void) +{ + xRecord *psrc, *ptarg; + testDiag("Link CP modifier"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbCaLinkTest1.db", NULL, "TARGET=target CP"); + + psrc = (xRecord*)testdbRecordPtr("source"); + ptarg= (xRecord*)testdbRecordPtr("target"); + + /* hook in before IOC init */ + waitCounter=0; + psrc->clbk = &wasproc; + + assert(!waitEvent); + waitEvent = epicsEventMustCreate(epicsEventEmpty); + + eltc(0); + testIocInitOk(); + eltc(1); + + epicsEventMustWait(waitEvent); + + dbScanLock((dbCommon*)psrc); + testOp("%u",waitCounter,==,1); // initial processing + dbScanUnlock((dbCommon*)psrc); + + dbScanLock((dbCommon*)ptarg); + ptarg->val = 42; + db_post_events(ptarg, &ptarg->val, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE); + dbScanUnlock((dbCommon*)ptarg); + + epicsEventMustWait(waitEvent); + + dbScanLock((dbCommon*)psrc); + testOp("%u",waitCounter,==,2); // process due to monitor update + dbScanUnlock((dbCommon*)psrc); + + testIocShutdownOk(); + + testdbCleanup(); + + epicsEventDestroy(waitEvent); + waitEvent = NULL; +} + +static void fillArray(epicsInt32 *buf, unsigned count, epicsInt32 first) +{ + for(;count;count--,first++) + *buf++ = first; +} + +static void fillArrayDouble(double *buf, unsigned count, double first) +{ + for(;count;count--,first++) + *buf++ = first; +} + +static void checkArray(const char *msg, + epicsInt32 *buf, epicsInt32 first, + unsigned used, unsigned total) +{ + int match = 1; + unsigned i; + epicsInt32 x, *b; + for(b=buf,x=first,i=0;ivalue.pv_link.pvt; + if(lnk->type!=CA_LINK || !pca->pputNative) + return; + epicsMutexMustLock(pca->lock); + memset(pca->pputNative, '!', + pca->nelements*dbr_value_size[ca_field_type(pca->chid)]); + epicsMutexUnlock(pca->lock); +} + +static void testArrayLink(unsigned nsrc, unsigned ntarg) +{ + char buf[100]; + arrRecord *psrc, *ptarg; + DBLINK *psrclnk; + epicsInt32 *bufsrc, *buftarg; + long nReq; + unsigned num; + + testDiag("Link to a array numeric field"); + + epicsSnprintf(buf, sizeof(buf), "TARGET=target CA,FTVL=LONG,SNELM=%u,TNELM=%u", + nsrc, ntarg); + testDiag("%s", buf); + + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbCaLinkTest2.db", NULL, buf); + + psrc = (arrRecord*)testdbRecordPtr("source"); + ptarg= (arrRecord*)testdbRecordPtr("target"); + psrclnk = &psrc->inp; + + eltc(0); + testIocInitOk(); + eltc(1); + + bufsrc = psrc->bptr; + buftarg= ptarg->bptr; + + num=psrc->nelm; + if(num>ptarg->nelm) + num=ptarg->nelm; + + startWait(psrclnk); + + dbScanLock((dbCommon*)ptarg); + fillArray(buftarg, ptarg->nelm, 1); + ptarg->nord = ptarg->nelm; + db_post_events(ptarg, ptarg->bptr, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE); + dbScanUnlock((dbCommon*)ptarg); + + waitForUpdate(psrclnk); + + dbScanLock((dbCommon*)psrc); + nReq = psrc->nelm; + if(dbGetLink(psrclnk, DBR_LONG, bufsrc, NULL, &nReq)==0) { + testPass("dbGetLink"); + testOp("%ld",nReq,==,(long)num); + checkArray("array update", bufsrc, 1, nReq, psrc->nelm); + } else { + testFail("dbGetLink"); + testSkip(2, "dbGetLink fails"); + } + dbScanUnlock((dbCommon*)psrc); + + fillArray(bufsrc, psrc->nelm, 2); + /* write buffer allocated on first put */ + putLink(psrclnk, DBR_LONG, bufsrc, psrc->nelm); + + dbScanLock((dbCommon*)ptarg); + /* CA links always write the full target array length */ + testOp("%ld",(long)ptarg->nord,==,(long)ptarg->nelm); + /* However, if the source length is less, then the target + * is zero filled + */ + checkArray("array update", buftarg, 2, num, ptarg->nelm); + dbScanUnlock((dbCommon*)ptarg); + + /* write again to ensure that buffer is completely updated */ + spoilputbuf(psrclnk); + fillArray(bufsrc, psrc->nelm, 3); + putLink(psrclnk, DBR_LONG, bufsrc, psrc->nelm); + + dbScanLock((dbCommon*)ptarg); + testOp("%ld",(long)ptarg->nord,==,(long)ptarg->nelm); + checkArray("array update", buftarg, 3, num, ptarg->nelm); + dbScanUnlock((dbCommon*)ptarg); + + testIocShutdownOk(); + + testdbCleanup(); + + /* records don't cleanup after themselves + * so do here to silence valgrind + */ + free(bufsrc); + free(buftarg); +} + + +static void softarr(arrRecord *prec) +{ + long nReq = prec->nelm; + long status = dbGetLink(&prec->inp, DBR_DOUBLE, prec->bptr, NULL, &nReq); + if(status) { + testFail("dbGetLink() -> %ld", status); + } else { + testPass("dbGetLink() succeeds"); + prec->nord = nReq; + if(nReq>0) + testDiag("%s.VAL[0] - %f", prec->name, *(double*)prec->bptr); + } + waitCB(NULL); +} + +static void testreTargetTypeChange(void) +{ + arrRecord *psrc, *ptarg1, *ptarg2; + double *bufsrc, *buftarg1; + epicsInt32 *buftarg2; + testDiag("Retarget an link to a PV with a different type DOUBLE->LONG"); + testdbPrepare(); + + testdbReadDatabase("dbTestIoc.dbd", NULL, NULL); + + dbTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("dbCaLinkTest3.db", NULL, "NELM=5,TARGET=target1 CP"); + + psrc = (arrRecord*)testdbRecordPtr("source"); + ptarg1= (arrRecord*)testdbRecordPtr("target1"); + ptarg2= (arrRecord*)testdbRecordPtr("target2"); + + /* hook in before IOC init */ + waitCounter=0; + psrc->clbk = &softarr; + + assert(!waitEvent); + waitEvent = epicsEventMustCreate(epicsEventEmpty); + + eltc(0); + testIocInitOk(); + eltc(1); + + epicsEventMustWait(waitEvent); // wait for initial processing + + bufsrc = psrc->bptr; + buftarg1= ptarg1->bptr; + buftarg2= ptarg2->bptr; + + testDiag("Update one with original target"); + + dbScanLock((dbCommon*)ptarg2); + fillArray(buftarg2, ptarg2->nelm, 2); + ptarg2->nord = ptarg2->nelm; + dbScanUnlock((dbCommon*)ptarg2); + + /* initialize buffers */ + dbScanLock((dbCommon*)ptarg1); + fillArrayDouble(buftarg1, ptarg1->nelm, 1); + ptarg1->nord = ptarg1->nelm; + db_post_events(ptarg1, ptarg1->bptr, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE); + dbScanUnlock((dbCommon*)ptarg1); + + epicsEventMustWait(waitEvent); // wait for update + + dbScanLock((dbCommon*)psrc); + testOp("%ld",(long)psrc->nord,==,(long)5); + checkArrayDouble("array update", bufsrc, 1, 5, psrc->nelm); + dbScanUnlock((dbCommon*)psrc); + + testDiag("Retarget"); + testdbPutFieldOk("source.INP", DBR_STRING, "target2 CP"); + + epicsEventMustWait(waitEvent); // wait for update + + dbScanLock((dbCommon*)psrc); + testOp("%ld",(long)psrc->nord,==,(long)5); + checkArrayDouble("array update", bufsrc, 2, 5, psrc->nelm); + dbScanUnlock((dbCommon*)psrc); + + testIocShutdownOk(); + + testdbCleanup(); + + /* records don't cleanup after themselves + * so do here to silence valgrind + */ + free(bufsrc); + free(buftarg1); + free(buftarg2); +} + +MAIN(dbCaLinkTest) +{ + testPlan(62); + testNativeLink(); + testStringLink(); + testCP(); + testArrayLink(1,1); + testArrayLink(10,1); + testArrayLink(1,10); + testArrayLink(10,10); + testreTargetTypeChange(); + return testDone(); +} diff --git a/src/ioc/db/test/dbCaLinkTest1.db b/src/ioc/db/test/dbCaLinkTest1.db new file mode 100644 index 000000000..aab5ceb86 --- /dev/null +++ b/src/ioc/db/test/dbCaLinkTest1.db @@ -0,0 +1,5 @@ +record(x, "target") {} + +record(x, "source") { + field(LNK, "$(TARGET)") +} diff --git a/src/ioc/db/test/dbCaLinkTest2.db b/src/ioc/db/test/dbCaLinkTest2.db new file mode 100644 index 000000000..9cfa4ba00 --- /dev/null +++ b/src/ioc/db/test/dbCaLinkTest2.db @@ -0,0 +1,10 @@ +record(arr, "target") { + field(FTVL, "$(TFTVL=$(FTVL=))") + field(NELM, "$(TNELM=$(NELM=))") +} + +record(arr, "source") { + field(INP, "$(TARGET)") + field(FTVL, "$(SFTVL=$(FTVL=))") + field(NELM, "$(SNELM=$(NELM=))") +} diff --git a/src/ioc/db/test/dbCaLinkTest3.db b/src/ioc/db/test/dbCaLinkTest3.db new file mode 100644 index 000000000..f820bd554 --- /dev/null +++ b/src/ioc/db/test/dbCaLinkTest3.db @@ -0,0 +1,14 @@ +record(arr, "target1") { + field(FTVL, "DOUBLE") + field(NELM, "$(TNELM=$(NELM=))") +} +record(arr, "target2") { + field(FTVL, "LONG") + field(NELM, "$(TNELM=$(NELM=))") +} + +record(arr, "source") { + field(INP, "$(TARGET)") + field(FTVL, "DOUBLE") + field(NELM, "$(SNELM=$(NELM=))") +} diff --git a/src/ioc/db/test/epicsRunDbTests.c b/src/ioc/db/test/epicsRunDbTests.c index 0ee631686..10f10e267 100644 --- a/src/ioc/db/test/epicsRunDbTests.c +++ b/src/ioc/db/test/epicsRunDbTests.c @@ -26,6 +26,7 @@ int dbScanTest(void); int scanIoTest(void); int dbLockTest(void); int dbPutLinkTest(void); +int dbCaLinkTest(void); int testDbChannel(void); int chfPluginTest(void); int arrShorthandTest(void); @@ -45,6 +46,7 @@ void epicsRunDbTests(void) runTest(scanIoTest); runTest(dbLockTest); runTest(dbPutLinkTest); + runTest(dbCaLinkTest); runTest(testDbChannel); runTest(arrShorthandTest); runTest(recGblCheckDeadbandTest);