Add arr plugin for array subsets (+ testcode)

This commit is contained in:
Michael Davidsaver
2012-04-27 13:25:07 -04:00
parent ef4b2e5892
commit 45a531b167
10 changed files with 706 additions and 3 deletions
+1
View File
@@ -88,4 +88,5 @@ dbCore_SRCS += chfPlugin.c
LIB_SRCS += ts.c
LIB_SRCS += dbnd.c
LIB_SRCS += arr.c
+201
View File
@@ -0,0 +1,201 @@
/*************************************************************************\
* Copyright (c) 2010 Brookhaven National Laboratory.
* Copyright (c) 2010 Helmholtz-Zentrum Berlin
* fuer Materialien und Energie GmbH.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/*
* Author: Ralph Lange <Ralph.Lange@bessy.de>
*/
#include <stdio.h>
#include <epicsExport.h>
#include <freeList.h>
#include <dbAccess.h>
#include <dbExtractArray.h>
#include <db_field_log.h>
#include <recSup.h>
#include <special.h>
#include <chfPlugin.h>
typedef struct myStruct {
epicsInt32 start;
epicsInt32 incr;
epicsInt32 end;
void *arrayFreeList;
long no_elements;
} myStruct;
static void *myStructFreeList;
static const
chfPluginArgDef opts[] = {
chfInt32 (myStruct, start, "s", 0, 1),
chfInt32 (myStruct, incr, "i", 0, 1),
chfInt32 (myStruct, end, "e", 0, 1),
chfPluginArgEnd
};
static void * allocPvt(void)
{
myStruct *my = (myStruct*) freeListCalloc(myStructFreeList);
my->incr = 1;
return (void *) my;
}
static void freePvt(void *pvt)
{
myStruct *my = (myStruct*) pvt;
if (my->arrayFreeList) freeListCleanup(my->arrayFreeList);
freeListFree(myStructFreeList, pvt);
}
static int parse_ok(void *pvt)
{
return 0;
}
static void freeArray(db_field_log *pfl) {
if (pfl->type == dbfl_type_ref) {
freeListFree(pfl->u.r.pvt, pfl->u.r.field);
}
}
static long wrapArrayIndices(long *start, const long increment, long *end, const long no_elements) {
long len = 0;
if (*start < 0) *start = no_elements + *start;
if (*start < 0) *start = 0;
if (*start > no_elements) *start = no_elements;
if (*end <= 0) *end = no_elements + *end;
if (*end <= 0) *end = 0;
if (*end > no_elements) *end = no_elements;
if (*end - *start >= 1) len = 1 + (*end - *start - 1 ) / increment;
return len;
}
static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
myStruct *my = (myStruct*) pvt;
struct dbCommon *prec;
struct rset *prset;
long start = my->start;
long end = my->end;
long nTarget = 0;
long offset = 0;
long status;
long nSource = chan->addr.no_elements;
/* Only array data */
if (pfl->type == dbfl_type_val) {
return pfl;
/* Extract from record */
} else if (pfl->type == dbfl_type_rec) {
if (chan->addr.special == SPC_DBADDR &&
nSource > 1 &&
(prset = dbGetRset(&chan->addr)) &&
prset->get_array_info) {
status = prset->get_array_info(&chan->addr, &nSource, &offset);
nTarget = wrapArrayIndices(&start, my->incr, &end, nSource);
prec = dbChannelRecord(chan);
pfl->type = dbfl_type_ref;
pfl->stat = prec->stat;
pfl->sevr = prec->sevr;
pfl->time = prec->time;
pfl->field_type = chan->addr.field_type;
pfl->field_size = chan->addr.field_size;
pfl->no_elements = nTarget;
if (nTarget) {
pfl->u.r.dtor = freeArray;
pfl->u.r.pvt = my->arrayFreeList;
void *pdst = freeListCalloc(my->arrayFreeList);
offset = (offset + start) % chan->addr.no_elements;
dbExtractArrayFromRec(&chan->addr, pdst, nTarget, nSource, offset, my->incr);
pfl->u.r.field = pdst;
}
}
/* Extract from buffer */
} else if (pfl->type == dbfl_type_ref) {
nSource = pfl->no_elements;
nTarget = wrapArrayIndices(&start, my->incr, &end, nSource);
pfl->no_elements = nTarget;
void *psrc = pfl->u.r.field;
void *pdst = NULL;
if (nTarget) { /* Copy the data out */
pdst = freeListCalloc(my->arrayFreeList);
offset = start;
dbExtractArrayFromBuf(psrc, pdst, pfl->field_size, pfl->field_type, nTarget, nSource, offset, my->incr);
}
if (pfl->u.r.dtor) pfl->u.r.dtor(pfl);
if (nTarget) {
pfl->u.r.dtor = freeArray;
pfl->u.r.pvt = my->arrayFreeList;
pfl->u.r.field = pdst;
}
}
return pfl;
}
static void channelRegisterPost(dbChannel *chan, void *pvt,
chPostEventFunc **cb_out, void **arg_out, db_field_log *probe)
{
myStruct *my = (myStruct*) pvt;
long start = my->start;
long end = my->end;
long max = 0;
if (probe->no_elements <= 1) return; /* array data only */
max = wrapArrayIndices(&start, my->incr, &end, probe->no_elements); /* wrap indices into array */
if (max) {
if (!my->arrayFreeList)
freeListInitPvt(&my->arrayFreeList, max * probe->field_size, 2);
if (!my->arrayFreeList) return;
}
probe->no_elements = my->no_elements = max;
*cb_out = filter;
*arg_out = pvt;
}
static void channel_report(dbChannel *chan, void *pvt, int level, const unsigned short indent)
{
myStruct *my = (myStruct*) pvt;
printf("%*s plugin arr, start=%d, incr=%d, end=%d\n", indent, "",
my->start, my->incr, my->end);
}
static chfPluginIf pif = {
allocPvt,
freePvt,
NULL, /* parse_error, */
parse_ok,
NULL, /* channel_open, */
NULL, /* channelRegisterPre, */
channelRegisterPost,
channel_report,
NULL /* channel_close */
};
static void arrInitialize(void)
{
static int firstTime = 1;
if (!firstTime) return;
firstTime = 0;
if (!myStructFreeList)
freeListInitPvt(&myStructFreeList, sizeof(myStruct), 64);
chfPluginRegister("arr", &pif, opts);
}
epicsExportRegistrar(arrInitialize);
+1
View File
@@ -1,2 +1,3 @@
registrar(tsInitialize)
registrar(dbndInitialize)
registrar(arrInitialize)
+12 -1
View File
@@ -10,18 +10,29 @@ TOP=../../../..
include $(TOP)/configure/CONFIG
PROD_LIBS += dbIoc registryIoc dbStaticHost ca Com
PROD_LIBS += dbIoc miscIoc registryIoc ca Com
TESTPROD_HOST += tsTest
tsTest_SRCS += tsTest.c
tsTest_LIBS += dbStaticHost
OBJS_IOC_vxWorks += tsTest
TESTS += tsTest
TESTPROD_HOST += dbndTest
dbndTest_SRCS += dbndTest.c
dbndTest_LIBS += dbStaticHost
OBJS_IOC_vxWorks += dbndTest
TESTS += dbndTest
DBDINC += arrRecord
TARGETS += $(COMMON_DIR)/arrTest.dbd
arrTest_DBD += arrRecord.dbd
TESTPROD_HOST += arrTest
arrTest_SRCS += arrTest.cpp arrRecord.c arrRecord_registerRecordDeviceDriver.cpp
arrTest_LIBS += dbStaticIoc
OBJS_IOC_vxWorks += arrTest
TESTS += arrTest
TESTSCRIPTS_HOST += $(TESTS:%=%.t)
include $(TOP)/configure/RULES
+135
View 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;
}
+32
View File
@@ -0,0 +1,32 @@
include "menuGlobal.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")
}
}
+307
View File
@@ -0,0 +1,307 @@
/*************************************************************************\
* 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) 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.
\*************************************************************************/
/*
* Author: Ralph Lange <Ralph.Lange@bessy.de>
*/
/* using stuff from softIoc.cpp by Andrew Johnson */
#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 "envDefs.h"
#include "dbStaticLib.h"
#include "subRecord.h"
#include "dbAddr.h"
#include "dbAccess.h"
#include "asDbLib.h"
#include "iocInit.h"
#include "iocsh.h"
#include "dbChannel.h"
#include "epicsInstallDir.h"
#include "epicsUnitTest.h"
#include "testMain.h"
#include "arrRecord.h"
extern "C" int arrRecord_registerRecordDeviceDriver(struct dbBase *pdbbase);
extern "C" void (*pvar_func_arrInitialize)(void);
#define DBD_FILE EPICS_BASE "/src/db/filters/test/O.Common/arrTest.dbd"
#define DB_FILE EPICS_BASE "/src/db/filters/test/arrTest.db"
#define EXIT_FILE EPICS_BASE "/db/softIocExit.db"
#define CA_SERVER_PORT "65535"
#define PATTERN 0x55
const char *base_dbd = DBD_FILE;
const char *arr_db = DB_FILE;
const char *exit_db = EXIT_FILE;
const char *server_port = CA_SERVER_PORT;
static void exitSubroutine(subRecord *precord) {
epicsExit((precord->a == 0.0) ? EXIT_SUCCESS : EXIT_FAILURE);
}
static int fl_equals_array(short type, const db_field_log *pfl1, void *p2) {
for (int i = 0; i < pfl1->no_elements; i++) {
switch (type) {
case DBR_DOUBLE:
if (((epicsFloat64*)pfl1->u.r.field)[i] != ((epicsInt32*)p2)[i]) {
testDiag("at index=%d: field log has %g, should be %d",
i, ((epicsFloat64*)pfl1->u.r.field)[i], ((epicsInt32*)p2)[i]);
return 0;
}
break;
case DBR_LONG:
if (((epicsInt32*)pfl1->u.r.field)[i] != ((epicsInt32*)p2)[i]) {
testDiag("at index=%d: field log has %d, should be %d",
i, ((epicsInt32*)pfl1->u.r.field)[i], ((epicsInt32*)p2)[i]);
return 0;
}
break;
case DBR_STRING:
if (strtol(&((const char*)pfl1->u.r.field)[i*MAX_STRING_SIZE], NULL, 0) != ((epicsInt32*)p2)[i]) {
testDiag("at index=%d: field log has '%s', should be '%d'",
i, &((const char*)pfl1->u.r.field)[i*MAX_STRING_SIZE], ((epicsInt32*)p2)[i]);
return 0;
}
break;
default:
return 0;
}
}
return 1;
}
static void createAndOpen(const char *chan, const char *json, const char *type, dbChannel**pch, short no) {
ELLNODE *node;
chFilter *filter;
char name[80];
strncpy(name, chan, sizeof(name)-1);
strncat(name, json, sizeof(name)-strlen(name)-1);
testOk(!!(*pch = dbChannelCreate(name)), "dbChannel with plugin arr %s created", type);
testOk((ellCount(&(*pch)->filters) == no), "channel has %d filter(s) in filter list", no);
testOk(!(dbChannelOpen(*pch)), "dbChannel with plugin arr opened");
node = ellFirst(&(*pch)->pre_chain);
filter = CONTAINER(node, chFilter, pre_node);
testOk((ellCount(&(*pch)->pre_chain) == 0), "arr has no filter in pre chain");
node = ellFirst(&(*pch)->post_chain);
filter = CONTAINER(node, chFilter, post_node);
testOk((ellCount(&(*pch)->post_chain) == no),
"arr has %d filter(s) in post chain", no);
}
static void testHead (const char *title, const char *typ = "") {
const char *line = "------------------------------------------------------------------------------";
testDiag(line);
testDiag(title, typ);
testDiag(line);
}
static void check(short dbr_type) {
dbChannel *pch;
db_field_log *pfl, *pfl2;
int status;
dbAddr valaddr;
dbAddr offaddr;
char *offname, *valname, *typname;
long ar[10] = {10,11,12,13,14,15,16,17,18,19};
long *ar10_0_1 = ar;
long ar10_4_1[10] = {14,15,16,17,18,19,10,11,12,13};
long ar5_0_1[10] = {12,13,14,15,16};
long ar5_3_1[10] = {15,16,17,18,19};
long ar5_5_1[10] = {17,18,19,10,11};
long ar5_9_1[10] = {11,12,13,14,15};
long ar5_0_2[10] = {12,14,16};
long ar5_3_2[10] = {15,17,19};
long ar5_5_2[10] = {17,19,11};
long ar5_9_2[10] = {11,13,15};
long ar5_0_3[10] = {12,15};
long ar5_3_3[10] = {15,18};
long ar5_5_3[10] = {17,10};
long ar5_9_3[10] = {11,14};
long off = 0;
switch (dbr_type) {
case DBR_LONG:
offname = "x.OFF";
valname = "x.VAL";
typname = "long";
break;
case DBR_DOUBLE:
offname = "y.OFF";
valname = "y.VAL";
typname = "double";
break;
case DBR_STRING:
offname = "z.OFF";
valname = "z.VAL";
typname = "string";
break;
default:
testDiag("Invalid data type %d", dbr_type);
}
status = dbNameToAddr(offname, &offaddr);
status = dbNameToAddr(valname, &valaddr);
status = dbPutField(&valaddr, DBR_LONG, ar, 10);
#define TEST1(Size, Offset, Incr, Text) \
testDiag("Offset: %d (%s)", Offset, Text); \
off = Offset; \
status = dbPutField(&offaddr, DBR_LONG, &off, 1); \
pfl = db_create_read_log(pch); \
testOk(pfl->type == dbfl_type_rec, "original field log has type rec"); \
pfl2 = dbChannelRunPostChain(pch, pfl); \
testOk(pfl2 == pfl, "call does not drop or replace field_log"); \
testOk(pfl->type == dbfl_type_ref, "filtered field log has type ref"); \
testOk(fl_equals_array(dbr_type, pfl2, ar##Size##_##Offset##_##Incr), "array data correct"); \
db_delete_field_log(pfl);
/* Default: should not change anything */
testHead("Ten %s elements from rec, increment 1, full size (default)", typname);
createAndOpen(valname, "{\"arr\":{}}", "(default)", &pch, 1);
testOk(pch->final_type == valaddr.field_type, "final type unchanged (%d)", valaddr.field_type);
testOk(pch->final_no_elements == valaddr.no_elements, "final no_elements unchanged (%ld)", valaddr.no_elements);
TEST1(10, 0, 1, "no offset");
TEST1(10, 4, 1, "wrapped");
dbChannelDelete(pch);
#define TEST5(Incr, Left, Right, Type) \
testHead("Five %s elements from rec, increment " #Incr ", " Type " addressing", typname); \
createAndOpen(valname, "{\"arr\":{\"s\":" #Left ",\"e\":" #Right ",\"i\":" #Incr "}}", \
"(" #Left ":" #Incr ":" #Right ")", &pch, 1); \
testOk(pch->final_type == valaddr.field_type, "final type unchanged (%d)", valaddr.field_type); \
testOk(pch->final_no_elements == 4 / Incr + 1, "final no_elements correct (%d)", 4 / Incr + 1); \
TEST1(5, 0, Incr, "no offset"); \
TEST1(5, 3, Incr, "from upper block"); \
TEST1(5, 5, Incr, "wrapped"); \
TEST1(5, 9, Incr, "from lower block"); \
dbChannelDelete(pch);
/* Contiguous block of 5 */
TEST5(1, 2, 7, "regular");
TEST5(1, -8, 7, "left side from-end");
TEST5(1, 2, -3, "right side from-end");
TEST5(1, -8, -3, "both sides from-end");
/* 5 elements with increment 2 */
TEST5(2, 2, 7, "regular");
TEST5(2, -8, 7, "left side from-end");
TEST5(2, 2, -3, "right side from-end");
TEST5(2, -8, -3, "both sides from-end");
/* 5 elements with increment 3 */
TEST5(3, 2, 7, "regular");
TEST5(3, -8, 7, "left side from-end");
TEST5(3, 2, -3, "right side from-end");
TEST5(3, -8, -3, "both sides from-end");
/* From buffer (plugin chain) */
#define TEST5B(Incr, Left, Right, Type) \
testHead("Five %s elements from buffer, increment " #Incr ", " Type " addressing", typname); \
createAndOpen(valname, "{\"arr\":{},\"arr\":{\"s\":" #Left ",\"e\":" #Right ",\"i\":" #Incr "}}", \
"(" #Left ":" #Incr ":" #Right ")", &pch, 2); \
testOk(pch->final_type == valaddr.field_type, "final type unchanged (%d)", valaddr.field_type); \
testOk(pch->final_no_elements == 4 / Incr + 1, "final no_elements correct (%d)", 4 / Incr + 1); \
TEST1(5, 0, Incr, "no offset"); \
dbChannelDelete(pch);
/* Contiguous block of 5 */
TEST5B(1, 2, 7, "regular");
TEST5B(1, -8, 7, "left side from-end");
TEST5B(1, 2, -3, "right side from-end");
TEST5B(1, -8, -3, "both sides from-end");
/* 5 elements with increment 2 */
TEST5B(2, 2, 7, "regular");
TEST5B(2, -8, 7, "left side from-end");
TEST5B(2, 2, -3, "right side from-end");
TEST5B(2, -8, -3, "both sides from-end");
/* 5 elements with increment 3 */
TEST5B(3, 2, 7, "regular");
TEST5B(3, -8, 7, "left side from-end");
TEST5B(3, 2, -3, "right side from-end");
TEST5B(3, -8, -3, "both sides from-end");
}
MAIN(arrTest)
{
char *dbd_file = const_cast<char*>(base_dbd);
char *db_file = const_cast<char*>(arr_db);
const chFilterPlugin *plug;
char arr[] = "arr";
int status;
testPlan(1272);
/* Prepare the IOC */
epicsEnvSet("EPICS_CA_SERVER_PORT", server_port);
testOk1(!(status = dbLoadDatabase(dbd_file, NULL, NULL)));
if (status) epicsExit(EXIT_FAILURE);
(*pvar_func_arrInitialize)();
arrRecord_registerRecordDeviceDriver(pdbbase);
registryFunctionAdd("exit", (REGISTRYFUNCTION) exitSubroutine);
testOk1(!(status = dbLoadRecords(db_file, NULL)));
if (status) epicsExit(EXIT_FAILURE);
/* Start the IOC */
iocInit();
db_init_events();
epicsThreadSleep(0.2);
testOk(!!(plug = dbFindFilter(arr, strlen(arr))), "plugin arr registered correctly");
check(DBR_LONG);
check(DBR_DOUBLE);
check(DBR_STRING);
dbFreeBase(pdbbase);
return testDone();
epicsExit(EXIT_SUCCESS);
/*Note that the following statement will never be executed*/
return 0;
}
+15
View File
@@ -0,0 +1,15 @@
record(arr, "x") {
field(DESC, "test array record")
field(NELM, "10")
field(FTVL, "LONG")
}
record(arr, "y") {
field(DESC, "test array record")
field(NELM, "10")
field(FTVL, "DOUBLE")
}
record(arr, "z") {
field(DESC, "test array record")
field(NELM, "10")
field(FTVL, "STRING")
}
+1 -1
View File
@@ -102,7 +102,7 @@ MAIN(dbndTest)
db_field_log *pfl, *pfl2;
db_field_log fl1;
testPlan(0);
testPlan(61);
db_init_events();
+1 -1
View File
@@ -48,7 +48,7 @@ MAIN(tsTest)
db_field_log fl1;
db_field_log *pfl2;
testPlan(0);
testPlan(14);
db_init_events();