Merge branch 'aliases' into PSI-7.0
This commit is contained in:
8
.github/workflows/ci-scripts-build.yml
vendored
8
.github/workflows/ci-scripts-build.yml
vendored
@ -60,7 +60,10 @@ jobs:
|
||||
# Remove respective -Wno-error=... flag once it is fixed.
|
||||
extra: "CMD_CXXFLAGS=-std=c++20
|
||||
CMD_CPPFLAGS='-fdiagnostics-color
|
||||
-fstack-protector-strong
|
||||
-Wformat
|
||||
-Werror
|
||||
-Werror=format-security
|
||||
-Wno-error=deprecated-declarations
|
||||
-Wno-error=stringop-truncation
|
||||
-Wno-error=restrict
|
||||
@ -68,8 +71,9 @@ jobs:
|
||||
-Wno-error=nonnull
|
||||
-Wno-error=dangling-pointer
|
||||
-Wno-error=format-overflow
|
||||
-Wno-error=format-security
|
||||
-Wno-error=stringop-overread'"
|
||||
-Wno-error=stringop-overread
|
||||
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3'
|
||||
CMD_LDFLAGS=-Wl,-z,relro"
|
||||
|
||||
- os: ubuntu-20.04
|
||||
cmp: gcc
|
||||
|
@ -22,6 +22,28 @@ should also be read to understand what has changed since earlier releases:
|
||||
|
||||
## Changes made on the 7.0 branch since 7.0.8.1
|
||||
|
||||
### Allow to load the same alias multiple times
|
||||
|
||||
Aliases can now be defined multiple times as long as they still refer to the
|
||||
same record, unless the shell variable dbRecordsOnceOnly is set.
|
||||
This allows to load database files multiple times, even if they contain
|
||||
alias definitions.
|
||||
|
||||
### Allow users to delete previously created records from the database
|
||||
|
||||
From this release, record instances and aliases that have already been loaded
|
||||
by an IOC can be removed from the database again before the call to iocInit
|
||||
by loading a second instance of the named records but using `"#"` in place of
|
||||
the record type. Values for the fields are not required or advised, just use
|
||||
an empty record body { }. This is useful when a template defines records that
|
||||
are not wanted in some IOCs, without having to split or duplicate the original
|
||||
template.
|
||||
|
||||
For example this will remove the record named "unwanted":
|
||||
|
||||
```
|
||||
record("#", "unwanted") { }
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
|
@ -676,7 +676,7 @@ union db_access_val{
|
||||
(type)%(LAST_TYPE+1) == DBR_DOUBLE)
|
||||
|
||||
#define dbf_type_to_text(type) \
|
||||
( ((type) >= -1 && (type) < dbf_text_dim-2) ? \
|
||||
( ((type+1) >= 0 && (type) < dbf_text_dim-2) ? \
|
||||
dbf_text[type+1] : dbf_text_invalid )
|
||||
|
||||
#define dbf_text_to_type(text, type) \
|
||||
|
@ -145,63 +145,66 @@ long dbPutSpecial(DBADDR *paddr,int pass)
|
||||
}
|
||||
|
||||
static void get_enum_strs(DBADDR *paddr, char **ppbuffer,
|
||||
rset *prset,long *options)
|
||||
rset *prset, long *options)
|
||||
{
|
||||
short field_type=paddr->field_type;
|
||||
dbFldDes *pdbFldDes = paddr->pfldDes;
|
||||
dbMenu *pdbMenu;
|
||||
dbDeviceMenu *pdbDeviceMenu;
|
||||
char **papChoice;
|
||||
unsigned long no_str;
|
||||
char *ptemp;
|
||||
struct dbr_enumStrs *pdbr_enumStrs=(struct dbr_enumStrs*)(*ppbuffer);
|
||||
unsigned int i;
|
||||
struct dbr_enumStrs *penum=(struct dbr_enumStrs*)(*ppbuffer);
|
||||
/* advance output buffer on success or failure for next option */
|
||||
*ppbuffer = dbr_enumStrs_size + (char*)penum;
|
||||
|
||||
memset(pdbr_enumStrs,'\0',dbr_enumStrs_size);
|
||||
switch(field_type) {
|
||||
case DBF_ENUM:
|
||||
if( prset && prset->get_enum_strs ) {
|
||||
(*prset->get_enum_strs)(paddr,pdbr_enumStrs);
|
||||
} else {
|
||||
*options = (*options)^DBR_ENUM_STRS;/*Turn off option*/
|
||||
}
|
||||
break;
|
||||
case DBF_MENU:
|
||||
pdbMenu = (dbMenu *)pdbFldDes->ftPvt;
|
||||
no_str = pdbMenu->nChoice;
|
||||
papChoice= pdbMenu->papChoiceValue;
|
||||
goto choice_common;
|
||||
case DBF_DEVICE:
|
||||
pdbDeviceMenu = (dbDeviceMenu *)pdbFldDes->ftPvt;
|
||||
if(!pdbDeviceMenu) {
|
||||
*options = (*options)^DBR_ENUM_STRS;/*Turn off option*/
|
||||
break;
|
||||
}
|
||||
no_str = pdbDeviceMenu->nChoice;
|
||||
papChoice = pdbDeviceMenu->papChoice;
|
||||
goto choice_common;
|
||||
choice_common:
|
||||
i = sizeof(pdbr_enumStrs->strs)/
|
||||
sizeof(pdbr_enumStrs->strs[0]);
|
||||
if(i<no_str) no_str = i;
|
||||
pdbr_enumStrs->no_str = no_str;
|
||||
ptemp = &(pdbr_enumStrs->strs[0][0]);
|
||||
for (i=0; i<no_str; i++) {
|
||||
if(papChoice[i]==NULL) *ptemp=0;
|
||||
else {
|
||||
strncpy(ptemp,papChoice[i],
|
||||
sizeof(pdbr_enumStrs->strs[0]));
|
||||
*(ptemp+sizeof(pdbr_enumStrs->strs[0])-1) = 0;
|
||||
}
|
||||
ptemp += sizeof(pdbr_enumStrs->strs[0]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
*options = (*options)^DBR_ENUM_STRS;/*Turn off option*/
|
||||
break;
|
||||
memset(penum, 0, dbr_enumStrs_size);
|
||||
|
||||
/* from this point
|
||||
* on success, return early
|
||||
* on failure, jump or fall through to clear *options bit.
|
||||
*/
|
||||
|
||||
if(paddr->field_type == DBF_ENUM) {
|
||||
if( prset && prset->get_enum_strs ) {
|
||||
(*prset->get_enum_strs)(paddr,penum);
|
||||
return;
|
||||
}
|
||||
|
||||
} else if(paddr->field_type == DBF_MENU || paddr->field_type == DBF_DEVICE) {
|
||||
char **ppchoices;
|
||||
epicsUInt32 i, nchoices = 0;
|
||||
|
||||
if(paddr->field_type == DBF_MENU) {
|
||||
dbMenu *pmenu = paddr->pfldDes->ftPvt;
|
||||
nchoices = pmenu->nChoice;
|
||||
ppchoices= pmenu->papChoiceValue;
|
||||
|
||||
} else if(paddr->field_type == DBF_DEVICE) {
|
||||
dbDeviceMenu *pdevs = paddr->pfldDes->ftPvt;
|
||||
if(!pdevs)
|
||||
goto nostrs;
|
||||
|
||||
nchoices = pdevs->nChoice;
|
||||
ppchoices = pdevs->papChoice;
|
||||
}
|
||||
|
||||
if(nchoices > NELEMENTS(penum->strs))
|
||||
nchoices = NELEMENTS(penum->strs); /* availible > capacity, truncated list */
|
||||
|
||||
penum->no_str = nchoices;
|
||||
|
||||
for(i=0; i<nchoices; i++) {
|
||||
if(ppchoices[i]) {
|
||||
strncpy(penum->strs[i], ppchoices[i],
|
||||
sizeof(penum->strs[i]));
|
||||
/* strs[i][] allowed to omit trailing nil */
|
||||
} else {
|
||||
penum->strs[i][0] = '\0';
|
||||
}
|
||||
}
|
||||
*ppbuffer = ((char *)*ppbuffer) + dbr_enumStrs_size;
|
||||
return;
|
||||
|
||||
} else {
|
||||
/* other DBF_* fall through to error */
|
||||
}
|
||||
|
||||
nostrs:
|
||||
/* indicate option data not available. distinct from no_str==0 */
|
||||
*options = (*options)^DBR_ENUM_STRS;
|
||||
}
|
||||
|
||||
static void get_graphics(DBADDR *paddr, char **ppbuffer,
|
||||
|
@ -165,7 +165,7 @@ unsigned long dbChannelIO::nativeElementCount (
|
||||
{
|
||||
guard.assertIdenticalMutex ( this->mutex );
|
||||
long elements = dbChannelElements ( this->dbch );
|
||||
if ( elements >= 0u ) {
|
||||
if ( elements >= 0 ) {
|
||||
return static_cast < unsigned long > ( elements );
|
||||
}
|
||||
return 0u;
|
||||
|
@ -15,13 +15,21 @@ typedef struct dbCommonPvt {
|
||||
/* Thread which is currently processing this record */
|
||||
struct epicsThreadOSD* procThread;
|
||||
|
||||
struct dbCommon common;
|
||||
/* actually followed by:
|
||||
* struct dbCommon common;
|
||||
*/
|
||||
} dbCommonPvt;
|
||||
|
||||
static EPICS_ALWAYS_INLINE
|
||||
dbCommonPvt* dbRec2Pvt(struct dbCommon *prec)
|
||||
{
|
||||
return CONTAINER(prec, dbCommonPvt, common);
|
||||
return (dbCommonPvt*)((char*)prec - sizeof(dbCommonPvt));
|
||||
}
|
||||
|
||||
static EPICS_ALWAYS_INLINE
|
||||
dbCommon* dbPvt2Rec(struct dbCommonPvt *pvt)
|
||||
{
|
||||
return (dbCommon*)&pvt[1];
|
||||
}
|
||||
|
||||
#endif // DBCOMMONPVT_H
|
||||
|
@ -94,7 +94,7 @@ static long cvt_st_UInt32(const char *from, void *pfield, const dbAddr *paddr)
|
||||
status = epicsParseFloat64(from, &dval, &end);
|
||||
if (!status &&
|
||||
dval >=0 &&
|
||||
dval <= ULONG_MAX)
|
||||
dval <= UINT_MAX)
|
||||
*to = dval;
|
||||
}
|
||||
return status;
|
||||
|
@ -302,7 +302,7 @@ static long getStringUlong(const dbAddr *paddr,
|
||||
epicsFloat64 dval;
|
||||
|
||||
status = epicsParseFloat64(psrc, &dval, &end);
|
||||
if (!status && 0 <= dval && dval <= ULONG_MAX)
|
||||
if (!status && 0 <= dval && dval <= UINT_MAX)
|
||||
*pdst = dval;
|
||||
}
|
||||
if (status)
|
||||
@ -1052,7 +1052,7 @@ static long putStringUlong(dbAddr *paddr,
|
||||
epicsFloat64 dval;
|
||||
|
||||
status = epicsParseFloat64(psrc, &dval, &end);
|
||||
if (!status && 0 <= dval && dval <= ULONG_MAX)
|
||||
if (!status && 0 <= dval && dval <= UINT_MAX)
|
||||
*pdst = dval;
|
||||
}
|
||||
if (status)
|
||||
|
@ -181,7 +181,7 @@ static long cvt_st_ul(const void *f, void *t, const dbAddr *paddr)
|
||||
status = epicsParseFloat64(from, &dval, &end);
|
||||
if (!status &&
|
||||
dval >=0 &&
|
||||
dval <= ULONG_MAX)
|
||||
dval <= UINT_MAX)
|
||||
*to = dval;
|
||||
}
|
||||
return status;
|
||||
|
@ -1119,6 +1119,20 @@ static void dbRecordHead(char *recordType, char *name, int visible)
|
||||
return;
|
||||
}
|
||||
|
||||
if (recordType[0] == '#' && recordType[1] == 0) {
|
||||
status = dbFindRecord(pdbentry, name);
|
||||
if (status == 0) {
|
||||
dbDeleteRecord(pdbentry);
|
||||
} else {
|
||||
fprintf(stderr, ERL_WARNING ": Unable to delete record \"%s\". Not found.\n"
|
||||
" at file %s line %d\n",
|
||||
name, pinputFileNow->filename, pinputFileNow->line_num);
|
||||
}
|
||||
popFirstTemp();
|
||||
dbFreeEntry(pdbentry);
|
||||
duplicate = TRUE;
|
||||
return;
|
||||
}
|
||||
status = dbFindRecordType(pdbentry, recordType);
|
||||
if (status) {
|
||||
fprintf(stderr, "Record \"%s\" is of unknown type \"%s\"\n",
|
||||
@ -1249,24 +1263,50 @@ static void dbRecordInfo(char *name, char *value)
|
||||
}
|
||||
}
|
||||
|
||||
static long createAlias(DBENTRY *pdbentry, const char *alias)
|
||||
{
|
||||
DBENTRY tempEntry;
|
||||
long status;
|
||||
dbRecordNode *precnode = pdbentry->precnode;
|
||||
|
||||
if (precnode->aliasedRecnode) precnode = precnode->aliasedRecnode;
|
||||
dbInitEntry(pdbentry->pdbbase, &tempEntry);
|
||||
status = dbFindRecord(&tempEntry, alias);
|
||||
if (status == 0) {
|
||||
if (tempEntry.precnode->aliasedRecnode != precnode) {
|
||||
if (tempEntry.precnode->aliasedRecnode)
|
||||
fprintf(stderr, ERL_ERROR ": Alias \"%s\" for \"%s\": name already used by an alias for \"%s\"\n",
|
||||
alias, dbGetRecordName(pdbentry),
|
||||
tempEntry.precnode->aliasedRecnode->recordname);
|
||||
else
|
||||
fprintf(stderr, ERL_ERROR ": Alias \"%s\" for \"%s\": name already used by a record\n",
|
||||
alias, dbGetRecordName(pdbentry));
|
||||
status = S_dbLib_recExists;
|
||||
} else if (dbRecordsOnceOnly) {
|
||||
fprintf(stderr, ERL_ERROR ": Alias \"%s\" already defined and dbRecordsOnceOnly set.\n",
|
||||
alias);
|
||||
status = S_dbLib_recExists;
|
||||
}
|
||||
} else {
|
||||
status = dbCreateAlias(pdbentry, alias);
|
||||
}
|
||||
dbFinishEntry(&tempEntry);
|
||||
return status;
|
||||
}
|
||||
|
||||
static void dbRecordAlias(char *name)
|
||||
{
|
||||
DBENTRY *pdbentry;
|
||||
tempListNode *ptempListNode;
|
||||
long status;
|
||||
|
||||
if(dbRecordNameValidate(name))
|
||||
return;
|
||||
|
||||
if (duplicate) return;
|
||||
ptempListNode = (tempListNode *)ellFirst(&tempList);
|
||||
pdbentry = ptempListNode->item;
|
||||
status = dbCreateAlias(pdbentry, name);
|
||||
if (status) {
|
||||
fprintf(stderr, "Can't create alias \"%s\" for \"%s\"\n",
|
||||
name, dbGetRecordName(pdbentry));
|
||||
|
||||
if (createAlias(pdbentry, name) != 0) {
|
||||
yyerror(NULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1284,9 +1324,7 @@ static void dbAlias(char *name, char *alias)
|
||||
alias, name);
|
||||
yyerror(NULL);
|
||||
}
|
||||
else if (dbCreateAlias(pdbEntry, alias)) {
|
||||
fprintf(stderr, "Can't create alias \"%s\" referring to \"%s\"\n",
|
||||
alias, name);
|
||||
else if (createAlias(pdbEntry, alias) != 0) {
|
||||
yyerror(NULL);
|
||||
}
|
||||
dbFinishEntry(pdbEntry);
|
||||
|
@ -1219,7 +1219,9 @@ long dbNextRecordType(DBENTRY *pdbentry)
|
||||
|
||||
char * dbGetRecordTypeName(DBENTRY *pdbentry)
|
||||
{
|
||||
return(pdbentry->precordType->name);
|
||||
dbRecordType *pdbRecordType = pdbentry->precordType;
|
||||
if(!pdbRecordType) return NULL;
|
||||
return(pdbRecordType->name);
|
||||
}
|
||||
|
||||
int dbGetNRecordTypes(DBENTRY *pdbentry)
|
||||
@ -1477,6 +1479,17 @@ long dbDeleteAliases(DBENTRY *pdbentry)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dbDeleteRecordLinks(dbRecordType *rtyp, struct dbCommon *prec)
|
||||
{
|
||||
short i;
|
||||
|
||||
for (i=0; i<rtyp->no_links; i++) {
|
||||
dbFldDes *pflddes = rtyp->papFldDes[rtyp->link_ind[i]];
|
||||
DBLINK *plink = (DBLINK *)(((char *)prec) + pflddes->offset);
|
||||
dbFreeLinkContents(plink);
|
||||
}
|
||||
}
|
||||
|
||||
long dbDeleteRecord(DBENTRY *pdbentry)
|
||||
{
|
||||
dbBase *pdbbase = pdbentry->pdbbase;
|
||||
@ -1492,6 +1505,7 @@ long dbDeleteRecord(DBENTRY *pdbentry)
|
||||
preclist = &precordType->recList;
|
||||
ellDelete(preclist, &precnode->node);
|
||||
dbPvdDelete(pdbbase, precnode);
|
||||
dbDeleteRecordLinks(precordType, precnode->precord);
|
||||
while (!dbFirstInfo(pdbentry)) {
|
||||
dbDeleteInfo(pdbentry);
|
||||
}
|
||||
|
@ -66,7 +66,17 @@ void devExtend(dsxt *pdsxt)
|
||||
pthisDevSup->pdsxt = pdsxt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Ensure that: sizeof(dbCommonPvt) + pdbRecordType->rec_size
|
||||
* accounts for necessary alignment of record type struct
|
||||
*/
|
||||
typedef struct {
|
||||
dbCommonPvt prefix;
|
||||
/* ensure no padding needed... */
|
||||
dbCommon suffix;
|
||||
} dbCommonPvtAlignmentTest;
|
||||
STATIC_ASSERT(offsetof(dbCommonPvtAlignmentTest, suffix)==sizeof(dbCommonPvt));
|
||||
|
||||
long dbAllocRecord(DBENTRY *pdbentry,const char *precordName)
|
||||
{
|
||||
dbRecordType *pdbRecordType = pdbentry->precordType;
|
||||
@ -90,8 +100,8 @@ long dbAllocRecord(DBENTRY *pdbentry,const char *precordName)
|
||||
precordName, pdbRecordType->name, pdbRecordType->rec_size);
|
||||
return(S_dbLib_noRecSup);
|
||||
}
|
||||
ppvt = dbCalloc(1, offsetof(dbCommonPvt, common) + pdbRecordType->rec_size);
|
||||
precord = &ppvt->common;
|
||||
ppvt = dbCalloc(1, sizeof(dbCommonPvt) + pdbRecordType->rec_size);
|
||||
precord = dbPvt2Rec(ppvt);
|
||||
ppvt->recnode = precnode;
|
||||
precord->rdes = pdbRecordType;
|
||||
precnode->precord = precord;
|
||||
|
@ -378,6 +378,7 @@ static int yyerror(char *str)
|
||||
dbIncludePrint();
|
||||
yyFailed = TRUE;
|
||||
}
|
||||
fprintf(stderr, "\n %d | %s\n", pinputFileNow->line_num, my_buffer);
|
||||
return(0);
|
||||
}
|
||||
static long pvt_yy_parse(void)
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <set>
|
||||
#include <map>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
@ -36,6 +36,7 @@ dbTestIoc_DBD += xLink.dbd
|
||||
dbTestIoc_DBD += devx.dbd
|
||||
dbTestIoc_DBD += jlinkz.dbd
|
||||
dbTestIoc_DBD += dbLinkdset.dbd
|
||||
dbTestIoc_DBD += dbCore.dbd
|
||||
TESTFILES += $(COMMON_DIR)/dbTestIoc.dbd ../xRecord.db
|
||||
|
||||
testHarness_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp
|
||||
@ -183,6 +184,12 @@ testHarness_SRCS += dbStaticTest.c
|
||||
TESTFILES += ../dbStaticTest.db
|
||||
TESTFILES += ../dbStaticTestAlias1.db
|
||||
TESTFILES += ../dbStaticTestAlias2.db
|
||||
TESTFILES += ../dbStaticTestAliasAgain1.db
|
||||
TESTFILES += ../dbStaticTestAliasAgain2.db
|
||||
TESTFILES += ../dbStaticTestAliasAgain3.db
|
||||
TESTFILES += ../dbStaticTestAliasAgainError1.db
|
||||
TESTFILES += ../dbStaticTestAliasAgainError2.db
|
||||
TESTFILES += ../dbStaticTestRemove.db
|
||||
TESTS += dbStaticTest
|
||||
|
||||
# This runs all the test programs in a known working order:
|
||||
|
@ -82,12 +82,12 @@ void checkDoubleGet(dbChannel *chan, db_field_log* pfl)
|
||||
testOk1(meta.status==UDF_ALARM);
|
||||
testOk1(meta.acks==MAJOR_ALARM);
|
||||
testOk1(meta.ackt==1);
|
||||
testOk1(strncmp(meta.amsg, "oops", DB_UNITS_SIZE)==0);
|
||||
testOk1(strncmp(meta.amsg, "oops", sizeof(meta.amsg))==0);
|
||||
testOk1(meta.time.secPastEpoch==0x12345678);
|
||||
testOk1(meta.time.nsec==0x90abcdef);
|
||||
testOk1(meta.utag==0x10203040);
|
||||
testOk1(meta.precision.dp==0x12345678);
|
||||
testOk1(strncmp(meta.units, "arbitrary", DB_UNITS_SIZE)==0);
|
||||
testOk1(strncmp(meta.units, "arbitrary", sizeof(meta.units))==0);
|
||||
#define limitEq(UL, FL, VAL) testOk(meta.UL ## _ ## FL ## _limit == (VAL), #UL "_" #FL "_limit (%f) == %f", meta.UL ## _ ## FL ## _limit, VAL)
|
||||
limitEq(lower, disp, 10000000.0-1.0);
|
||||
limitEq(upper, disp, 10000000.0+1.0);
|
||||
@ -114,6 +114,8 @@ void testdbMetaDoubleGet(void)
|
||||
STATIC_ASSERT(sizeof(meta.amsg)==sizeof(prec->amsg));
|
||||
STATIC_ASSERT(sizeof(meta.amsg)==sizeof(pfl->amsg));
|
||||
|
||||
testDiag("testdbMetaDoubleGet");
|
||||
|
||||
if(!chan)
|
||||
testAbort("Missing recmeta OTST");
|
||||
if((status=dbChannelOpen(chan))!=0)
|
||||
@ -163,6 +165,8 @@ typedef struct {
|
||||
DBRenumStrs
|
||||
} dbMetaEnum;
|
||||
|
||||
enum {dbMetaMetaMask = DBR_STATUS | DBR_AMSG | DBR_ENUM_STRS | DBR_TIME | DBR_UTAG};
|
||||
|
||||
static
|
||||
void testdbMetaEnumSizes(void)
|
||||
{
|
||||
@ -189,6 +193,66 @@ void testdbMetaEnumSizes(void)
|
||||
testOk(sizeof(dbMetaEnum)==pos, "sizeof(dbMetaEnum), %u == %u", (unsigned)sizeof(dbMetaEnum), (unsigned)pos);
|
||||
}
|
||||
|
||||
static
|
||||
void checkEnumGet(dbChannel *chan, db_field_log* pfl)
|
||||
{
|
||||
dbMetaEnum meta;
|
||||
long options = (long)dbMetaMetaMask;
|
||||
long nReq = 0;
|
||||
long status;
|
||||
|
||||
/* spoil */
|
||||
meta.no_str = -1;
|
||||
meta.strs[0][0] = '!';
|
||||
|
||||
status=dbChannelGet(chan, DBF_DOUBLE, &meta, &options, &nReq, pfl);
|
||||
testOk(status==0, "dbGet OTST : %ld", status);
|
||||
|
||||
testOk1(meta.severity==INVALID_ALARM);
|
||||
testOk1(meta.status==UDF_ALARM);
|
||||
testOk1(meta.acks==MAJOR_ALARM);
|
||||
testOk1(meta.ackt==1);
|
||||
testOk1(strncmp(meta.amsg, "oops", sizeof(meta.amsg))==0);
|
||||
testOk1(meta.time.secPastEpoch==0x12345678);
|
||||
testOk1(meta.time.nsec==0x90abcdef);
|
||||
testOk1(meta.utag==0x10203040);
|
||||
if(testOk1(meta.no_str==3)) {
|
||||
testOk1(strncmp(meta.strs[0], "Before", sizeof(meta.strs[0]))==0);
|
||||
testOk1(strncmp(meta.strs[1], "After", sizeof(meta.strs[0]))==0);
|
||||
testOk1(strncmp(meta.strs[2], "None", sizeof(meta.strs[0]))==0);
|
||||
}
|
||||
}
|
||||
|
||||
void testdbMetaEnumGet(void)
|
||||
{
|
||||
long status;
|
||||
xRecord* prec = (xRecord*)testdbRecordPtr("recmeta");
|
||||
dbChannel *chan = dbChannelCreate("recmeta.SFX");
|
||||
|
||||
testDiag("testdbMetaEnumGet");
|
||||
|
||||
if(!chan)
|
||||
testAbort("Missing recmeta SFX");
|
||||
if((status=dbChannelOpen(chan))!=0)
|
||||
testAbort("can't open recmeta SFX : %ld", status);
|
||||
|
||||
dbScanLock((dbCommon*)prec);
|
||||
/* ensure that all meta-data has different non-zero values */
|
||||
prec->otst = 10000000.0;
|
||||
prec->sevr = INVALID_ALARM;
|
||||
prec->stat = UDF_ALARM;
|
||||
strcpy(prec->amsg, "oops");
|
||||
prec->acks = MAJOR_ALARM;
|
||||
prec->time.secPastEpoch = 0x12345678;
|
||||
prec->time.nsec = 0x90abcdef;
|
||||
prec->utag = 0x10203040;
|
||||
|
||||
testDiag("dbGet directly from record");
|
||||
checkEnumGet(chan, NULL);
|
||||
|
||||
dbScanUnlock((dbCommon*)prec);
|
||||
}
|
||||
|
||||
static
|
||||
void testdbGetStringEqual(const char *pv, const char *expected)
|
||||
{
|
||||
@ -347,7 +411,7 @@ void dbTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
MAIN(dbPutGet)
|
||||
{
|
||||
testPlan(124);
|
||||
testPlan(137);
|
||||
testdbPrepare();
|
||||
|
||||
testdbMetaDoubleSizes();
|
||||
@ -366,6 +430,7 @@ MAIN(dbPutGet)
|
||||
eltc(1);
|
||||
|
||||
testdbMetaDoubleGet();
|
||||
testdbMetaEnumGet();
|
||||
|
||||
testLongLink();
|
||||
testLongAttr();
|
||||
|
@ -13,6 +13,37 @@
|
||||
#include <dbStaticPvt.h>
|
||||
#include <dbUnitTest.h>
|
||||
#include <testMain.h>
|
||||
#include <epicsString.h>
|
||||
#include <iocsh.h>
|
||||
|
||||
|
||||
static void testEntryRemoved(const char *pv)
|
||||
{
|
||||
DBENTRY entry;
|
||||
|
||||
testDiag("testEntryRemoved(\"%s\")", pv);
|
||||
|
||||
dbInitEntry(pdbbase, &entry);
|
||||
|
||||
testOk(dbFindRecord(&entry, pv)==S_dbLib_recNotFound,
|
||||
"Record '%s' not present", pv);
|
||||
|
||||
dbFinishEntry(&entry);
|
||||
}
|
||||
|
||||
static void testEntryPresent(const char *pv)
|
||||
{
|
||||
DBENTRY entry;
|
||||
|
||||
testDiag("testEntryPresent(\"%s\")", pv);
|
||||
|
||||
dbInitEntry(pdbbase, &entry);
|
||||
|
||||
testOk(dbFindRecord(&entry, pv)==0,
|
||||
"Record '%s' present", pv);
|
||||
|
||||
dbFinishEntry(&entry);
|
||||
}
|
||||
|
||||
static void testEntry(const char *pv)
|
||||
{
|
||||
@ -291,16 +322,19 @@ static void testDbVerify(const char *record)
|
||||
dbFinishEntry(&entry);
|
||||
}
|
||||
|
||||
static void testWrongAliasRecord(const char *filename)
|
||||
static void testReadDatabase(const char *filename, int expectToFail)
|
||||
{
|
||||
FILE *fp = NULL;
|
||||
long status;
|
||||
dbPath(pdbbase,"." OSI_PATH_LIST_SEPARATOR "..");
|
||||
dbOpenFile(pdbbase, filename, &fp);
|
||||
if(!fp) {
|
||||
testAbort("Unable to read %s", filename);
|
||||
testAbort("Unable to open %s", filename);
|
||||
}
|
||||
testOk(dbReadDatabaseFP(&pdbbase, fp, NULL, NULL) != 0,
|
||||
"Wrong alias record in %s is expected to fail", filename);
|
||||
status = dbReadDatabaseFP(&pdbbase, fp, NULL, NULL);
|
||||
testOk(!status == !expectToFail,
|
||||
"Reading %s%s", filename,
|
||||
expectToFail ? " is expected to fail" : "");
|
||||
}
|
||||
|
||||
void dbTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
@ -308,9 +342,10 @@ void dbTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
MAIN(dbStaticTest)
|
||||
{
|
||||
const char *ldir;
|
||||
char *ldirDup;
|
||||
FILE *fp = NULL;
|
||||
|
||||
testPlan(312);
|
||||
testPlan(350);
|
||||
testdbPrepare();
|
||||
|
||||
testdbReadDatabase("dbTestIoc.dbd", NULL, NULL);
|
||||
@ -320,13 +355,40 @@ MAIN(dbStaticTest)
|
||||
if(!fp) {
|
||||
testAbort("Unable to read dbStaticTest.db");
|
||||
}
|
||||
ldirDup = epicsStrDup(ldir);
|
||||
if(dbReadDatabaseFP(&pdbbase, fp, NULL, NULL)) {
|
||||
testAbort("Unable to load %s%sdbStaticTest.db",
|
||||
ldir, OSI_PATH_LIST_SEPARATOR);
|
||||
ldirDup, OSI_PATH_LIST_SEPARATOR);
|
||||
}
|
||||
free(ldirDup);
|
||||
|
||||
testWrongAliasRecord("dbStaticTestAlias1.db");
|
||||
testWrongAliasRecord("dbStaticTestAlias2.db");
|
||||
dbPath(pdbbase,"." OSI_PATH_LIST_SEPARATOR "..");
|
||||
ldir = dbOpenFile(pdbbase, "dbStaticTestRemove.db", &fp);
|
||||
if(!fp) {
|
||||
testAbort("Unable to read dbStaticTestRemove.db");
|
||||
}
|
||||
ldirDup = epicsStrDup(ldir);
|
||||
if(dbReadDatabaseFP(&pdbbase, fp, NULL, NULL)) {
|
||||
testAbort("Unable to load %s%sdbStaticTestRemove.db",
|
||||
ldirDup, OSI_PATH_LIST_SEPARATOR);
|
||||
}
|
||||
free(ldirDup);
|
||||
|
||||
testReadDatabase("dbStaticTestAlias1.db", 1);
|
||||
testReadDatabase("dbStaticTestAlias2.db", 1);
|
||||
|
||||
/* Test re-defining aliases */
|
||||
testReadDatabase("dbStaticTestAliasAgain1.db", 0);
|
||||
testReadDatabase("dbStaticTestAliasAgain1.db", 0);
|
||||
testReadDatabase("dbStaticTestAliasAgain2.db", 0);
|
||||
testReadDatabase("dbStaticTestAliasAgain2.db", 0);
|
||||
testReadDatabase("dbStaticTestAliasAgain3.db", 0);
|
||||
testReadDatabase("dbStaticTestAliasAgain3.db", 0);
|
||||
testReadDatabase("dbStaticTestAliasAgainError1.db", 1);
|
||||
testReadDatabase("dbStaticTestAliasAgainError2.db", 1);
|
||||
iocshCmd("var dbRecordsOnceOnly 1");
|
||||
testReadDatabase("dbStaticTestAliasAgain2.db", 1);
|
||||
testReadDatabase("dbStaticTestAliasAgain3.db", 1);
|
||||
|
||||
testEntry("testrec.VAL");
|
||||
testEntry("testalias.VAL");
|
||||
@ -341,6 +403,21 @@ MAIN(dbStaticTest)
|
||||
testRec2Entry("testalias2");
|
||||
testRec2Entry("testalias3");
|
||||
|
||||
testEntryPresent("testdelrec");
|
||||
testEntryPresent("testdelrec6");
|
||||
testEntryPresent("testdelalias66");
|
||||
testEntryRemoved("testdelrec1");
|
||||
testEntryRemoved("testdelrec2");
|
||||
testEntryRemoved("testdelrec3");
|
||||
testEntryRemoved("testdelrec4");
|
||||
testEntryRemoved("testdelrec5");
|
||||
testEntryRemoved("testdelalias6");
|
||||
testEntryRemoved("testdelrec7");
|
||||
testEntryRemoved("testdelalias7");
|
||||
testEntryRemoved("testdelalias77");
|
||||
testEntryRemoved("testdelrec8");
|
||||
testEntryRemoved("testdelrec11");
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
@ -358,6 +435,21 @@ MAIN(dbStaticTest)
|
||||
testRec2Entry("testalias2");
|
||||
testRec2Entry("testalias3");
|
||||
|
||||
testEntryPresent("testdelrec");
|
||||
testEntryPresent("testdelrec6");
|
||||
testEntryPresent("testdelalias66");
|
||||
testEntryRemoved("testdelrec1");
|
||||
testEntryRemoved("testdelrec2");
|
||||
testEntryRemoved("testdelrec3");
|
||||
testEntryRemoved("testdelrec4");
|
||||
testEntryRemoved("testdelrec5");
|
||||
testEntryRemoved("testdelalias6");
|
||||
testEntryRemoved("testdelrec7");
|
||||
testEntryRemoved("testdelalias7");
|
||||
testEntryRemoved("testdelalias77");
|
||||
testEntryRemoved("testdelrec8");
|
||||
testEntryRemoved("testdelrec11");
|
||||
|
||||
testDbVerify("testrec");
|
||||
|
||||
testIocShutdownOk();
|
||||
|
4
modules/database/test/ioc/db/dbStaticTestAliasAgain1.db
Normal file
4
modules/database/test/ioc/db/dbStaticTestAliasAgain1.db
Normal file
@ -0,0 +1,4 @@
|
||||
# Test re-load alias in record
|
||||
record(x, "testrecAgain") {
|
||||
alias("testaliasAgain1")
|
||||
}
|
2
modules/database/test/ioc/db/dbStaticTestAliasAgain2.db
Normal file
2
modules/database/test/ioc/db/dbStaticTestAliasAgain2.db
Normal file
@ -0,0 +1,2 @@
|
||||
# Test re-load alias for record
|
||||
alias("testrecAgain", "testaliasAgain2")
|
2
modules/database/test/ioc/db/dbStaticTestAliasAgain3.db
Normal file
2
modules/database/test/ioc/db/dbStaticTestAliasAgain3.db
Normal file
@ -0,0 +1,2 @@
|
||||
# Test re-load alias for alias
|
||||
alias("testaliasAgain2", "testaliasAgain3")
|
@ -0,0 +1,2 @@
|
||||
# ERROR: alias using name of exising alias for different record
|
||||
alias("testrec", "testaliasAgain1")
|
@ -0,0 +1,2 @@
|
||||
# ERROR: alias using name of exising record fails
|
||||
alias("testrecAgain", "testrec")
|
55
modules/database/test/ioc/db/dbStaticTestRemove.db
Normal file
55
modules/database/test/ioc/db/dbStaticTestRemove.db
Normal file
@ -0,0 +1,55 @@
|
||||
record(x, "testdelrec") { }
|
||||
|
||||
record(x, "testdelrec1") { }
|
||||
|
||||
record(x, "testdelrec2") {
|
||||
field("INP", "foobar")
|
||||
}
|
||||
|
||||
record(x, "testdelrec3") {
|
||||
info("foo", "bar")
|
||||
}
|
||||
|
||||
record(x, "testdelrec4") {
|
||||
field("INP", "testdelrec.VAL")
|
||||
}
|
||||
|
||||
record(x, "testdelrec5") {
|
||||
field("FLNK", "testdelrec.VAL")
|
||||
}
|
||||
|
||||
record(x, "testdelrec6") { }
|
||||
|
||||
alias("testdelrec6", "testdelalias6")
|
||||
alias("testdelrec6", "testdelalias66")
|
||||
|
||||
|
||||
record(x, "testdelrec7") { }
|
||||
|
||||
alias("testdelrec7", "testdelalias7")
|
||||
alias("testdelrec7", "testdelalias77")
|
||||
|
||||
|
||||
record(x, "testdelrec8") {
|
||||
field("INP", "{z:{good:1}}")
|
||||
}
|
||||
|
||||
record("#", "no:such:record") { }
|
||||
record("#", "also:non:existant") {
|
||||
field(FOO, "5")
|
||||
}
|
||||
|
||||
record("#", "testdelrec1") { }
|
||||
record("#", "testdelrec2") {
|
||||
field("INP", "foobar2")
|
||||
}
|
||||
record("#", "testdelrec3") {
|
||||
field("INP", "foobar2")
|
||||
field("VAL", "1")
|
||||
}
|
||||
record("#", "testdelrec4") { }
|
||||
record("#", "testdelrec5") { }
|
||||
record("#", "testdelalias6") { }
|
||||
record("#", "testdelrec7") { }
|
||||
record("#", "testdelrec8") { }
|
||||
record("#", "testdelrec11") { }
|
@ -109,7 +109,7 @@ osdReadline (const char *prompt, struct readlineContext *context)
|
||||
line[linelen] = '\0';
|
||||
}
|
||||
context->line = line;
|
||||
if (line && *line)
|
||||
if (line && *line && !context->in)
|
||||
add_history(line);
|
||||
return line;
|
||||
}
|
||||
|
Reference in New Issue
Block a user