Merge remote-tracking branch 'launchpad/3.15'
* launchpad/3.15: db/test: dbPutGetTest check dbGet() of long string field db: dbGet() ensure long string nil and actual string length db/test: dbPutGetTest check for dbGet() attribute crash db: fix dbGet() for attributes as long string db/test: dbPutGetTest add test for lp:1678494 db: fix dbGet() of link fields as DBF_CHAR dbUnitTest: add testdbGetArrFieldEqual() db/test: add dbPutGetTest Adjusted .gitignore patterns Move .bzrignore to .gitignore
This commit is contained in:
@@ -739,28 +739,34 @@ static long getLinkValue(DBADDR *paddr, short dbrType,
|
||||
{
|
||||
dbCommon *precord = paddr->precord;
|
||||
dbFldDes *pfldDes = paddr->pfldDes;
|
||||
/* size of pbuf storage in bytes, including space for trailing nil */
|
||||
int maxlen;
|
||||
DBENTRY dbEntry;
|
||||
long status;
|
||||
long nReq = nRequest ? *nRequest : 1;
|
||||
|
||||
/* dbFindRecord() below will always succeed as we have a
|
||||
* valid DBADDR, so no point to check again.
|
||||
* Request for zero elements always succeeds
|
||||
*/
|
||||
if(!nReq)
|
||||
return 0;
|
||||
|
||||
switch (dbrType) {
|
||||
case DBR_STRING:
|
||||
maxlen = MAX_STRING_SIZE - 1;
|
||||
if (nRequest && *nRequest > 1) *nRequest = 1;
|
||||
maxlen = MAX_STRING_SIZE;
|
||||
nReq = 1;
|
||||
break;
|
||||
|
||||
case DBR_DOUBLE: /* Needed for dbCa links */
|
||||
if (nRequest && *nRequest) *nRequest = 1;
|
||||
if (nRequest) *nRequest = 1;
|
||||
*(double *)pbuf = epicsNAN;
|
||||
return 0;
|
||||
|
||||
case DBR_CHAR:
|
||||
case DBR_UCHAR:
|
||||
if (nRequest && *nRequest > 0) {
|
||||
maxlen = *nRequest - 1;
|
||||
break;
|
||||
}
|
||||
/* else fall through ... */
|
||||
maxlen = nReq;
|
||||
break;
|
||||
default:
|
||||
return S_db_badDbrtype;
|
||||
}
|
||||
@@ -769,10 +775,13 @@ static long getLinkValue(DBADDR *paddr, short dbrType,
|
||||
status = dbFindRecord(&dbEntry, precord->name);
|
||||
if (!status) status = dbFindField(&dbEntry, pfldDes->name);
|
||||
if (!status) {
|
||||
char *rtnString = dbGetString(&dbEntry);
|
||||
const char *rtnString = dbGetString(&dbEntry);
|
||||
|
||||
strncpy(pbuf, rtnString, --maxlen);
|
||||
pbuf[maxlen] = 0;
|
||||
strncpy(pbuf, rtnString, maxlen-1);
|
||||
pbuf[maxlen-1] = 0;
|
||||
if(dbrType!=DBR_STRING)
|
||||
nReq = strlen(pbuf)+1;
|
||||
if(nRequest) *nRequest = nReq;
|
||||
}
|
||||
dbFinishEntry(&dbEntry);
|
||||
return status;
|
||||
@@ -782,28 +791,31 @@ static long getAttrValue(DBADDR *paddr, short dbrType,
|
||||
char *pbuf, long *nRequest)
|
||||
{
|
||||
int maxlen;
|
||||
long nReq = nRequest ? *nRequest : 1;
|
||||
|
||||
if (!paddr->pfield) return S_db_badField;
|
||||
|
||||
switch (dbrType) {
|
||||
case DBR_STRING:
|
||||
maxlen = MAX_STRING_SIZE - 1;
|
||||
if (nRequest && *nRequest > 1) *nRequest = 1;
|
||||
maxlen = MAX_STRING_SIZE;
|
||||
nReq = 1;
|
||||
break;
|
||||
|
||||
case DBR_CHAR:
|
||||
case DBR_UCHAR:
|
||||
if (nRequest && *nRequest > 0) {
|
||||
maxlen = *nRequest - 1;
|
||||
break;
|
||||
}
|
||||
maxlen = nReq;
|
||||
break;
|
||||
|
||||
/* else fall through ... */
|
||||
default:
|
||||
return S_db_badDbrtype;
|
||||
}
|
||||
|
||||
strncpy(pbuf, paddr->pfield, --maxlen);
|
||||
pbuf[maxlen] = 0;
|
||||
strncpy(pbuf, paddr->pfield, maxlen-1);
|
||||
pbuf[maxlen-1] = 0;
|
||||
if(dbrType!=DBR_STRING)
|
||||
nReq = strlen(pbuf)+1;
|
||||
if(nRequest) *nRequest = nReq;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -931,6 +943,15 @@ long dbGet(DBADDR *paddr, short dbrType,
|
||||
localAddr.pfield = (char *) pfl->u.r.field;
|
||||
status = convert(&localAddr, pbuf, n, capacity, offset);
|
||||
}
|
||||
|
||||
if(!status && dbrType==DBF_CHAR && nRequest &&
|
||||
paddr->pfldDes && paddr->pfldDes->field_type==DBF_STRING)
|
||||
{
|
||||
/* long string ensure nil and truncate to actual length */
|
||||
long nReq = *nRequest;
|
||||
pbuf[nReq-1] = '\0';
|
||||
*nRequest = strlen(pbuf)+1;
|
||||
}
|
||||
}
|
||||
done:
|
||||
paddr->pfield = pfieldsave;
|
||||
|
||||
@@ -222,6 +222,71 @@ void testdbVGetFieldEqual(const char* pv, short dbrType, va_list ap)
|
||||
}
|
||||
}
|
||||
|
||||
void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsigned long cnt, const void *pbuf)
|
||||
{
|
||||
DBADDR addr;
|
||||
const long vSize = dbValueSize(dbfType);
|
||||
const long nStore = vSize * nRequest;
|
||||
long status;
|
||||
void *gbuf, *gstore;
|
||||
|
||||
if(dbNameToAddr(pv, &addr)) {
|
||||
testFail("Missing PV \"%s\"", pv);
|
||||
return;
|
||||
}
|
||||
|
||||
gbuf = gstore = malloc(nStore);
|
||||
if(!gbuf && nStore!=0) { /* note that malloc(0) is allowed to return NULL on success */
|
||||
testFail("Allocation failed esize=%ld total=%ld", vSize, nStore);
|
||||
return;
|
||||
}
|
||||
|
||||
status = dbGetField(&addr, dbfType, gbuf, NULL, &nRequest, NULL);
|
||||
if (status) {
|
||||
testFail("dbGetField(\"%s\", %d, ...) -> %#lx", pv, dbfType, status);
|
||||
|
||||
} else {
|
||||
unsigned match = nRequest==cnt;
|
||||
long n, N = nRequest < cnt ? nRequest : cnt;
|
||||
|
||||
if(!match)
|
||||
testDiag("Length mis-match. expected=%lu actual=%lu", cnt, nRequest);
|
||||
|
||||
for(n=0; n<N; n++, gbuf+=vSize, pbuf+=vSize) {
|
||||
|
||||
switch(dbfType) {
|
||||
case DBR_STRING: {
|
||||
const char *expect = (const char*)pbuf,
|
||||
*actual = (const char*)gbuf;
|
||||
/* expected (pbuf) is allowed to omit storage for trailing nils for last element */
|
||||
unsigned int eq = strncmp(expect, actual, MAX_STRING_SIZE)==0 && actual[MAX_STRING_SIZE-1]=='\0';
|
||||
match &= eq;
|
||||
if(!eq)
|
||||
testDiag("[%lu] = expected=\"%s\" actual=\"%s\"", n, expect, actual);
|
||||
break;
|
||||
}
|
||||
#define OP(DBR,Type,pat) case DBR: {Type expect = *(Type*)pbuf, actual = *(Type*)gbuf; assert(vSize==sizeof(Type)); match &= expect==actual; \
|
||||
if(expect!=actual) testDiag("[%lu] expected=" pat " actual=" pat, n, expect, actual); break;}
|
||||
|
||||
OP(DBR_CHAR, char, "%c");
|
||||
OP(DBR_UCHAR, unsigned char, "%u");
|
||||
OP(DBR_SHORT, short, "%d");
|
||||
OP(DBR_USHORT, unsigned short, "%u");
|
||||
OP(DBR_LONG, int, "%d");
|
||||
OP(DBR_ULONG, unsigned int, "%u");
|
||||
OP(DBR_FLOAT, float, "%e");
|
||||
OP(DBR_DOUBLE, double, "%e");
|
||||
OP(DBR_ENUM, int, "%d");
|
||||
#undef OP
|
||||
}
|
||||
}
|
||||
|
||||
testOk(match, "dbGetField(\"%s\", dbrType=%d, nRequest=%ld ...) match", pv, dbfType, nRequest);
|
||||
}
|
||||
|
||||
free(gstore);
|
||||
}
|
||||
|
||||
dbCommon* testdbRecordPtr(const char* pv)
|
||||
{
|
||||
DBADDR addr;
|
||||
|
||||
@@ -55,6 +55,23 @@ epicsShareFunc long testdbVPutField(const char* pv, short dbrType, va_list ap);
|
||||
epicsShareFunc void testdbGetFieldEqual(const char* pv, short dbrType, ...);
|
||||
epicsShareFunc void testdbVGetFieldEqual(const char* pv, short dbrType, va_list ap);
|
||||
|
||||
/**
|
||||
* @param pv PV name string
|
||||
* @param dbfType One of the DBF_* macros from dbAccess.h
|
||||
* @param nRequest Number of elements to request from pv
|
||||
* @param pbufcnt Number of elements pointed to be pbuf
|
||||
* @param pbuf Expected value buffer
|
||||
*
|
||||
* Execute dbGet() of nRequest elements and compare the result with
|
||||
* pbuf (pbufcnt is an element count).
|
||||
* Element size is derived from dbfType.
|
||||
*
|
||||
* nRequest > pbufcnt will detect truncation.
|
||||
* nRequest < pbufcnt always fails.
|
||||
* nRequest ==pbufcnt checks prefix (actual may be longer than expected)
|
||||
*/
|
||||
epicsShareFunc void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsigned long pbufcnt, const void *pbuf);
|
||||
|
||||
epicsShareFunc dbCommon* testdbRecordPtr(const char* pv);
|
||||
|
||||
typedef struct testMonitor testMonitor;
|
||||
|
||||
@@ -152,7 +152,14 @@ recGblCheckDeadbandTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp
|
||||
testHarness_SRCS += recGblCheckDeadbandTest.c
|
||||
TESTS += recGblCheckDeadbandTest
|
||||
|
||||
# The testHarness runs all the test programs in a known working order.
|
||||
TESTPROD_HOST += testPutGetTest
|
||||
testPutGetTest_SRCS += dbPutGetTest.c
|
||||
testPutGetTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp
|
||||
testHarness_SRCS += dbPutGetTest.db
|
||||
TESTFILES += ../dbPutGetTest.db
|
||||
TESTS += testPutGetTest
|
||||
|
||||
# This runs all the test programs in a known working order:
|
||||
testHarness_SRCS += epicsRunDbTests.c
|
||||
|
||||
dbTestHarness_SRCS += $(testHarness_SRCS)
|
||||
|
||||
150
src/ioc/db/test/dbPutGetTest.c
Normal file
150
src/ioc/db/test/dbPutGetTest.c
Normal file
@@ -0,0 +1,150 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <errlog.h>
|
||||
#include <dbAccess.h>
|
||||
#include <dbStaticLib.h>
|
||||
#include <dbStaticPvt.h>
|
||||
#include <dbUnitTest.h>
|
||||
#include <testMain.h>
|
||||
|
||||
static
|
||||
void testdbGetStringEqual(const char *pv, const char *expected)
|
||||
{
|
||||
const char *actual;
|
||||
int ok;
|
||||
DBENTRY ent;
|
||||
|
||||
dbInitEntry(pdbbase, &ent);
|
||||
|
||||
if(dbFindRecord(&ent, pv)) {
|
||||
testFail("Failed to find record '%s'", pv);
|
||||
return;
|
||||
}
|
||||
|
||||
actual = dbGetString(&ent);
|
||||
ok = (!actual && !expected)
|
||||
|| (actual && expected && strcmp(actual, expected)==0);
|
||||
|
||||
testOk(ok, "dbGetString(\"%s\") -> \"%s\" == \"%s\"", pv, actual, expected);
|
||||
|
||||
dbFinishEntry(&ent);
|
||||
}
|
||||
|
||||
static
|
||||
void testGetString(void)
|
||||
{
|
||||
testDiag("testGetString()");
|
||||
|
||||
testdbGetStringEqual("recempty.DTYP", "Soft Channel");
|
||||
testdbGetStringEqual("recempty.DESC", "");
|
||||
testdbGetStringEqual("recempty.PHAS", "0");
|
||||
testdbGetStringEqual("recempty.TSE" , "0");
|
||||
testdbGetStringEqual("recempty.DISA", "0");
|
||||
testdbGetStringEqual("recempty.DISV", "0");
|
||||
|
||||
testdbGetStringEqual("recoverwrite.DTYP", "Soft Channel");
|
||||
testdbGetStringEqual("recoverwrite.DESC", "");
|
||||
testdbGetStringEqual("recoverwrite.PHAS", "0");
|
||||
testdbGetStringEqual("recoverwrite.TSE" , "0");
|
||||
testdbGetStringEqual("recoverwrite.DISA", "0");
|
||||
testdbGetStringEqual("recoverwrite.DISV", "0");
|
||||
}
|
||||
|
||||
static
|
||||
void testStringMax(void)
|
||||
{
|
||||
testDiag("testStringMax()");
|
||||
|
||||
testdbGetStringEqual("recmax.DISA", "-1");
|
||||
}
|
||||
|
||||
static
|
||||
void testLongLink(void)
|
||||
{
|
||||
testDiag("testLonkLink()");
|
||||
|
||||
testdbGetFieldEqual("lnktest.INP", DBR_STRING, "lnktarget NPP NMS");
|
||||
testdbGetFieldEqual("lnktest.INP$", DBR_STRING, "lnktarget NPP NMS");
|
||||
testDiag("dbGet() w/ nRequest==1 gets only trailing nil");
|
||||
testdbGetFieldEqual("lnktest.INP$", DBR_CHAR, '\0');
|
||||
testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 19, 18, "lnktarget NPP NMS");
|
||||
|
||||
testDiag("get w/ truncation");
|
||||
testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 0, 0, NULL);
|
||||
testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 1, 1, "");
|
||||
testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 2, 2, "l");
|
||||
testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 3, 3, "ln");
|
||||
testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 17, 17, "lnktarget NPP NM");
|
||||
testdbGetArrFieldEqual("lnktest.INP$", DBR_CHAR, 18, 18, "lnktarget NPP NMS");
|
||||
|
||||
testdbGetArrFieldEqual("lnktest.INP", DBR_STRING, 2, 1, "lnktarget NPP NMS");
|
||||
}
|
||||
|
||||
static
|
||||
void testLongAttr(void)
|
||||
{
|
||||
testDiag("testLongAttr()");
|
||||
|
||||
testdbGetFieldEqual("lnktest.RTYP", DBR_STRING, "x");
|
||||
testdbGetFieldEqual("lnktest.RTYP$", DBR_STRING, "x");
|
||||
testDiag("dbGet() w/ nRequest==1 gets only trailing nil");
|
||||
testdbGetFieldEqual("lnktest.RTYP$", DBR_CHAR, '\0');
|
||||
|
||||
testdbGetArrFieldEqual("lnktest.RTYP$", DBR_CHAR, 4, 2, "x");
|
||||
|
||||
testDiag("get w/ truncation");
|
||||
testdbGetArrFieldEqual("lnktest.RTYP$", DBR_CHAR, 0, 0, NULL);
|
||||
testdbGetArrFieldEqual("lnktest.RTYP$", DBR_CHAR, 1, 1, "");
|
||||
testdbGetArrFieldEqual("lnktest.RTYP$", DBR_CHAR, 2, 2, "x");
|
||||
}
|
||||
|
||||
static
|
||||
void testLongField(void)
|
||||
{
|
||||
testDiag("testLongField()");
|
||||
|
||||
testdbGetFieldEqual("lnktest.NAME", DBR_STRING, "lnktest");
|
||||
testdbGetFieldEqual("lnktest.NAME$", DBR_STRING, "108");
|
||||
testDiag("dbGet() w/ nRequest==1 gets only trailing nil");
|
||||
testdbGetFieldEqual("lnktest.NAME$", DBR_CHAR, '\0');
|
||||
testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 10, 8, "lnktest");
|
||||
|
||||
testDiag("get w/ truncation");
|
||||
testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 0, 0, NULL);
|
||||
testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 1, 1, "");
|
||||
testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 2, 2, "l");
|
||||
testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 3, 3, "ln");
|
||||
testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 7, 7, "lnktes");
|
||||
testdbGetArrFieldEqual("lnktest.NAME$", DBR_CHAR, 8, 8, "lnktest");
|
||||
}
|
||||
|
||||
void dbTestIoc_registerRecordDeviceDriver(struct dbBase *);
|
||||
|
||||
MAIN(dbPutGet)
|
||||
{
|
||||
testPlan(41);
|
||||
testdbPrepare();
|
||||
|
||||
testdbReadDatabase("dbTestIoc.dbd", NULL, NULL);
|
||||
dbTestIoc_registerRecordDeviceDriver(pdbbase);
|
||||
testdbReadDatabase("dbPutGetTest.db", NULL, NULL);
|
||||
|
||||
testGetString();
|
||||
|
||||
testStringMax();
|
||||
|
||||
eltc(0);
|
||||
testIocInitOk();
|
||||
eltc(1);
|
||||
|
||||
testLongLink();
|
||||
testLongAttr();
|
||||
testLongField();
|
||||
|
||||
testIocShutdownOk();
|
||||
|
||||
testdbCleanup();
|
||||
|
||||
return testDone();
|
||||
}
|
||||
41
src/ioc/db/test/dbPutGetTest.db
Normal file
41
src/ioc/db/test/dbPutGetTest.db
Normal file
@@ -0,0 +1,41 @@
|
||||
|
||||
record(x, "recempty") {
|
||||
# empty string is the same "0" for numeric fields
|
||||
field(DTYP, "")
|
||||
field(DESC, "")
|
||||
field(PHAS, "")
|
||||
field(TSE , "")
|
||||
field(DISA, "")
|
||||
field(DISV, "")
|
||||
}
|
||||
|
||||
record(x, "recoverwrite") {
|
||||
# first with non-default values
|
||||
# field(DTYP, "Scan I/O")
|
||||
field(DESC, "hello")
|
||||
field(PHAS, "2")
|
||||
field(TSE , "5")
|
||||
field(DISA, "6")
|
||||
field(DISV, "7")
|
||||
}
|
||||
|
||||
record(x, "recoverwrite") {
|
||||
# now restore default values
|
||||
field(DTYP, "")
|
||||
field(DESC, "")
|
||||
field(PHAS, "")
|
||||
field(TSE , "")
|
||||
field(TSEL, "")
|
||||
field(DISA, "")
|
||||
field(DISV, "")
|
||||
}
|
||||
|
||||
record(x, "recmax") {
|
||||
field(DISA, "0xffffffff")
|
||||
}
|
||||
|
||||
record(x, "lnktarget") {}
|
||||
|
||||
record(x, "lnktest") {
|
||||
field(INP, "lnktarget NPP NMS")
|
||||
}
|
||||
Reference in New Issue
Block a user