Add tests for dbGet() with db_field_log
Regression testing for a bug where an array field with a sub-array channel filter can read past the end of the db_field_log array, because in dbGet() the call to prset->get_array_info() occurs even when pfl is supposed to be overriding the record's array.
This commit is contained in:
@@ -12,6 +12,7 @@ include $(TOP)/configure/CONFIG
|
||||
|
||||
TESTLIBRARY = dbTestIoc
|
||||
|
||||
dbTestIoc_SRCS += arrRecord.c
|
||||
dbTestIoc_SRCS += xRecord.c
|
||||
dbTestIoc_SRCS += dbLinkdset.c
|
||||
dbTestIoc_LIBS = dbCore ca Com
|
||||
@@ -20,6 +21,7 @@ TARGETS += $(COMMON_DIR)/dbTestIoc.dbd
|
||||
dbTestIoc_DBD += menuGlobal.dbd
|
||||
dbTestIoc_DBD += menuConvert.dbd
|
||||
dbTestIoc_DBD += menuScan.dbd
|
||||
#dbTestIoc_DBD += arrRecord.dbd
|
||||
dbTestIoc_DBD += xRecord.dbd
|
||||
dbTestIoc_DBD += dbLinkdset.dbd
|
||||
TESTFILES += $(COMMON_DIR)/dbTestIoc.dbd ../xRecord.db
|
||||
@@ -95,6 +97,16 @@ dbChannelTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp
|
||||
testHarness_SRCS += dbChannelTest.c
|
||||
TESTS += dbChannelTest
|
||||
|
||||
TARGETS += $(COMMON_DIR)/dbChArrTest.dbd
|
||||
dbChArrTest_DBD += arrRecord.dbd
|
||||
TESTPROD_HOST += dbChArrTest
|
||||
dbChArrTest_SRCS += dbChArrTest.cpp
|
||||
dbChArrTest_SRCS += dbChArrTest_registerRecordDeviceDriver.cpp
|
||||
testHarness_SRCS += dbChArrTest.cpp
|
||||
testHarness_SRCS += dbChArrTest_registerRecordDeviceDriver.cpp
|
||||
TESTFILES += $(COMMON_DIR)/dbChArrTest.dbd ../dbChArrTest.db
|
||||
TESTS += dbChArrTest
|
||||
|
||||
TESTPROD_HOST += chfPluginTest
|
||||
chfPluginTest_SRCS += chfPluginTest.c
|
||||
chfPluginTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp
|
||||
@@ -132,6 +144,7 @@ TESTSCRIPTS_HOST += $(TESTS:%=%.t)
|
||||
|
||||
include $(TOP)/configure/RULES
|
||||
|
||||
arrRecord$(DEP): $(COMMON_DIR)/arrRecord.h
|
||||
xRecord$(DEP): $(COMMON_DIR)/xRecord.h
|
||||
dbPutLinkTest$(DEP): $(COMMON_DIR)/xRecord.h
|
||||
scanIoTest$(DEP): $(COMMON_DIR)/yRecord.h
|
||||
|
||||
135
src/ioc/db/test/arrRecord.c
Normal file
135
src/ioc/db/test/arrRecord.c
Normal file
@@ -0,0 +1,135 @@
|
||||
/*************************************************************************\
|
||||
* 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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
34
src/ioc/db/test/arrRecord.dbd
Normal file
34
src/ioc/db/test/arrRecord.dbd
Normal file
@@ -0,0 +1,34 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
242
src/ioc/db/test/dbChArrTest.cpp
Normal file
242
src/ioc/db/test/dbChArrTest.cpp
Normal file
@@ -0,0 +1,242 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2010 Brookhaven National Laboratory.
|
||||
* Copyright (c) 2010 Helmholtz-Zentrum Berlin
|
||||
* fuer Materialien und Energie GmbH.
|
||||
* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne
|
||||
* National Laboratory.
|
||||
* Copyright (c) 2003 The Regents of the University of California, as
|
||||
* Operator of Los Alamos National Laboratory.
|
||||
* EPICS BASE is distributed subject to the Software License Agreement
|
||||
* found in the file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
|
||||
/*
|
||||
* Authors: Ralph Lange <ralph.lange@gmx.de>,
|
||||
* Andrew Johnson <anj@aps.anl.gov>
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "registryFunction.h"
|
||||
#include "epicsThread.h"
|
||||
#include "epicsExit.h"
|
||||
#include "epicsStdio.h"
|
||||
#include "epicsString.h"
|
||||
#include "envDefs.h"
|
||||
#include "dbStaticLib.h"
|
||||
#include "dbmf.h"
|
||||
#include "registry.h"
|
||||
#include "dbAddr.h"
|
||||
#include "dbAccess.h"
|
||||
#include "asDbLib.h"
|
||||
#include "iocInit.h"
|
||||
#include "iocsh.h"
|
||||
#include "dbChannel.h"
|
||||
#include "epicsUnitTest.h"
|
||||
#include "testMain.h"
|
||||
#include "osiFileName.h"
|
||||
|
||||
extern "C" {
|
||||
int dbChArrTest_registerRecordDeviceDriver(struct dbBase *pdbbase);
|
||||
}
|
||||
|
||||
#define CA_SERVER_PORT "65535"
|
||||
|
||||
const char *server_port = CA_SERVER_PORT;
|
||||
|
||||
static void createAndOpen(const char *name, dbChannel**pch)
|
||||
{
|
||||
testOk(!!(*pch = dbChannelCreate(name)), "dbChannel %s created", name);
|
||||
testOk(!(dbChannelOpen(*pch)), "dbChannel opened");
|
||||
testOk((ellCount(&(*pch)->pre_chain) == 0), "no filters in pre chain");
|
||||
testOk((ellCount(&(*pch)->post_chain) == 0), "no filters in post chain");
|
||||
}
|
||||
|
||||
static void freeArray(db_field_log *pfl) {
|
||||
if (pfl->type == dbfl_type_ref) {
|
||||
free(pfl->u.r.field);
|
||||
}
|
||||
}
|
||||
|
||||
static void testHead (const char *title, const char *typ = "") {
|
||||
const char *line = "------------------------------------------------------------------------------";
|
||||
testDiag("%s", line);
|
||||
testDiag(title, typ);
|
||||
testDiag("%s", line);
|
||||
}
|
||||
|
||||
static void check(short dbr_type) {
|
||||
dbChannel *pch;
|
||||
db_field_log *pfl;
|
||||
dbAddr valaddr;
|
||||
dbAddr offaddr;
|
||||
const char *offname = NULL, *valname = NULL, *typname = NULL;
|
||||
epicsInt32 buf[26];
|
||||
long off, req;
|
||||
int i;
|
||||
|
||||
switch (dbr_type) {
|
||||
case DBR_LONG:
|
||||
offname = "i32.OFF";
|
||||
valname = "i32.VAL";
|
||||
typname = "long";
|
||||
break;
|
||||
case DBR_DOUBLE:
|
||||
offname = "f64.OFF";
|
||||
valname = "f64.VAL";
|
||||
typname = "double";
|
||||
break;
|
||||
case DBR_STRING:
|
||||
offname = "c40.OFF";
|
||||
valname = "c40.VAL";
|
||||
typname = "string";
|
||||
break;
|
||||
default:
|
||||
testDiag("Invalid data type %d", dbr_type);
|
||||
}
|
||||
|
||||
(void) dbNameToAddr(offname, &offaddr);
|
||||
(void) dbNameToAddr(valname, &valaddr);
|
||||
|
||||
testHead("Ten %s elements", typname);
|
||||
|
||||
/* Fill the record's array field with data, 10..19 */
|
||||
|
||||
epicsInt32 ar[10] = {10,11,12,13,14,15,16,17,18,19};
|
||||
(void) dbPutField(&valaddr, DBR_LONG, ar, 10);
|
||||
|
||||
/* Open a channel to it, make sure no filters present */
|
||||
|
||||
createAndOpen(valname, &pch);
|
||||
testOk(pch->final_type == valaddr.field_type,
|
||||
"final type unchanged (%d->%d)", valaddr.field_type, pch->final_type);
|
||||
testOk(pch->final_no_elements == valaddr.no_elements,
|
||||
"final no_elements unchanged (%ld->%ld)", valaddr.no_elements, pch->final_no_elements);
|
||||
|
||||
/* TEST1 sets the record's OFF field, then requests 10 elements from the channel,
|
||||
* passing in a transparent db_field_log and converting the data to LONG on the way in.
|
||||
* It checks that it got back the expected data and the right number of elements.
|
||||
*/
|
||||
|
||||
#define TEST1(Size, Offset, Text, Expected) \
|
||||
testDiag("Reading from offset = %d (%s)", Offset, Text); \
|
||||
off = Offset; req = 10; \
|
||||
memset(buf, sizeof(buf), 0); \
|
||||
(void) dbPutField(&offaddr, DBR_LONG, &off, 1); \
|
||||
pfl = db_create_read_log(pch); \
|
||||
testOk(pfl && pfl->type == dbfl_type_rec, "Valid pfl, type = rec"); \
|
||||
testOk(!dbChannelGetField(pch, DBR_LONG, buf, NULL, &req, pfl), "Got Field value"); \
|
||||
testOk(req == Size, "Got %ld elements (expected %d)", req, Size); \
|
||||
if (!testOk(!memcmp(buf, Expected, sizeof(Expected)), "Data correct")) \
|
||||
for (i=0; i<Size; i++) \
|
||||
testDiag("Element %d expected %d got %d", i, Expected[i], buf[i]); \
|
||||
db_delete_field_log(pfl);
|
||||
|
||||
const epicsInt32 res_10_0[] = {10,11,12,13,14,15,16,17,18,19};
|
||||
TEST1(10, 0, "no offset", res_10_0);
|
||||
|
||||
const epicsInt32 res_10_4[] = {14,15,16,17,18,19,10,11,12,13};
|
||||
TEST1(10, 4, "wrapped", res_10_4);
|
||||
|
||||
/* Partial array */
|
||||
|
||||
testHead("Five %s elements", typname);
|
||||
off = 0; /* Reset offset for writing the next buffer */
|
||||
(void) dbPutField(&offaddr, DBR_LONG, &off, 1);
|
||||
(void) dbPutField(&valaddr, DBR_LONG, &ar[5], 5);
|
||||
|
||||
createAndOpen(valname, &pch);
|
||||
testOk(pch->final_type == valaddr.field_type,
|
||||
"final type unchanged (%d->%d)", valaddr.field_type, pch->final_type);
|
||||
testOk(pch->final_no_elements == valaddr.no_elements,
|
||||
"final no_elements unchanged (%ld->%ld)", valaddr.no_elements, pch->final_no_elements);
|
||||
|
||||
const epicsInt32 res_5_0[] = {15,16,17,18,19};
|
||||
TEST1(5, 0, "no offset", res_5_0);
|
||||
|
||||
const epicsInt32 res_5_3[] = {18,19,15,16,17};
|
||||
TEST1(5, 3, "wrapped", res_5_3);
|
||||
|
||||
/* TEST2 sets the record's OFF field, then requests 15 elements from the channel
|
||||
* but passes in a db_field_log with alternate data, converting that data to LONG.
|
||||
* It checks that it got back the expected data and the right number of elements.
|
||||
*/
|
||||
|
||||
#define TEST2(Size, Offset, Text, Expected) \
|
||||
testDiag("Reading from offset = %d (%s)", Offset, Text); \
|
||||
off = Offset; req = 15; \
|
||||
memset(buf, sizeof(buf), 0); \
|
||||
(void) dbPutField(&offaddr, DBR_LONG, &off, 1); \
|
||||
pfl = db_create_read_log(pch); \
|
||||
pfl->type = dbfl_type_ref; \
|
||||
pfl->field_type = DBF_CHAR; \
|
||||
pfl->field_size = 1; \
|
||||
pfl->no_elements = 26; \
|
||||
pfl->u.r.dtor = freeArray; \
|
||||
pfl->u.r.field = epicsStrDup("abcdefghijklmnopqrsstuvwxyz"); \
|
||||
testOk(!dbChannelGetField(pch, DBR_LONG, buf, NULL, &req, pfl), "Got Field value"); \
|
||||
testOk(req == Size, "Got %ld elements (expected %d)", req, Size); \
|
||||
if (!testOk(!memcmp(buf, Expected, sizeof(Expected)), "Data correct")) \
|
||||
for (i=0; i<Size; i++) \
|
||||
testDiag("Element %d expected '%c' got '%c'", i, Expected[i], buf[i]); \
|
||||
db_delete_field_log(pfl);
|
||||
|
||||
testHead("Fifteen letters from field-log instead of %s", typname);
|
||||
|
||||
const epicsInt32 res_15[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
|
||||
'm', 'n', 'o'};
|
||||
TEST2(15, 0, "no offset", res_15);
|
||||
TEST2(15, 10, "ignored", res_15);
|
||||
|
||||
dbChannelDelete(pch);
|
||||
}
|
||||
|
||||
static dbEventCtx evtctx;
|
||||
|
||||
static void dbChArrTestCleanup(void* junk)
|
||||
{
|
||||
dbFreeBase(pdbbase);
|
||||
registryFree();
|
||||
pdbbase=0;
|
||||
|
||||
db_close_events(evtctx);
|
||||
|
||||
dbmfFreeChunks();
|
||||
}
|
||||
|
||||
MAIN(dbChArrTest)
|
||||
{
|
||||
testPlan(102);
|
||||
|
||||
/* Prepare the IOC */
|
||||
|
||||
epicsEnvSet("EPICS_CA_SERVER_PORT", server_port);
|
||||
|
||||
if (dbReadDatabase(&pdbbase, "dbChArrTest.dbd",
|
||||
"." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR
|
||||
"../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL))
|
||||
testAbort("Database description not loaded");
|
||||
|
||||
dbChArrTest_registerRecordDeviceDriver(pdbbase);
|
||||
|
||||
if (dbReadDatabase(&pdbbase, "dbChArrTest.db",
|
||||
"." OSI_PATH_LIST_SEPARATOR "..", NULL))
|
||||
testAbort("Test database not loaded");
|
||||
|
||||
epicsAtExit(&dbChArrTestCleanup,NULL);
|
||||
|
||||
/* Start the IOC */
|
||||
|
||||
iocInit();
|
||||
evtctx = db_init_events();
|
||||
|
||||
check(DBR_LONG);
|
||||
check(DBR_DOUBLE);
|
||||
check(DBR_STRING);
|
||||
|
||||
return testDone();
|
||||
}
|
||||
15
src/ioc/db/test/dbChArrTest.db
Normal file
15
src/ioc/db/test/dbChArrTest.db
Normal file
@@ -0,0 +1,15 @@
|
||||
record(arr, "i32") {
|
||||
field(DESC, "test array record")
|
||||
field(NELM, "10")
|
||||
field(FTVL, "LONG")
|
||||
}
|
||||
record(arr, "f64") {
|
||||
field(DESC, "test array record")
|
||||
field(NELM, "10")
|
||||
field(FTVL, "DOUBLE")
|
||||
}
|
||||
record(arr, "c40") {
|
||||
field(DESC, "test array record")
|
||||
field(NELM, "10")
|
||||
field(FTVL, "STRING")
|
||||
}
|
||||
Reference in New Issue
Block a user