Merge remote-tracking branch 'lp-zimoch/dbChannelForDBLinks' into 7.0
* lp-zimoch/dbChannelForDBLinks: (43 commits) add tests for empty array filter results Fix linkFilterTest, move Release Notes to the right place do not handle empty arrays (undefined behavior) Revert "new error code for empty arrays" test code beautification make db_init_event_freelists private remove unnecessary check remove needless pointer access new error code for empty arrays clean up code structure Release notes updated set number of planned link filter tests removed unnecessary recGblSetSevr call re-order link filter tests to alternate between success and failure unused variable removed Revert "fix crash in PINI: use local db_field_log" initialize free lists when starting dbChannel db link filter tests added bugfix: dbGet should not crash because of empty array requests fix crash in PINI: use local db_field_log ... # Conflicts: # documentation/RELEASE_NOTES.md
This commit is contained in:
@@ -17,6 +17,95 @@ should also be read to understand what has changed since earlier releases.
|
||||
|
||||
<!-- Insert new items immediately below here ... -->
|
||||
|
||||
### Filters in database input links
|
||||
|
||||
Input database links can now use channel filters, it is not necessary to
|
||||
make them CA links for the filters to work.
|
||||
|
||||
### ai Soft Channel support
|
||||
|
||||
The Soft Channel device support for ai records now returns failure when
|
||||
fetching the INP link fails.
|
||||
|
||||
### Support for zero-length arrays
|
||||
|
||||
Several modifications have been made to properly support zero-length
|
||||
array values inside the IOC and over Channel Access. Some of these changes
|
||||
may affect external code that interfaces with the IOC, either directly or
|
||||
over the CA client API so we recommend thorough testing of any external
|
||||
code that handles array fields when upgrading to this release.
|
||||
|
||||
Since these changes affect the Channel Access client-side API they will
|
||||
require rebuilding any CA Gateways against this version or Base to
|
||||
properly handle zero-length arrays. The `caget`, `caput` and `camonitor`
|
||||
client programs are known to work with empty arrays as long as they were
|
||||
built with this or a later version of EPICS.
|
||||
|
||||
#### Change to the db_access.h `dbr_size_n(TYPE, COUNT)` macro
|
||||
|
||||
When called with COUNT=0 this macro no longer returns the number of bytes
|
||||
required for a scalar (1 element) but for an empty array (0 elements).
|
||||
Make sure code that uses this doesn't call it with COUNT=0 when it really
|
||||
means COUNT=1.
|
||||
|
||||
Note that the db_access.h header file is included by cadef.h so the change
|
||||
can impact Channel Access client programs that use this macro.
|
||||
|
||||
#### Channel Access support for zero-length arrays
|
||||
|
||||
The `ca_array_put()` and `ca_array_put_callback()` routines now accept an
|
||||
element count of zero, and will write a zero-length array to the PV if
|
||||
possible. No error will be raised if the target is a scalar field though,
|
||||
and the field's value will not be changed.
|
||||
|
||||
The `ca_array_get_callback()` and `ca_create_subscription()` routines
|
||||
still accept a count of zero to mean fetch as many elements as the PV
|
||||
currently holds.
|
||||
|
||||
Client programs should be prepared for the `count` fields of any
|
||||
`struct event_handler_args` or `struct exception_handler_args` passed to
|
||||
their callback routines to be zero.
|
||||
|
||||
#### Array records
|
||||
|
||||
The soft device support for the array records aai, waveform, and subArray
|
||||
as well as the aSub record type now correctly report reading 0 elements
|
||||
when getting an empty array from an input link.
|
||||
|
||||
#### Array support for dbpf
|
||||
|
||||
The dbpf command now accepts array values, including empty arrays, when
|
||||
provided as a JSON string. This must be enclosed in quotes so the iocsh
|
||||
argument parser sees the JSON as a single argument:
|
||||
|
||||
```
|
||||
epics> dbpf wf10:i32 '[1, 2, 3, 4, 5]'
|
||||
DBF_LONG[5]: 1 = 0x1 2 = 0x2 3 = 0x3 4 = 0x4 5 = 0x5
|
||||
```
|
||||
|
||||
#### Reading empty arrays as scalar values
|
||||
|
||||
Record links that get a scalar value from an array that is currently
|
||||
empty will cause the record that has the link field to be set to an
|
||||
`INVALID/LINK` alarm status.
|
||||
The record code must call `dbGetLink()` with `pnRequest=NULL` for it to
|
||||
be recognized as a request for a scalar value though.
|
||||
|
||||
This changes the semantics of passing `pnRequest=NULL` to `dbGetLink()`,
|
||||
which now behaves differently than passing it a pointer to a long integer
|
||||
containing the value 1, which was previously equivalent.
|
||||
The latter can successfully fetch a zero-element array without triggering
|
||||
a LINK alarm.
|
||||
|
||||
#### Writing empty arrays to scalar fields
|
||||
|
||||
Record links that put a zero-element array into a scalar field will now set
|
||||
the target record to `INVALID/LINK` alarm without changing the field's value.
|
||||
Previously the field was set to 0 in this case (with no alarm).
|
||||
The target field must be marked as `special(SPC_DBADDR)` to be recognized
|
||||
as an array field, and its record support must define a `put_array_info()`
|
||||
routine.
|
||||
|
||||
### Timestamp before processing output links
|
||||
The record processing code for records with output links has been modified
|
||||
to update the timestamp via recGblGetTimeStamp() before processing the
|
||||
|
||||
@@ -517,7 +517,7 @@ struct dbr_ctrl_double{
|
||||
};
|
||||
|
||||
#define dbr_size_n(TYPE,COUNT)\
|
||||
((unsigned)((COUNT)<=0?dbr_size[TYPE]:dbr_size[TYPE]+((COUNT)-1)*dbr_value_size[TYPE]))
|
||||
((unsigned)((COUNT)<0?dbr_size[TYPE]:dbr_size[TYPE]+((COUNT)-1)*dbr_value_size[TYPE]))
|
||||
|
||||
/* size for each type - array indexed by the DBR_ type code */
|
||||
LIBCA_API extern const unsigned short dbr_size[];
|
||||
|
||||
@@ -329,7 +329,7 @@ void nciu::write (
|
||||
if ( ! this->accessRightState.writePermit() ) {
|
||||
throw cacChannel::noWriteAccess();
|
||||
}
|
||||
if ( countIn > this->count || countIn == 0 ) {
|
||||
if ( countIn > this->count) {
|
||||
throw cacChannel::outOfBounds();
|
||||
}
|
||||
if ( type == DBR_STRING ) {
|
||||
@@ -350,7 +350,7 @@ cacChannel::ioStatus nciu::write (
|
||||
if ( ! this->accessRightState.writePermit() ) {
|
||||
throw cacChannel::noWriteAccess();
|
||||
}
|
||||
if ( countIn > this->count || countIn == 0 ) {
|
||||
if ( countIn > this->count) {
|
||||
throw cacChannel::outOfBounds();
|
||||
}
|
||||
if ( type == DBR_STRING ) {
|
||||
|
||||
@@ -946,13 +946,18 @@ long dbGet(DBADDR *paddr, short dbrType,
|
||||
if (offset == 0 && (!nRequest || no_elements == 1)) {
|
||||
if (nRequest)
|
||||
*nRequest = 1;
|
||||
else if (no_elements < 1) {
|
||||
status = S_db_onlyOne;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!pfl || pfl->type == dbfl_type_rec) {
|
||||
status = dbFastGetConvertRoutine[field_type][dbrType]
|
||||
(paddr->pfield, pbuf, paddr);
|
||||
} else {
|
||||
DBADDR localAddr = *paddr; /* Structure copy */
|
||||
|
||||
if (pfl->no_elements < 1) {
|
||||
if (no_elements < 1) {
|
||||
status = S_db_badField;
|
||||
goto done;
|
||||
}
|
||||
@@ -996,6 +1001,11 @@ long dbGet(DBADDR *paddr, short dbrType,
|
||||
} else {
|
||||
DBADDR localAddr = *paddr; /* Structure copy */
|
||||
|
||||
if (pfl->no_elements < 1) {
|
||||
status = S_db_badField;
|
||||
goto done;
|
||||
}
|
||||
|
||||
localAddr.field_type = pfl->field_type;
|
||||
localAddr.field_size = pfl->field_size;
|
||||
localAddr.no_elements = pfl->no_elements;
|
||||
@@ -1037,7 +1047,7 @@ static long dbPutFieldLink(DBADDR *paddr,
|
||||
short dbrType, const void *pbuffer, long nRequest)
|
||||
{
|
||||
dbLinkInfo link_info;
|
||||
DBADDR *pdbaddr = NULL;
|
||||
dbChannel *chan = NULL;
|
||||
dbCommon *precord = paddr->precord;
|
||||
dbCommon *lockrecs[2];
|
||||
dbLocker locker;
|
||||
@@ -1075,16 +1085,11 @@ static long dbPutFieldLink(DBADDR *paddr,
|
||||
|
||||
if (link_info.ltype == PV_LINK &&
|
||||
(link_info.modifiers & (pvlOptCA | pvlOptCP | pvlOptCPP)) == 0) {
|
||||
DBADDR tempaddr;
|
||||
|
||||
if (dbNameToAddr(link_info.target, &tempaddr)==0) {
|
||||
/* This will become a DB link. */
|
||||
pdbaddr = malloc(sizeof(*pdbaddr));
|
||||
if (!pdbaddr) {
|
||||
status = S_db_noMemory;
|
||||
goto cleanup;
|
||||
}
|
||||
*pdbaddr = tempaddr; /* struct copy */
|
||||
chan = dbChannelCreate(link_info.target);
|
||||
if (chan && dbChannelOpen(chan) != 0) {
|
||||
errlogPrintf("ERROR: dbPutFieldLink %s.%s=%s: dbChannelOpen() failed\n",
|
||||
precord->name, pfldDes->name, link_info.target);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1093,7 +1098,7 @@ static long dbPutFieldLink(DBADDR *paddr,
|
||||
|
||||
memset(&locker, 0, sizeof(locker));
|
||||
lockrecs[0] = precord;
|
||||
lockrecs[1] = pdbaddr ? pdbaddr->precord : NULL;
|
||||
lockrecs[1] = chan ? dbChannelRecord(chan) : NULL;
|
||||
dbLockerPrepare(&locker, lockrecs, 2);
|
||||
|
||||
dbScanLockMany(&locker);
|
||||
@@ -1181,7 +1186,8 @@ static long dbPutFieldLink(DBADDR *paddr,
|
||||
case PV_LINK:
|
||||
case CONSTANT:
|
||||
case JSON_LINK:
|
||||
dbAddLink(&locker, plink, pfldDes->field_type, pdbaddr);
|
||||
dbAddLink(&locker, plink, pfldDes->field_type, chan);
|
||||
chan = NULL; /* don't clean it up */
|
||||
break;
|
||||
|
||||
case DB_LINK:
|
||||
@@ -1211,6 +1217,8 @@ unlock:
|
||||
dbScanUnlockMany(&locker);
|
||||
dbLockerFinalize(&locker);
|
||||
cleanup:
|
||||
if (chan)
|
||||
dbChannelDelete(chan);
|
||||
free(link_info.target);
|
||||
return status;
|
||||
}
|
||||
@@ -1330,25 +1338,21 @@ long dbPut(DBADDR *paddr, short dbrType,
|
||||
status = prset->get_array_info(paddr, &dummy, &offset);
|
||||
/* paddr->pfield may be modified */
|
||||
if (status) goto done;
|
||||
} else
|
||||
offset = 0;
|
||||
|
||||
if (no_elements <= 1) {
|
||||
status = dbFastPutConvertRoutine[dbrType][field_type](pbuffer,
|
||||
paddr->pfield, paddr);
|
||||
nRequest = 1;
|
||||
} else {
|
||||
if (no_elements < nRequest)
|
||||
nRequest = no_elements;
|
||||
status = dbPutConvertRoutine[dbrType][field_type](paddr, pbuffer,
|
||||
nRequest, no_elements, offset);
|
||||
}
|
||||
|
||||
/* update array info */
|
||||
if (!status &&
|
||||
paddr->pfldDes->special == SPC_DBADDR &&
|
||||
prset && prset->put_array_info) {
|
||||
status = prset->put_array_info(paddr, nRequest);
|
||||
/* update array info */
|
||||
if (!status && prset->put_array_info)
|
||||
status = prset->put_array_info(paddr, nRequest);
|
||||
} else {
|
||||
if (nRequest < 1) {
|
||||
recGblSetSevr(precord, LINK_ALARM, INVALID_ALARM);
|
||||
} else {
|
||||
status = dbFastPutConvertRoutine[dbrType][field_type](pbuffer,
|
||||
paddr->pfield, paddr);
|
||||
nRequest = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Always do special processing if needed */
|
||||
|
||||
@@ -411,9 +411,15 @@ long dbCaGetLink(struct link *plink, short dbrType, void *pdest,
|
||||
goto done;
|
||||
}
|
||||
newType = dbDBRoldToDBFnew[pca->dbrType];
|
||||
if (!nelements || *nelements == 1) {
|
||||
if (!nelements) {
|
||||
long (*fConvert)(const void *from, void *to, struct dbAddr *paddr);
|
||||
|
||||
if (pca->usedelements < 1) {
|
||||
pca->sevr = INVALID_ALARM;
|
||||
pca->stat = LINK_ALARM;
|
||||
status = -1;
|
||||
goto done;
|
||||
}
|
||||
fConvert = dbFastGetConvertRoutine[newType][dbrType];
|
||||
assert(pca->pgetNative);
|
||||
status = fConvert(pca->pgetNative, pdest, 0);
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#define EPICS_PRIVATE_API
|
||||
|
||||
#include "cantProceed.h"
|
||||
#include "epicsAssert.h"
|
||||
#include "epicsString.h"
|
||||
@@ -68,6 +70,7 @@ void dbChannelInit (void)
|
||||
freeListInitPvt(&dbChannelFreeList, sizeof(dbChannel), 128);
|
||||
freeListInitPvt(&chFilterFreeList, sizeof(chFilter), 64);
|
||||
freeListInitPvt(&dbchStringFreeList, sizeof(epicsOldString), 128);
|
||||
db_init_event_freelists();
|
||||
}
|
||||
|
||||
static void chf_value(parseContext *parser, parse_result *presult)
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
#include "dbConvertFast.h"
|
||||
#include "dbConvert.h"
|
||||
#include "db_field_log.h"
|
||||
#include "db_access_routines.h"
|
||||
#include "dbFldTypes.h"
|
||||
#include "dbLink.h"
|
||||
#include "dbLockPvt.h"
|
||||
@@ -74,7 +75,7 @@
|
||||
#include "recSup.h"
|
||||
#include "special.h"
|
||||
#include "dbDbLink.h"
|
||||
|
||||
#include "dbChannel.h"
|
||||
|
||||
/***************************** Database Links *****************************/
|
||||
|
||||
@@ -83,45 +84,51 @@ static lset dbDb_lset;
|
||||
|
||||
static long processTarget(dbCommon *psrc, dbCommon *pdst);
|
||||
|
||||
#define linkChannel(plink) ((dbChannel *) (plink)->value.pv_link.pvt)
|
||||
|
||||
long dbDbInitLink(struct link *plink, short dbfType)
|
||||
{
|
||||
DBADDR dbaddr;
|
||||
long status;
|
||||
DBADDR *pdbAddr;
|
||||
dbChannel *chan;
|
||||
dbCommon *precord;
|
||||
|
||||
status = dbNameToAddr(plink->value.pv_link.pvname, &dbaddr);
|
||||
chan = dbChannelCreate(plink->value.pv_link.pvname);
|
||||
if (!chan)
|
||||
return S_db_notFound;
|
||||
status = dbChannelOpen(chan);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
precord = dbChannelRecord(chan);
|
||||
|
||||
plink->lset = &dbDb_lset;
|
||||
plink->type = DB_LINK;
|
||||
pdbAddr = dbCalloc(1, sizeof(struct dbAddr));
|
||||
*pdbAddr = dbaddr; /* structure copy */
|
||||
plink->value.pv_link.pvt = pdbAddr;
|
||||
ellAdd(&dbaddr.precord->bklnk, &plink->value.pv_link.backlinknode);
|
||||
plink->value.pv_link.pvt = chan;
|
||||
ellAdd(&precord->bklnk, &plink->value.pv_link.backlinknode);
|
||||
/* merging into the same lockset is deferred to the caller.
|
||||
* cf. initPVLinks()
|
||||
*/
|
||||
dbLockSetMerge(NULL, plink->precord, dbaddr.precord);
|
||||
assert(plink->precord->lset->plockSet == dbaddr.precord->lset->plockSet);
|
||||
dbLockSetMerge(NULL, plink->precord, precord);
|
||||
assert(plink->precord->lset->plockSet == precord->lset->plockSet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dbDbAddLink(struct dbLocker *locker, struct link *plink, short dbfType,
|
||||
DBADDR *ptarget)
|
||||
dbChannel *chan)
|
||||
{
|
||||
plink->lset = &dbDb_lset;
|
||||
plink->type = DB_LINK;
|
||||
plink->value.pv_link.pvt = ptarget;
|
||||
ellAdd(&ptarget->precord->bklnk, &plink->value.pv_link.backlinknode);
|
||||
plink->value.pv_link.pvt = chan;
|
||||
ellAdd(&dbChannelRecord(chan)->bklnk, &plink->value.pv_link.backlinknode);
|
||||
|
||||
/* target record is already locked in dbPutFieldLink() */
|
||||
dbLockSetMerge(locker, plink->precord, ptarget->precord);
|
||||
dbLockSetMerge(locker, plink->precord, dbChannelRecord(chan));
|
||||
}
|
||||
|
||||
static void dbDbRemoveLink(struct dbLocker *locker, struct link *plink)
|
||||
{
|
||||
DBADDR *pdbAddr = (DBADDR *) plink->value.pv_link.pvt;
|
||||
dbChannel *chan = linkChannel(plink);
|
||||
dbCommon *precord = dbChannelRecord(chan);
|
||||
|
||||
plink->type = PV_LINK;
|
||||
|
||||
@@ -131,10 +138,10 @@ static void dbDbRemoveLink(struct dbLocker *locker, struct link *plink)
|
||||
plink->value.pv_link.getCvt = 0;
|
||||
plink->value.pv_link.pvlMask = 0;
|
||||
plink->value.pv_link.lastGetdbrType = 0;
|
||||
ellDelete(&pdbAddr->precord->bklnk, &plink->value.pv_link.backlinknode);
|
||||
dbLockSetSplit(locker, plink->precord, pdbAddr->precord);
|
||||
ellDelete(&precord->bklnk, &plink->value.pv_link.backlinknode);
|
||||
dbLockSetSplit(locker, plink->precord, precord);
|
||||
}
|
||||
free(pdbAddr);
|
||||
dbChannelDelete(chan);
|
||||
}
|
||||
|
||||
static int dbDbIsConnected(const struct link *plink)
|
||||
@@ -144,16 +151,14 @@ static int dbDbIsConnected(const struct link *plink)
|
||||
|
||||
static int dbDbGetDBFtype(const struct link *plink)
|
||||
{
|
||||
DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
|
||||
|
||||
return paddr->field_type;
|
||||
dbChannel *chan = linkChannel(plink);
|
||||
return dbChannelFinalFieldType(chan);
|
||||
}
|
||||
|
||||
static long dbDbGetElements(const struct link *plink, long *nelements)
|
||||
{
|
||||
DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
|
||||
|
||||
*nelements = paddr->no_elements;
|
||||
dbChannel *chan = linkChannel(plink);
|
||||
*nelements = dbChannelFinalElements(chan);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -161,47 +166,75 @@ static long dbDbGetValue(struct link *plink, short dbrType, void *pbuffer,
|
||||
long *pnRequest)
|
||||
{
|
||||
struct pv_link *ppv_link = &plink->value.pv_link;
|
||||
DBADDR *paddr = ppv_link->pvt;
|
||||
dbChannel *chan = linkChannel(plink);
|
||||
DBADDR *paddr = &chan->addr;
|
||||
dbCommon *precord = plink->precord;
|
||||
db_field_log *pfl = NULL;
|
||||
long status;
|
||||
|
||||
/* scan passive records if link is process passive */
|
||||
if (ppv_link->pvlMask & pvlOptPP) {
|
||||
status = dbScanPassive(precord, paddr->precord);
|
||||
status = dbScanPassive(precord, dbChannelRecord(chan));
|
||||
if (status)
|
||||
return status;
|
||||
}
|
||||
|
||||
if (ppv_link->getCvt && ppv_link->lastGetdbrType == dbrType) {
|
||||
status = ppv_link->getCvt(paddr->pfield, pbuffer, paddr);
|
||||
} else {
|
||||
unsigned short dbfType = paddr->field_type;
|
||||
if (ppv_link->getCvt && ppv_link->lastGetdbrType == dbrType)
|
||||
{
|
||||
/* shortcut: scalar with known conversion, no filter */
|
||||
status = ppv_link->getCvt(dbChannelField(chan), pbuffer, paddr);
|
||||
}
|
||||
else if (dbChannelFinalElements(chan) == 1 && (!pnRequest || *pnRequest == 1)
|
||||
&& dbChannelSpecial(chan) != SPC_DBADDR
|
||||
&& dbChannelSpecial(chan) != SPC_ATTRIBUTE
|
||||
&& ellCount(&chan->filters) == 0)
|
||||
{
|
||||
/* simple scalar: set up shortcut */
|
||||
unsigned short dbfType = dbChannelFinalFieldType(chan);
|
||||
|
||||
if (dbrType < 0 || dbrType > DBR_ENUM || dbfType > DBF_DEVICE)
|
||||
return S_db_badDbrtype;
|
||||
|
||||
if (paddr->no_elements == 1 && (!pnRequest || *pnRequest == 1)
|
||||
&& paddr->special != SPC_DBADDR
|
||||
&& paddr->special != SPC_ATTRIBUTE) {
|
||||
ppv_link->getCvt = dbFastGetConvertRoutine[dbfType][dbrType];
|
||||
status = ppv_link->getCvt(paddr->pfield, pbuffer, paddr);
|
||||
} else {
|
||||
ppv_link->getCvt = NULL;
|
||||
status = dbGet(paddr, dbrType, pbuffer, NULL, pnRequest, NULL);
|
||||
}
|
||||
ppv_link->getCvt = dbFastGetConvertRoutine[dbfType][dbrType];
|
||||
ppv_link->lastGetdbrType = dbrType;
|
||||
status = ppv_link->getCvt(dbChannelField(chan), pbuffer, paddr);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* filter, array, or special */
|
||||
ppv_link->getCvt = NULL;
|
||||
|
||||
if (ellCount(&chan->filters)) {
|
||||
/* If filters are involved in a read, create field log and run filters */
|
||||
pfl = db_create_read_log(chan);
|
||||
if (!pfl)
|
||||
return S_db_noMemory;
|
||||
|
||||
pfl = dbChannelRunPreChain(chan, pfl);
|
||||
pfl = dbChannelRunPostChain(chan, pfl);
|
||||
}
|
||||
|
||||
status = dbChannelGet(chan, dbrType, pbuffer, NULL, pnRequest, pfl);
|
||||
|
||||
if (pfl)
|
||||
db_delete_field_log(pfl);
|
||||
|
||||
if (status)
|
||||
return status;
|
||||
}
|
||||
|
||||
if (!status && precord != paddr->precord)
|
||||
if (!status && precord != dbChannelRecord(chan))
|
||||
recGblInheritSevr(plink->value.pv_link.pvlMask & pvlOptMsMode,
|
||||
plink->precord, paddr->precord->stat, paddr->precord->sevr);
|
||||
plink->precord,
|
||||
dbChannelRecord(chan)->stat, dbChannelRecord(chan)->sevr);
|
||||
return status;
|
||||
}
|
||||
|
||||
static long dbDbGetControlLimits(const struct link *plink, double *low,
|
||||
double *high)
|
||||
{
|
||||
DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
|
||||
dbChannel *chan = linkChannel(plink);
|
||||
DBADDR *paddr = &chan->addr;
|
||||
struct buffer {
|
||||
DBRctrlDouble
|
||||
double value;
|
||||
@@ -222,7 +255,8 @@ static long dbDbGetControlLimits(const struct link *plink, double *low,
|
||||
static long dbDbGetGraphicLimits(const struct link *plink, double *low,
|
||||
double *high)
|
||||
{
|
||||
DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
|
||||
dbChannel *chan = linkChannel(plink);
|
||||
DBADDR *paddr = &chan->addr;
|
||||
struct buffer {
|
||||
DBRgrDouble
|
||||
double value;
|
||||
@@ -243,7 +277,8 @@ static long dbDbGetGraphicLimits(const struct link *plink, double *low,
|
||||
static long dbDbGetAlarmLimits(const struct link *plink, double *lolo,
|
||||
double *low, double *high, double *hihi)
|
||||
{
|
||||
DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
|
||||
dbChannel *chan = linkChannel(plink);
|
||||
DBADDR *paddr = &chan->addr;
|
||||
struct buffer {
|
||||
DBRalDouble
|
||||
double value;
|
||||
@@ -265,7 +300,8 @@ static long dbDbGetAlarmLimits(const struct link *plink, double *lolo,
|
||||
|
||||
static long dbDbGetPrecision(const struct link *plink, short *precision)
|
||||
{
|
||||
DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
|
||||
dbChannel *chan = linkChannel(plink);
|
||||
DBADDR *paddr = &chan->addr;
|
||||
struct buffer {
|
||||
DBRprecision
|
||||
double value;
|
||||
@@ -284,7 +320,8 @@ static long dbDbGetPrecision(const struct link *plink, short *precision)
|
||||
|
||||
static long dbDbGetUnits(const struct link *plink, char *units, int unitsSize)
|
||||
{
|
||||
DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
|
||||
dbChannel *chan = linkChannel(plink);
|
||||
DBADDR *paddr = &chan->addr;
|
||||
struct buffer {
|
||||
DBRunits
|
||||
double value;
|
||||
@@ -304,20 +341,20 @@ static long dbDbGetUnits(const struct link *plink, char *units, int unitsSize)
|
||||
static long dbDbGetAlarm(const struct link *plink, epicsEnum16 *status,
|
||||
epicsEnum16 *severity)
|
||||
{
|
||||
DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
|
||||
|
||||
dbChannel *chan = linkChannel(plink);
|
||||
dbCommon *precord = dbChannelRecord(chan);
|
||||
if (status)
|
||||
*status = paddr->precord->stat;
|
||||
*status = precord->stat;
|
||||
if (severity)
|
||||
*severity = paddr->precord->sevr;
|
||||
*severity = precord->sevr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long dbDbGetTimeStamp(const struct link *plink, epicsTimeStamp *pstamp)
|
||||
{
|
||||
DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
|
||||
|
||||
*pstamp = paddr->precord->time;
|
||||
dbChannel *chan = linkChannel(plink);
|
||||
dbCommon *precord = dbChannelRecord(chan);
|
||||
*pstamp = precord->time;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -325,9 +362,10 @@ static long dbDbPutValue(struct link *plink, short dbrType,
|
||||
const void *pbuffer, long nRequest)
|
||||
{
|
||||
struct pv_link *ppv_link = &plink->value.pv_link;
|
||||
dbChannel *chan = linkChannel(plink);
|
||||
struct dbCommon *psrce = plink->precord;
|
||||
DBADDR *paddr = (DBADDR *) ppv_link->pvt;
|
||||
dbCommon *pdest = paddr->precord;
|
||||
DBADDR *paddr = &chan->addr;
|
||||
dbCommon *pdest = dbChannelRecord(chan);
|
||||
long status = dbPut(paddr, dbrType, pbuffer, nRequest);
|
||||
|
||||
recGblInheritSevr(ppv_link->pvlMask & pvlOptMsMode, pdest, psrce->nsta,
|
||||
@@ -335,7 +373,7 @@ static long dbDbPutValue(struct link *plink, short dbrType,
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
if (paddr->pfield == (void *) &pdest->proc ||
|
||||
if (dbChannelField(chan) == (void *) &pdest->proc ||
|
||||
(ppv_link->pvlMask & pvlOptPP && pdest->scan == 0)) {
|
||||
status = processTarget(psrce, pdest);
|
||||
}
|
||||
@@ -346,9 +384,8 @@ static long dbDbPutValue(struct link *plink, short dbrType,
|
||||
static void dbDbScanFwdLink(struct link *plink)
|
||||
{
|
||||
dbCommon *precord = plink->precord;
|
||||
dbAddr *paddr = (dbAddr *) plink->value.pv_link.pvt;
|
||||
|
||||
dbScanPassive(precord, paddr->precord);
|
||||
dbChannel *chan = linkChannel(plink);
|
||||
dbScanPassive(precord, dbChannelRecord(chan));
|
||||
}
|
||||
|
||||
static long doLocked(struct link *plink, dbLinkUserCallback rtn, void *priv)
|
||||
|
||||
@@ -27,7 +27,7 @@ struct dbLocker;
|
||||
|
||||
epicsShareFunc long dbDbInitLink(struct link *plink, short dbfType);
|
||||
epicsShareFunc void dbDbAddLink(struct dbLocker *locker, struct link *plink,
|
||||
short dbfType, DBADDR *ptarget);
|
||||
short dbfType, dbChannel *ptarget);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -252,18 +252,15 @@ int dbel ( const char *pname, unsigned level )
|
||||
}
|
||||
|
||||
/*
|
||||
* DB_INIT_EVENTS()
|
||||
* DB_INIT_EVENT_FREELISTS()
|
||||
*
|
||||
*
|
||||
* Initialize the event facility for this task. Must be called at least once
|
||||
* by each task which uses the db event facility
|
||||
* Initialize the free lists used by the event facility.
|
||||
* Safe to be called multiple times.
|
||||
*
|
||||
* returns: ptr to event user block or NULL if memory can't be allocated
|
||||
*/
|
||||
dbEventCtx db_init_events (void)
|
||||
void db_init_event_freelists (void)
|
||||
{
|
||||
struct event_user * evUser;
|
||||
|
||||
if (!dbevEventUserFreeList) {
|
||||
freeListInitPvt(&dbevEventUserFreeList,
|
||||
sizeof(struct event_user),8);
|
||||
@@ -280,6 +277,22 @@ dbEventCtx db_init_events (void)
|
||||
freeListInitPvt(&dbevFieldLogFreeList,
|
||||
sizeof(struct db_field_log),2048);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* DB_INIT_EVENTS()
|
||||
*
|
||||
*
|
||||
* Initialize the event facility for this task. Must be called at least once
|
||||
* by each task which uses the db event facility
|
||||
*
|
||||
* returns: ptr to event user block or NULL if memory can't be allocated
|
||||
*/
|
||||
dbEventCtx db_init_events (void)
|
||||
{
|
||||
struct event_user * evUser;
|
||||
|
||||
db_init_event_freelists();
|
||||
|
||||
evUser = (struct event_user *)
|
||||
freeListCalloc(dbevEventUserFreeList);
|
||||
|
||||
@@ -66,6 +66,7 @@ epicsShareFunc void db_event_change_priority ( dbEventCtx ctx, unsigned epicsPri
|
||||
|
||||
#ifdef EPICS_PRIVATE_API
|
||||
epicsShareFunc void db_cleanup_events(void);
|
||||
epicsShareFunc void db_init_event_freelists (void);
|
||||
#endif
|
||||
|
||||
typedef void EVENTFUNC (void *user_arg, struct dbChannel *chan,
|
||||
|
||||
@@ -144,7 +144,7 @@ void dbInitLink(struct link *plink, short dbfType)
|
||||
}
|
||||
|
||||
void dbAddLink(struct dbLocker *locker, struct link *plink, short dbfType,
|
||||
DBADDR *ptarget)
|
||||
dbChannel *ptarget)
|
||||
{
|
||||
struct dbCommon *precord = plink->precord;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "epicsTypes.h"
|
||||
#include "epicsTime.h"
|
||||
#include "dbAddr.h"
|
||||
#include "dbChannel.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -369,7 +370,7 @@ epicsShareFunc const char * dbLinkFieldName(const struct link *plink);
|
||||
|
||||
epicsShareFunc void dbInitLink(struct link *plink, short dbfType);
|
||||
epicsShareFunc void dbAddLink(struct dbLocker *locker, struct link *plink,
|
||||
short dbfType, DBADDR *ptarget);
|
||||
short dbfType, dbChannel *ptarget);
|
||||
|
||||
epicsShareFunc void dbLinkOpen(struct link *plink);
|
||||
epicsShareFunc void dbRemoveLink(struct dbLocker *locker, struct link *plink);
|
||||
|
||||
@@ -743,14 +743,14 @@ void dbLockSetSplit(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond)
|
||||
for(i=0; i<rtype->no_links; i++) {
|
||||
dbFldDes *pdesc = rtype->papFldDes[rtype->link_ind[i]];
|
||||
DBLINK *plink = (DBLINK*)((char*)prec + pdesc->offset);
|
||||
DBADDR *ptarget;
|
||||
dbChannel *chan;
|
||||
lockRecord *lr;
|
||||
|
||||
if(plink->type!=DB_LINK)
|
||||
continue;
|
||||
|
||||
ptarget = plink->value.pv_link.pvt;
|
||||
lr = ptarget->precord->lset;
|
||||
chan = plink->value.pv_link.pvt;
|
||||
lr = dbChannelRecord(chan)->lset;
|
||||
assert(lr);
|
||||
|
||||
if(lr->precord==pfirst) {
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include "recGbl.h"
|
||||
#include "recSup.h"
|
||||
#include "special.h"
|
||||
#include "dbConvertJSON.h"
|
||||
|
||||
#define MAXLINE 80
|
||||
#define MAXMESS 128
|
||||
@@ -364,8 +365,9 @@ long dbpf(const char *pname,const char *pvalue)
|
||||
{
|
||||
DBADDR addr;
|
||||
long status;
|
||||
short dbrType;
|
||||
size_t n = 1;
|
||||
short dbrType = DBR_STRING;
|
||||
long n = 1;
|
||||
char *array = NULL;
|
||||
|
||||
if (!pname || !*pname || !pvalue) {
|
||||
printf("Usage: dbpf \"pv name\", \"value\"\n");
|
||||
@@ -380,16 +382,25 @@ long dbpf(const char *pname,const char *pvalue)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (addr.no_elements > 1 &&
|
||||
(addr.dbr_field_type == DBR_CHAR || addr.dbr_field_type == DBR_UCHAR)) {
|
||||
if (addr.no_elements > 1) {
|
||||
dbrType = addr.dbr_field_type;
|
||||
n = strlen(pvalue) + 1;
|
||||
if (addr.dbr_field_type == DBR_CHAR || addr.dbr_field_type == DBR_UCHAR) {
|
||||
n = (long)strlen(pvalue) + 1;
|
||||
} else {
|
||||
n = addr.no_elements;
|
||||
array = calloc(n, dbValueSize(dbrType));
|
||||
if (!array) {
|
||||
printf("Out of memory\n");
|
||||
return -1;
|
||||
}
|
||||
status = dbPutConvertJSON(pvalue, dbrType, array, &n);
|
||||
if (status)
|
||||
return status;
|
||||
pvalue = array;
|
||||
}
|
||||
}
|
||||
else {
|
||||
dbrType = DBR_STRING;
|
||||
}
|
||||
|
||||
status = dbPutField(&addr, dbrType, pvalue, (long) n);
|
||||
status = dbPutField(&addr, dbrType, pvalue, n);
|
||||
free(array);
|
||||
dbgf(pname);
|
||||
return status;
|
||||
}
|
||||
@@ -943,13 +954,13 @@ static void printBuffer(
|
||||
}
|
||||
|
||||
/* Now print values */
|
||||
if (no_elements == 0)
|
||||
return;
|
||||
|
||||
if (no_elements == 1)
|
||||
sprintf(pmsg, "DBF_%s: ", dbr[dbr_type]);
|
||||
else
|
||||
else {
|
||||
sprintf(pmsg, "DBF_%s[%ld]: ", dbr[dbr_type], no_elements);
|
||||
if (no_elements == 0)
|
||||
strcat(pmsg, "(empty)");
|
||||
}
|
||||
dbpr_msgOut(pMsgBuff, tab_size);
|
||||
|
||||
if (status != 0) {
|
||||
|
||||
@@ -61,9 +61,10 @@ static long init_record(dbCommon *pcommon)
|
||||
}
|
||||
|
||||
status = dbLoadLinkArray(plink, prec->ftvl, prec->bptr, &nRequest);
|
||||
if (!status && nRequest > 0) {
|
||||
if (!status) {
|
||||
prec->nord = nRequest;
|
||||
prec->udf = FALSE;
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -75,7 +76,7 @@ static long readLocked(struct link *pinp, void *dummy)
|
||||
long nRequest = prec->nelm;
|
||||
long status = dbGetLink(pinp, prec->ftvl, prec->bptr, 0, &nRequest);
|
||||
|
||||
if (!status && nRequest > 0) {
|
||||
if (!status) {
|
||||
prec->nord = nRequest;
|
||||
prec->udf = FALSE;
|
||||
|
||||
@@ -90,8 +91,12 @@ static long read_aai(aaiRecord *prec)
|
||||
{
|
||||
epicsUInt32 nord = prec->nord;
|
||||
struct link *pinp = prec->simm == menuYesNoYES ? &prec->siol : &prec->inp;
|
||||
long status = dbLinkDoLocked(pinp, readLocked, NULL);
|
||||
long status;
|
||||
|
||||
if (dbLinkIsConstant(pinp))
|
||||
return 0;
|
||||
|
||||
status = dbLinkDoLocked(pinp, readLocked, NULL);
|
||||
if (status == S_db_noLSET)
|
||||
status = readLocked(pinp, NULL);
|
||||
|
||||
|
||||
@@ -86,9 +86,10 @@ static long read_ai(aiRecord *prec)
|
||||
|
||||
prec->udf = FALSE;
|
||||
prec->dpvt = &devAiSoft; /* Any non-zero value */
|
||||
return 2;
|
||||
}
|
||||
else
|
||||
prec->dpvt = NULL;
|
||||
|
||||
return 2;
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ static long init_record(dbCommon *pcommon)
|
||||
|
||||
status = dbLoadLinkArray(&prec->inp, prec->ftvl, prec->bptr, &nRequest);
|
||||
|
||||
if (!status && nRequest > 0)
|
||||
if (!status)
|
||||
subset(prec, nRequest);
|
||||
|
||||
return status;
|
||||
@@ -116,7 +116,7 @@ static long read_sa(subArrayRecord *prec)
|
||||
status = readLocked(&prec->inp, &rt);
|
||||
}
|
||||
|
||||
if (!status && rt.nRequest > 0) {
|
||||
if (!status) {
|
||||
subset(prec, rt.nRequest);
|
||||
|
||||
if (nord != prec->nord)
|
||||
|
||||
@@ -42,7 +42,7 @@ static long init_record(dbCommon *pcommon)
|
||||
long nelm = prec->nelm;
|
||||
long status = dbLoadLinkArray(&prec->inp, prec->ftvl, prec->bptr, &nelm);
|
||||
|
||||
if (!status && nelm > 0) {
|
||||
if (!status) {
|
||||
prec->nord = nelm;
|
||||
prec->udf = FALSE;
|
||||
}
|
||||
@@ -78,11 +78,14 @@ static long read_wf(waveformRecord *prec)
|
||||
rt.ptime = (dbLinkIsConstant(&prec->tsel) &&
|
||||
prec->tse == epicsTimeEventDeviceTime) ? &prec->time : NULL;
|
||||
|
||||
if (dbLinkIsConstant(&prec->inp))
|
||||
return 0;
|
||||
|
||||
status = dbLinkDoLocked(&prec->inp, readLocked, &rt);
|
||||
if (status == S_db_noLSET)
|
||||
status = readLocked(&prec->inp, &rt);
|
||||
|
||||
if (!status && rt.nRequest > 0) {
|
||||
if (!status) {
|
||||
prec->nord = rt.nRequest;
|
||||
prec->udf = FALSE;
|
||||
if (nord != prec->nord)
|
||||
|
||||
@@ -278,10 +278,9 @@ static long fetch_values(aSubRecord *prec)
|
||||
long nRequest = (&prec->noa)[i];
|
||||
status = dbGetLink(&(&prec->inpa)[i], (&prec->fta)[i], (&prec->a)[i], 0,
|
||||
&nRequest);
|
||||
if (nRequest > 0)
|
||||
(&prec->nea)[i] = nRequest;
|
||||
if (status)
|
||||
return status;
|
||||
(&prec->nea)[i] = nRequest;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -159,6 +159,13 @@ asyncproctest_SRCS += asyncproctest_registerRecordDeviceDriver.cpp
|
||||
TESTFILES += $(COMMON_DIR)/asyncproctest.dbd ../asyncproctest.db
|
||||
TESTS += asyncproctest
|
||||
|
||||
TESTPROD_HOST += linkFilterTest
|
||||
linkFilterTest_SRCS += linkFilterTest.c
|
||||
linkFilterTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
|
||||
testHarness_SRCS += linkFilterTest.c
|
||||
TESTFILES += ../linkFilterTest.db
|
||||
TESTS += linkFilterTest
|
||||
|
||||
# dbHeader* is only a compile test
|
||||
# no need to actually run
|
||||
TESTPROD += dbHeaderTest
|
||||
|
||||
158
modules/database/test/std/rec/linkFilterTest.c
Normal file
158
modules/database/test/std/rec/linkFilterTest.c
Normal file
@@ -0,0 +1,158 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2020 Dirk Zimoch
|
||||
* EPICS BASE is distributed subject to a Software License Agreement found
|
||||
* in file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "dbAccess.h"
|
||||
#include "devSup.h"
|
||||
#include "alarm.h"
|
||||
#include "dbUnitTest.h"
|
||||
#include "errlog.h"
|
||||
#include "epicsThread.h"
|
||||
|
||||
#include "longinRecord.h"
|
||||
|
||||
#include "testMain.h"
|
||||
|
||||
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
static void startTestIoc(const char *dbfile)
|
||||
{
|
||||
testdbPrepare();
|
||||
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
|
||||
recTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase(dbfile, NULL, NULL);
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
}
|
||||
|
||||
static void expectProcSuccess(const char *rec)
|
||||
{
|
||||
char fieldname[20];
|
||||
testDiag("expecting success from %s", rec);
|
||||
sprintf(fieldname, "%s.PROC", rec);
|
||||
testdbPutFieldOk(fieldname, DBF_LONG, 1);
|
||||
sprintf(fieldname, "%s.SEVR", rec);
|
||||
testdbGetFieldEqual(fieldname, DBF_LONG, NO_ALARM);
|
||||
sprintf(fieldname, "%s.STAT", rec);
|
||||
testdbGetFieldEqual(fieldname, DBF_LONG, NO_ALARM);
|
||||
}
|
||||
|
||||
static void expectProcFailure(const char *rec)
|
||||
{
|
||||
char fieldname[20];
|
||||
testDiag("expecting failure S_db_badField %#x from %s", S_db_badField, rec);
|
||||
sprintf(fieldname, "%s.PROC", rec);
|
||||
testdbPutFieldFail(S_db_onlyOne, fieldname, DBF_LONG, 1);
|
||||
sprintf(fieldname, "%s.SEVR", rec);
|
||||
testdbGetFieldEqual(fieldname, DBF_LONG, INVALID_ALARM);
|
||||
sprintf(fieldname, "%s.STAT", rec);
|
||||
testdbGetFieldEqual(fieldname, DBF_LONG, LINK_ALARM);
|
||||
}
|
||||
|
||||
static void changeRange(long start, long stop, long step)
|
||||
{
|
||||
char linkstring[60];
|
||||
if (step)
|
||||
sprintf(linkstring, "src.[%ld:%ld:%ld]", start, step, stop);
|
||||
else if (stop)
|
||||
sprintf(linkstring, "src.[%ld:%ld]", start, stop);
|
||||
else
|
||||
sprintf(linkstring, "src.[%ld]", start);
|
||||
testDiag("modifying link: %s", linkstring);
|
||||
testdbPutFieldOk("ai.INP", DBF_STRING, linkstring);
|
||||
testdbPutFieldOk("wf.INP", DBF_STRING, linkstring);
|
||||
}
|
||||
|
||||
static const double buf[] = {1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 2, 4, 6};
|
||||
|
||||
static void expectRange(long start, long end)
|
||||
{
|
||||
long n = end-start+1;
|
||||
testdbGetFieldEqual("ai.VAL", DBF_DOUBLE, buf[start]);
|
||||
testdbGetFieldEqual("wf.NORD", DBF_LONG, n);
|
||||
testdbGetArrFieldEqual("wf.VAL", DBF_DOUBLE, n+2, n, buf+start);
|
||||
}
|
||||
|
||||
static void expectEmptyArray(void)
|
||||
{
|
||||
testDiag("expecting empty array");
|
||||
testdbGetFieldEqual("wf.NORD", DBF_LONG, 0);
|
||||
}
|
||||
|
||||
MAIN(linkFilterTest)
|
||||
{
|
||||
testPlan(102);
|
||||
startTestIoc("linkFilterTest.db");
|
||||
|
||||
testDiag("PINI");
|
||||
expectRange(2,4);
|
||||
|
||||
testDiag("modify range");
|
||||
changeRange(3,6,0);
|
||||
expectProcSuccess("ai");
|
||||
expectProcSuccess("wf");
|
||||
expectRange(3,6);
|
||||
|
||||
testDiag("backward range");
|
||||
changeRange(5,3,0);
|
||||
expectProcFailure("ai");
|
||||
expectProcSuccess("wf");
|
||||
expectEmptyArray();
|
||||
|
||||
testDiag("step 2");
|
||||
changeRange(1,6,2);
|
||||
expectProcSuccess("ai");
|
||||
expectProcSuccess("wf");
|
||||
expectRange(10,12);
|
||||
|
||||
testDiag("range start beyond src.NORD");
|
||||
changeRange(8,9,0);
|
||||
expectProcFailure("ai");
|
||||
expectProcSuccess("wf");
|
||||
expectEmptyArray();
|
||||
|
||||
testDiag("range end beyond src.NORD");
|
||||
changeRange(3,9,0);
|
||||
expectProcSuccess("ai");
|
||||
expectProcSuccess("wf");
|
||||
expectRange(3,7); /* clipped range */
|
||||
|
||||
testDiag("range start beyond src.NELM");
|
||||
changeRange(11,12,0);
|
||||
expectProcFailure("ai");
|
||||
expectProcSuccess("wf");
|
||||
|
||||
testDiag("range end beyond src.NELM");
|
||||
changeRange(4,12,0);
|
||||
expectProcSuccess("ai");
|
||||
expectProcSuccess("wf");
|
||||
expectRange(4,7); /* clipped range */
|
||||
|
||||
testDiag("single value beyond src.NORD");
|
||||
changeRange(8,0,0);
|
||||
expectProcFailure("ai");
|
||||
expectProcSuccess("wf");
|
||||
expectEmptyArray();
|
||||
|
||||
testDiag("single value");
|
||||
changeRange(5,0,0);
|
||||
expectProcSuccess("ai");
|
||||
expectProcSuccess("wf");
|
||||
expectRange(5,5);
|
||||
|
||||
testDiag("single beyond rec.NELM");
|
||||
changeRange(12,0,0);
|
||||
expectProcFailure("ai");
|
||||
expectProcSuccess("wf");
|
||||
expectEmptyArray();
|
||||
|
||||
testIocShutdownOk();
|
||||
testdbCleanup();
|
||||
return testDone();
|
||||
}
|
||||
16
modules/database/test/std/rec/linkFilterTest.db
Normal file
16
modules/database/test/std/rec/linkFilterTest.db
Normal file
@@ -0,0 +1,16 @@
|
||||
record(waveform, "src") {
|
||||
field(NELM, "10")
|
||||
field(FTVL, "SHORT")
|
||||
field(INP, [1, 2, 3, 4, 5, 6, 7, 8])
|
||||
}
|
||||
record(ai, "ai") {
|
||||
field(INP, "src.[2]") # expect 3
|
||||
field(PINI, "YES")
|
||||
}
|
||||
record(waveform, "wf") {
|
||||
field(NELM, "5")
|
||||
field(FTVL, "DOUBLE")
|
||||
field(INP, "src.[2:4]") # expect 3,4,5
|
||||
field(PINI, "YES")
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ void testCADisconn(void)
|
||||
|
||||
startRegressTestIoc("badCaLink.db");
|
||||
|
||||
testdbPutFieldOk("ai:disconn.PROC", DBF_LONG, 1);
|
||||
testdbPutFieldFail(-1, "ai:disconn.PROC", DBF_LONG, 1);
|
||||
testdbGetFieldEqual("ai:disconn.SEVR", DBF_LONG, INVALID_ALARM);
|
||||
testdbGetFieldEqual("ai:disconn.STAT", DBF_LONG, LINK_ALARM);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user