diff --git a/src/ioc/db/filters/dbnd.c b/src/ioc/db/filters/dbnd.c index e9d517ba1..33e79198d 100644 --- a/src/ioc/db/filters/dbnd.c +++ b/src/ioc/db/filters/dbnd.c @@ -9,6 +9,7 @@ /* * Author: Ralph Lange */ + #include #include @@ -37,17 +38,17 @@ chfPluginArgDef opts[] = { chfPluginArgEnd }; -void * allocPvt(void) +static void * allocPvt(void) { return freeListCalloc(myStructFreeList); } -void freePvt(void *pvt) +static void freePvt(void *pvt) { freeListFree(myStructFreeList, pvt); } -int parse_ok(void *pvt) +static int parse_ok(void *pvt) { myStruct *my = (myStruct*) pvt; my->hyst = my->cval; diff --git a/src/ioc/db/filters/test/Makefile b/src/ioc/db/filters/test/Makefile new file mode 100644 index 000000000..1e32b3056 --- /dev/null +++ b/src/ioc/db/filters/test/Makefile @@ -0,0 +1,28 @@ +#************************************************************************* +# Copyright (c) 2006 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 the file LICENSE that is included with this distribution. +#************************************************************************* +TOP=../../../.. + +include $(TOP)/configure/CONFIG + +PROD_LIBS += dbIoc registryIoc dbStaticHost ca Com + +TESTPROD_HOST += tsTest +tsTest_SRCS += tsTest.c +OBJS_IOC_vxWorks += tsTest +TESTS += tsTest + +TESTPROD_HOST += dbndTest +dbndTest_SRCS += dbndTest.c +OBJS_IOC_vxWorks += dbndTest +TESTS += dbndTest + +TESTSCRIPTS_HOST += $(TESTS:%=%.t) + +include $(TOP)/configure/RULES + diff --git a/src/ioc/db/filters/test/dbndTest.c b/src/ioc/db/filters/test/dbndTest.c new file mode 100644 index 000000000..727b02906 --- /dev/null +++ b/src/ioc/db/filters/test/dbndTest.c @@ -0,0 +1,214 @@ +/*************************************************************************\ +* 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 + */ + +#include + +#include "dbStaticLib.h" +#include "dbAccessDefs.h" +#include "db_field_log.h" +#include "dbCommon.h" +#include "chfPlugin.h" +#include "epicsUnitTest.h" +#include "epicsTime.h" +#include "testMain.h" + +#define PATTERN 0x55 + +epicsShareExtern void (*pvar_func_dbndInitialize)(void); + +static db_field_log fl; + +static int fl_equal(const db_field_log *pfl1, const db_field_log *pfl2) { + return !(memcmp(pfl1, pfl2, sizeof(db_field_log))); +} + +static void fl_setup(dbChannel *chan, db_field_log *pfl) { + struct dbCommon *prec = dbChannelRecord(chan); + + pfl->ctx = dbfl_context_read; + pfl->type = dbfl_type_val; + pfl->stat = prec->stat; + pfl->sevr = prec->sevr; + pfl->time = prec->time; + pfl->field_type = dbChannelFieldType(chan); + pfl->no_elements = dbChannelElements(chan); + /* + * use memcpy to avoid a bus error on + * union copy of char in the db at an odd + * address + */ + memcpy(&pfl->u.v.field, + dbChannelField(chan), + dbChannelFieldSize(chan)); +} + +static changeValue(db_field_log *pfl2, long val) { + pfl2->u.v.field.dbf_long = val; + testDiag("new value: %ld", val); +} + +static void mustPassOnce(dbChannel *pch, db_field_log *pfl2, char* m, double d, long val) { + changeValue(pfl2, val); + testDiag("mode=%s delta=%g filter must pass once", m, d); + db_field_log *pfl = dbChannelRunPreChain(pch, pfl2); + testOk(pfl2 == pfl, "call 1 does not drop or replace field_log"); + testOk(fl_equal(pfl, pfl2), "call 1 does not change field_log data"); + pfl = dbChannelRunPreChain(pch, pfl2); + testOk(NULL == pfl, "call 2 drops field_log"); +} + +static void mustDrop(dbChannel *pch, db_field_log *pfl2, char* m, double d, long val) { + changeValue(pfl2, val); + testDiag("mode=%s delta=%g filter must drop", m, d); + db_field_log *pfl = dbChannelRunPreChain(pch, pfl2); + testOk(NULL == pfl, "call 1 drops field_log"); +} + +static void mustPassTwice(dbChannel *pch, db_field_log *pfl2, char* m, double d, long val) { + changeValue(pfl2, val); + testDiag("mode=%s delta=%g filter must pass twice", m, d); + db_field_log *pfl = dbChannelRunPreChain(pch, pfl2); + testOk(pfl2 == pfl, "call 1 does not drop or replace field_log"); + testOk(fl_equal(pfl, pfl2), "call 1 does not change field_log data"); + pfl = dbChannelRunPreChain(pch, pfl2); + testOk(pfl2 == pfl, "call 2 does not drop or replace field_log"); + testOk(fl_equal(pfl, pfl2), "call 2 does not change field_log data"); +} + +static void testHead (char* title) { + testDiag("--------------------------------------------------------"); + testDiag(title); + testDiag("--------------------------------------------------------"); +} + +MAIN(dbndTest) +{ + dbChannel *pch; + chFilter *filter; + const chFilterPlugin *plug; + char dbnd[] = "dbnd"; + ELLNODE *node; + chPostEventFunc *cb_out = NULL; + void *arg_out = NULL; + db_field_log *pfl, *pfl2; + db_field_log fl1; + + testPlan(0); + + db_init_events(); + + testOk1(!dbReadDatabase(&pdbbase, "filterTest.dbx", ".:..", NULL)); + testOk(!!pdbbase, "pdbbase was set"); + + (*pvar_func_dbndInitialize)(); /* manually initialize plugin */ + + testOk(!!(plug = dbFindFilter(dbnd, strlen(dbnd))), "plugin dbnd registered correctly"); + + testOk(!!(pch = dbChannelCreate("x.VAL{\"dbnd\":{}}")), "dbChannel with plugin dbnd (delta=0) created"); + testOk((ellCount(&pch->filters) == 1), "channel has one plugin"); + + memset(&fl, PATTERN, sizeof(fl)); + fl1 = fl; + node = ellFirst(&pch->filters); + filter = CONTAINER(node, chFilter, list_node); + plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl1); + testOk(!!(cb_out) && !!(arg_out), "register_pre registers one filter with argument"); + testOk(fl_equal(&fl1, &fl), "register_pre does not change field_log data type"); + + testOk(!(dbChannelOpen(pch)), "dbChannel with plugin dbnd opened"); + node = ellFirst(&pch->pre_chain); + filter = CONTAINER(node, chFilter, pre_node); + testOk((ellCount(&pch->pre_chain) == 1 && filter->pre_arg != NULL), + "dbnd has one filter with argument in pre chain"); + testOk((ellCount(&pch->post_chain) == 0), "dbnd has no filter in post chain"); + + /* Field logs of type ref and rec: pass any update */ + + testHead("Field logs of type ref and rec"); + fl1.type = dbfl_type_rec; + mustPassTwice(pch, &fl1, "abs field_log=rec", 0., 0); + + fl1.type = dbfl_type_ref; + mustPassTwice(pch, &fl1, "abs field_log=ref", 0., 0); + + /* Delta = 0: pass any change */ + + testHead("Delta = 0: pass any change"); + pfl2 = db_create_read_log(pch); + testDiag("new field_log from record"); + fl_setup(pch, pfl2); + + mustPassOnce(pch, pfl2, "abs", 0., 0); + mustPassOnce(pch, pfl2, "abs", 0., 1); + + db_delete_field_log(pfl2); + dbChannelDelete(pch); + + /* Delta = -1: pass any update */ + + testHead("Delta = -1: pass any update"); + testOk(!!(pch = dbChannelCreate("x.VAL{\"dbnd\":{\"d\":-1.0}}")), "dbChannel with plugin dbnd (delta=-1) created"); + testOk(!(dbChannelOpen(pch)), "dbChannel with plugin dbnd opened"); + + pfl2 = db_create_read_log(pch); + testDiag("new field_log from record"); + fl_setup(pch, pfl2); + + mustPassTwice(pch, pfl2, "abs", -1., 0); + mustPassTwice(pch, pfl2, "abs", -1., 1); + + db_delete_field_log(pfl2); + dbChannelDelete(pch); + + /* Delta = absolute */ + + testHead("Delta = absolute"); + testOk(!!(pch = dbChannelCreate("x.VAL{\"dbnd\":{\"d\":3}}")), "dbChannel with plugin dbnd (delta=3) created"); + testOk(!(dbChannelOpen(pch)), "dbChannel with plugin dbnd opened"); + + pfl2 = db_create_read_log(pch); + testDiag("new field_log from record"); + fl_setup(pch, pfl2); + + mustPassOnce(pch, pfl2, "abs", 3., 1); + mustDrop(pch, pfl2, "abs", 3., 3); + mustDrop(pch, pfl2, "abs", 3., 4); + mustPassOnce(pch, pfl2, "abs", 3., 5); + + db_delete_field_log(pfl2); + dbChannelDelete(pch); + + /* Delta = relative */ + + testHead("Delta = relative"); + testOk(!!(pch = dbChannelCreate("x.VAL{\"dbnd\":{\"m\":\"rel\",\"d\":50}}")), + "dbChannel with plugin dbnd (mode=rel, delta=50) created"); + testOk(!(dbChannelOpen(pch)), "dbChannel with plugin dbnd opened"); + + pfl2 = db_create_read_log(pch); + testDiag("new field_log from record"); + fl_setup(pch, pfl2); + + mustPassOnce(pch, pfl2, "rel", 50., 1); + mustPassOnce(pch, pfl2, "rel", 50., 2); + mustDrop(pch, pfl2, "rel", 50., 3); + mustPassOnce(pch, pfl2, "rel", 50., 4); + mustDrop(pch, pfl2, "rel", 50., 5); + mustDrop(pch, pfl2, "rel", 50., 6); + mustPassOnce(pch, pfl2, "rel", 50., 7); + + db_delete_field_log(pfl2); + dbChannelDelete(pch); + dbFreeBase(pdbbase); + + return testDone(); +} diff --git a/src/ioc/db/filters/test/filterTest.dbx b/src/ioc/db/filters/test/filterTest.dbx new file mode 100644 index 000000000..b743359c1 --- /dev/null +++ b/src/ioc/db/filters/test/filterTest.dbx @@ -0,0 +1,13 @@ +# This is a combined minimal DBD and DB file + +recordtype(x) { + field(NAME, DBF_STRING) { + prompt("Record Name") + special(SPC_NOMOD) + size(61) + } + field(VAL, DBF_LONG) { + prompt("Value") + } +} +record(x, x) {} diff --git a/src/ioc/db/filters/test/tsTest.c b/src/ioc/db/filters/test/tsTest.c new file mode 100644 index 000000000..6dda76a21 --- /dev/null +++ b/src/ioc/db/filters/test/tsTest.c @@ -0,0 +1,97 @@ +/*************************************************************************\ +* 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 + */ + +#include + +#include "dbStaticLib.h" +#include "dbAccessDefs.h" +#include "chfPlugin.h" +#include "epicsUnitTest.h" +#include "epicsTime.h" +#include "testMain.h" + +#define PATTERN 0x55 + +epicsShareExtern void (*pvar_func_tsInitialize)(void); + +static db_field_log fl; + +static int fl_equal(const db_field_log *pfl1, const db_field_log *pfl2) { + return !(memcmp(pfl1, pfl2, sizeof(db_field_log))); +} + +static int fl_equal_ex_ts(const db_field_log *pfl1, const db_field_log *pfl2) { + db_field_log fl1 = *pfl1; + + fl1.time = pfl2->time; + return fl_equal(&fl1, pfl2); +} + +MAIN(tsTest) +{ + dbChannel *pch; + chFilter *filter; + const chFilterPlugin *plug; + char ts[] = "ts"; + ELLNODE *node; + chPostEventFunc *cb_out = NULL; + void *arg_out = NULL; + db_field_log fl1; + db_field_log *pfl2; + + testPlan(0); + + db_init_events(); + + testOk1(!dbReadDatabase(&pdbbase, "filterTest.dbx", ".:..", NULL)); + testOk(!!pdbbase, "pdbbase was set"); + + (*pvar_func_tsInitialize)(); /* manually initialize plugin */ + + testOk(!!(plug = dbFindFilter(ts, strlen(ts))), "plugin ts registered correctly"); + + testOk(!!(pch = dbChannelCreate("x.VAL{\"ts\":{}}")), "dbChannel with plugin ts created"); + testOk((ellCount(&pch->filters) == 1), "channel has one plugin"); + + memset(&fl, PATTERN, sizeof(fl)); + fl1 = fl; + node = ellFirst(&pch->filters); + filter = CONTAINER(node, chFilter, list_node); + plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl1); + testOk(!!(cb_out) && !(arg_out), "register_pre registers one filter w/o argument"); + testOk(fl_equal(&fl1, &fl), "register_pre does not change field_log data type"); + + testOk(!(dbChannelOpen(pch)), "dbChannel with plugin ts opened"); + node = ellFirst(&pch->pre_chain); + filter = CONTAINER(node, chFilter, pre_node); + testOk((ellCount(&pch->pre_chain) == 1 && filter->pre_arg == NULL), + "ts has one filter w/o argument in pre chain"); + testOk((ellCount(&pch->post_chain) == 0), "ts has no filter in post chain"); + + memset(&fl, PATTERN, sizeof(fl)); + fl1 = fl; + pfl2 = dbChannelRunPreChain(pch, &fl1); + testOk(pfl2 == &fl1, "ts filter does not drop or replace field_log"); + testOk(fl_equal_ex_ts(&fl1, pfl2), "ts filter does not change field_log data"); + + testOk(pfl2 = db_create_read_log(pch), "create field log from channel"); + epicsTimeStamp stamp = pfl2->time; + pfl2 = dbChannelRunPreChain(pch, &fl1); + epicsTimeStamp now; epicsTimeGetCurrent(&now); + testOk(epicsTimeDiffInSeconds(&pfl2->time, &stamp) > 0. && + epicsTimeDiffInSeconds(&now, &pfl2->time) > 0., "ts filter sets time stamp to \"now\""); + + dbChannelDelete(pch); + dbFreeBase(pdbbase); + + return testDone(); +}