Files
epics-base/modules/database/test/ioc/db/dbCaLinkTest.c

627 lines
16 KiB
C

/*************************************************************************\
* Copyright (c) 2015 Brookhaven Science Assoc. as operator of Brookhaven
* National Laboratory.
* SPDX-License-Identifier: EPICS
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/*
* Author: Michael Davidsaver <mdavidsaver@bnl.gov>
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define EPICS_DBCA_PRIVATE_API
#include "epicsString.h"
#include "dbUnitTest.h"
#include "epicsThread.h"
#include "cantProceed.h"
#include "epicsEvent.h"
#include "iocInit.h"
#include "dbBase.h"
#include "link.h"
#include "dbAccess.h"
#include "epicsStdio.h"
#include "dbEvent.h"
#include "shareLib.h"
/* Declarations from cadef.h and db_access.h which we can't include here */
typedef void * chid;
typedef void * evid;
epicsShareExtern const unsigned short dbr_value_size[];
epicsShareExtern short epicsShareAPI ca_field_type (chid chan);
#define MAX_UNITS_SIZE 8
#include "dbCaPvt.h"
#include "errlog.h"
#include "testMain.h"
#include "xRecord.h"
#include "arrRecord.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 putLink(DBLINK *plink, short dbr, const void*buf, long nReq)
{
long ret;
ret = dbPutLink(plink, dbr, buf, nReq);
if(ret) {
testFail("putLink fails %ld", ret);
} else {
testPass("putLink ok");
dbCaSync();
}
}
static long getTwice(struct link *psrclnk, void *dummy)
{
epicsInt32 val1, val2;
long status = dbGetLink(psrclnk, DBR_LONG, &val1, 0, 0);
if (status) return status;
epicsThreadSleep(0.5);
status = dbGetLink(psrclnk, DBR_LONG, &val2, 0, 0);
if (status) return status;
testDiag("val1 = %d, val2 = %d", val1, val2);
return (val1 == val2) ? 0 : -1;
}
static void countUp(void *parm)
{
xRecord *ptarg = (xRecord *)parm;
epicsInt32 val;
for (val = 1; val < 10; val++) {
dbScanLock((dbCommon*)ptarg);
ptarg->val = val;
db_post_events(ptarg, &ptarg->val, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE);
dbScanUnlock((dbCommon*)ptarg);
epicsThreadSleep(0.1);
}
if (waitEvent)
epicsEventMustTrigger(waitEvent);
}
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);
testdbCaWaitForUpdateCount(psrclnk, 1);
dbScanLock((dbCommon*)ptarg);
ptarg->val = 42;
db_post_events(ptarg, &ptarg->val, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE);
dbScanUnlock((dbCommon*)ptarg);
testdbCaWaitForUpdateCount(psrclnk, 2);
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, &temp, NULL, &nReq)==0);
testOp("%d",temp,==,42);
dbScanUnlock((dbCommon*)psrc);
temp = 1010;
nReq = 1;
putLink(psrclnk, DBR_LONG, &temp, nReq);
dbScanLock((dbCommon*)ptarg);
testOk1(ptarg->val==1010);
dbScanUnlock((dbCommon*)ptarg);
assert(!waitEvent);
waitEvent = epicsEventMustCreate(epicsEventEmpty);
/* Start counter */
epicsThreadCreate("countUp", epicsThreadPriorityHigh,
epicsThreadGetStackSize(epicsThreadStackSmall), countUp, ptarg);
dbScanLock((dbCommon*)psrc);
/* Check that unlocked gets change */
temp = getTwice(psrclnk, NULL);
testOk(temp == -1, "unlocked, getTwice returned %d (-1)", temp);
/* Check locked gets are atomic */
temp = dbLinkDoLocked(psrclnk, getTwice, NULL);
testOk(temp == 0, "locked, getTwice returned %d (0)", temp);
dbScanUnlock((dbCommon*)psrc);
epicsEventMustWait(waitEvent);
epicsEventDestroy(waitEvent);
waitEvent = NULL;
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);
testdbCaWaitForUpdateCount(psrclnk, 1);
dbScanLock((dbCommon*)ptarg);
strcpy(ptarg->desc, "hello");
db_post_events(ptarg, &ptarg->desc, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE);
dbScanUnlock((dbCommon*)ptarg);
testdbCaWaitForUpdateCount(psrclnk, 2);
dbScanLock((dbCommon*)psrc);
/* local CA_LINK connects immediately */
testOk1(dbCaIsLinkConnected(psrclnk));
nReq = 1;
memset(temp, '!', sizeof(temp));
testOk1(dbGetLink(psrclnk, DBR_STRING, &temp, NULL, &nReq)==0);
testOk(strcmp(temp, "hello")==0, "%s == hello", temp);
dbScanUnlock((dbCommon*)psrc);
strcpy(temp, "world");
putLink(psrclnk, DBR_STRING, &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)
{
waitCounter++;
epicsEventTrigger(waitEvent);
}
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);
dbCaSync();
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)
{
int match = 1;
unsigned i;
epicsInt32 x, *b;
for(b=buf,x=first,i=0;i<used;i++,x++,b++)
match &= (*b)==x;
testOk(match, "%s", msg);
if(!match) {
for(b=buf,x=first,i=0;i<used;i++,x++,b++)
if((*b)!=x)
testDiag("%u %u != %u", i, (unsigned)*b, (unsigned)x);
}
}
static void checkArrayDouble(const char *msg,
double *buf, double first,
unsigned used)
{
int match = 1;
unsigned i;
double x, *b;
for(b=buf,x=first,i=0;i<used;i++,x++,b++)
match &= (*b)==x;
testOk(match, "%s", msg);
if(!match) {
for(b=buf,x=first,i=0;i<used;i++,x++,b++)
if((*b)!=x)
testDiag("%u %u != %u", i, (unsigned)*b, (unsigned)x);
}
}
static void spoilputbuf(DBLINK *lnk)
{
caLink *pca = lnk->value.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, *tmpbuf;
long nReq;
unsigned num_min, num_max;
testDiag("Link to a array numeric field");
/* source.INP = "target CA" */
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);
testdbCaWaitForUpdateCount(psrclnk, 1);
bufsrc = psrc->bptr;
buftarg= ptarg->bptr;
num_max=num_min=psrc->nelm;
if(num_min>ptarg->nelm)
num_min=ptarg->nelm;
if(num_max<ptarg->nelm)
num_max=ptarg->nelm;
/* always request more than can possibly be filled */
num_max += 2;
tmpbuf = callocMustSucceed(num_max, sizeof(*tmpbuf), "tmpbuf");
dbScanLock((dbCommon*)ptarg);
fillArray(buftarg, ptarg->nelm, 1);
ptarg->nord = ptarg->nelm;
db_post_events(ptarg, &ptarg->val, DBE_VALUE|DBE_ALARM|DBE_ARCHIVE);
dbScanUnlock((dbCommon*)ptarg);
testdbCaWaitForUpdateCount(psrclnk, 2);
dbScanLock((dbCommon*)psrc);
testDiag("fetch source.INP into source.BPTR");
nReq = psrc->nelm;
if(dbGetLink(psrclnk, DBR_LONG, bufsrc, NULL, &nReq)==0) {
testPass("dbGetLink");
testOp("%ld",nReq,==,(long)num_min);
checkArray("array update", bufsrc, 1, nReq);
} else {
testFail("dbGetLink");
testSkip(2, "dbGetLink fails");
}
testDiag("fetch source.INP into temp buffer w/ larger capacity");
nReq = num_max;
if(dbGetLink(psrclnk, DBR_LONG, tmpbuf, NULL, &nReq)==0) {
testPass("dbGetLink");
testOp("%ld",nReq,==,(long)ntarg);
checkArray("array update", tmpbuf, 1, nReq);
} 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);
testOp("%ld",(long)ptarg->nord,==,(long)num_min);
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)num_min);
checkArray("array update", buftarg, 3, num_min);
dbScanUnlock((dbCommon*)ptarg);
testIocShutdownOk();
testdbCleanup();
free(tmpbuf);
/* 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);
}
waitCounter++;
epicsEventTrigger(waitEvent);
}
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->val, 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);
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);
dbScanUnlock((dbCommon*)psrc);
testIocShutdownOk();
testdbCleanup();
/* records don't cleanup after themselves
* so do here to silence valgrind
*/
free(bufsrc);
free(buftarg1);
free(buftarg2);
}
void dbCaLinkTest_testCAC(void);
static void testCAC(void)
{
arrRecord *psrc, *ptarg1, *ptarg2;
double *bufsrc, *buftarg1;
epicsInt32 *buftarg2;
testDiag("Check local CA through libca");
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");
eltc(0);
testIocInitOk();
eltc(1);
bufsrc = psrc->bptr;
buftarg1= ptarg1->bptr;
buftarg2= ptarg2->bptr;
dbCaLinkTest_testCAC();
testIocShutdownOk();
testdbCleanup();
/* records don't cleanup after themselves
* so do here to silence valgrind
*/
free(bufsrc);
free(buftarg1);
free(buftarg2);
}
MAIN(dbCaLinkTest)
{
testPlan(101);
testNativeLink();
testStringLink();
testCP();
testArrayLink(1,1);
testArrayLink(10,1);
testArrayLink(1,10);
testArrayLink(10,10);
testreTargetTypeChange();
testCAC();
return testDone();
}