Merged calinktest branch

This commit is contained in:
Andrew Johnson
2015-07-28 14:22:06 -05:00
27 changed files with 1588 additions and 485 deletions

View File

@@ -205,6 +205,7 @@ private:
cacContextNotify & notify;
epics_auto_ptr < cacContext > pNetContext;
char * pStateNotifyCache;
bool isolated;
cacChannel & createChannel (
epicsGuard < epicsMutex > &,

View File

@@ -52,8 +52,11 @@
#include "link.h"
#include "recSup.h"
/* defined in dbContext.cpp
* Setup local CA access
*/
extern void dbServiceIOInit();
extern int dbServiceIsolate;
static ELLLIST workList = ELLLIST_INIT; /* Work list for dbCaTask */
static epicsMutexId workListLock; /*Mutual exclusions semaphores for workList*/
@@ -220,19 +223,16 @@ void dbCaShutdown(void)
}
}
void dbCaLinkInitIsolated(void)
static void dbCaLinkInitImpl(int isolate)
{
dbServiceIsolate = isolate;
dbServiceIOInit();
if (!workListLock)
workListLock = epicsMutexMustCreate();
if (!workListEvent)
workListEvent = epicsEventMustCreate(epicsEventEmpty);
dbCaCtl = ctlExit;
}
void dbCaLinkInit(void)
{
dbServiceIOInit();
dbCaLinkInitIsolated();
startStopEvent = epicsEventMustCreate(epicsEventEmpty);
dbCaCtl = ctlPause;
@@ -242,6 +242,16 @@ void dbCaLinkInit(void)
epicsEventMustWait(startStopEvent);
}
void dbCaLinkInitIsolated(void)
{
dbCaLinkInitImpl(1);
}
void dbCaLinkInit(void)
{
dbCaLinkInitImpl(0);
}
void dbCaRun(void)
{
if (dbCaCtl == ctlPause) {

118
src/ioc/db/dbChannelNOOP.h Normal file
View File

@@ -0,0 +1,118 @@
#ifndef DBCHANNELNOOP_H
#define DBCHANNELNOOP_H
#include <string.h>
#include <string>
#include "cacIO.h"
#include "caerr.h"
/** @brief A channel which never connects
*
* Used when dbCa is placed in isolated mode for unittests
*/
class dbChannelNOOP : public cacChannel
{
std::string myname;
public:
dbChannelNOOP(const char *name, cacChannelNotify &notify)
:cacChannel(notify)
,myname(name)
{}
virtual void destroy (
CallbackGuard & /*callbackGuard*/,
epicsGuard < epicsMutex > & /*mutualExclusionGuard*/ )
{
delete this; // goodbye cruel world
}
virtual unsigned getName (
epicsGuard < epicsMutex > &,
char * pBuf, unsigned bufLen ) const throw ()
{
const char* name = myname.c_str();
if(bufLen>myname.size()+1) {
bufLen=myname.size()+1;
}
memcpy(pBuf, name, bufLen);
pBuf[--bufLen] = '\0';
return bufLen;
}
// !! deprecated, avoid use !!
virtual const char * pName (
epicsGuard < epicsMutex > & guard ) const throw ()
{return myname.c_str();}
virtual void show (
epicsGuard < epicsMutex > &,
unsigned level ) const
{}
virtual void initiateConnect (
epicsGuard < epicsMutex > & )
{}
virtual unsigned requestMessageBytesPending (
epicsGuard < epicsMutex > & /*mutualExclusionGuard*/ )
{return 0;}
virtual void flush (
epicsGuard < epicsMutex > & /*mutualExclusionGuard*/ )
{}
virtual ioStatus read (
epicsGuard < epicsMutex > &mut,
unsigned type, arrayElementCount count,
cacReadNotify &notify, ioid * = 0 )
{
notify.exception(mut, ECA_NORDACCESS, "dbChannelNOOP", type, count);
return iosSynch;
}
virtual void write (
epicsGuard < epicsMutex > &,
unsigned type, arrayElementCount count,
const void *pValue )
{}
virtual ioStatus write (
epicsGuard < epicsMutex > &mut,
unsigned type, arrayElementCount count,
const void */*pValue*/, cacWriteNotify & notify, ioid * = 0 )
{
notify.exception(mut, ECA_NOWTACCESS, "dbChannelNOOP", type, count);
return iosSynch;
}
virtual void subscribe (
epicsGuard < epicsMutex > &mut, unsigned type,
arrayElementCount count, unsigned /*mask*/, cacStateNotify & notify,
ioid * = 0 )
{
// should never subscribe
notify.exception(mut, ECA_BADMASK, "dbChannelNOOP", type, count);
}
virtual void ioCancel (
CallbackGuard & callbackGuard,
epicsGuard < epicsMutex > & mutualExclusionGuard,
const ioid & )
{}
virtual void ioShow (
epicsGuard < epicsMutex > &,
const ioid &, unsigned level ) const
{}
virtual short nativeType (
epicsGuard < epicsMutex > & ) const
{return 0;} // DBR_STRING
virtual arrayElementCount nativeElementCount (
epicsGuard < epicsMutex > & ) const
{return 1;}
};
#endif // DBCHANNELNOOP_H

View File

@@ -39,6 +39,7 @@
#include "dbCAC.h"
#include "dbChannel.h"
#include "dbChannelIO.h"
#include "dbChannelNOOP.h"
#include "dbPutNotifyBlocker.h"
class dbService : public cacService {
@@ -61,9 +62,16 @@ cacContext & dbService::contextCreate (
mutualExclusion, notify );
}
extern "C" int dbServiceIsolate;
int dbServiceIsolate = 0;
extern "C" void dbServiceIOInit ()
{
caInstallDefaultService ( dbs );
static int init=0;
if(!init) {
caInstallDefaultService ( dbs );
init=1;
}
}
dbBaseIO::dbBaseIO () {}
@@ -72,7 +80,8 @@ dbContext::dbContext ( epicsMutex & cbMutexIn,
epicsMutex & mutexIn, cacContextNotify & notifyIn ) :
readNotifyCache ( mutexIn ), ctx ( 0 ),
stateNotifyCacheSize ( 0 ), mutex ( mutexIn ), cbMutex ( cbMutexIn ),
notify ( notifyIn ), pNetContext ( 0 ), pStateNotifyCache ( 0 )
notify ( notifyIn ), pNetContext ( 0 ), pStateNotifyCache ( 0 ),
isolated(dbServiceIsolate)
{
}
@@ -92,7 +101,10 @@ cacChannel & dbContext::createChannel (
dbChannel *dbch = dbChannel_create ( pName );
if ( ! dbch ) {
if ( ! this->pNetContext.get() ) {
if ( isolated ) {
return *new dbChannelNOOP(pName, notifyIn);
} else if ( ! this->pNetContext.get() ) {
this->pNetContext.reset (
& this->notify.createNetworkContext (
this->mutex, this->cbMutex ) );

View File

@@ -109,6 +109,15 @@ static void notifyCallback(CALLBACK *pcallback);
(listnode)->isOnList=0; \
}
static void notifyFree(void *raw)
{
notifyPvt *pnotifyPvt = raw;
assert(pnotifyPvt->magic==MAGIC);
epicsEventDestroy(pnotifyPvt->cancelEvent);
epicsEventDestroy(pnotifyPvt->userCallbackEvent);
free(pnotifyPvt);
}
static void notifyInit(processNotify *ppn)
{
notifyPvt *pnotifyPvt;
@@ -301,7 +310,7 @@ static void notifyCallback(CALLBACK *pcallback)
void dbProcessNotifyExit(void)
{
assert(ellCount(&pnotifyGlobal->freeList)==0);
ellFree2(&pnotifyGlobal->freeList, &notifyFree);
epicsMutexDestroy(pnotifyGlobal->lock);
free(pnotifyGlobal);
pnotifyGlobal = NULL;

View File

@@ -10,10 +10,15 @@ TOP=../../../..
include $(TOP)/configure/CONFIG
# Find private headers
USR_CFLAGS += -I../..
TESTLIBRARY = dbTestIoc
dbTestIoc_SRCS += xRecord.c
dbTestIoc_SRCS += arrRecord.c
dbTestIoc_SRCS += dbLinkdset.c
dbTestIoc_SRCS += devx.c
dbTestIoc_LIBS = dbCore ca Com
TARGETS += $(COMMON_DIR)/dbTestIoc.dbd
@@ -21,6 +26,8 @@ dbTestIoc_DBD += menuGlobal.dbd
dbTestIoc_DBD += menuConvert.dbd
dbTestIoc_DBD += menuScan.dbd
dbTestIoc_DBD += xRecord.dbd
dbTestIoc_DBD += arrRecord.dbd
dbTestIoc_DBD += devx.dbd
dbTestIoc_DBD += dbLinkdset.dbd
TESTFILES += $(COMMON_DIR)/dbTestIoc.dbd ../xRecord.db
@@ -81,18 +88,21 @@ testHarness_SRCS += dbCaStatsTest.c
TESTS += dbCaStatsTest
TESTFILES += ../dbCaStatsTest.db
TARGETS += $(COMMON_DIR)/scanIoTest.dbd
scanIoTest_DBD += menuGlobal.dbd
scanIoTest_DBD += menuConvert.dbd
TESTPROD_HOST += dbCaLinkTest
dbCaLinkTest_SRCS += dbCaLinkTest.c
dbCaLinkTest_SRCS += dbCACTest.cpp
dbCaLinkTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp
testHarness_SRCS += dbCaLinkTest.c
testHarness_SRCS += dbCACTest.cpp
TESTS += dbCaLinkTest
TESTFILES += ../dbCaLinkTest1.db ../dbCaLinkTest2.db ../dbCaLinkTest3.db
scanIoTest_DBD += menuScan.dbd
scanIoTest_DBD += yRecord.dbd
TESTPROD_HOST += scanIoTest
scanIoTest_SRCS += scanIoTest.c
scanIoTest_REGRDDFLAGS = -l
scanIoTest_SRCS += scanIoTest_registerRecordDeviceDriver.cpp
scanIoTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp
testHarness_SRCS += scanIoTest.c
testHarness_SRCS += scanIoTest_registerRecordDeviceDriver.cpp
TESTFILES += $(COMMON_DIR)/scanIoTest.dbd ../scanIoTest.db
TESTFILES += ../scanIoTest.db
TESTS += scanIoTest
TESTPROD_HOST += dbChannelTest
@@ -139,6 +149,9 @@ TESTSCRIPTS_HOST += $(TESTS:%=%.t)
include $(TOP)/configure/RULES
xRecord$(DEP): $(COMMON_DIR)/xRecord.h
arrRecord$(DEP): $(COMMON_DIR)/arrRecord.h
dbPutLinkTest$(DEP): $(COMMON_DIR)/xRecord.h
scanIoTest$(DEP): $(COMMON_DIR)/yRecord.h
devx$(DEP): $(COMMON_DIR)/xRecord.h
scanIoTest$(DEP): $(COMMON_DIR)/xRecord.h
dbCaLinkTest$(DEP): $(COMMON_DIR)/xRecord.h $(COMMON_DIR)/arrRecord.h

141
src/ioc/db/test/arrRecord.c Normal file
View File

@@ -0,0 +1,141 @@
/*************************************************************************\
* Copyright (c) 2010 Brookhaven National Laboratory.
* Copyright (c) 2010 Helmholtz-Zentrum Berlin
* fuer Materialien und Energie GmbH.
* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne
* National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
* Operator of Los Alamos National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/* arrRecord.c - minimal array record for test purposes: no processing */
/*
* Author: Ralph Lange <Ralph.Lange@bessy.de>
*
* vaguely implemented like parts of recWaveform.c by Bob Dalesio
*
*/
#include <stdio.h>
#include "dbDefs.h"
#include "epicsPrint.h"
#include "dbAccess.h"
#include "dbEvent.h"
#include "dbFldTypes.h"
#include "recSup.h"
#include "recGbl.h"
#include "cantProceed.h"
#define GEN_SIZE_OFFSET
#include "arrRecord.h"
#undef GEN_SIZE_OFFSET
#include "epicsExport.h"
/* Create RSET - Record Support Entry Table*/
#define report NULL
#define initialize NULL
static long init_record(arrRecord *, int);
static long process(arrRecord *);
#define special NULL
#define get_value NULL
static long cvt_dbaddr(DBADDR *);
static long get_array_info(DBADDR *, long *, long *);
static long put_array_info(DBADDR *, long);
#define get_units NULL
#define get_precision NULL
#define get_enum_str NULL
#define get_enum_strs NULL
#define put_enum_str NULL
#define get_graphic_double NULL
#define get_control_double NULL
#define get_alarm_double NULL
rset arrRSET = {
RSETNUMBER,
report,
initialize,
init_record,
process,
special,
get_value,
cvt_dbaddr,
get_array_info,
put_array_info,
get_units,
get_precision,
get_enum_str,
get_enum_strs,
put_enum_str,
get_graphic_double,
get_control_double,
get_alarm_double
};
epicsExportAddress(rset, arrRSET);
static long init_record(arrRecord *prec, int pass)
{
if (pass == 0) {
if (prec->nelm <= 0)
prec->nelm = 1;
if (prec->ftvl > DBF_ENUM)
prec->ftvl = DBF_UCHAR;
prec->bptr = callocMustSucceed(prec->nelm, dbValueSize(prec->ftvl),
"arr calloc failed");
if (prec->nelm == 1) {
prec->nord = 1;
} else {
prec->nord = 0;
}
return 0;
}
return 0;
}
static long process(arrRecord *prec)
{
if(prec->clbk)
(*prec->clbk)(prec);
prec->pact = TRUE;
recGblGetTimeStamp(prec);
recGblFwdLink(prec);
prec->pact = FALSE;
return 0;
}
static long cvt_dbaddr(DBADDR *paddr)
{
arrRecord *prec = (arrRecord *) paddr->precord;
paddr->pfield = prec->bptr;
paddr->no_elements = prec->nelm;
paddr->field_type = prec->ftvl;
paddr->field_size = dbValueSize(prec->ftvl);
paddr->dbr_field_type = prec->ftvl;
return 0;
}
static long get_array_info(DBADDR *paddr, long *no_elements, long *offset)
{
arrRecord *prec = (arrRecord *) paddr->precord;
*no_elements = prec->nord;
*offset = prec->off;
return 0;
}
static long put_array_info(DBADDR *paddr, long nNew)
{
arrRecord *prec = (arrRecord *) paddr->precord;
prec->nord = nNew;
if (prec->nord > prec->nelm)
prec->nord = prec->nelm;
return 0;
}

View File

@@ -0,0 +1,42 @@
include "menuGlobal.dbd"
include "menuConvert.dbd"
include "menuScan.dbd"
recordtype(arr) {
include "dbCommon.dbd"
field(VAL, DBF_NOACCESS) {
prompt("Value")
special(SPC_DBADDR)
pp(TRUE)
extra("void *val")
}
field(NELM, DBF_ULONG) {
prompt("Number of Elements")
special(SPC_NOMOD)
initial("1")
}
field(FTVL, DBF_MENU) {
prompt("Field Type of Value")
special(SPC_NOMOD)
menu(menuFtype)
}
field(NORD, DBF_ULONG) {
prompt("Number elements read")
special(SPC_NOMOD)
}
field(OFF, DBF_ULONG) {
prompt("Offset into array")
}
field(BPTR, DBF_NOACCESS) {
prompt("Buffer Pointer")
special(SPC_NOMOD)
extra("void *bptr")
}
field(INP, DBF_INLINK) {
prompt("Input Link")
}
field(CLBK, DBF_NOACCESS) {
prompt("Processing callback")
special(SPC_NOMOD)
extra("void (*clbk)(struct arrRecord*)")
}
}

View File

@@ -0,0 +1,84 @@
/*************************************************************************\
* 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.
\*************************************************************************/
/*
* Part of dbCaLinkTest, compiled seperately to avoid
* dbAccess.h vs. db_access.h conflicts
*/
#include <stdio.h>
#include <vector>
#include <stdexcept>
#include <epicsEvent.h>
#include "epicsUnitTest.h"
#include "cadef.h"
#define testECA(OP) if((OP)!=ECA_NORMAL) {testAbort("%s", #OP);} else {testPass("%s", #OP);}
void putgetarray(chid chanid, double first, size_t count)
{
testDiag("putgetarray(%f,%u)", first, (unsigned)count);
std::vector<double> buf(count);
for(size_t i=0; i<count ;i++)
buf[i] = first + i*1.0;
testDiag("Put");
testECA(ca_array_put(DBR_DOUBLE, count, chanid, &buf[0]));
testECA(ca_pend_io(1.0));
testDiag("Get");
std::vector<double> buf2(count);
testECA(ca_array_get(DBR_DOUBLE, count, chanid, &buf2[0]));
testECA(ca_pend_io(1.0));
for(size_t i=0; i<count ;i++)
testOk(buf[i]==buf2[i], "%f == %f", buf[i], buf2[i]);
}
struct CATestContext
{
CATestContext()
{
if(ca_context_create(ca_enable_preemptive_callback)!=ECA_NORMAL)
throw std::runtime_error("Failed to create CA context");
}
~CATestContext()
{
ca_context_destroy();
}
};
extern "C"
void dbCaLinkTest_testCAC(void)
{
try {
CATestContext ctxt;
chid chanid = 0;
testECA(ca_create_channel("target1", NULL, NULL, 0, &chanid));
testECA(ca_pend_io(1.0));
putgetarray(chanid, 1.0, 1);
putgetarray(chanid, 2.0, 2);
// repeat to ensure a cache hit in dbContextReadNotifyCacheAllocator
putgetarray(chanid, 2.0, 2);
putgetarray(chanid, 5.0, 5);
testECA(ca_clear_channel(chanid));
}catch(std::exception& e){
testAbort("Unexpected exception in testCAC: %s", e.what());
}
}

View File

@@ -0,0 +1,597 @@
/*************************************************************************\
* 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 <mdavidsaver@bnl.gov>
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#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;i<used;i++,x++,b++)
match &= (*b)==x;
for(;i<total;i++,x++,b++)
match &= (*b)==0;
testOk(match, 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);
for(;i<total;i++,x++,b++)
if((*b)!=0)
testDiag("%u %u != %u", i, (unsigned)*b, (unsigned)x);
}
}
static void checkArrayDouble(const char *msg,
double *buf, double first,
unsigned used, unsigned total)
{
int match = 1;
unsigned i;
double x, *b;
for(b=buf,x=first,i=0;i<used;i++,x++,b++)
match &= (*b)==x;
for(;i<total;i++,x++,b++)
match &= (*b)==0;
testOk(match, 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);
for(;i<total;i++,x++,b++)
if((*b)!=0)
testDiag("%u %u != %u", i, (unsigned)*b, (unsigned)x);
}
}
static void spoilputbuf(DBLINK *lnk)
{
extern const unsigned short dbr_value_size[];
short epicsShareAPI ca_field_type (chid chan);
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;
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);
}
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(91);
testNativeLink();
testStringLink();
testCP();
testArrayLink(1,1);
testArrayLink(10,1);
testArrayLink(1,10);
testArrayLink(10,10);
testreTargetTypeChange();
testCAC();
return testDone();
}

View File

@@ -0,0 +1,5 @@
record(x, "target") {}
record(x, "source") {
field(LNK, "$(TARGET)")
}

View File

@@ -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=))")
}

View File

@@ -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=))")
}

View File

@@ -29,7 +29,6 @@ long link_test_noop(void *junk)
static dset devxLTest ## LTYPE = {4, NULL, &link_test_init, &link_test_noop, &link_test_noop}; \
epicsExportAddress(dset, devxLTest ## LTYPE);
DEFDSET(Soft)
DEFDSET(VME_IO)
DEFDSET(CAMAC_IO)
DEFDSET(AB_IO)

View File

@@ -1,5 +1,3 @@
device(x, CONSTANT,devxLTestSoft,"Soft Channel")
device(x, VME_IO, devxLTestVME_IO, "Unit Test VME_IO")
device(x, CAMAC_IO, devxLTestCAMAC_IO, "Unit Test CAMAC_IO")
device(x, AB_IO, devxLTestAB_IO, "Unit Test AB_IO")

163
src/ioc/db/test/devx.c Normal file
View File

@@ -0,0 +1,163 @@
/*************************************************************************\
* Copyright (c) 2014 Brookhaven Science Assoc. as Operator of Brookhaven
* National Lab
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include <string.h>
#include <stdio.h>
#include <epicsAssert.h>
#include <cantProceed.h>
#include <ellLib.h>
#include <dbDefs.h>
#include <dbAccess.h>
#include <dbScan.h>
#include <devSup.h>
#include <recGbl.h>
#include <link.h>
#include <dbLink.h>
#include <xRecord.h>
#include <epicsExport.h>
#include "devx.h"
/* xRecord DTYP="Scan I/O"
*
* dset to test I/O Intr scanning.
* INP="@drvname" names a "driver" which
* provides a IOSCANPVT.
* The driver also defines a callback function which
* is invoked when the record is processed.
*/
struct ELLLIST xdrivers;
/* Add a new "driver" with the given group id
* and processing callback
*/
xdrv* xdrv_add(int group, xdrvcb cb, void *arg)
{
xdrv *drv=callocMustSucceed(1, sizeof(*drv), "xdrv_add");
drv->cb = cb;
drv->arg = arg;
drv->group = group;
scanIoInit(&drv->scan);
ellAdd(&xdrivers, &drv->drvnode);
return drv;
}
/* Trigger the named "driver" group to scan */
xdrv *xdrv_get(int group)
{
ELLNODE *cur;
for(cur=ellFirst(&xdrivers); cur; cur=ellNext(cur)) {
xdrv *curd = CONTAINER(cur, xdrv, drvnode);
if(curd->group==group) {
return curd;
}
}
cantProceed("xdrv_get() for non-existant group");
return NULL;
}
/* Free all "driver" groups and record private structures.
* Call after testdbCleanup()
*/
void xdrv_reset()
{
ELLNODE *cur;
while((cur=ellGet(&xdrivers))!=NULL) {
ELLNODE *cur2;
xdrv *curd = CONTAINER(cur, xdrv, drvnode);
while((cur2=ellGet(&curd->privlist))!=NULL) {
xpriv *priv = CONTAINER(cur2, xpriv, privnode);
free(priv);
}
free(curd);
}
}
static long xscanio_init_record(xRecord *prec)
{
ELLNODE *cur;
xpriv *priv;
xdrv *drv = NULL;
int group, member;
assert(prec->inp.type==INST_IO);
if(sscanf(prec->inp.value.instio.string, "%d %d",
&group, &member)!=2)
cantProceed("xscanio_init_record invalid INP string");
for(cur=ellFirst(&xdrivers); cur; cur=ellNext(cur)) {
xdrv *curd = CONTAINER(cur, xdrv, drvnode);
if(curd->group==group) {
drv = curd;
break;
}
}
assert(drv!=NULL);
priv = mallocMustSucceed(sizeof(*priv), "xscanio_init_record");
priv->prec = prec;
priv->drv = drv;
priv->member = member;
ellAdd(&drv->privlist, &priv->privnode);
prec->dpvt = priv;
return 0;
}
static long xscanio_get_ioint_info(int cmd, xRecord *prec, IOSCANPVT *ppvt)
{
xpriv *priv = prec->dpvt;
if(!priv || !priv->drv)
return 0;
*ppvt = priv->drv->scan;
return 0;
}
static long xscanio_read(xRecord *prec)
{
xpriv *priv = prec->dpvt;
if(!priv || !priv->drv)
return 0;
if(priv->drv->cb)
(*priv->drv->cb)(priv, priv->drv->arg);
return 0;
}
static xdset devxScanIO = {
5, NULL, NULL,
&xscanio_init_record,
&xscanio_get_ioint_info,
&xscanio_read
};
epicsExportAddress(dset, devxScanIO);
/* basic DTYP="Soft Channel" */
static long xsoft_init_record(xRecord *prec)
{
if(prec->inp.type==CONSTANT)
recGblInitConstantLink(&prec->inp, DBF_LONG, &prec->val);
return 0;
}
static long xsoft_read(xRecord *prec)
{
if(prec->inp.type==CONSTANT)
return 0;
dbGetLink(&prec->inp, DBR_DOUBLE, &prec->val, NULL, NULL);
return 0;
}
static struct xdset devxSoft = {
5, NULL, NULL,
&xsoft_init_record,
NULL,
&xsoft_read
};
epicsExportAddress(dset, devxSoft);

2
src/ioc/db/test/devx.dbd Normal file
View File

@@ -0,0 +1,2 @@
device(x, CONSTANT, devxSoft, "Soft Channel")
device(x, INST_IO , devxScanIO, "Scan I/O")

52
src/ioc/db/test/devx.h Normal file
View File

@@ -0,0 +1,52 @@
/*************************************************************************\
* Copyright (c) 2014 Brookhaven Science Assoc. as Operator of Brookhaven
* National Lab
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#ifndef DEVXSCANIO_H
#define DEVXSCANIO_H
#include <ellLib.h>
#include <dbScan.h>
#include <shareLib.h>
struct xRecord;
struct xpriv;
epicsShareExtern struct ELLLIST xdrivers;
typedef void (*xdrvcb)(struct xpriv *, void *);
typedef struct {
ELLNODE drvnode;
IOSCANPVT scan;
xdrvcb cb;
void *arg;
ELLLIST privlist;
int group;
} xdrv;
typedef struct xpriv {
ELLNODE privnode;
xdrv *drv;
struct xRecord *prec;
int member;
} xpriv;
epicsShareFunc xdrv *xdrv_add(int group, xdrvcb cb, void *arg);
epicsShareFunc xdrv *xdrv_get(int group);
epicsShareFunc void xdrv_reset();
typedef struct xdset {
long number;
long (*report)(int);
long (*init)(int);
long (*init_record)(struct xRecord *);
long (*get_ioint_info)(int, struct xRecord*, IOSCANPVT*);
long (*process)(struct xRecord *);
} xdset;
#endif /* DEVXSCANIO_H */

View File

@@ -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);

View File

@@ -12,6 +12,8 @@
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "epicsEvent.h"
#include "epicsMessageQueue.h"
@@ -26,9 +28,6 @@
#include "dbLock.h"
#include "dbUnitTest.h"
#include "dbCommon.h"
#include "registry.h"
#include "registryRecordType.h"
#include "registryDeviceSupport.h"
#include "recSup.h"
#include "devSup.h"
#include "iocInit.h"
@@ -38,482 +37,272 @@
#include "testMain.h"
#include "osiFileName.h"
#define GEN_SIZE_OFFSET
#include "yRecord.h"
#include "epicsExport.h"
#define ONE_THREAD_LOOPS 101
#define PAR_THREAD_LOOPS 53
#define CB_THREAD_LOOPS 13
#include "devx.h"
#include "xRecord.h"
#define NO_OF_THREADS 7
#define NO_OF_MEMBERS 5
#define NO_OF_GROUPS 11
STATIC_ASSERT(NUM_CALLBACK_PRIORITIES==3);
#define NO_OF_MID_THREADS 3
void dbTestIoc_registerRecordDeviceDriver(struct dbBase *);
static int noOfGroups = NO_OF_GROUPS;
static int noOfIoscans = NO_OF_GROUPS;
static IOSCANPVT *ioscanpvt; /* Soft interrupt sources */
static ELLLIST *pvtList; /* Per group private part lists */
static int executionOrder;
static int orderFail;
static int testNo;
static epicsMessageQueueId *mq; /* Per group message queue */
static epicsEventId *barrier; /* Per group barrier event */
static int *cbCounter;
struct pvtY {
ELLNODE node;
yRecord *prec;
int group;
int member;
int count;
int processed;
int callback;
};
/* test2: priority and ioscan index for each group
* used priorities are expressed in the bit pattern of (ioscan index + 1) */
struct groupItem {
int prio;
int ioscan;
} groupTable[12] = {
{ 0, 0 },
{ 1, 1 },
{ 0, 2 }, { 1, 2 },
{ 2, 3 },
{ 0, 4 }, { 2, 4 },
{ 1, 5 }, { 2, 5 },
{ 0, 6 }, { 1, 6 }, { 2, 6 }
};
static int recsProcessed = 1;
static int noDoubleCallback = 1;
void scanIoTest_registerRecordDeviceDriver(struct dbBase *);
long count_bits(long n) {
unsigned int c; /* c accumulates the total bits set in v */
for (c = 0; n; c++)
n &= n - 1; /* clear the least significant bit set */
return c;
}
/*************************************************************************\
* yRecord: minimal record needed to test I/O Intr scanning
\*************************************************************************/
static long get_ioint_info(int cmd, yRecord *prec, IOSCANPVT *ppvt)
static void loadRecord(int group, int member, const char *prio)
{
struct pvtY *pvt = (struct pvtY *)(prec->dpvt);
if (testNo == 2)
*ppvt = ioscanpvt[groupTable[pvt->group].ioscan];
else
*ppvt = ioscanpvt[pvt->group];
return 0;
char buf[40];
sprintf(buf, "GROUP=%d,MEMBER=%d,PRIO=%s",
group, member, prio);
testdbReadDatabase("scanIoTest.db", NULL, buf);
}
struct ydset {
long number;
DEVSUPFUN report;
DEVSUPFUN init;
DEVSUPFUN init_record;
DEVSUPFUN get_ioint_info;
DEVSUPFUN process;
} devY = {
5,
NULL,
NULL,
NULL,
get_ioint_info,
NULL
};
epicsExportAddress(dset, devY);
typedef struct {
int hasprocd[NUM_CALLBACK_PRIORITIES];
int getcomplete[NUM_CALLBACK_PRIORITIES];
epicsEventId wait[NUM_CALLBACK_PRIORITIES];
epicsEventId wake[NUM_CALLBACK_PRIORITIES];
} testsingle;
static long init_record(yRecord *prec, int pass)
static void testcb(xpriv *priv, void *raw)
{
struct pvtY *pvt;
testsingle *td = raw;
int prio = priv->prec->prio;
if (pass == 0) return 0;
pvt = (struct pvtY *) calloc(1, sizeof(struct pvtY));
prec->dpvt = pvt;
pvt->prec = prec;
sscanf(prec->name, "g%dm%d", &pvt->group, &pvt->member);
ellAdd(&pvtList[pvt->group], &pvt->node);
return 0;
testOk1(td->hasprocd[prio]==0);
td->hasprocd[prio] = 1;
}
static long process(yRecord *prec)
static void testcomp(void *raw, IOSCANPVT scan, int prio)
{
struct pvtY *pvt = (struct pvtY *)(prec->dpvt);
testsingle *td = raw;
if (testNo == 0) {
/* Single callback thread */
if (executionOrder != pvt->member) {
orderFail = 1;
}
pvt->count++;
if (++executionOrder == NO_OF_MEMBERS) executionOrder = 0;
} else {
pvt->count++;
if (pvt->member == 0) {
epicsMessageQueueSend(mq[pvt->group], NULL, 0);
epicsEventMustWait(barrier[pvt->group]);
}
}
pvt->processed = 1;
return 0;
testOk1(td->hasprocd[prio]==1);
testOk1(td->getcomplete[prio]==0);
td->getcomplete[prio] = 1;
epicsEventMustTrigger(td->wait[prio]);
epicsEventMustWait(td->wake[prio]);
}
rset yRSET={
4,
NULL, /* report */
NULL, /* initialize */
init_record,
process
};
epicsExportAddress(rset, yRSET);
static void startMockIoc(void) {
char substitutions[256];
int i, j;
char *prio[] = { "LOW", "MEDIUM", "HIGH" };
if (testNo == 2) {
noOfGroups = 12;
noOfIoscans = 7;
}
ioscanpvt = calloc(noOfIoscans, sizeof(IOSCANPVT));
mq = calloc(noOfGroups, sizeof(epicsMessageQueueId));
barrier = calloc(noOfGroups, sizeof(epicsEventId));
pvtList = calloc(noOfGroups, sizeof(ELLLIST));
cbCounter = calloc(noOfGroups, sizeof(int));
if (dbReadDatabase(&pdbbase, "scanIoTest.dbd",
"." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR
"../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL))
testAbort("Error reading database description 'scanIoTest.dbd'");
callbackParallelThreads(1, "Low");
callbackParallelThreads(NO_OF_MID_THREADS, "Medium");
callbackParallelThreads(NO_OF_THREADS, "High");
for (i = 0; i < noOfIoscans; i++) {
scanIoInit(&ioscanpvt[i]);
}
for (i = 0; i < noOfGroups; i++) {
mq[i] = epicsMessageQueueCreate(NO_OF_MEMBERS, 1);
barrier[i] = epicsEventMustCreate(epicsEventEmpty);
ellInit(&pvtList[i]);
}
scanIoTest_registerRecordDeviceDriver(pdbbase);
for (i = 0; i < noOfGroups; i++) {
for (j = 0; j < NO_OF_MEMBERS; j++) {
sprintf(substitutions, "GROUP=%d,MEMBER=%d,PRIO=%s", i, j,
testNo==0?"LOW":(testNo==1?"HIGH":prio[groupTable[i].prio]));
if (dbReadDatabase(&pdbbase, "scanIoTest.db",
"." OSI_PATH_LIST_SEPARATOR "..", substitutions))
testAbort("Error reading test database 'scanIoTest.db'");
}
}
testIocInitOk();
}
static void stopMockIoc(void) {
static void testSingleThreading(void)
{
int i;
testsingle data[2];
xdrv *drvs[2];
memset(data, 0, sizeof(data));
for(i=0; i<2; i++) {
int p;
for(p=0; p<NUM_CALLBACK_PRIORITIES; p++) {
data[i].wake[p] = epicsEventMustCreate(epicsEventEmpty);
data[i].wait[p] = epicsEventMustCreate(epicsEventEmpty);
}
}
testDiag("Test single-threaded I/O Intr scanning");
testdbPrepare();
testdbReadDatabase("dbTestIoc.dbd", NULL, NULL);
dbTestIoc_registerRecordDeviceDriver(pdbbase);
/* create two scan lists with one record on each of three priorities */
/* group#, member#, priority */
loadRecord(0, 0, "LOW");
loadRecord(1, 0, "LOW");
loadRecord(0, 1, "MEDIUM");
loadRecord(1, 1, "MEDIUM");
loadRecord(0, 2, "HIGH");
loadRecord(1, 2, "HIGH");
drvs[0] = xdrv_add(0, &testcb, &data[0]);
drvs[1] = xdrv_add(1, &testcb, &data[1]);
scanIoSetComplete(drvs[0]->scan, &testcomp, &data[0]);
scanIoSetComplete(drvs[1]->scan, &testcomp, &data[1]);
eltc(0);
testIocInitOk();
eltc(1);
testDiag("Scan first list");
scanIoRequest(drvs[0]->scan);
testDiag("Scan second list");
scanIoRequest(drvs[1]->scan);
testDiag("Wait for first list to complete");
for(i=0; i<NUM_CALLBACK_PRIORITIES; i++)
epicsEventMustWait(data[0].wait[i]);
testDiag("Wait one more second");
epicsThreadSleep(1.0);
testOk1(data[0].hasprocd[0]==1);
testOk1(data[0].hasprocd[1]==1);
testOk1(data[0].hasprocd[2]==1);
testOk1(data[0].getcomplete[0]==1);
testOk1(data[0].getcomplete[1]==1);
testOk1(data[0].getcomplete[2]==1);
testOk1(data[1].hasprocd[0]==0);
testOk1(data[1].hasprocd[1]==0);
testOk1(data[1].hasprocd[2]==0);
testOk1(data[1].getcomplete[0]==0);
testOk1(data[1].getcomplete[1]==0);
testOk1(data[1].getcomplete[2]==0);
testDiag("Release the first scan and wait for the second");
for(i=0; i<NUM_CALLBACK_PRIORITIES; i++)
epicsEventMustTrigger(data[0].wake[i]);
for(i=0; i<NUM_CALLBACK_PRIORITIES; i++)
epicsEventMustWait(data[1].wait[i]);
testOk1(data[0].hasprocd[0]==1);
testOk1(data[0].hasprocd[1]==1);
testOk1(data[0].hasprocd[2]==1);
testOk1(data[0].getcomplete[0]==1);
testOk1(data[0].getcomplete[1]==1);
testOk1(data[0].getcomplete[2]==1);
testOk1(data[1].hasprocd[0]==1);
testOk1(data[1].hasprocd[1]==1);
testOk1(data[1].hasprocd[2]==1);
testOk1(data[1].getcomplete[0]==1);
testOk1(data[1].getcomplete[1]==1);
testOk1(data[1].getcomplete[2]==1);
testDiag("Release the second scan and complete");
for(i=0; i<NUM_CALLBACK_PRIORITIES; i++)
epicsEventMustTrigger(data[1].wake[i]);
testIocShutdownOk();
epicsThreadSleep(0.1);
for (i = 0; i < noOfGroups; i++) {
epicsMessageQueueDestroy(mq[i]); mq[i] = NULL;
epicsEventDestroy(barrier[i]); barrier[i] = NULL;
ellFree(&pvtList[i]);
}
free(mq);
free(barrier);
free(pvtList);
free(cbCounter);
testdbCleanup();
xdrv_reset();
for(i=0; i<2; i++) {
int p;
for(p=0; p<NUM_CALLBACK_PRIORITIES; p++) {
epicsEventDestroy(data[i].wake[p]);
epicsEventDestroy(data[i].wait[p]);
}
}
}
static void checkProcessed(void *user, IOSCANPVT ioscan, int prio) {
struct pvtY *pvt;
int group = -1;
typedef struct {
int hasprocd;
int getcomplete;
epicsEventId wait;
epicsEventId wake;
} testmulti;
static void testcbmulti(xpriv *priv, void *raw)
{
testmulti *td = raw;
td += priv->member;
testOk1(td->hasprocd==0);
td->hasprocd = 1;
epicsEventMustTrigger(td->wait);
epicsEventMustWait(td->wake);
td->getcomplete = 1;
}
static void testcompmulti(void *raw, IOSCANPVT scan, int prio)
{
int *mask = raw;
testOk(((*mask)&(1<<prio))==0, "(0x%x)&(0x%x)==0", *mask, 1<<prio);
*mask |= 1<<prio;
}
static void testMultiThreading(void)
{
int i;
int masks[2];
testmulti data[6];
xdrv *drvs[2];
for (i = 0; i < noOfGroups; i++) {
if (ioscanpvt[groupTable[i].ioscan] == ioscan
&& groupTable[i].prio == prio) {
group = i;
break;
}
memset(masks, 0, sizeof(masks));
memset(data, 0, sizeof(data));
for(i=0; i<NELEMENTS(data); i++) {
data[i].wake = epicsEventMustCreate(epicsEventEmpty);
data[i].wait = epicsEventMustCreate(epicsEventEmpty);
}
if (group == -1)
testAbort("invalid ioscanpvt in scanio callback");
cbCounter[group]++;
for (pvt = (struct pvtY *)ellFirst(&pvtList[group]);
pvt;
pvt = (struct pvtY *)ellNext(&pvt->node)) {
if (pvt->callback == 1) {
testDiag("callback for rec %s arrived twice\n", pvt->prec->name);
noDoubleCallback = 0;
}
if (pvt->processed == 0) {
testDiag("rec %s was not processed\n", pvt->prec->name);
recsProcessed = 0;
}
pvt->callback = 1;
testDiag("Test multi-threaded I/O Intr scanning");
testdbPrepare();
testdbReadDatabase("dbTestIoc.dbd", NULL, NULL);
dbTestIoc_registerRecordDeviceDriver(pdbbase);
/* create two scan lists with one record on each of three priorities */
/* group#, member#, priority */
loadRecord(0, 0, "LOW");
loadRecord(1, 1, "LOW");
loadRecord(0, 2, "MEDIUM");
loadRecord(1, 3, "MEDIUM");
loadRecord(0, 4, "HIGH");
loadRecord(1, 5, "HIGH");
drvs[0] = xdrv_add(0, &testcbmulti, data);
drvs[1] = xdrv_add(1, &testcbmulti, data);
scanIoSetComplete(drvs[0]->scan, &testcompmulti, &masks[0]);
scanIoSetComplete(drvs[1]->scan, &testcompmulti, &masks[1]);
/* just enough workers to process all records concurrently */
callbackParallelThreads(2, "LOW");
callbackParallelThreads(2, "MEDIUM");
callbackParallelThreads(2, "HIGH");
eltc(0);
testIocInitOk();
eltc(1);
testDiag("Scan first list");
testOk1(scanIoRequest(drvs[0]->scan)==0x7);
testDiag("Scan second list");
testOk1(scanIoRequest(drvs[1]->scan)==0x7);
testDiag("Wait for everything to start");
for(i=0; i<NELEMENTS(data); i++)
epicsEventMustWait(data[i].wait);
testDiag("Wait one more second");
epicsThreadSleep(1.0);
for(i=0; i<NELEMENTS(data); i++) {
testOk(data[i].hasprocd==1, "data[%d].hasprocd==1 (%d)", i, data[i].hasprocd);
testOk(data[i].getcomplete==0, "data[%d].getcomplete==0 (%d)", i, data[i].getcomplete);
}
testDiag("Release all and complete");
for(i=0; i<NELEMENTS(data); i++)
epicsEventMustTrigger(data[i].wake);
testIocShutdownOk();
for(i=0; i<NELEMENTS(data); i++) {
testOk(data[i].getcomplete==1, "data[%d].getcomplete==0 (%d)", i, data[i].getcomplete);
}
testOk1(masks[0]==0x7);
testOk1(masks[1]==0x7);
testdbCleanup();
xdrv_reset();
for(i=0; i<NELEMENTS(data); i++) {
epicsEventDestroy(data[i].wake);
epicsEventDestroy(data[i].wait);
}
}
/*************************************************************************\
* scanIoTest: Test I/O Intr scanning
* including parallel callback threads and scanio callbacks
\*************************************************************************/
MAIN(scanIoTest)
{
int i, j;
int loop;
int max_one, max_one_all;
int parallel, parallel_all;
int result;
int cbCountOk;
long waiting;
struct pvtY *pvt;
testPlan(10);
if (noOfGroups < NO_OF_THREADS)
testAbort("ERROR: This test requires number of ioscan sources >= number of parallel threads");
/**************************************\
* Single callback thread
\**************************************/
testNo = 0;
startMockIoc();
testDiag("Testing single callback thread");
testDiag(" using %d ioscan sources, %d records for each, and %d loops",
noOfGroups, NO_OF_MEMBERS, ONE_THREAD_LOOPS);
for (j = 0; j < ONE_THREAD_LOOPS; j++) {
for (i = 0; i < noOfIoscans; i++) {
scanIoRequest(ioscanpvt[i]);
}
}
epicsThreadSleep(1.0);
testOk((orderFail==0), "No out-of-order processing");
result = 1;
for (i = 0; i < noOfGroups; i++) {
for (pvt = (struct pvtY *)ellFirst(&pvtList[i]);
pvt;
pvt = (struct pvtY *)ellNext(&pvt->node)) {
if (pvt->count != ONE_THREAD_LOOPS) result = 0;
}
}
testOk(result, "All per-record process counters match number of loops");
stopMockIoc();
/**************************************\
* Multiple parallel callback threads
\**************************************/
testNo = 1;
startMockIoc();
testDiag("Testing multiple parallel callback threads");
testDiag(" using %d ioscan sources, %d records for each, %d loops, and %d parallel threads",
noOfIoscans, NO_OF_MEMBERS, PAR_THREAD_LOOPS, NO_OF_THREADS);
for (j = 0; j < PAR_THREAD_LOOPS; j++) {
for (i = 0; i < noOfIoscans; i++) {
scanIoRequest(ioscanpvt[i]);
}
}
/* With parallel cb threads, order and distribution to threads are not guaranteed.
* We have stop barrier events for each request (in the first record).
* Test schedule:
* - After the requests have been put in the queue, NO_OF_THREADS threads should have taken
* one request each.
* - Each barrier event is given PAR_THREAD_LOOPS times.
* - Whenever things stop, there should be four threads waiting, one request each.
* - After all loops, each record should have processed PAR_THREAD_LOOPS times.
*/
max_one_all = 1;
parallel_all = 1;
for (loop = 0; loop < (PAR_THREAD_LOOPS * noOfGroups) / NO_OF_THREADS + 1; loop++) {
max_one = 1;
parallel = 0;
waiting = 0;
j = 0;
do {
epicsThreadSleep(0.001);
j++;
for (i = 0; i < noOfGroups; i++) {
int l = epicsMessageQueuePending(mq[i]);
while (epicsMessageQueueTryReceive(mq[i], NULL, 0) != -1);
if (l == 1) {
waiting |= 1 << i;
} else if (l > 1) {
max_one = 0;
}
}
parallel = count_bits(waiting);
} while (j < 5 && parallel < NO_OF_THREADS);
if (!max_one) max_one_all = 0;
if (loop < (PAR_THREAD_LOOPS * noOfGroups) / NO_OF_THREADS) {
if (!(parallel == NO_OF_THREADS)) parallel_all = 0;
} else {
/* In the last run of the loop only the remaining requests are processed */
if (!(parallel == PAR_THREAD_LOOPS * noOfGroups % NO_OF_THREADS)) parallel_all = 0;
}
for (i = 0; i < noOfGroups; i++) {
if (waiting & (1 << i)) {
epicsEventTrigger(barrier[i]);
}
}
}
testOk(max_one_all, "No thread took more than one request per loop");
testOk(parallel_all, "Correct number of requests were being processed in parallel in each loop");
epicsThreadSleep(0.1);
result = 1;
for (i = 0; i < noOfGroups; i++) {
for (pvt = (struct pvtY *)ellFirst(&pvtList[i]);
pvt;
pvt = (struct pvtY *)ellNext(&pvt->node)) {
if (pvt->count != PAR_THREAD_LOOPS) {
testDiag("Process counter for record %s (%d) does not match loop count (%d)",
pvt->prec->name, pvt->count, PAR_THREAD_LOOPS);
result = 0;
}
}
}
testOk(result, "All per-record process counters match number of loops");
stopMockIoc();
/**************************************\
* Scanio callback mechanism
\**************************************/
testNo = 2;
startMockIoc();
for (i = 0; i < noOfIoscans; i++) {
scanIoSetComplete(ioscanpvt[i], checkProcessed, NULL);
}
testDiag("Testing scanio callback mechanism");
testDiag(" using %d ioscan sources, %d records for each, %d loops, and 1 LOW / %d MEDIUM / %d HIGH parallel threads",
noOfIoscans, NO_OF_MEMBERS, CB_THREAD_LOOPS, NO_OF_MID_THREADS, NO_OF_THREADS);
result = 1;
for (j = 0; j < CB_THREAD_LOOPS; j++) {
for (i = 0; i < noOfIoscans; i++) {
int prio_used;
prio_used = scanIoRequest(ioscanpvt[i]);
if (i+1 != prio_used)
result = 0;
}
}
testOk(result, "All requests return the correct priority callback mask (all 7 permutations covered)");
/* Test schedule:
* After the requests have been put in the queue, it is checked
* - that each callback arrives exactly once,
* - after all records in the group have been processed.
*/
/* loop count times 4 since (worst case) one loop triggers 4 groups for the single LOW thread */
for (loop = 0; loop < CB_THREAD_LOOPS * 4; loop++) {
max_one = 1;
parallel = 0;
waiting = 0;
j = 0;
do {
epicsThreadSleep(0.001);
j++;
for (i = 0; i < noOfGroups; i++) {
int l = epicsMessageQueuePending(mq[i]);
while (epicsMessageQueueTryReceive(mq[i], NULL, 0) != -1);
if (l == 1) {
waiting |= 1 << i;
} else if (l > 1) {
max_one = 0;
}
}
parallel = count_bits(waiting);
} while (j < 5);
\
for (i = 0; i < noOfGroups; i++) {
if (waiting & (1 << i)) {
for (pvt = (struct pvtY *)ellFirst(&pvtList[i]);
pvt;
pvt = (struct pvtY *)ellNext(&pvt->node)) {
pvt->processed = 0;
pvt->callback = 0;
/* record processing will set this at the end of process() */
}
epicsEventTrigger(barrier[i]);
}
}
}
epicsThreadSleep(0.1);
testOk(recsProcessed, "Each callback occured after all records in the group were processed");
testOk(noDoubleCallback, "No double callbacks occured in any loop");
result = 1;
cbCountOk = 1;
for (i = 0; i < noOfGroups; i++) {
if (cbCounter[i] != CB_THREAD_LOOPS) {
testDiag("Callback counter for group %d (%d) does not match loop count (%d)",
i, cbCounter[i], CB_THREAD_LOOPS);
cbCountOk = 0;
}
for (pvt = (struct pvtY *)ellFirst(&pvtList[i]);
pvt;
pvt = (struct pvtY *)ellNext(&pvt->node)) {
if (pvt->count != CB_THREAD_LOOPS) {
testDiag("Process counter for record %s (%d) does not match loop count (%d)",
pvt->prec->name, pvt->count, CB_THREAD_LOOPS);
result = 0;
}
}
}
testOk(result, "All per-record process counters match number of loops");
testOk(cbCountOk, "All per-group callback counters match number of loops");
stopMockIoc();
testPlan(152);
testSingleThreading();
testDiag("run a second time to verify shutdown and restart works");
testSingleThreading();
testMultiThreading();
testDiag("run a second time to verify shutdown and restart works");
testMultiThreading();
return testDone();
}

View File

@@ -1,4 +1,6 @@
record(y, g$(GROUP)m$(MEMBER)) {
record(x, "g$(GROUP)m$(MEMBER)") {
field(DTYP, "Scan I/O")
field(INP , "@$(GROUP) $(MEMBER)")
field(SCAN, "I/O Intr")
field(PRIO, "$(PRIO)")
}

View File

@@ -16,22 +16,47 @@
#include "dbAccessDefs.h"
#include "recSup.h"
#include "recGbl.h"
#include "devSup.h"
#include "dbScan.h"
#define GEN_SIZE_OFFSET
#include "xRecord.h"
#include <epicsExport.h>
#include "devx.h"
static long init_record(xRecord *prec, int pass)
{
long ret = 0;
xdset *xset = (xdset*)prec->dset;
if(!pass) return 0;
if(!xset) {
recGblRecordError(S_dev_noDSET, prec, "x: init_record");
return S_dev_noDSET;
}
if(xset->init_record)
ret = (*xset->init_record)(prec);
return ret;
}
static long process(xRecord *prec)
{
long ret = 0;
xdset *xset = (xdset*)prec->dset;
if(prec->clbk)
(*prec->clbk)(prec);
prec->pact = TRUE;
if(xset && xset->process)
ret = (*xset->process)(prec);
recGblGetTimeStamp(prec);
recGblFwdLink(prec);
prec->pact = FALSE;
return 0;
return ret;
}
static rset xRSET = {
RSETNUMBER, NULL, NULL, NULL, process
RSETNUMBER, NULL, NULL, init_record, process
};
epicsExportAddress(rset,xRSET);

View File

@@ -11,4 +11,9 @@ recordtype(x) {
field(INP, DBF_INLINK) {
prompt("Input Link")
}
field(CLBK, DBF_NOACCESS) {
prompt("Processing callback")
special(SPC_NOMOD)
extra("void (*clbk)(struct xRecord*)")
}
}

View File

@@ -1,10 +0,0 @@
# This is a minimal I/O scanned record
recordtype(y) {
include "dbCommon.dbd"
field(VAL, DBF_LONG) {
prompt("Value")
}
}
device(y,CONSTANT,devY,"ScanIO Test")

View File

@@ -679,6 +679,9 @@ static void doFreeRecord(dbRecordType *pdbRecordType, dbCommon *precord,
dbFreeLinkContents(plink);
}
// may be allocated in dbNotify.c
free(precord->ppnr);
}
int iocShutdown(void)
@@ -698,6 +701,9 @@ int iocShutdown(void)
iterateRecords(doFreeRecord, NULL);
dbLockCleanupRecords(pdbbase);
asShutdown();
}
dbCaShutdown(); /* must be before dbChannelExit */
if (iocBuildMode==buildIsolated) {
dbChannelExit();
dbProcessNotifyExit();
iocshFree();

View File

@@ -97,6 +97,12 @@ static long init_record(arrRecord *prec, int pass)
static long process(arrRecord *prec)
{
if(prec->clbk)
(*prec->clbk)(prec);
prec->pact = TRUE;
recGblGetTimeStamp(prec);
recGblFwdLink(prec);
prec->pact = FALSE;
return 0;
}

View File

@@ -31,4 +31,12 @@ recordtype(arr) {
special(SPC_NOMOD)
extra("void *bptr")
}
field(INP, DBF_INLINK) {
prompt("Input Link")
}
field(CLBK, DBF_NOACCESS) {
prompt("Processing callback")
special(SPC_NOMOD)
extra("void (*clbk)(struct arrRecord*)")
}
}