Merge branch 'database/master' back

This commit is contained in:
Ralph Lange
2018-06-20 09:32:23 +02:00
560 changed files with 94784 additions and 0 deletions
@@ -0,0 +1,81 @@
#*************************************************************************
# Copyright (c) 2012 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
TESTLIBRARY = Recs
Recs_SRCS += xRecord.c
Recs_SRCS += arrRecord.c
Recs_LIBS += dbCore ca Com
PROD_LIBS = Recs dbRecStd dbCore ca Com
DBDDEPENDS_FILES += filterTest.dbd$(DEP)
TARGETS += $(COMMON_DIR)/filterTest.dbd
filterTest_DBD += menuGlobal.dbd
filterTest_DBD += menuConvert.dbd
filterTest_DBD += menuScan.dbd
filterTest_DBD += filters.dbd
filterTest_DBD += xRecord.dbd
filterTest_DBD += arrRecord.dbd
TESTFILES += $(COMMON_DIR)/filterTest.dbd
testHarness_SRCS += filterTest_registerRecordDeviceDriver.cpp
TESTPROD_HOST += tsTest
tsTest_SRCS += tsTest.c
tsTest_SRCS += filterTest_registerRecordDeviceDriver.cpp
testHarness_SRCS += tsTest.c
TESTFILES += ../xRecord.db
TESTS += tsTest
TESTPROD_HOST += dbndTest
dbndTest_SRCS += dbndTest.c
dbndTest_SRCS += filterTest_registerRecordDeviceDriver.cpp
testHarness_SRCS += dbndTest.c
TESTS += dbndTest
TESTPROD_HOST += arrTest
arrTest_SRCS += arrTest.cpp
arrTest_SRCS += filterTest_registerRecordDeviceDriver.cpp
testHarness_SRCS += arrTest.cpp
TESTFILES += ../arrTest.db
TESTS += arrTest
TESTPROD_HOST += syncTest
syncTest_SRCS += syncTest.c
syncTest_SRCS += filterTest_registerRecordDeviceDriver.cpp
testHarness_SRCS += syncTest.c
TESTS += syncTest
# epicsRunFilterTests runs all the test programs in a known working order.
testHarness_SRCS += epicsRunFilterTests.c
filterTestHarness_SRCS += $(testHarness_SRCS)
filterTestHarness_SRCS_RTEMS += rtemsTestHarness.c
PROD_vxWorks = filterTestHarness
PROD_RTEMS = filterTestHarness
TESTSPEC_vxWorks = filterTestHarness.munch; epicsRunFilterTests
TESTSPEC_RTEMS = filterTestHarness.boot; epicsRunFilterTests
TESTSCRIPTS_HOST += $(TESTS:%=%.t)
include $(TOP)/configure/RULES
xRecord$(DEP): $(COMMON_DIR)/xRecord.h
tsTest$(DEP): $(COMMON_DIR)/xRecord.h
dbndTest$(DEP): $(COMMON_DIR)/xRecord.h
syncTest$(DEP): $(COMMON_DIR)/xRecord.h
arrRecord$(DEP): $(COMMON_DIR)/arrRecord.h
arrTest$(DEP): $(COMMON_DIR)/arrRecord.h
@@ -0,0 +1,145 @@
/*************************************************************************\
* 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(struct dbCommon *, int);
static long process(struct dbCommon *);
#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(struct dbCommon *pcommon, int pass)
{
struct arrRecord *prec = (struct arrRecord *)pcommon;
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(struct dbCommon *pcommon)
{
struct arrRecord *prec = (struct arrRecord *)pcommon;
if(prec->clbk)
(*prec->clbk)(prec);
prec->pact = TRUE;
recGblGetTimeStamp(prec);
recGblFwdLink(prec);
prec->pact = FALSE;
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;
}
@@ -0,0 +1,42 @@
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")
}
field(INP, DBF_INLINK) {
prompt("Input Link")
}
field(CLBK, DBF_NOACCESS) {
prompt("Processing callback")
special(SPC_NOMOD)
extra("void (*clbk)(struct arrRecord*)")
}
}
@@ -0,0 +1,336 @@
/*************************************************************************\
* 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 "dbmf.h"
#include "errlog.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 "dbUnitTest.h"
#include "testMain.h"
#include "osiFileName.h"
#include "arrRecord.h"
extern "C" {
void filterTest_registerRecordDeviceDriver(struct dbBase *);
}
#define CA_SERVER_PORT "65535"
#define PATTERN 0x55
const char *server_port = CA_SERVER_PORT;
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;
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);
(void) CONTAINER(node, chFilter, pre_node);
testOk((ellCount(&(*pch)->pre_chain) == 0), "arr has no filter in pre chain");
node = ellFirst(&(*pch)->post_chain);
(void) 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("%s", line);
testDiag(title, typ);
testDiag("%s", line);
}
#define TEST1(Size, Offset, Incr, Text) \
testDiag("Offset: %d (%s)", Offset, Text); \
off = Offset; \
(void) 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);
static void check(short dbr_type) {
dbChannel *pch;
db_field_log *pfl, *pfl2;
dbAddr valaddr;
dbAddr offaddr;
const char *offname = NULL, *valname = NULL, *typname = NULL;
epicsInt32 ar[10] = {10,11,12,13,14,15,16,17,18,19};
epicsInt32 *ar10_0_1 = ar;
epicsInt32 ar10_4_1[10] = {14,15,16,17,18,19,10,11,12,13};
epicsInt32 ar5_0_1[10] = {12,13,14,15,16};
epicsInt32 ar5_3_1[10] = {15,16,17,18,19};
epicsInt32 ar5_5_1[10] = {17,18,19,10,11};
epicsInt32 ar5_9_1[10] = {11,12,13,14,15};
epicsInt32 ar5_0_2[10] = {12,14,16};
epicsInt32 ar5_3_2[10] = {15,17,19};
epicsInt32 ar5_5_2[10] = {17,19,11};
epicsInt32 ar5_9_2[10] = {11,13,15};
epicsInt32 ar5_0_3[10] = {12,15};
epicsInt32 ar5_3_3[10] = {15,18};
epicsInt32 ar5_5_3[10] = {17,10};
epicsInt32 ar5_9_3[10] = {11,14};
epicsInt32 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);
}
(void) dbNameToAddr(offname, &offaddr);
(void) dbNameToAddr(valname, &valaddr);
(void) dbPutField(&valaddr, DBR_LONG, ar, 10);
/* 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->%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(10, 0, 1, "no offset");
TEST1(10, 4, 1, "wrapped");
dbChannelDelete(pch);
testHead("Ten %s elements from rec, increment 1, out-of-bound start parameter", typname);
createAndOpen(valname, "{\"arr\":{\"s\":-500}}", "out-of-bound start", &pch, 1);
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(10, 4, 1, "wrapped");
dbChannelDelete(pch);
testHead("Ten %s elements from rec, increment 1, out-of-bound end parameter", typname);
createAndOpen(valname, "{\"arr\":{\"e\":500}}", "out-of-bound end", &pch, 1);
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(10, 4, 1, "wrapped");
dbChannelDelete(pch);
testHead("Ten %s elements from rec, increment 1, zero increment parameter", typname);
createAndOpen(valname, "{\"arr\":{\"i\":0}}", "zero increment", &pch, 1);
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(10, 4, 1, "wrapped");
dbChannelDelete(pch);
testHead("Ten %s elements from rec, increment 1, invalid increment parameter", typname);
createAndOpen(valname, "{\"arr\":{\"i\":-30}}", "invalid increment", &pch, 1);
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(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->%d)", valaddr.field_type, pch->final_type); \
testOk(pch->final_no_elements == 4 / Incr + 1, \
"final no_elements correct (%ld->%ld)", valaddr.no_elements, pch->final_no_elements); \
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, 6, "regular");
TEST5(1, -8, 6, "left side from-end");
TEST5(1, 2, -4, "right side from-end");
TEST5(1, -8, -4, "both sides from-end");
/* 5 elements with increment 2 */
TEST5(2, 2, 6, "regular");
TEST5(2, -8, 6, "left side from-end");
TEST5(2, 2, -4, "right side from-end");
TEST5(2, -8, -4, "both sides from-end");
/* 5 elements with increment 3 */
TEST5(3, 2, 6, "regular");
TEST5(3, -8, 6, "left side from-end");
TEST5(3, 2, -4, "right side from-end");
TEST5(3, -8, -4, "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->%d)", valaddr.field_type, pch->final_type); \
testOk(pch->final_no_elements == 4 / Incr + 1, \
"final no_elements correct (%ld->%ld)", valaddr.no_elements, pch->final_no_elements); \
TEST1(5, 0, Incr, "no offset"); \
dbChannelDelete(pch);
/* Contiguous block of 5 */
TEST5B(1, 2, 6, "regular");
TEST5B(1, -8, 6, "left side from-end");
TEST5B(1, 2, -4, "right side from-end");
TEST5B(1, -8, -4, "both sides from-end");
/* 5 elements with increment 2 */
TEST5B(2, 2, 6, "regular");
TEST5B(2, -8, 6, "left side from-end");
TEST5B(2, 2, -4, "right side from-end");
TEST5B(2, -8, -4, "both sides from-end");
/* 5 elements with increment 3 */
TEST5B(3, 2, 6, "regular");
TEST5B(3, -8, 6, "left side from-end");
TEST5B(3, 2, -4, "right side from-end");
TEST5B(3, -8, -4, "both sides from-end");
}
MAIN(arrTest)
{
dbEventCtx evtctx;
const chFilterPlugin *plug;
char arr[] = "arr";
testPlan(1402);
/* Prepare the IOC */
epicsEnvSet("EPICS_CA_SERVER_PORT", server_port);
testdbPrepare();
testdbReadDatabase("filterTest.dbd", NULL, NULL);
filterTest_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("arrTest.db", NULL, NULL);
eltc(0);
testIocInitOk();
eltc(1);
/* Start the IOC */
evtctx = db_init_events();
testOk(!!(plug = dbFindFilter(arr, strlen(arr))), "plugin arr registered correctly");
check(DBR_LONG);
check(DBR_DOUBLE);
check(DBR_STRING);
db_close_events(evtctx);
testIocShutdownOk();
testdbCleanup();
return testDone();
}
@@ -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")
}
@@ -0,0 +1,285 @@
/*************************************************************************\
* 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 <string.h>
#include "dbStaticLib.h"
#include "dbAccessDefs.h"
#include "db_field_log.h"
#include "dbCommon.h"
#include "registry.h"
#include "errlog.h"
#include "chfPlugin.h"
#include "epicsUnitTest.h"
#include "dbUnitTest.h"
#include "epicsTime.h"
#include "dbmf.h"
#include "testMain.h"
#include "osiFileName.h"
#define PATTERN 0x55
void filterTest_registerRecordDeviceDriver(struct dbBase *);
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 void 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) {
db_field_log *pfl;
changeValue(pfl2, val);
testDiag("mode=%s delta=%g filter must pass once", m, d);
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) {
db_field_log *pfl;
changeValue(pfl2, val);
testDiag("mode=%s delta=%g filter must drop", m, d);
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) {
db_field_log *pfl;
changeValue(pfl2, val);
testDiag("mode=%s delta=%g filter must pass twice", m, d);
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("%s", 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 *pfl2;
db_field_log fl1;
dbEventCtx evtctx;
testPlan(59);
testdbPrepare();
testdbReadDatabase("filterTest.dbd", NULL, NULL);
filterTest_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("xRecord.db", NULL, NULL);
eltc(0);
testIocInitOk();
eltc(1);
evtctx = db_init_events();
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);
pfl2 = db_create_read_log(pch);
testDiag("new field_log from record");
fl_setup(pch, pfl2);
mustPassOnce(pch, pfl2, "abs", 0., 1);
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);
pfl2 = db_create_read_log(pch);
testDiag("new field_log from record");
fl_setup(pch, pfl2);
mustDrop(pch, pfl2, "abs", 3., 3);
pfl2 = db_create_read_log(pch);
testDiag("new field_log from record");
fl_setup(pch, pfl2);
mustDrop(pch, pfl2, "abs", 3., 4);
pfl2 = db_create_read_log(pch);
testDiag("new field_log from record");
fl_setup(pch, pfl2);
mustPassOnce(pch, pfl2, "abs", 3., 5);
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);
pfl2 = db_create_read_log(pch);
testDiag("new field_log from record");
fl_setup(pch, pfl2);
mustPassOnce(pch, pfl2, "rel", 50., 2);
pfl2 = db_create_read_log(pch);
testDiag("new field_log from record");
fl_setup(pch, pfl2);
mustDrop(pch, pfl2, "rel", 50., 3);
pfl2 = db_create_read_log(pch);
testDiag("new field_log from record");
fl_setup(pch, pfl2);
mustPassOnce(pch, pfl2, "rel", 50., 4);
pfl2 = db_create_read_log(pch);
testDiag("new field_log from record");
fl_setup(pch, pfl2);
mustDrop(pch, pfl2, "rel", 50., 5);
pfl2 = db_create_read_log(pch);
testDiag("new field_log from record");
fl_setup(pch, pfl2);
mustDrop(pch, pfl2, "rel", 50., 6);
pfl2 = db_create_read_log(pch);
testDiag("new field_log from record");
fl_setup(pch, pfl2);
mustPassOnce(pch, pfl2, "rel", 50., 7);
dbChannelDelete(pch);
db_close_events(evtctx);
testIocShutdownOk();
testdbCleanup();
return testDone();
}
@@ -0,0 +1,33 @@
/*************************************************************************\
* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne
* National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/*
* Run filter tests as a batch.
*/
#include "epicsUnitTest.h"
#include "epicsExit.h"
#include "dbmf.h"
int tsTest(void);
int dbndTest(void);
int syncTest(void);
int arrTest(void);
void epicsRunFilterTests(void)
{
testHarness();
runTest(tsTest);
runTest(dbndTest);
runTest(syncTest);
runTest(arrTest);
dbmfFreeChunks();
epicsExit(0); /* Trigger test harness */
}
@@ -0,0 +1,14 @@
/*************************************************************************\
* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne
* National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
extern void epicsRunFilterTests(void);
int main(int argc, char **argv)
{
epicsRunFilterTests(); /* calls epicsExit(0) */
return 0;
}
@@ -0,0 +1,378 @@
/*************************************************************************\
* 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 <string.h>
#include "dbStaticLib.h"
#include "dbAccessDefs.h"
#include "db_field_log.h"
#include "dbCommon.h"
#include "dbChannel.h"
#include "registry.h"
#include "chfPlugin.h"
#include "errlog.h"
#include "dbmf.h"
#include "epicsUnitTest.h"
#include "dbUnitTest.h"
#include "epicsTime.h"
#include "dbState.h"
#include "testMain.h"
#include "osiFileName.h"
#define PATTERN 0x55
void filterTest_registerRecordDeviceDriver(struct dbBase *);
static db_field_log fl;
static dbStateId red;
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, long val) {
struct dbCommon *prec = dbChannelRecord(chan);
pfl->ctx = dbfl_context_event;
pfl->type = dbfl_type_val;
pfl->stat = prec->stat;
pfl->sevr = prec->sevr;
pfl->time = prec->time;
pfl->field_type = DBF_LONG;
pfl->no_elements = 1;
/*
* 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));
pfl->u.v.field.dbf_long = val;
}
static void testHead (char* title) {
testDiag("--------------------------------------------------------");
testDiag("%s", title);
testDiag("--------------------------------------------------------");
}
static void mustDrop(dbChannel *pch, db_field_log *pfl2, char* m) {
db_field_log *pfl = dbChannelRunPreChain(pch, pfl2);
testOk(NULL == pfl, "filter drops field_log (%s)", m);
}
static void mustPassTwice(dbChannel *pch, db_field_log *pfl2, char* m) {
db_field_log *pfl;
testDiag("%s: filter must pass twice", m);
pfl = dbChannelRunPreChain(pch, pfl2);
testOk(pfl2 == pfl, "call 1 does not drop or replace field_log");
pfl = dbChannelRunPreChain(pch, pfl2);
testOk(pfl2 == pfl, "call 2 does not drop or replace field_log");
}
static void mustPassOld(dbChannel *pch, db_field_log *old, db_field_log *cur, char* m) {
db_field_log *pfl = dbChannelRunPreChain(pch, cur);
testOk(old == pfl, "filter passes previous field log (%s)", m);
}
static void mustPass(dbChannel *pch, db_field_log *cur, char* m) {
db_field_log *pfl = dbChannelRunPreChain(pch, cur);
testOk(cur == pfl, "filter passes field_log (%s)", m);
}
static void checkCtxRead(dbChannel *pch, dbStateId id) {
fl.ctx = dbfl_context_read;
dbStateClear(id);
mustPassTwice(pch, &fl, "ctx='read', state=FALSE");
dbStateSet(id);
mustPassTwice(pch, &fl, "ctx='read', state=TRUE");
dbStateClear(id);
mustPassTwice(pch, &fl, "ctx='read', state=FALSE");
fl.ctx = dbfl_context_event;
}
static void checkAndOpenChannel(dbChannel *pch, const chFilterPlugin *plug) {
ELLNODE *node;
chFilter *filter;
chPostEventFunc *cb_out = NULL;
void *arg_out = NULL;
db_field_log fl1;
testDiag("Test filter structure and open channel");
testOk((ellCount(&pch->filters) == 1), "channel has one plugin");
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 sync opened");
node = ellFirst(&pch->pre_chain);
filter = CONTAINER(node, chFilter, pre_node);
testOk((ellCount(&pch->pre_chain) == 1 && filter->pre_arg != NULL),
"sync has one filter with argument in pre chain");
testOk((ellCount(&pch->post_chain) == 0), "sync has no filter in post chain");
checkCtxRead(pch, red);
}
MAIN(syncTest)
{
dbChannel *pch;
const chFilterPlugin *plug;
char myname[] = "sync";
db_field_log *pfl[10];
int i;
dbEventCtx evtctx;
testPlan(139);
testdbPrepare();
testdbReadDatabase("filterTest.dbd", NULL, NULL);
filterTest_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("xRecord.db", NULL, NULL);
eltc(0);
testIocInitOk();
eltc(1);
evtctx = db_init_events();
testOk(!!(plug = dbFindFilter(myname, strlen(myname))), "plugin %s registered correctly", myname);
testOk(!!(red = dbStateCreate("red")), "state 'red' created successfully");
/* nonexisting state */
testOk(!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"while\",\"s\":\"blue\"}}")),
"dbChannel with sync (m='while' s='blue') (nonex state) failed");
/* missing state */
testOk(!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"while\"}}")),
"dbChannel with sync (m='while') (no state) failed");
/* missing mode */
testOk(!(pch = dbChannelCreate("x.VAL{\"sync\":{\"s\":\"red\"}}")),
"dbChannel with sync (s='red') (no mode) failed");
/* mode WHILE */
testHead("Mode WHILE (m='while', s='red')");
testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"while\",\"s\":\"red\"}}")),
"dbChannel with plugin sync (m='while' s='red') created");
checkAndOpenChannel(pch, plug);
for (i = 0; i < 10; i++) {
pfl[i] = db_create_read_log(pch);
fl_setup(pch, pfl[i], 120 + i);
}
testDiag("Test event stream");
dbStateClear(red);
mustDrop(pch, pfl[0], "state=FALSE, log0");
mustDrop(pch, pfl[1], "state=FALSE, log1");
mustDrop(pch, pfl[2], "state=FALSE, log2");
dbStateSet(red);
mustPass(pch, pfl[3], "state=TRUE, log3");
mustPass(pch, pfl[4], "state=TRUE, log4");
mustPass(pch, pfl[5], "state=TRUE, log5");
dbStateClear(red);
mustDrop(pch, pfl[6], "state=FALSE, log6");
mustDrop(pch, pfl[7], "state=FALSE, log7");
mustDrop(pch, pfl[8], "state=FALSE, log8");
for (i = 0; i < 10; i++)
db_delete_field_log(pfl[i]);
dbChannelDelete(pch);
/* mode UNLESS */
testHead("Mode UNLESS (m='unless', s='red')");
testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"unless\",\"s\":\"red\"}}")),
"dbChannel with plugin sync (m='unless' s='red') created");
checkAndOpenChannel(pch, plug);
for (i = 0; i < 10; i++) {
pfl[i] = db_create_read_log(pch);
fl_setup(pch, pfl[i], 120 + i);
}
testDiag("Test event stream");
dbStateClear(red);
mustPass(pch, pfl[0], "state=FALSE, log0");
mustPass(pch, pfl[1], "state=FALSE, log1");
mustPass(pch, pfl[2], "state=FALSE, log2");
dbStateSet(red);
mustDrop(pch, pfl[3], "state=TRUE, log3");
mustDrop(pch, pfl[4], "state=TRUE, log4");
mustDrop(pch, pfl[5], "state=TRUE, log5");
dbStateClear(red);
mustPass(pch, pfl[6], "state=FALSE, log6");
mustPass(pch, pfl[7], "state=FALSE, log7");
mustPass(pch, pfl[8], "state=FALSE, log8");
for (i = 0; i < 10; i++)
db_delete_field_log(pfl[i]);
dbChannelDelete(pch);
/* mode BEFORE */
testHead("Mode BEFORE (m='before', s='red')");
testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"before\",\"s\":\"red\"}}")),
"dbChannel with plugin sync (m='before' s='red') created");
checkAndOpenChannel(pch, plug);
for (i = 0; i < 10; i++) {
pfl[i] = db_create_read_log(pch);
fl_setup(pch, pfl[i], 120 + i);
}
testDiag("Test event stream");
dbStateClear(red);
mustDrop(pch, pfl[0], "state=FALSE, log0");
mustDrop(pch, pfl[1], "state=FALSE, log1");
mustDrop(pch, pfl[2], "state=FALSE, log2");
dbStateSet(red);
mustPassOld(pch, pfl[2], pfl[3], "state=TRUE, log3, pass=log2");
mustDrop(pch, pfl[4], "state=TRUE, log4");
mustDrop(pch, pfl[5], "state=TRUE, log5");
mustDrop(pch, pfl[6], "state=TRUE, log6");
dbStateClear(red);
mustDrop(pch, pfl[7], "state=FALSE, log7");
mustDrop(pch, pfl[8], "state=FALSE, log8");
mustDrop(pch, pfl[9], "state=FALSE, log9");
db_delete_field_log(pfl[2]);
dbChannelDelete(pch);
/* mode FIRST */
testHead("Mode FIRST (m='first', s='red')");
testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"first\",\"s\":\"red\"}}")),
"dbChannel with plugin sync (m='first' s='red') created");
checkAndOpenChannel(pch, plug);
for (i = 0; i < 10; i++) {
pfl[i] = db_create_read_log(pch);
fl_setup(pch, pfl[i], 120 + i);
}
testDiag("Test event stream");
dbStateClear(red);
mustDrop(pch, pfl[0], "state=FALSE, log0");
mustDrop(pch, pfl[1], "state=FALSE, log1");
mustDrop(pch, pfl[2], "state=FALSE, log2");
dbStateSet(red);
mustPass(pch, pfl[3], "state=TRUE, log3");
mustDrop(pch, pfl[4], "state=TRUE, log4");
mustDrop(pch, pfl[5], "state=TRUE, log5");
dbStateClear(red);
mustDrop(pch, pfl[6], "state=FALSE, log6");
mustDrop(pch, pfl[7], "state=FALSE, log7");
mustDrop(pch, pfl[8], "state=FALSE, log8");
db_delete_field_log(pfl[3]);
db_delete_field_log(pfl[9]);
dbChannelDelete(pch);
/* mode LAST */
testHead("Mode LAST (m='last', s='red')");
testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"last\",\"s\":\"red\"}}")),
"dbChannel with plugin sync (m='last' s='red') created");
checkAndOpenChannel(pch, plug);
for (i = 0; i < 10; i++) {
pfl[i] = db_create_read_log(pch);
fl_setup(pch, pfl[i], 120 + i);
}
testDiag("Test event stream");
dbStateClear(red);
mustDrop(pch, pfl[0], "state=FALSE, log0");
mustDrop(pch, pfl[1], "state=FALSE, log1");
mustDrop(pch, pfl[2], "state=FALSE, log2");
dbStateSet(red);
mustDrop(pch, pfl[3], "state=TRUE, log3");
mustDrop(pch, pfl[4], "state=TRUE, log4");
mustDrop(pch, pfl[5], "state=TRUE, log5");
dbStateClear(red);
mustPassOld(pch, pfl[5], pfl[6], "state=TRUE, log6, pass=log5");
mustDrop(pch, pfl[7], "state=FALSE, log7");
mustDrop(pch, pfl[8], "state=FALSE, log8");
mustDrop(pch, pfl[9], "state=FALSE, log9");
db_delete_field_log(pfl[5]);
dbChannelDelete(pch);
/* mode AFTER */
testHead("Mode AFTER (m='after', s='red')");
testOk(!!(pch = dbChannelCreate("x.VAL{\"sync\":{\"m\":\"after\",\"s\":\"red\"}}")),
"dbChannel with plugin sync (m='after' s='red') created");
checkAndOpenChannel(pch, plug);
for (i = 0; i < 10; i++) {
pfl[i] = db_create_read_log(pch);
fl_setup(pch, pfl[i], 120 + i);
}
testDiag("Test event stream");
dbStateClear(red);
mustDrop(pch, pfl[0], "state=FALSE, log0");
mustDrop(pch, pfl[1], "state=FALSE, log1");
mustDrop(pch, pfl[2], "state=FALSE, log2");
dbStateSet(red);
mustDrop(pch, pfl[3], "state=TRUE, log3");
mustDrop(pch, pfl[4], "state=TRUE, log4");
mustDrop(pch, pfl[5], "state=TRUE, log5");
dbStateClear(red);
mustPass(pch, pfl[6], "state=FALSE, log6");
mustDrop(pch, pfl[7], "state=FALSE, log7");
mustDrop(pch, pfl[8], "state=FALSE, log8");
db_delete_field_log(pfl[6]);
db_delete_field_log(pfl[9]);
dbChannelDelete(pch);
db_close_events(evtctx);
testIocShutdownOk();
testdbCleanup();
return testDone();
}
+119
View File
@@ -0,0 +1,119 @@
/*************************************************************************\
* 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 <string.h>
#include "dbStaticLib.h"
#include "dbAccessDefs.h"
#include "chfPlugin.h"
#include "errlog.h"
#include "epicsUnitTest.h"
#include "dbUnitTest.h"
#include "registry.h"
#include "dbmf.h"
#include "epicsTime.h"
#include "testMain.h"
#include "osiFileName.h"
#define PATTERN 0x55
void filterTest_registerRecordDeviceDriver(struct dbBase *);
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;
epicsTimeStamp stamp, now;
dbEventCtx evtctx;
testPlan(12);
testdbPrepare();
testdbReadDatabase("filterTest.dbd", NULL, NULL);
filterTest_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("xRecord.db", NULL, NULL);
eltc(0);
testIocInitOk();
eltc(1);
evtctx = db_init_events();
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");
stamp = pfl2->time;
db_delete_field_log(pfl2);
pfl2 = dbChannelRunPreChain(pch, &fl1);
epicsTimeGetCurrent(&now);
testOk(epicsTimeDiffInSeconds(&pfl2->time, &stamp) >= 0 &&
epicsTimeDiffInSeconds(&now, &pfl2->time) >= 0,
"ts filter sets time stamp to \"now\"");
dbChannelDelete(pch);
db_close_events(evtctx);
testIocShutdownOk();
testdbCleanup();
return testDone();
}
@@ -0,0 +1,25 @@
/*************************************************************************\
* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne
* National Laboratory.
* 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: Andrew Johnson <anj@aps.anl.gov>
* Ralph Lange <Ralph.Lange@bessy.de>
*/
#include "dbAccessDefs.h"
#include <recSup.h>
#define GEN_SIZE_OFFSET
#include "xRecord.h"
#include <epicsExport.h>
static rset xRSET;
epicsExportAddress(rset,xRSET);
@@ -0,0 +1,2 @@
record(x, x) {}
@@ -0,0 +1,8 @@
# This is a combined minimal DBD and DB file
recordtype(x) {
include "dbCommon.dbd"
field(VAL, DBF_LONG) {
prompt("Value")
}
}
+135
View File
@@ -0,0 +1,135 @@
#*************************************************************************
# Copyright (c) 2012 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
TESTLIBRARY = dbRecStdTest
dbRecStdTest_SRCS += asTestLib.c
dbRecStdTest_LIBS += dbRecStd dbCore ca Com
PROD_LIBS = dbRecStdTest dbRecStd dbCore ca Com
TARGETS += $(COMMON_DIR)/recTestIoc.dbd
DBDDEPENDS_FILES += recTestIoc.dbd$(DEP)
recTestIoc_DBD = base.dbd
TESTFILES += $(COMMON_DIR)/recTestIoc.dbd
testHarness_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
testHarness_SRCS += asTestIoc_registerRecordDeviceDriver.cpp
TESTPROD_HOST += arrayOpTest
arrayOpTest_SRCS += arrayOpTest.c
arrayOpTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
testHarness_SRCS += arrayOpTest.c
TESTFILES += ../arrayOpTest.db
TESTS += arrayOpTest
TESTPROD_HOST += recMiscTest
recMiscTest_SRCS += recMiscTest.c
recMiscTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
testHarness_SRCS += recMiscTest.c
TESTFILES += ../recMiscTest.db
TESTS += recMiscTest
TESTPROD_HOST += linkRetargetLinkTest
linkRetargetLinkTest_SRCS += linkRetargetLinkTest.c
linkRetargetLinkTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
testHarness_SRCS += linkRetargetLinkTest.c
TESTFILES += ../linkRetargetLink.db
TESTS += linkRetargetLinkTest
TESTPROD_HOST += linkInitTest
linkInitTest_SRCS += linkInitTest.c
linkInitTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
testHarness_SRCS += linkInitTest.c
TESTFILES += ../linkInitTest.db
TESTS += linkInitTest
TESTPROD_HOST += compressTest
compressTest_SRCS += compressTest.c
compressTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
testHarness_SRCS += compressTest.c
TESTFILES += ../compressTest.db
TESTS += compressTest
TESTPROD_HOST += asyncSoftTest
asyncSoftTest_SRCS += asyncSoftTest.c
asyncSoftTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
testHarness_SRCS += asyncSoftTest.c
TESTFILES += ../asyncSoftTest.db
TESTS += asyncSoftTest
TESTPROD_HOST += softTest
softTest_SRCS += softTest.c
softTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
testHarness_SRCS += softTest.c
TESTFILES += ../softTest.db
TESTS += softTest
TARGETS += $(COMMON_DIR)/asTestIoc.dbd
DBDDEPENDS_FILES += asTestIoc.dbd$(DEP)
asTestIoc_DBD += base.dbd
asTestIoc_DBD += asTest.dbd
TESTPROD_HOST += asTest
asTest_SRCS += asTest.c
asTest_SRCS += asTestIoc_registerRecordDeviceDriver.cpp
testHarness_SRCS += asTest.c
TESTFILES += $(COMMON_DIR)/asTestIoc.dbd ../asTest.db
TESTS += asTest
TARGETS += $(COMMON_DIR)/analogMonitorTest.dbd
DBDDEPENDS_FILES += analogMonitorTest.dbd$(DEP)
analogMonitorTest_DBD += base.dbd
TESTPROD_HOST += analogMonitorTest
analogMonitorTest_SRCS += analogMonitorTest.c
analogMonitorTest_SRCS += analogMonitorTest_registerRecordDeviceDriver.cpp
testHarness_SRCS += analogMonitorTest.c
testHarness_SRCS += analogMonitorTest_registerRecordDeviceDriver.cpp
TESTFILES += $(COMMON_DIR)/analogMonitorTest.dbd ../analogMonitorTest.db
TESTS += analogMonitorTest
TARGETS += $(COMMON_DIR)/regressTest.dbd
DBDDEPENDS_FILES += regressTest.dbd$(DEP)
regressTest_DBD += base.dbd
TESTPROD_HOST += regressTest
regressTest_SRCS += regressTest.c
regressTest_SRCS += regressTest_registerRecordDeviceDriver.cpp
TESTFILES += $(COMMON_DIR)/regressTest.dbd ../regressArray1.db ../regressHex.db ../regressLinkMS.db
TESTS += regressTest
TARGETS += $(COMMON_DIR)/simmTest.dbd
TARGETS += $(COMMON_DIR)/simmTest.db
DBDDEPENDS_FILES += simmTest.dbd$(DEP)
DBDDEPENDS_FILES += simmTest.db$(DEP)
simmTest_DBD += base.dbd
TESTPROD_HOST += simmTest
simmTest_SRCS += simmTest.c
simmTest_SRCS += simmTest_registerRecordDeviceDriver.cpp
testHarness_SRCS += simmTest.c
testHarness_SRCS += simmTest_registerRecordDeviceDriver.cpp
TESTFILES += $(COMMON_DIR)/simmTest.dbd $(COMMON_DIR)/simmTest.db
TESTS += simmTest
# epicsRunRecordTests runs all the test programs in a known working order.
testHarness_SRCS += epicsRunRecordTests.c
recordTestHarness_SRCS += $(testHarness_SRCS)
recordTestHarness_SRCS_RTEMS += rtemsTestHarness.c
PROD_vxWorks = recordTestHarness
PROD_RTEMS = recordTestHarness
TESTSPEC_vxWorks = recordTestHarness.munch; epicsRunRecordTests
TESTSPEC_RTEMS = recordTestHarness.boot; epicsRunRecordTests
TESTSCRIPTS_HOST += $(TESTS:%=%.t)
include $(TOP)/configure/RULES
@@ -0,0 +1,240 @@
/*************************************************************************\
* Copyright (c) 2014 ITER Organization.
* 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@gmx.de>
*/
#include <string.h>
#include "registryFunction.h"
#include "osiFileName.h"
#include "epicsThread.h"
#include "epicsMath.h"
#include "epicsUnitTest.h"
#include "dbAccessDefs.h"
#include "dbStaticLib.h"
#include "dbEvent.h"
#include "caeventmask.h"
#include "db_field_log.h"
#include "chfPlugin.h"
#include "iocInit.h"
#include "testMain.h"
#include "epicsExport.h"
/* Test parameters */
#define NO_OF_RECORD_TYPES 7
#define NO_OF_DEADBANDS 3
#define NO_OF_PATTERNS 16
#define NO_OF_VALUES_PER_SEQUENCE 2
void analogMonitorTest_registerRecordDeviceDriver(struct dbBase *);
/* Indices for record type, deadband type, deadband value, test number, val in sequence */
static int irec, ityp, idbnd, itest, iseq;
/* Records to test with */
static const char t_Record[NO_OF_RECORD_TYPES][10] = {
{"ai"}, {"ao"}, {"calc"}, {"calcout"}, {"dfanout"}, {"sel"}, {"sub"},
};
/* Deadband types to test */
static const char t_DbndType[2][6] = { {".MDEL"}, {".ADEL"} };
/* Different deadbands to test with */
static double t_Deadband[NO_OF_DEADBANDS] = { -1, 0, 1.5 };
/* Value sequences for each of the 16 tests */
static double t_SetValues[NO_OF_PATTERNS][NO_OF_VALUES_PER_SEQUENCE];
/* Expected updates (1=yes) for each sequence of each test of each deadband */
static int t_ExpectedUpdates[NO_OF_DEADBANDS][NO_OF_PATTERNS][NO_OF_VALUES_PER_SEQUENCE] = {
{ /* deadband = -1 */
{1, 1}, {1, 1}, {1, 1}, {1, 1},
{1, 1}, {1, 1}, {1, 1}, {1, 1},
{1, 1}, {1, 1}, {1, 1}, {1, 1},
{1, 1}, {1, 1}, {1, 1}, {1, 1},
},
{ /* deadband = 0 */
{1, 1}, {0, 1}, {0, 0}, {0, 0},
{1, 1}, {1, 0}, {1, 1}, {1, 1},
{1, 1}, {1, 1}, {1, 0}, {1, 1},
{1, 1}, {1, 1}, {1, 1}, {1, 0},
},
{ /* deadband = 1.5 */
{0, 1}, {0, 1}, {0, 0}, {0, 0},
{1, 1}, {1, 0}, {1, 1}, {1, 1},
{1, 1}, {1, 1}, {1, 0}, {1, 1},
{1, 1}, {1, 1}, {1, 1}, {1, 0},
},
};
static int t_ReceivedUpdates[NO_OF_PATTERNS][NO_OF_VALUES_PER_SEQUENCE];
/* Dummy subroutine needed for sub record */
static long myTestSub(void *p) {
return 0;
}
/* Minimal pre-chain plugin to divert all monitors back into the test (before they hit the queue) */
static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
double val = *((double*)chan->addr.pfield);
/* iseq == -1 is the value reset before the test pattern -> do not count */
if (iseq >= 0) {
t_ReceivedUpdates[itest][iseq] = 1;
testOk((val == t_SetValues[itest][iseq]) || (isnan(val) && isnan(t_SetValues[itest][iseq])),
"update %d pattern %2d with %s = %2.1f (expected %f, got %f)",
iseq, itest, (ityp==0?"MDEL":"ADEL"), t_Deadband[idbnd], t_SetValues[itest][iseq], val);
}
db_delete_field_log(pfl);
return NULL;
}
static void channelRegisterPre(dbChannel *chan, void *pvt,
chPostEventFunc **cb_out, void **arg_out, db_field_log *probe)
{
*cb_out = filter;
}
static chfPluginIf pif = {
NULL, /* allocPvt, */
NULL, /* freePvt, */
NULL, /* parse_error, */
NULL, /* parse_ok, */
NULL, /* channel_open, */
channelRegisterPre,
NULL, /* channelRegisterPost, */
NULL, /* channel_report, */
NULL /* channel_close */
};
MAIN(analogMonitorTest)
{
dbChannel *pch;
const chFilterPlugin *plug;
const char test[] = "test";
dbEventCtx evtctx;
dbEventSubscription subscr;
unsigned mask;
struct dbAddr vaddr, daddr;
double val;
char chan[50]; /* Channel name */
char cval[50]; /* Name for test values */
char cdel[50]; /* Name for deadband values */
/* Test patterns:
* 0: step less than deadband (of 1.5)
* 1: step larger than deadband (of 1.5)
* 2: no change
* 3: -0.0 -> +0.0
* ... all possible combinations of steps
* between: finite / NaN / -inf / +inf
*/
t_SetValues[ 0][0] = 1.0; t_SetValues[ 0][1] = 2.0;
t_SetValues[ 1][0] = 0.0; t_SetValues[ 1][1] = 2.0;
t_SetValues[ 2][0] = 0.0; t_SetValues[ 2][1] = 0.0;
t_SetValues[ 3][0] = -0.0; t_SetValues[ 3][1] = 0.0;
t_SetValues[ 4][0] = epicsNAN; t_SetValues[ 4][1] = 1.0;
t_SetValues[ 5][0] = epicsNAN; t_SetValues[ 5][1] = epicsNAN;
t_SetValues[ 6][0] = epicsNAN; t_SetValues[ 6][1] = epicsINF;
t_SetValues[ 7][0] = epicsNAN; t_SetValues[ 7][1] = -epicsINF;
t_SetValues[ 8][0] = epicsINF; t_SetValues[ 8][1] = 1.0;
t_SetValues[ 9][0] = epicsINF; t_SetValues[ 9][1] = epicsNAN;
t_SetValues[10][0] = epicsINF; t_SetValues[10][1] = epicsINF;
t_SetValues[11][0] = epicsINF; t_SetValues[11][1] = -epicsINF;
t_SetValues[12][0] = -epicsINF; t_SetValues[12][1] = 1.0;
t_SetValues[13][0] = -epicsINF; t_SetValues[13][1] = epicsNAN;
t_SetValues[14][0] = -epicsINF; t_SetValues[14][1] = epicsINF;
t_SetValues[15][0] = -epicsINF; t_SetValues[15][1] = -epicsINF;
registryFunctionAdd("myTestSub", (REGISTRYFUNCTION) myTestSub);
testPlan(1793);
if (dbReadDatabase(&pdbbase, "analogMonitorTest.dbd",
"." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR
"../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL))
testAbort("Error reading database description 'analogMonitorTest.dbd'");
analogMonitorTest_registerRecordDeviceDriver(pdbbase);
if (dbReadDatabase(&pdbbase, "analogMonitorTest.db",
"." OSI_PATH_LIST_SEPARATOR "..", NULL))
testAbort("Error reading test database 'analogMonitorTest.db'");
/* Start the core IOC (no CA) */
iocBuildIsolated();
evtctx = db_init_events();
chfPluginRegister(test, &pif, NULL);
plug = dbFindFilter(test, strlen(test));
testOk(!!plug, "interceptor plugin registered");
/* Loop over all analog record types (one instance each) */
for (irec = 0; irec < NO_OF_RECORD_TYPES; irec++) {
strcpy(cval, t_Record[irec]);
strcpy(chan, cval);
strcat(chan, ".VAL{\"test\":{}}");
if ((strcmp(t_Record[irec], "sel") == 0)
|| (strcmp(t_Record[irec], "calc") == 0)
|| (strcmp(t_Record[irec], "calcout") == 0)) {
strcat(cval, ".A");
} else {
strcat(cval, ".VAL");
}
testDiag("--------------------------------------------------------");
testDiag("Testing the %s record", t_Record[irec]);
testDiag("--------------------------------------------------------");
pch = dbChannelCreate(chan);
testOk(!!pch, "dbChannel with test plugin created");
testOk(!dbChannelOpen(pch), "dbChannel opened");
dbNameToAddr(cval, &vaddr);
/* Loop over both tested deadband types */
for (ityp = 0; ityp < 2; ityp++) {
strcpy(cdel, t_Record[irec]);
strcat(cdel, t_DbndType[ityp]);
dbNameToAddr(cdel, &daddr);
mask = (ityp==0?DBE_VALUE:DBE_ARCHIVE);
subscr = db_add_event(evtctx, pch, NULL, NULL, mask);
db_event_enable(subscr);
/* Loop over all tested deadband values */
for (idbnd = 0; idbnd < NO_OF_DEADBANDS; idbnd++) {
testDiag("Test %s%s = %g", t_Record[irec], t_DbndType[ityp], t_Deadband[idbnd]);
dbPutField(&daddr, DBR_DOUBLE, (void*) &t_Deadband[idbnd], 1);
memset(t_ReceivedUpdates, 0, sizeof(t_ReceivedUpdates));
/* Loop over all test patterns */
for (itest = 0; itest < NO_OF_PATTERNS; itest++) {
iseq = -1;
val = 0.0;
dbPutField(&vaddr, DBR_DOUBLE, (void*) &val, 1);
/* Loop over the test sequence */
for (iseq = 0; iseq < NO_OF_VALUES_PER_SEQUENCE; iseq++) {
dbPutField(&vaddr, DBR_DOUBLE, (void*) &t_SetValues[itest][iseq], 1);
}
/* Check expected vs. actual monitors */
testOk( (t_ExpectedUpdates[idbnd][itest][0] == t_ReceivedUpdates[itest][0]) &&
(t_ExpectedUpdates[idbnd][itest][1] == t_ReceivedUpdates[itest][1]),
"pattern %2d with %s = %2.1f (expected %d-%d, got %d-%d)",
itest, (ityp==0?"MDEL":"ADEL"), t_Deadband[idbnd],
t_ExpectedUpdates[idbnd][itest][0], t_ExpectedUpdates[idbnd][itest][1],
t_ReceivedUpdates[itest][0], t_ReceivedUpdates[itest][1]);
}
}
db_cancel_event(subscr);
}
}
iocShutdown();
return testDone();
}
@@ -0,0 +1,13 @@
record(ai, "ai") {}
record(ao, "ao") {}
record(calc, "calc") {
field(CALC, "A")
}
record(calcout, "calcout") {
field(CALC, "A")
}
record(dfanout, "dfanout") {}
record(sel, "sel") {}
record(sub, "sub") {
field(SNAM, "myTestSub")
}
+166
View File
@@ -0,0 +1,166 @@
/*************************************************************************\
* Copyright (c) 2014 Brookhaven Science Assoc. as operator of Brookhaven
* National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include <string.h>
#include "dbAccess.h"
#include "dbTest.h"
#include "dbUnitTest.h"
#include "errlog.h"
#include "waveformRecord.h"
#include "testMain.h"
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
static void testGetPutArray(void)
{
double data[4] = {11, 12, 13, 14};
DBADDR addr, save;
long nreq;
epicsInt32 *pbtr;
waveformRecord *prec;
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
recTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("arrayOpTest.db", NULL, NULL);
eltc(0);
testIocInitOk();
eltc(1);
testDiag("Test dbGet() and dbPut() from/to an array");
prec = (waveformRecord*)testdbRecordPtr("wfrec");
if(!prec || dbNameToAddr("wfrec", &addr))
testAbort("Failed to find record wfrec");
memcpy(&save, &addr, sizeof(save));
testDiag("Fetch initial value of %s", prec->name);
dbScanLock(addr.precord);
testOk(prec->nord==3, "prec->nord==3 (got %d)", prec->nord);
nreq = NELEMENTS(data);
if(dbGet(&addr, DBF_DOUBLE, &data, NULL, &nreq, NULL)) {
testFail("dbGet fails");
testSkip(1, "failed get");
} else {
testOk(nreq==3, "nreq==3 (got %ld)", nreq);
testOk1(data[0]==1.0 && data[1]==2.0 && data[2]==3.0);
}
dbScanUnlock(addr.precord);
testOk1(memcmp(&addr, &save, sizeof(save))==0);
addr=save;
testDiag("Write a new value");
data[0] = 4.0;
data[1] = 5.0;
data[2] = 6.0;
data[3] = 7.0;
dbScanLock(addr.precord);
testOk1(dbPut(&addr, DBF_DOUBLE, &data, NELEMENTS(data))==0);
pbtr = prec->bptr;
testOk(prec->nord==4, "prec->nord==4 (got %u)", prec->nord);
testOk1(pbtr[0]==4 && pbtr[1]==5 && pbtr[2]==6 && pbtr[3]==7);
dbScanUnlock(addr.precord);
testOk1(memcmp(&addr, &save, sizeof(save))==0);
addr=save;
memset(&data, 0, sizeof(data));
testDiag("Reread the value");
dbScanLock(addr.precord);
nreq = NELEMENTS(data);
if(dbGet(&addr, DBF_DOUBLE, &data, NULL, &nreq, NULL))
testFail("dbGet fails");
else {
testOk1(nreq==NELEMENTS(data));
testOk1(data[0]==4.0 && data[1]==5.0 && data[2]==6.0 && data[3]==7.0);
}
dbScanUnlock(addr.precord);
testOk1(memcmp(&addr, &save, sizeof(save))==0);
testDiag("Test dbGet() and dbPut() from/to an array of size 1");
prec = (waveformRecord*)testdbRecordPtr("wfrec1");
if(!prec || dbNameToAddr("wfrec1", &addr))
testAbort("Failed to find record wfrec1");
memcpy(&save, &addr, sizeof(save));
testDiag("Fetch initial value of %s", prec->name);
dbScanLock(addr.precord);
testOk(prec->nord==0, "prec->nord==0 (got %d)", prec->nord);
nreq = NELEMENTS(data);
if(dbGet(&addr, DBF_DOUBLE, &data, NULL, &nreq, NULL))
testFail("dbGet fails");
else {
testOk(nreq==0, "nreq==0 (got %ld)", nreq);
}
dbScanUnlock(addr.precord);
testOk1(memcmp(&addr, &save, sizeof(save))==0);
addr=save;
testDiag("Write a new value");
data[0] = 4.0;
data[1] = 5.0;
data[2] = 6.0;
data[3] = 7.0;
dbScanLock(addr.precord);
testOk1(dbPut(&addr, DBF_DOUBLE, &data, 1)==0);
pbtr = prec->bptr;
testOk(prec->nord==1, "prec->nord==1 (got %u)", prec->nord);
testOk1(pbtr[0]==4);
dbScanUnlock(addr.precord);
testOk1(memcmp(&addr, &save, sizeof(save))==0);
addr=save;
memset(&data, 0, sizeof(data));
testDiag("Reread the value");
dbScanLock(addr.precord);
nreq = NELEMENTS(data);
if(dbGet(&addr, DBF_DOUBLE, &data, NULL, &nreq, NULL))
testFail("dbGet fails");
else {
testOk1(nreq==1);
testOk1(data[0]==4.0);
}
dbScanUnlock(addr.precord);
testOk1(memcmp(&addr, &save, sizeof(save))==0);
testIocShutdownOk();
testdbCleanup();
}
MAIN(arrayOpTest)
{
testPlan(21);
testGetPutArray();
return testDone();
}
@@ -0,0 +1,9 @@
record(waveform, "wfrec") {
field(NELM, "10")
field(FTVL, "LONG")
field(INP, [1, 2, 3])
}
record(waveform, "wfrec1") {
field(NELM, "1")
field(FTVL, "LONG")
}
+48
View File
@@ -0,0 +1,48 @@
/*************************************************************************\
* Copyright (c) 2015 Brookhaven Science Assoc. as operator of Brookhaven
* National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/*
* Author: Michael Davidsaver <mdavidsaver@bnl.gov>
*
* Test the hooks that autosave uses during initialization
*/
#include "string.h"
#include "epicsString.h"
#include "dbUnitTest.h"
#include "epicsThread.h"
#include "iocInit.h"
#include "dbBase.h"
#include "link.h"
#include "recSup.h"
#include "dbAccess.h"
#include "dbConvert.h"
#include "dbStaticLib.h"
#include "registry.h"
#include "dbStaticLib.h"
#include "dbStaticPvt.h"
#include "osiFileName.h"
#include "initHooks.h"
#include "devSup.h"
#include "errlog.h"
#include "aoRecord.h"
#include "waveformRecord.h"
#include "testMain.h"
epicsShareFunc void testRestore(void);
#include "epicsExport.h"
MAIN(asTest)
{
testPlan(42);
testRestore();
return testDone();
}
+12
View File
@@ -0,0 +1,12 @@
record(ao, "rec0") {
field(DESC, "foobar")
field(DTYP, "asTest")
field(VAL, "1")
field(OUT, "rec0.DISV")
}
record(waveform, "rec1") {
field(DTYP, "asTest")
field(FTVL, "DOUBLE")
field(NELM, "5")
}
+2
View File
@@ -0,0 +1,2 @@
device(ao, CONSTANT, devAOasTest, "asTest")
device(waveform, CONSTANT, devWFasTest, "asTest")
+291
View File
@@ -0,0 +1,291 @@
/*************************************************************************\
* Copyright (c) 2015 Brookhaven Science Assoc. as operator of Brookhaven
* National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/*
* Author: Michael Davidsaver <mdavidsaver@bnl.gov>
*
* Test the hooks that autosave uses during initialization
*/
#include "string.h"
#include "epicsString.h"
#include "dbUnitTest.h"
#include "epicsThread.h"
#include "iocInit.h"
#include "dbBase.h"
#include "link.h"
#include "recSup.h"
#include "iocsh.h"
#include "dbAccess.h"
#include "dbConvert.h"
#include "dbStaticLib.h"
#include "registry.h"
#include "dbStaticLib.h"
#include "dbStaticPvt.h"
#include "osiFileName.h"
#include "initHooks.h"
#include "devSup.h"
#include "errlog.h"
#include "aoRecord.h"
#include "waveformRecord.h"
#include "epicsExport.h"
static unsigned iran;
static
int checkGetString(DBENTRY *pent, const char *expect)
{
dbCommon *prec = pent->precnode->precord;
const char *actual = dbGetString(pent);
int ret = strcmp(actual, expect);
testOk(ret==0, "dbGetString(\"%s.%s\") -> '%s' == '%s'", prec->name,
pent->pflddes->name, actual, expect);
return ret;
}
static void hookPass0(initHookState state)
{
DBENTRY entry;
if(state!=initHookAfterInitDevSup)
return;
testDiag("initHookAfterInitDevSup");
dbInitEntry(pdbbase, &entry);
testDiag("restore integer pass0");
/* rec0.VAL is initially 1, set it to 2 */
if(dbFindRecord(&entry, "rec0.VAL")==0) {
aoRecord *prec = entry.precnode->precord;
testOk(prec->val==1, "VAL %d==1 (initial value from .db)", (int)prec->val);
checkGetString(&entry, "1");
testOk1(dbPutString(&entry, "2")==0);
testOk(prec->val==2, "VAL %d==2", (int)prec->val);
checkGetString(&entry, "2");
} else {
testFail("Missing rec0");
testSkip(4, "missing record");
}
testDiag("restore string pass0");
if(dbFindRecord(&entry, "rec0.DESC")==0) {
aoRecord *prec = entry.precnode->precord;
testOk1(strcmp(prec->desc, "foobar")==0);
checkGetString(&entry, "foobar");
testOk1(dbPutString(&entry, "hello")==0);
testOk1(strcmp(prec->desc, "hello")==0);
checkGetString(&entry, "hello");
} else {
testFail("Missing rec0");
testSkip(4, "missing record");
}
if(dbFindRecord(&entry, "rec1.DESC")==0) {
aoRecord *prec = entry.precnode->precord;
testOk1(strcmp(prec->desc, "")==0);
checkGetString(&entry, "");
testOk1(dbPutString(&entry, "world")==0);
testOk1(strcmp(prec->desc, "world")==0);
checkGetString(&entry, "world");
} else {
testFail("Missing rec1");
testSkip(4, "missing record");
}
testDiag("restore link pass0");
/* rec0.OUT is initially "rec0.DISV", set it to "rec0.SEVR" */
if(dbFindRecord(&entry, "rec0.OUT")==0) {
aoRecord *prec = entry.precnode->precord;
if(prec->out.type==CONSTANT)
testOk(strcmp(prec->out.text,"rec0.DISV")==0,
"%s==rec0.DISV (initial value from .db)",
prec->out.text);
else
testFail("Wrong link type: %d", (int)prec->out.type);
checkGetString(&entry, "rec0.DISV");
testOk1(dbPutString(&entry, "rec0.SEVR")==0);
} else{
testFail("Missing rec0");
testSkip(1, "missing record");
}
/* rec0.SDIS is initially NULL, set it to "rec0.STAT" */
if(dbFindRecord(&entry, "rec0.SDIS")==0) {
aoRecord *prec = entry.precnode->precord;
if(prec->sdis.type==CONSTANT)
testOk1(prec->sdis.value.constantStr==NULL);
else
testFail("Wrong link type: %d", (int)prec->sdis.type);
testOk1(dbPutString(&entry, "rec0.STAT")==0);
} else{
testFail("Missing rec0");
testSkip(1, "missing record");
}
/* can't restore array field in pass0 */
dbFinishEntry(&entry);
}
static long initRec0(aoRecord *prec)
{
DBLINK *plink = &prec->out;
testDiag("init_record(%s)", prec->name);
testOk(prec->val==2, "VAL %d==2 (pass0 value)", (int)prec->val);
prec->val = 3;
testOk(prec->val==3, "VAL %d==3", (int)prec->val);
testOk1(plink->type==DB_LINK);
if(plink->type==DB_LINK)
testOk(strcmp(plink->value.pv_link.pvname,"rec0.SEVR")==0,
"%s==rec0.SEVR (pass0 value)", plink->value.pv_link.pvname);
else
testFail("Wrong link type");
plink = &prec->sdis;
testOk1(plink->type==DB_LINK);
if(plink->type==DB_LINK)
testOk(strcmp(plink->value.pv_link.pvname,"rec0.STAT")==0,
"%s==rec0.STAT (pass0 value)", plink->value.pv_link.pvname);
else
testFail("Wrong link type");
iran |= 1;
return 2; /* we set .VAL, so don't use RVAL */
}
static long initRec1(waveformRecord *prec)
{
testDiag("init_record(%s)", prec->name);
testOk(prec->nord==0, "NORD %d==0", (int)prec->nord);
iran |= 2;
return 0;
}
static double values[] = {1,2,3,4,5};
static void hookPass1(initHookState state)
{
DBENTRY entry;
DBADDR addr;
if(state!=initHookAfterInitDatabase)
return;
testDiag("initHookAfterInitDatabase");
dbInitEntry(pdbbase, &entry);
if(dbFindRecord(&entry, "rec0.VAL")==0) {
aoRecord *prec = entry.precnode->precord;
testOk(prec->val==3, "VAL %d==3 (init_record value)", (int)prec->val);
testOk1(dbPutString(&entry, "4")==0);
testOk(prec->val==4, "VAL %d==4", (int)prec->val);
} else{
testFail("Missing rec0");
testSkip(1, "missing record");
}
/* Can't restore links in pass 1 */
if(dbNameToAddr("rec1.VAL", &addr)) {
testFail("missing rec1");
testSkip(3, "missing record");
} else {
rset *prset = dbGetRset(&addr);
dbfType ftype = addr.field_type;
long count=-1, offset=-1, maxcount = addr.no_elements;
testOk1(prset && prset->get_array_info && prset->put_array_info);
testOk1((*prset->get_array_info)(&addr, &count, &offset)==0);
/* count is ignored */
testOk1((*dbPutConvertRoutine[DBF_DOUBLE][ftype])(&addr, values, NELEMENTS(values), maxcount,offset)==0);
testOk1((*prset->put_array_info)(&addr, NELEMENTS(values))==0);
}
dbFinishEntry(&entry);
}
#if defined(__rtems__) || defined(vxWorks)
void asTestIoc_registerRecordDeviceDriver(struct dbBase *);
#endif
epicsShareFunc
void testRestore(void)
{
aoRecord *rec0;
waveformRecord *rec1;
testDiag("test Restore");
initHookRegister(hookPass0);
initHookRegister(hookPass1);
testdbPrepare();
testdbReadDatabase("asTestIoc.dbd", NULL, NULL);
/* since this test has device support it must appear in a
* DLL for windows dynamic builds.
* However, the rRDD function is in the executable,
* and not accessible here. So use iocsh.
* For rtems/vxworks the test harness clears
* iocsh registrations, so iocsh can't work here.
*/
#if defined(__rtems__) || defined(vxWorks)
asTestIoc_registerRecordDeviceDriver(pdbbase);
#else
iocshCmd("asTestIoc_registerRecordDeviceDriver(pdbbase)");
#endif
testdbReadDatabase("asTest.db", NULL, NULL);
rec0 = (aoRecord*)testdbRecordPtr("rec0");
rec1 = (waveformRecord*)testdbRecordPtr("rec1");
eltc(0);
testIocInitOk();
eltc(1);
testDiag("Post initialization");
testOk1(iran==3);
testOk1(rec0->val==4);
testOk1(rec1->nord==5);
{
double *buf = rec1->bptr;
testOk(buf[0]==1, "buf[0] %f==1", buf[0]);
testOk1(buf[1]==2);
testOk1(buf[2]==3);
testOk1(buf[3]==4);
testOk1(buf[4]==5);
}
testIocShutdownOk();
/* recSup doesn't cleanup after itself */
free(rec1->bptr);
testdbCleanup();
}
struct dset6 {
dset common;
DEVSUPFUN proc;
DEVSUPFUN linconv;
};
static long noop() {return 0;}
static struct dset6 devAOasTest = { {6, NULL, NULL, (DEVSUPFUN)initRec0, NULL}, (DEVSUPFUN)noop, NULL};
static struct dset6 devWFasTest = { {6, NULL, NULL, (DEVSUPFUN)initRec1, NULL}, (DEVSUPFUN)noop, NULL};
epicsExportAddress(dset, devAOasTest);
epicsExportAddress(dset, devWFasTest);
@@ -0,0 +1,189 @@
/*************************************************************************\
* Copyright (c) 2017 UChicago Argonne LLC, as operator of Argonne
* National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include <string.h>
#include "dbAccess.h"
#include "dbStaticLib.h"
#include "dbTest.h"
#include "dbUnitTest.h"
#include "epicsEvent.h"
#include "errlog.h"
#include "registryFunction.h"
#include "subRecord.h"
#include "testMain.h"
static int startCounter, doneCounter;
static epicsEventId asyncEvent, doneEvent;
static
long asyncSubr(subRecord *prec)
{
testDiag("Processing %s, pact=%d", prec->name, prec->pact);
if (!prec->pact) {
epicsEventTrigger(asyncEvent);
prec->pact = 1; /* Make asynchronous */
}
return 0;
}
static
long doneSubr(subRecord *prec)
{
epicsEventTrigger(doneEvent);
return 0;
}
static
void checkAsyncInput(const char *rec, int init, dbCommon *async)
{
char inp[16], proc[16];
testDiag("Checking record '%s'", rec);
strcpy(inp, rec);
strcat(inp, ".INP");
strcpy(proc, rec);
strcat(proc, ".PROC");
if (init) {
testdbGetFieldEqual(rec, DBF_LONG, init);
testdbPutFieldOk(inp, DBF_STRING, "async");
}
testdbPutFieldOk(proc, DBF_CHAR, 1);
epicsEventWait(asyncEvent);
testdbGetFieldEqual("startCounter", DBF_LONG, ++startCounter);
testdbGetFieldEqual("doneCounter", DBF_LONG, doneCounter);
dbScanLock(async);
async->rset->process(async);
dbScanUnlock(async);
epicsEventWait(doneEvent);
testdbGetFieldEqual("startCounter", DBF_LONG, startCounter);
testdbGetFieldEqual("doneCounter", DBF_LONG, ++doneCounter);
}
static
void testAsynInputs(dbCommon *async)
{
const char * records[] = {
"ai0", "bi0", "di0", "ii0", "li0", "mi0", "si0", NULL,
"bi1", /* bi1 must be first in this group */
"ai1", "di1", "ii1", "li1", "mi1", "si1", NULL,
};
const char ** rec = &records[0];
int init = 1; /* bi1 initializes to 1 */
testDiag("============ Starting %s ============", EPICS_FUNCTION);
startCounter = doneCounter = 0;
testdbPutFieldOk("startCounter", DBF_LONG, startCounter);
testdbPutFieldOk("doneCounter", DBF_LONG, doneCounter);
epicsEventTryWait(asyncEvent);
epicsEventTryWait(doneEvent);
while (*rec) { /* 1st group don't need initializing */
checkAsyncInput(*rec++, 0, async);
}
rec++;
while (*rec) {
checkAsyncInput(*rec++, init, async);
init = 9; /* remainder initialize to 9 */
}
testDiag("============= Ending %s =============", EPICS_FUNCTION);
}
static
void checkAsyncOutput(const char *rec, dbCommon *async)
{
char proc[16];
testDiag("Checking record '%s'", rec);
strcpy(proc, rec);
strcat(proc, ".PROC");
testdbPutFieldOk(proc, DBF_CHAR, 1);
epicsEventWait(asyncEvent);
testdbGetFieldEqual("startCounter", DBF_LONG, ++startCounter);
testdbGetFieldEqual("doneCounter", DBF_LONG, doneCounter);
dbScanLock(async);
async->rset->process(async);
dbScanUnlock(async);
epicsEventWait(doneEvent);
testdbGetFieldEqual("startCounter", DBF_LONG, startCounter);
testdbGetFieldEqual("doneCounter", DBF_LONG, ++doneCounter);
}
static
void testAsyncOutputs(dbCommon *async)
{
const char * records[] = {
"ao1", "bo1", "do1", "io1", "lo1", "lso1", "mo1", "so1", NULL,
};
const char ** rec = &records[0];
testDiag("============ Starting %s ============", EPICS_FUNCTION);
startCounter = doneCounter = 0;
testdbPutFieldOk("startCounter", DBF_LONG, startCounter);
testdbPutFieldOk("doneCounter", DBF_LONG, doneCounter);
epicsEventTryWait(asyncEvent);
epicsEventTryWait(doneEvent);
while (*rec) {
checkAsyncOutput(*rec++, async);
}
testDiag("============= Ending %s =============", EPICS_FUNCTION);
}
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
MAIN(asyncSoftTest)
{
dbCommon *async;
testPlan(128);
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
recTestIoc_registerRecordDeviceDriver(pdbbase);
registryFunctionAdd("asyncSubr", (REGISTRYFUNCTION) asyncSubr);
registryFunctionAdd("doneSubr", (REGISTRYFUNCTION) doneSubr);
testdbReadDatabase("asyncSoftTest.db", NULL, NULL);
eltc(0);
testIocInitOk();
eltc(1);
async = testdbRecordPtr("async");
asyncEvent = epicsEventCreate(epicsEventEmpty);
doneEvent = epicsEventCreate(epicsEventEmpty);
testAsynInputs(async);
testAsyncOutputs(async);
testIocShutdownOk();
testdbCleanup();
return testDone();
}
@@ -0,0 +1,188 @@
record(ai, "ai0") {
field(DTYP, "Async Soft Channel")
field(INP, "async")
field(FLNK, "done")
}
record(bi, "bi0") {
field(DTYP, "Async Soft Channel")
field(INP, "async")
field(FLNK, "done")
field(ZNAM, "Zero")
field(ONAM, "One")
}
record(int64in, "ii0") {
field(DTYP, "Async Soft Channel")
field(INP, "async")
field(FLNK, "done")
}
record(longin, "li0") {
field(DTYP, "Async Soft Channel")
field(INP, "async")
field(FLNK, "done")
}
record(mbbiDirect, "di0") {
field(DTYP, "Async Soft Channel")
field(NOBT, 4)
field(INP, "async")
field(FLNK, "done")
}
record(mbbi, "mi0") {
field(DTYP, "Async Soft Channel")
field(NOBT, 4)
field(INP, "async")
field(FLNK, "done")
field(ZRST, "Zero")
field(ONST, "One")
field(TWST, "Two")
field(THST, "Three")
field(FRST, "Four")
field(FVST, "Five")
field(SXST, "Six")
field(SVST, "Seven")
field(EIST, "Eight")
field(NIST, "Nine")
field(TEST, "Ten")
field(ELST, "Eleven")
field(TWST, "Twelve")
field(TTST, "Thirteen")
field(FTST, "Fourteen")
field(FFST, "Fifteen")
}
record(stringin, "si0") {
field(DTYP, "Async Soft Channel")
field(INP, "async")
field(FLNK, "done")
}
record(ai, "ai1") {
field(DTYP, "Async Soft Channel")
field(INP, {const:9})
field(FLNK, "done")
}
record(bi, "bi1") {
field(DTYP, "Async Soft Channel")
field(INP, {const:1})
field(FLNK, "done")
field(ZNAM, "Zero")
field(ONAM, "One")
}
record(int64in, "ii1") {
field(DTYP, "Async Soft Channel")
field(INP, {const:9})
field(FLNK, "done")
}
record(longin, "li1") {
field(DTYP, "Async Soft Channel")
field(INP, {const:9})
field(FLNK, "done")
}
record(mbbiDirect, "di1") {
field(DTYP, "Async Soft Channel")
field(NOBT, 4)
field(INP, {const:9})
field(FLNK, "done")
}
record(mbbi, "mi1") {
field(DTYP, "Async Soft Channel")
field(NOBT, 4)
field(INP, {const:9})
field(FLNK, "done")
field(ZRST, "Zero")
field(ONST, "One")
field(TWST, "Two")
field(THST, "Three")
field(FRST, "Four")
field(FVST, "Five")
field(SXST, "Six")
field(SVST, "Seven")
field(EIST, "Eight")
field(NIST, "Nine")
field(TEST, "Ten")
field(ELST, "Eleven")
field(TWST, "Twelve")
field(TTST, "Thirteen")
field(FTST, "Fourteen")
field(FFST, "Fifteen")
}
record(stringin, "si1") {
field(DTYP, "Async Soft Channel")
field(INP, {const:"9"})
field(FLNK, "done")
}
record(sub, "async") {
field(INPA, "startCounter PP")
field(SNAM, "asyncSubr")
}
record(calc, "startCounter") {
field(CALC, "VAL+1")
}
record(sub, "done") {
field(INPA, "doneCounter PP")
field(SNAM, "doneSubr")
}
record(calc, "doneCounter") {
field(CALC, "VAL+1")
}
record(ao, "ao1") {
field(DTYP, "Async Soft Channel")
field(OUT, "async.PROC CA")
field(FLNK, "done")
}
record(bo, "bo1") {
field(DTYP, "Async Soft Channel")
field(OUT, "async.PROC CA")
field(FLNK, "done")
field(ZNAM, "Zero")
field(ONAM, "One")
}
record(int64out, "io1") {
field(DTYP, "Async Soft Channel")
field(OUT, "async.PROC CA")
field(FLNK, "done")
}
record(longout, "lo1") {
field(DTYP, "Async Soft Channel")
field(OUT, "async.PROC CA")
field(FLNK, "done")
}
record(mbboDirect, "do1") {
field(DTYP, "Async Soft Channel")
field(NOBT, 4)
field(OUT, "async.PROC CA")
field(FLNK, "done")
}
record(mbbo, "mo1") {
field(DTYP, "Async Soft Channel")
field(NOBT, 4)
field(OUT, "async.PROC CA")
field(FLNK, "done")
field(ZRST, "Zero")
field(ONST, "One")
field(TWST, "Two")
field(THST, "Three")
field(FRST, "Four")
field(FVST, "Five")
field(SXST, "Six")
field(SVST, "Seven")
field(EIST, "Eight")
field(NIST, "Nine")
field(TEST, "Ten")
field(ELST, "Eleven")
field(TWST, "Twelve")
field(TTST, "Thirteen")
field(FTST, "Fourteen")
field(FFST, "Fifteen")
}
record(lso, "lso1") {
field(DTYP, "Async Soft Channel")
field(OUT, "async.PROC CA")
field(FLNK, "done")
field(SIZV, 40)
}
record(stringout, "so1") {
field(DTYP, "Async Soft Channel")
field(OUT, "async.PROC CA")
field(FLNK, "done")
}
@@ -0,0 +1,354 @@
/*************************************************************************\
* Copyright (c) 2016 Michael Davidsaver
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include "dbUnitTest.h"
#include "testMain.h"
#include "dbLock.h"
#include "errlog.h"
#include "dbAccess.h"
#include "epicsMath.h"
#include "aiRecord.h"
#include "compressRecord.h"
#define testDEq(A,B,D) testOk(fabs((A)-(B))<(D), #A " (%f) ~= " #B " (%f)", A, B)
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
static
void checkArrD(const char *pv, long elen, double a, double b, double c, double d)
{
double buf[4];
double expect[4];
long nReq = NELEMENTS(buf), i;
unsigned match;
DBADDR addr;
expect[0] = a;
expect[1] = b;
expect[2] = c;
expect[3] = d;
if (dbNameToAddr(pv, &addr))
testAbort("Unknown PV '%s'", pv);
if (dbGet(&addr, DBR_DOUBLE, buf, NULL, &nReq, NULL))
testAbort("Failed to get '%s'", pv);
match = elen==nReq;
for (i=0; i<nReq && i<elen; i++) {
match &= fabs(buf[i]-expect[i])<0.01;
}
testOk(match, "dbGet(\"%s\") matches", pv);
if (elen!=nReq)
testDiag("lengths don't match %ld != %ld", elen, nReq);
for (i=0; i<nReq && i<elen; i++) {
if (fabs(buf[i]-expect[i])>=0.01)
testDiag("[%ld] -> %f != %f", i, expect[i], buf[i]);
}
}
static
void checkArrI(const char *pv, long elen, epicsInt32 a, epicsInt32 b, epicsInt32 c, epicsInt32 d)
{
epicsInt32 buf[4];
epicsInt32 expect[4];
long nReq = NELEMENTS(buf), i;
unsigned match;
DBADDR addr;
expect[0] = a;
expect[1] = b;
expect[2] = c;
expect[3] = d;
if (dbNameToAddr(pv, &addr))
testAbort("Unknown PV '%s'", pv);
if (dbGet(&addr, DBR_LONG, buf, NULL, &nReq, NULL))
testAbort("Failed to get '%s'", pv);
match = elen==nReq;
for (i=0; i<nReq && i<elen; i++) {
match &= buf[i]==expect[i];
}
testOk(match, "dbGet(\"%s\") matches", pv);
if (elen!=nReq)
testDiag("lengths don't match %ld != %ld", elen, nReq);
for (i=0; i<nReq && i<elen; i++) {
if(buf[i]!=expect[i])
testDiag("[%ld] -> %d != %d", i, (int)expect[i], (int)buf[i]);
}
}
static
void testFIFOCirc(void)
{
aiRecord *vrec;
compressRecord *crec;
double *cbuf;
testDiag("Test FIFO");
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
recTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("compressTest.db", NULL, "ALG=Circular Buffer,BALG=FIFO Buffer,NSAM=4");
vrec = (aiRecord*)testdbRecordPtr("val");
crec = (compressRecord*)testdbRecordPtr("comp");
eltc(0);
testIocInitOk();
eltc(1);
dbScanLock((dbCommon*)crec);
cbuf = crec->bptr;
testOk1(crec->off==0);
testOk1(crec->inx==0);
testOk1(crec->nuse==0);
testDiag("Push 1.1");
vrec->val = 1.1;
dbProcess((dbCommon*)crec);
/* In FIFO mode the valid elements are
* cbuf[(off-nuse-1) % size] through cbuf[(off-1) % size]
*/
testOk1(crec->off==1);
testOk1(crec->inx==0);
testOk1(crec->nuse==1);
testDEq(cbuf[0], 1.1, 0.1);
testDEq(cbuf[1], 0.0, 0.1);
testDEq(cbuf[2], 0.0, 0.1);
testDEq(cbuf[3], 0.0, 0.1);
checkArrD("comp", 1, 1.1, 0, 0, 0);
testDiag("Push 2.1");
vrec->val = 2.1;
dbProcess((dbCommon*)crec);
testOk1(crec->off==2);
testOk1(crec->inx==0);
testOk1(crec->nuse==2);
testDEq(cbuf[0], 1.1, 0.1);
testDEq(cbuf[1], 2.1, 0.1);
testDEq(cbuf[2], 0.0, 0.1);
testDEq(cbuf[3], 0.0, 0.1);
checkArrD("comp", 2, 1.1, 2.1, 0, 0);
testDiag("Push 3.1");
vrec->val = 3.1;
dbProcess((dbCommon*)crec);
testOk1(crec->off==3);
testOk1(crec->inx==0);
testOk1(crec->nuse==3);
testDEq(cbuf[0], 1.1, 0.1);
testDEq(cbuf[1], 2.1, 0.1);
testDEq(cbuf[2], 3.1, 0.1);
testDEq(cbuf[3], 0.0, 0.1);
checkArrD("comp", 3, 1.1, 2.1, 3.1, 0);
testDiag("Push 4.1");
vrec->val = 4.1;
dbProcess((dbCommon*)crec);
testOk1(crec->off==0);
testOk1(crec->inx==0);
testOk1(crec->nuse==4);
testDEq(cbuf[0], 1.1, 0.1);
testDEq(cbuf[1], 2.1, 0.1);
testDEq(cbuf[2], 3.1, 0.1);
testDEq(cbuf[3], 4.1, 0.1);
checkArrD("comp", 4, 1.1, 2.1, 3.1, 4.1);
testDiag("Push 5.1");
vrec->val = 5.1;
dbProcess((dbCommon*)crec);
testOk1(crec->off==1);
testOk1(crec->inx==0);
testOk1(crec->nuse==4);
testDEq(cbuf[0], 5.1, 0.1);
testDEq(cbuf[1], 2.1, 0.1);
testDEq(cbuf[2], 3.1, 0.1);
testDEq(cbuf[3], 4.1, 0.1);
checkArrD("comp", 4, 2.1, 3.1, 4.1, 5.1);
testDiag("Push 6.1");
vrec->val = 6.1;
dbProcess((dbCommon*)crec);
testOk1(crec->off==2);
testOk1(crec->inx==0);
testOk1(crec->nuse==4);
testDEq(cbuf[0], 5.1, 0.1);
testDEq(cbuf[1], 6.1, 0.1);
testDEq(cbuf[2], 3.1, 0.1);
testDEq(cbuf[3], 4.1, 0.1);
checkArrD("comp", 4, 3.1, 4.1, 5.1, 6.1);
dbScanUnlock((dbCommon*)crec);
testDiag("Reset");
testdbPutFieldOk("comp.RES", DBF_LONG, 0);
dbScanLock((dbCommon*)crec);
testOk1(crec->off==0);
testOk1(crec->inx==0);
testOk1(crec->nuse==0);
checkArrD("comp", 0, 0, 0, 0, 0);
dbScanUnlock((dbCommon*)crec);
testIocShutdownOk();
testdbCleanup();
}
static
void testLIFOCirc(void)
{
aiRecord *vrec;
compressRecord *crec;
double *cbuf;
testDiag("Test LIFO");
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
recTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("compressTest.db", NULL,
"ALG=Circular Buffer,BALG=LIFO Buffer,NSAM=4");
vrec = (aiRecord*)testdbRecordPtr("val");
crec = (compressRecord*)testdbRecordPtr("comp");
eltc(0);
testIocInitOk();
eltc(1);
dbScanLock((dbCommon*)crec);
cbuf = crec->bptr;
testOk1(crec->off==0);
testOk1(crec->inx==0);
testOk1(crec->nuse==0);
testDiag("Push 1.1");
vrec->val = 1.1;
dbProcess((dbCommon*)crec);
testDiag("off %u", crec->off);
testOk1(crec->off==3);
testOk1(crec->inx==0);
testOk1(crec->nuse==1);
testDEq(cbuf[0], 0.0, 0.1);
testDEq(cbuf[1], 0.0, 0.1);
testDEq(cbuf[2], 0.0, 0.1);
testDEq(cbuf[3], 1.1, 0.1);
checkArrD("comp", 1, 1.1, 0, 0, 0);
testDiag("Push 2.1");
vrec->val = 2.1;
dbProcess((dbCommon*)crec);
testOk1(crec->off==2);
testOk1(crec->inx==0);
testOk1(crec->nuse==2);
testDEq(cbuf[0], 0.0, 0.1);
testDEq(cbuf[1], 0.0, 0.1);
testDEq(cbuf[2], 2.1, 0.1);
testDEq(cbuf[3], 1.1, 0.1);
checkArrD("comp", 2, 2.1, 1.1, 0, 0);
checkArrI("comp", 2, 2, 1, 0, 0);
testDiag("Push 3.1");
vrec->val = 3.1;
dbProcess((dbCommon*)crec);
testOk1(crec->off==1);
testOk1(crec->inx==0);
testOk1(crec->nuse==3);
testDEq(cbuf[0], 0.0, 0.1);
testDEq(cbuf[1], 3.1, 0.1);
testDEq(cbuf[2], 2.1, 0.1);
testDEq(cbuf[3], 1.1, 0.1);
checkArrD("comp", 3, 3.1, 2.1, 1.1, 0);
checkArrI("comp", 3, 3, 2, 1, 0);
testDiag("Push 4.1");
vrec->val = 4.1;
dbProcess((dbCommon*)crec);
testOk1(crec->off==0);
testOk1(crec->inx==0);
testOk1(crec->nuse==4);
testDEq(cbuf[0], 4.1, 0.1);
testDEq(cbuf[1], 3.1, 0.1);
testDEq(cbuf[2], 2.1, 0.1);
testDEq(cbuf[3], 1.1, 0.1);
checkArrD("comp", 4, 4.1, 3.1, 2.1, 1.1);
checkArrI("comp", 4, 4, 3, 2, 1);
testDiag("Push 5.1");
vrec->val = 5.1;
dbProcess((dbCommon*)crec);
testOk1(crec->off==3);
testOk1(crec->inx==0);
testOk1(crec->nuse==4);
testDEq(cbuf[0], 4.1, 0.1);
testDEq(cbuf[1], 3.1, 0.1);
testDEq(cbuf[2], 2.1, 0.1);
testDEq(cbuf[3], 5.1, 0.1);
checkArrD("comp", 4, 5.1, 4.1, 3.1, 2.1);
checkArrI("comp", 4, 5, 4, 3, 2);
testDiag("Push 6.1");
vrec->val = 6.1;
dbProcess((dbCommon*)crec);
testOk1(crec->off==2);
testOk1(crec->inx==0);
testOk1(crec->nuse==4);
testDEq(cbuf[0], 4.1, 0.1);
testDEq(cbuf[1], 3.1, 0.1);
testDEq(cbuf[2], 6.1, 0.1);
testDEq(cbuf[3], 5.1, 0.1);
checkArrD("comp", 4, 6.1, 5.1, 4.1, 3.1);
dbScanUnlock((dbCommon*)crec);
testDiag("Reset");
testdbPutFieldOk("comp.RES", DBF_LONG, 0);
dbScanLock((dbCommon*)crec);
testOk1(crec->off==0);
testOk1(crec->inx==0);
testOk1(crec->nuse==0);
checkArrD("comp", 0, 0, 0, 0, 0);
dbScanUnlock((dbCommon*)crec);
testIocShutdownOk();
testdbCleanup();
}
MAIN(compressTest)
{
testPlan(116);
testFIFOCirc();
testLIFOCirc();
return testDone();
}
@@ -0,0 +1,7 @@
record(ai, "val") {}
record(compress, "comp") {
field(INP, "val NPP")
field(ALG, "$(ALG)")
field(BALG,"$(BALG)")
field(NSAM,"$(NSAM)")
}
@@ -0,0 +1,48 @@
/*************************************************************************\
* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne
* National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/*
* Run tests as a batch.
*/
#include "epicsUnitTest.h"
#include "epicsExit.h"
int analogMonitorTest(void);
int compressTest(void);
int recMiscTest(void);
int arrayOpTest(void);
int asTest(void);
int linkRetargetLinkTest(void);
int linkInitTest(void);
int asyncSoftTest(void);
int simmTest(void);
void epicsRunRecordTests(void)
{
testHarness();
runTest(analogMonitorTest);
runTest(compressTest);
runTest(recMiscTest);
runTest(arrayOpTest);
runTest(asTest);
runTest(linkRetargetLinkTest);
runTest(linkInitTest);
runTest(asyncSoftTest);
runTest(simmTest);
epicsExit(0); /* Trigger test harness */
}
@@ -0,0 +1,243 @@
/*************************************************************************\
* Copyright (c) 2015 Michael Davidsaver
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include <string.h>
#include "dbAccess.h"
#include "alarm.h"
#include "dbUnitTest.h"
#include "errlog.h"
#include "epicsThread.h"
#include "testMain.h"
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
static void startTestIoc(const char *dbfile)
{
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
recTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase(dbfile, NULL, NULL);
eltc(0);
testIocInitOk();
eltc(1);
}
static void testLongStringInit()
{
testDiag("testLongStringInit");
startTestIoc("linkInitTest.db");
{
const char buf[] = "!----------------------------------------------!";
testdbGetArrFieldEqual("longstr1.VAL$", DBF_CHAR, NELEMENTS(buf)+2, NELEMENTS(buf), buf);
testdbGetFieldEqual("longstr1.VAL", DBR_STRING, "!--------------------------------------");
}
{
const char buf[] = "!----------------------------------------------!";
testdbGetArrFieldEqual("longstr2.VAL$", DBF_CHAR, NELEMENTS(buf)+2, NELEMENTS(buf), buf);
testdbGetFieldEqual("longstr2.VAL", DBR_STRING, "!--------------------------------------");
}
{
const char buf[] = "!----------------------------------------------!";
testdbGetArrFieldEqual("longstr3.VAL$", DBF_CHAR, NELEMENTS(buf)+2, NELEMENTS(buf), buf);
testdbGetFieldEqual("longstr3.VAL", DBR_STRING, "!--------------------------------------");
}
testdbGetFieldEqual("longstr4.VAL", DBR_STRING, "One");
testIocShutdownOk();
testdbCleanup();
}
static void testCalcInit()
{
testDiag("testCalcInit");
startTestIoc("linkInitTest.db");
testdbGetFieldEqual("emptylink.VAL", DBR_DOUBLE, 0.0);
testdbGetFieldEqual("emptylink.SEVR", DBR_LONG, INVALID_ALARM);
testdbPutFieldOk("emptylink.PROC", DBF_LONG, 1);
testdbGetFieldEqual("emptylink.VAL", DBR_DOUBLE, 0.0);
testdbGetFieldEqual("emptylink.SEVR", DBR_LONG, 0);
testdbGetFieldEqual("emptylink1.VAL", DBR_DOUBLE, 0.0);
testdbGetFieldEqual("emptylink1.SEVR", DBR_LONG, INVALID_ALARM);
testdbPutFieldOk("emptylink1.PROC", DBF_LONG, 1);
testdbGetFieldEqual("emptylink1.VAL", DBR_DOUBLE, 1.0);
testdbGetFieldEqual("emptylink1.SEVR", DBR_LONG, 0);
testIocShutdownOk();
testdbCleanup();
}
static void testPrintfStrings()
{
testDiag("testPrintfStrings");
startTestIoc("linkInitTest.db");
{
const char buf1[] = "Test string, exactly 40 characters long";
const char buf2[] = "Longer test string, more that 40 characters long";
const char buf2t[] = "Longer test string, more that 40 charac";
/* The FMT field is pp(TRUE), so this put triggers processing */
testdbPutFieldOk("printf1.FMT", DBF_STRING, "%s");
testdbGetArrFieldEqual("printf1.VAL$", DBF_CHAR, NELEMENTS(buf1)+2,
NELEMENTS(buf1), buf1);
testdbGetFieldEqual("printf1.VAL", DBR_STRING, buf1);
testdbPutFieldOk("printf1.FMT", DBF_STRING, "%ls");
testdbGetArrFieldEqual("printf1.VAL$", DBF_CHAR, NELEMENTS(buf1)+2,
NELEMENTS(buf1), buf1);
testdbGetFieldEqual("printf1.VAL", DBR_STRING, buf1);
testdbPutFieldOk("printf2.FMT", DBF_STRING, "%s");
testdbGetArrFieldEqual("printf2.VAL$", DBF_CHAR, NELEMENTS(buf2)+2,
NELEMENTS(buf2t), buf2t);
testdbGetFieldEqual("printf2.VAL", DBR_STRING, buf2t);
testdbPutFieldOk("printf2.FMT", DBF_STRING, "%ls");
testdbGetArrFieldEqual("printf2.VAL$", DBF_CHAR, NELEMENTS(buf2)+2,
NELEMENTS(buf2), buf2);
testdbGetFieldEqual("printf2.VAL", DBR_STRING, buf2t);
testdbPutFieldOk("printf2.FMT", DBF_STRING, "%.39ls");
testdbGetArrFieldEqual("printf2.VAL$", DBF_CHAR, NELEMENTS(buf2)+2,
NELEMENTS(buf2t), buf2t);
}
testIocShutdownOk();
testdbCleanup();
}
static void testArrayInputs()
{
epicsInt32 oneToTwelve[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
testDiag("testArrayInputs");
startTestIoc("linkInitTest.db");
testdbGetFieldEqual("aai1.NORD", DBR_LONG, 10);
testdbGetFieldEqual("aai1.UDF", DBR_UCHAR, 0);
testdbGetFieldEqual("aai2.NORD", DBR_LONG, 10);
testdbGetFieldEqual("aai2.UDF", DBR_UCHAR, 0);
testdbGetFieldEqual("sa1.NORD", DBR_LONG, 10);
testdbGetFieldEqual("sa1.UDF", DBR_UCHAR, 0);
testdbGetFieldEqual("sa2.NORD", DBR_LONG, 0);
testdbGetFieldEqual("sa2.UDF", DBR_UCHAR, 1);
testdbGetFieldEqual("wf1.NORD", DBR_LONG, 10);
testdbGetFieldEqual("wf1.UDF", DBR_UCHAR, 0);
testdbGetFieldEqual("wf2.NORD", DBR_LONG, 10);
testdbGetFieldEqual("wf2.UDF", DBR_UCHAR, 0);
testdbGetArrFieldEqual("aai1.VAL", DBF_LONG, 12, 10, &oneToTwelve[0]);
testdbGetArrFieldEqual("aai2.VAL", DBF_LONG, 12, 10, &oneToTwelve[0]);
testdbGetArrFieldEqual("sa1.VAL", DBF_LONG, 12, 10, &oneToTwelve[2]);
testdbGetArrFieldEqual("sa2.VAL", DBF_LONG, 10, 0, NULL);
testdbGetArrFieldEqual("wf1.VAL", DBF_LONG, 12, 10, &oneToTwelve[0]);
testdbGetArrFieldEqual("wf2.VAL", DBF_LONG, 12, 10, &oneToTwelve[0]);
testdbPutFieldOk("sa1.INDX", DBF_LONG, 3);
testdbGetArrFieldEqual("sa1.VAL", DBF_LONG, 12, 9, &oneToTwelve[3]);
testdbPutFieldOk("sa1.NELM", DBF_LONG, 3);
testdbGetArrFieldEqual("sa1.VAL", DBF_LONG, 12, 3, &oneToTwelve[3]);
testdbPutFieldOk("sa2.VAL", DBF_LONG, 1);
testdbGetArrFieldEqual("sa2.VAL", DBF_LONG, 10, 1, &oneToTwelve[0]);
testDiag("testScalarInputs");
testdbGetFieldEqual("li1", DBR_LONG, 1);
testdbGetFieldEqual("i64i1", DBR_INT64, 1LL);
testdbGetFieldEqual("li2", DBR_LONG, 1);
testdbGetFieldEqual("i64i2", DBR_INT64, 1LL);
testIocShutdownOk();
testdbCleanup();
}
static void testEventRecord()
{
testMonitor *countmon;
testDiag("testEventRecord");
startTestIoc("linkInitTest.db");
countmon = testMonitorCreate("count1.VAL", DBR_LONG, 0);
testdbGetFieldEqual("ev1.VAL", DBR_STRING, "soft event 1");
testdbGetFieldEqual("ev1.UDF", DBR_UCHAR, 0);
testdbGetFieldEqual("ev2.VAL", DBR_STRING, "");
testdbGetFieldEqual("ev2.UDF", DBR_UCHAR, 1);
testdbGetFieldEqual("count1.VAL", DBR_LONG, 0);
testdbPutFieldOk("ev1.PROC", DBF_UCHAR, 1);
testMonitorWait(countmon);
testdbGetFieldEqual("count1.VAL", DBR_LONG, 1);
testdbPutFieldOk("ev2.PROC", DBF_UCHAR, 1);
testMonitorWait(countmon);
testdbGetFieldEqual("ev2.UDF", DBR_UCHAR, 0);
testdbGetFieldEqual("count1.VAL", DBR_LONG, 2);
testdbPutFieldOk("count1.EVNT", DBF_STRING, "Tock");
testdbPutFieldOk("ev2.PROC", DBF_UCHAR, 1);
testMonitorWait(countmon);
testdbGetFieldEqual("count1.VAL", DBR_LONG, 3);
testMonitorDestroy(countmon);
testIocShutdownOk();
testdbCleanup();
}
void testInt64Inputs(void)
{
testDiag("testInt64Inputs");
startTestIoc("linkInitTest.db");
testdbGetFieldEqual("i1.VAL", DBR_INT64, 1234567890123456789LL);
testdbGetFieldEqual("i2.VAL", DBR_INT64, 1234567890123456789LL);
testdbGetFieldEqual("i3.VAL", DBR_INT64, 1234567890123456789LL);
testdbGetFieldEqual("i4.NORD", DBR_LONG, 1);
testdbGetFieldEqual("i4.VAL", DBR_INT64, 1234567890123456789LL);
testIocShutdownOk();
testdbCleanup();
}
MAIN(linkInitTest)
{
testPlan(77);
testLongStringInit();
testCalcInit();
testPrintfStrings();
testArrayInputs();
testEventRecord();
testInt64Inputs();
return testDone();
}
@@ -0,0 +1,107 @@
record(lsi, "longstr1") {
field(SIZV, "100")
field(INP, ["!----------------------------------------------!"])
}
record(lsi, "longstr2") {
field(SIZV, "100")
field(INP, {const: ["!----------------------------------------------!"]})
}
record(lsi, "longstr3") {
field(SIZV, "100")
field(INP, {const: "!----------------------------------------------!"})
}
record(lsi, "longstr4") {
field(SIZV, "100")
field(INP, ["One","Two","Three","Four"])
}
record(ai, "emptylink" ) {
field(INP, {calc: {expr:"0"}})
}
record(ai, "emptylink1" ) {
field(INP, {calc: {expr:"A", args:[1], time:"a"}})
field(TSEL, -2)
}
record(printf, "printf1") {
field(SIZV, "100")
field(INP0, ["Test string, exactly 40 characters long"])
}
record(printf, "printf2") {
field(SIZV, "100")
field(INP0, ["Longer test string, more that 40 characters long"])
}
record(aai, "aai1") {
field(NELM, 10)
field(FTVL, "LONG")
field(INP, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
}
record(aai, "aai2") {
field(NELM, 10)
field(FTVL, "LONG")
field(INP, {const:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]})
}
record(subArray, "sa1") {
field(FTVL, "LONG")
field(MALM, 12)
field(INP, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
field(INDX, 2)
field(NELM, 10)
}
record(subArray, "sa2") {
field(FTVL, "LONG")
field(MALM, 10)
field(NELM, 1)
}
record(waveform, "wf1") {
field(NELM, 10)
field(FTVL, "LONG")
field(INP, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
}
record(waveform, "wf2") {
field(NELM, 10)
field(FTVL, "LONG")
field(INP, {const:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]})
}
record(longin, "li1") {
field(INP, 1)
}
record(int64in, "i64i1") {
field(INP, 1)
}
record(longin, "li2") {
field(INP, {const:1})
}
record(int64in, "i64i2") {
field(INP, {const:1})
}
record(longin, "count1" ) {
field(INP, {calc: {expr:"VAL+1"}})
field(SCAN, "Event")
field(EVNT, "soft event 1")
}
record(event, "ev1") {
field(INP, ["soft event 1"])
}
record(event, "ev2") {
field(INP, "count1.EVNT")
}
record(int64in, "i1") {
field(INP, [1234567890123456789,])
}
record(int64in, "i2") {
field(INP, {const:1234567890123456789,})
}
record(int64in, "i3") {
field(INP, 1234567890123456789)
}
record(waveform, "i4") {
field(NELM, 1)
field(FTVL, "INT64")
field(INP, [1234567890123456789,])
}
@@ -0,0 +1,25 @@
record(ai, "rec:ai") {
field(INP, "0")
}
record(ai, "rec:src1") {
field(VAL, "1")
}
record(ai, "rec:src2") {
field(VAL, "2")
}
record(stringout, "rec:link1") {
field(VAL, "rec:src1")
field(OUT, "rec:ai.INP CA")
}
record(stringout, "rec:link2") {
field(VAL, "rec:src2 CP")
field(OUT, "rec:ai.INP CA")
}
record(ai, "rec:j1") {
field(INP, {calc:{
expr:"A+5",
args:5
}})
field(PINI, "YES")
}
@@ -0,0 +1,122 @@
/*************************************************************************\
* Copyright (c) 2015 Michael Davidsaver
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/*
* Author: Michael Davidsaver <mdavidsaver@gmail.com>
*
* Test using several stringout records to retarget the link of another record
*/
#define EPICS_DBCA_PRIVATE_API
#include <string.h>
#include "dbAccess.h"
#include "dbUnitTest.h"
#include "errlog.h"
#include "epicsThread.h"
#include "testMain.h"
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
static void testRetarget(void)
{
testMonitor *lnkmon, *valmon;
testDiag("In testRetarget");
lnkmon = testMonitorCreate("rec:ai.INP", DBE_VALUE, 0);
valmon = testMonitorCreate("rec:ai", DBE_VALUE, 0);
/* initially rec:ai.INP is CONSTANT */
testdbGetFieldEqual("rec:ai", DBR_DOUBLE, 0.0);
testdbGetFieldEqual("rec:ai.INP", DBR_STRING, "0");
/* rec:ai.INP becomes DB_LINK, but no processing is triggered */
testdbPutFieldOk("rec:link1.PROC", DBF_LONG, 0);
testMonitorWait(lnkmon);
testdbGetFieldEqual("rec:ai", DBR_DOUBLE, 0.0);
testdbGetFieldEqual("rec:ai.INP", DBR_STRING, "rec:src1 NPP NMS");
/* trigger a read from rec:ai.INP */
testdbPutFieldOk("rec:ai.PROC", DBF_LONG, 0);
testMonitorWait(valmon);
testdbGetFieldEqual("rec:ai", DBR_DOUBLE, 1.0);
/* rec:ai.INP becomes CA_LINK w/ CP, processing is triggered */
testdbPutFieldOk("rec:link2.PROC", DBF_LONG, 0);
testMonitorWait(lnkmon);
testMonitorWait(valmon);
testdbGetFieldEqual("rec:ai", DBR_DOUBLE, 2.0);
testdbGetFieldEqual("rec:ai.INP", DBR_STRING, "rec:src2 CP NMS");
testMonitorDestroy(lnkmon);
testMonitorDestroy(valmon);
}
#define testLongStrEq(PV, VAL) testdbGetArrFieldEqual(PV, DBF_CHAR, sizeof(VAL)+2, sizeof(VAL), VAL)
#define testPutLongStr(PV, VAL) testdbPutArrFieldOk(PV, DBF_CHAR, sizeof(VAL), VAL);
static void testRetargetJLink(void)
{
testDiag("In testRetargetJLink");
testdbGetFieldEqual("rec:j1", DBF_DOUBLE, 10.0);
/* minimal args */
testLongStrEq("rec:j1.INP$", "{\"calc\":{\"expr\":\"A+5\",\"args\":5}}");
/* with [] */
testPutLongStr("rec:j1.INP$", "{\"calc\":{\"expr\":\"A+5\",\"args\":[7]}}");
testdbPutFieldOk("rec:j1.PROC", DBF_LONG, 1);
/* with const */
testPutLongStr("rec:j1.INP$", "{\"calc\":{\"expr\":\"A+5\",\"args\":[{\"const\":7}]}}");
testdbPutFieldOk("rec:j1.PROC", DBF_LONG, 1);
testdbGetFieldEqual("rec:j1", DBF_DOUBLE, 12.0);
testLongStrEq("rec:j1.INP$", "{\"calc\":{\"expr\":\"A+5\",\"args\":[{\"const\":7}]}}");
}
MAIN(linkRetargetLinkTest)
{
testPlan(18);
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
recTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("linkRetargetLink.db", NULL, NULL);
eltc(0);
testIocInitOk();
eltc(1);
/* wait for local CA links to be connected or dbPutField() will fail */
/* wait for initial CA_CONNECT actions to be processed.
* Assume that local CA links deliver callbacks synchronously
* eg. that ca_create_channel() will invoke the connection callback
* before returning.
*/
dbCaSync();
testRetarget();
testRetargetJLink();
testIocShutdownOk();
testdbCleanup();
return testDone();
}
@@ -0,0 +1,88 @@
/*************************************************************************\
* Copyright (c) 2017 Michael Davidsaver
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include <string.h>
#include "dbAccess.h"
#include "errlog.h"
#include "dbStaticLib.h"
#include "dbUnitTest.h"
#include "testMain.h"
static
void testint64BeforeInit(void)
{
const char *S;
DBENTRY dbent;
/* check dbGet/PutString */
testDiag("In %s", EPICS_FUNCTION);
dbInitEntryFromRecord(testdbRecordPtr("out64"), &dbent);
if(dbFindField(&dbent, "VAL"))
testAbort("Failed to find out64.VAL");
S = dbGetString(&dbent);
testOk(S && strcmp(S, "0")==0, "initial value \"%s\"", S);
testOk1(dbPutString(&dbent, "0x12345678abcdef00")==0);
S = dbGetString(&dbent);
testOk(S && strcmp(S, "1311768467750121216")==0, "1311768467750121216 \"%s\"", S);
dbFinishEntry(&dbent);
}
static
void testint64AfterInit(void)
{
testDiag("In %s", EPICS_FUNCTION);
/* check dbGet/PutField and DB links */
testdbGetFieldEqual("in64", DBF_UINT64, 0ULL);
testdbGetFieldEqual("out64", DBF_UINT64, 0x12345678abcdef00ULL);
testdbPutFieldOk("out64.PROC", DBF_LONG, 1);
testdbGetFieldEqual("in64", DBF_UINT64, 0x12345678abcdef00ULL);
testdbPutFieldOk("out64.VAL", DBF_UINT64, 0x22345678abcdef00ULL);
testdbPutFieldOk("in64.PROC", DBF_LONG, 1);
testdbGetFieldEqual("in64", DBF_UINT64, 0x22345678abcdef00ULL);
}
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
MAIN(recMiscTest)
{
testPlan(10);
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
recTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("recMiscTest.db", NULL, NULL);
testint64BeforeInit();
eltc(0);
testIocInitOk();
eltc(1);
testint64AfterInit();
testIocShutdownOk();
testdbCleanup();
return testDone();
}
@@ -0,0 +1,10 @@
# check int64in/out
record(int64in, "in64") {
field(INP , "out64 NPP")
}
record(int64out, "out64") {
field(OUT , "in64 NPP")
}
@@ -0,0 +1,9 @@
record(waveform, "wf") {
field(FTVL, "DOUBLE")
field(NELM, "1")
field(FLNK, "co")
}
record(calcout, "co") {
field(CALC, "A")
field(INPA, "wf")
}
@@ -0,0 +1,28 @@
record(ai, "ai1") {
field(INP, 0x10)
}
record(longin, "li1") {
field(INP, 0x10)
}
record(mbbiDirect, "mi1") {
field(INP, 0x10)
field(NOBT, 8)
}
record(aSub, "as1") {
field(INPA, 0x10)
field(FTA, "CHAR")
field(INPB, 0x10)
field(FTB, "UCHAR")
field(INPC, 0x10)
field(FTC, "SHORT")
field(INPD, 0x10)
field(FTD, "USHORT")
field(INPE, 0x10)
field(FTE, "LONG")
field(INPF, 0x10)
field(FTF, "ULONG")
field(INPG, 0x10)
field(FTG, "FLOAT")
field(INPH, 0x10)
field(FTH, "DOUBLE")
}
@@ -0,0 +1,13 @@
record(ai, "alarm") {
field(HIGH, 1)
field(HSV, MINOR)
field(HIHI, 2)
field(HHSV, MAJOR)
field(FLNK, "latch")
}
record(calc, "latch") {
field(INPA, "alarm NPP MS")
field(INPB, "latch NPP MS")
field(CALC, "A")
}
+134
View File
@@ -0,0 +1,134 @@
/*************************************************************************\
* Copyright (c) 2016 Michael Davidsaver
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include <dbUnitTest.h>
#include <testMain.h>
#include <dbAccess.h>
#include <errlog.h>
#include <calcoutRecord.h>
#include <waveformRecord.h>
/*
* Tests for specific regressions
*/
void regressTest_registerRecordDeviceDriver(struct dbBase *);
static
void startRegressTestIoc(const char *dbfile)
{
testdbPrepare();
testdbReadDatabase("regressTest.dbd", NULL, NULL);
regressTest_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase(dbfile, NULL, NULL);
eltc(0);
testIocInitOk();
eltc(1);
}
/*
* https://bugs.launchpad.net/epics-base/+bug/1577108
*/
static
void testArrayLength1(void)
{
waveformRecord *precwf;
calcoutRecord *precco;
double *pbuf;
startRegressTestIoc("regressArray1.db");
precwf = (waveformRecord*)testdbRecordPtr("wf");
precco = (calcoutRecord*)testdbRecordPtr("co");
dbScanLock((dbCommon*)precwf);
pbuf = (double*)precwf->bptr;
dbScanUnlock((dbCommon*)precwf);
testdbPutFieldOk("wf", DBF_DOUBLE, 2.0);
dbScanLock((dbCommon*)precwf);
testOk(precwf->nord==1, "wf.NORD = %u == 1", (unsigned)precwf->nord);
testOk(pbuf[0]==2.0, "wf.VAL[0] = %f == 2.0", pbuf[0]);
dbScanUnlock((dbCommon*)precwf);
dbScanLock((dbCommon*)precco);
testOk(precco->a==2.0, "co.A = %f == 2.0", precco->a);
dbScanUnlock((dbCommon*)precco);
testdbGetFieldEqual("co", DBF_DOUBLE, 2.0);
testIocShutdownOk();
testdbCleanup();
}
/*
* https://bugs.launchpad.net/epics-base/+bug/1699445
*/
static
void testHexConstantLinks(void)
{
startRegressTestIoc("regressHex.db");
testdbGetFieldEqual("ai1", DBR_LONG, 0x10);
testdbGetFieldEqual("li1", DBR_LONG, 0x10);
testdbGetFieldEqual("mi1", DBR_LONG, 0x10);
testTodoBegin("Needs JSON5 for hex arrays");
testdbGetFieldEqual("as1.A", DBR_LONG, 0x10);
testdbGetFieldEqual("as1.B", DBR_LONG, 0x10);
testdbGetFieldEqual("as1.C", DBR_LONG, 0x10);
testdbGetFieldEqual("as1.D", DBR_LONG, 0x10);
testdbGetFieldEqual("as1.E", DBR_LONG, 0x10);
testdbGetFieldEqual("as1.F", DBR_LONG, 0x10);
testdbGetFieldEqual("as1.G", DBR_LONG, 0x10);
testdbGetFieldEqual("as1.H", DBR_LONG, 0x10);
testTodoEnd();
testIocShutdownOk();
testdbCleanup();
}
static
void testLinkMS(void)
{
startRegressTestIoc("regressLinkMS.db");
testdbPutFieldOk("alarm", DBF_DOUBLE, 0.5);
testdbGetFieldEqual("latch", DBR_DOUBLE, 0.5);
testdbGetFieldEqual("latch.SEVR", DBR_LONG, 0);
testdbPutFieldOk("alarm", DBF_DOUBLE, 1.5);
testdbGetFieldEqual("latch", DBR_DOUBLE, 1.5);
testdbGetFieldEqual("latch.SEVR", DBR_LONG, 1);
testdbPutFieldOk("alarm", DBF_DOUBLE, 0.5);
testdbGetFieldEqual("latch", DBR_DOUBLE, 0.5);
testdbGetFieldEqual("latch.SEVR", DBR_LONG, 0);
testdbPutFieldOk("alarm", DBF_DOUBLE, 2.5);
testdbGetFieldEqual("latch", DBR_DOUBLE, 2.5);
testdbGetFieldEqual("latch.SEVR", DBR_LONG, 2);
testdbPutFieldOk("alarm", DBF_DOUBLE, 0.5);
testdbGetFieldEqual("latch", DBR_DOUBLE, 0.5);
testdbGetFieldEqual("latch.SEVR", DBR_LONG, 0);
testIocShutdownOk();
testdbCleanup();
}
MAIN(regressTest)
{
testPlan(31);
testArrayLength1();
testHexConstantLinks();
testLinkMS();
return testDone();
}
@@ -0,0 +1,14 @@
/*************************************************************************\
* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne
* National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
extern void epicsRunRecordTests(void);
int main(int argc, char **argv)
{
epicsRunRecordTests(); /* calls epicsExit(0) */
return 0;
}
@@ -0,0 +1,16 @@
# no info
record(ai, "ai-0") {
}
# only scan
record(ai, "ai-1") {
field(SSCN,"2 second")
}
# only delay
record(ai, "ai-2") {
field(SDLY,".234")
}
# scan and delay
record(ai, "ai-3") {
field(SSCN,"5 second")
field(SDLY,".345")
}
@@ -0,0 +1,6 @@
# siml target doesn't exist
record(ao, "ao-0") {
field(SIML, "non-exist")
field(HIGH, "1.5")
field(HSV, "MINOR")
}
+502
View File
@@ -0,0 +1,502 @@
/*************************************************************************\
* Copyright (c) 2017 ITER Organization
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include <limits.h>
#include <string.h>
#include <dbUnitTest.h>
#include <testMain.h>
#include <dbAccess.h>
#include <epicsTime.h>
#include <epicsThread.h>
#include <errlog.h>
#include <alarm.h>
#include "recSup.h"
#include "aiRecord.h"
#include "aoRecord.h"
#include "aaiRecord.h"
#include "aaoRecord.h"
#include "biRecord.h"
#include "boRecord.h"
#include "mbbiRecord.h"
#include "mbboRecord.h"
#include "mbbiDirectRecord.h"
#include "mbboDirectRecord.h"
#include "longinRecord.h"
#include "longoutRecord.h"
#include "int64inRecord.h"
#include "int64outRecord.h"
#include "stringinRecord.h"
#include "stringoutRecord.h"
#include "lsiRecord.h"
#include "lsoRecord.h"
#include "eventRecord.h"
#include "histogramRecord.h"
#include "waveformRecord.h"
/*
* Tests for simulation mode
*/
void simmTest_registerRecordDeviceDriver(struct dbBase *);
static
void startSimmTestIoc(const char *dbfile)
{
testdbPrepare();
testdbReadDatabase("simmTest.dbd", NULL, NULL);
simmTest_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase(dbfile, NULL, NULL);
eltc(0);
testIocInitOk();
eltc(1);
}
static char *rawSupp[] = {
"ai",
"bi",
"mbbi",
"mbbiDirect",
};
static
int hasRawSimmSupport(const char *rectype) {
int i;
for (i = 0; i < (sizeof(rawSupp)/sizeof(rawSupp[0])); i++)
if (strcmp(rectype, rawSupp[i]) == 0) return 1;
return 0;
}
#define PVNAMELENGTH 60
static char nameVAL[PVNAMELENGTH];
static char nameB0[PVNAMELENGTH];
static char nameRVAL[PVNAMELENGTH];
static char nameSGNL[PVNAMELENGTH];
static char nameSIMM[PVNAMELENGTH];
static char nameSIML[PVNAMELENGTH];
static char nameSVAL[PVNAMELENGTH];
static char nameSIOL[PVNAMELENGTH];
static char nameSCAN[PVNAMELENGTH];
static char namePROC[PVNAMELENGTH];
static char namePACT[PVNAMELENGTH];
static char nameSTAT[PVNAMELENGTH];
static char nameSEVR[PVNAMELENGTH];
static char nameSIMS[PVNAMELENGTH];
static char nameTSE[PVNAMELENGTH];
static char nameSimmode[PVNAMELENGTH];
static char nameSimval[PVNAMELENGTH];
static char nameSimvalNORD[PVNAMELENGTH];
static char nameSimvalLEN[PVNAMELENGTH];
#define SETNAME(field) strcpy(name ## field, name); strcat(name ## field, "." #field)
static
void setNames(const char *name)
{
SETNAME(VAL); SETNAME(B0); SETNAME(RVAL); SETNAME(SGNL);
SETNAME(SVAL); SETNAME(SIMM); SETNAME(SIML); SETNAME(SIOL); SETNAME(SIMS);
SETNAME(SCAN); SETNAME(PROC); SETNAME(PACT);
SETNAME(STAT); SETNAME(SEVR); SETNAME(TSE);
strcpy(nameSimmode, name); strcat(nameSimmode, ":simmode");
strcpy(nameSimval, name); strcat(nameSimval, ":simval");
strcpy(nameSimvalNORD, name); strcat(nameSimvalNORD, ":simval.NORD");
strcpy(nameSimvalLEN, name); strcat(nameSimvalLEN, ":simval.LEN");
}
/*
* Parsing of info items and xsimm structure setting
*/
static
void testSimmSetup(void)
{
aiRecord *precai;
testDiag("##### Simm initialization #####");
/* no config */
precai = (aiRecord*)testdbRecordPtr("ai-0");
testOk(precai->simpvt == NULL, "ai-0.SIMPVT = %p == NULL [no callback]", precai->simpvt);
testOk(precai->sscn == USHRT_MAX, "ai-0.SSCN = %u == USHRT_MAX (not set)", precai->sscn);
testOk(precai->sdly < 0., "ai-0.SDLY = %g < 0.0 (not set)", precai->sdly);
/* with SCAN */
precai = (aiRecord*)testdbRecordPtr("ai-1");
testOk(precai->sscn == 5, "ai-1.SSCN = %u == 5 (2 second)", precai->sscn);
testOk(precai->sdly < 0., "ai-1.SDLY = %g < 0.0 (not set)", precai->sdly);
/* with DELAY */
precai = (aiRecord*)testdbRecordPtr("ai-2");
testOk(precai->sscn == USHRT_MAX, "ai-2.SSCN = %u == USHRT_MAX (not set)", precai->sscn);
testOk(precai->sdly == 0.234, "ai-2.SDLY = %g == 0.234", precai->sdly);
/* with SCAN and DELAY */
precai = (aiRecord*)testdbRecordPtr("ai-3");
testOk(precai->sscn == 4, "ai-3.SSCN = %u == 4 (5 second)", precai->sscn);
testOk(precai->sdly == 0.345, "ai-3.SDLY = %g == 0.345", precai->sdly);
}
/*
* Invalid SIML link sets LINK/NO_ALARM if in NO_ALARM
*/
static
void testSimlFail(void)
{
aoRecord *precao;
testDiag("##### Behavior for failing SIML #####");
precao = (aoRecord*)testdbRecordPtr("ao-0");
/* before anything: UDF INVALID */
testOk(precao->stat == UDF_ALARM, "ao-0.STAT = %u [%s] == %u [UDF]",
precao->stat, epicsAlarmConditionStrings[precao->stat], UDF_ALARM);
testOk(precao->sevr == INVALID_ALARM, "ao-0.SEVR = %u [%s] == %u [INVALID]",
precao->sevr, epicsAlarmSeverityStrings[precao->sevr], INVALID_ALARM);
/* legal value: LINK NO_ALARM */
testdbPutFieldOk("ao-0.VAL", DBR_DOUBLE, 1.0);
testOk(precao->stat == LINK_ALARM, "ao-0.STAT = %u [%s] == %u [LINK]",
precao->stat, epicsAlarmConditionStrings[precao->stat], LINK_ALARM);
testOk(precao->sevr == NO_ALARM, "ao-0.SEVR = %u [%s] == %u [NO_ALARM]",
precao->sevr, epicsAlarmSeverityStrings[precao->sevr], NO_ALARM);
/* HIGH/MINOR overrides */
testdbPutFieldOk("ao-0.VAL", DBR_DOUBLE, 2.0);
testOk(precao->stat == HIGH_ALARM, "ao-0.STAT = %u [%s] == %u [HIGH]",
precao->stat, epicsAlarmConditionStrings[precao->stat], HIGH_ALARM);
testOk(precao->sevr == MINOR_ALARM, "ao-0.SEVR = %u [%s] == %u [MINOR]",
precao->sevr, epicsAlarmSeverityStrings[precao->sevr], MINOR_ALARM);
}
/*
* SIMM triggered SCAN swapping, by writing to SIMM and through SIML
*/
static
void testSimmToggle(const char *name, epicsEnum16 *psscn)
{
testDiag("## SIMM toggle and SCAN swapping ##");
/* SIMM mode by setting the field */
testdbGetFieldEqual(nameSCAN, DBR_USHORT, 0);
testOk(*psscn == 1, "SSCN = %u == 1 (Event)", *psscn);
testDiag("set SIMM to YES");
testdbPutFieldOk(nameSIMM, DBR_STRING, "YES");
testdbGetFieldEqual(nameSCAN, DBR_USHORT, 1);
testOk(*psscn == 0, "SSCN = %u == 0 (Passive)", *psscn);
/* Change simm:SCAN when simmYES */
testdbPutFieldOk(nameSCAN, DBR_USHORT, 3);
testDiag("set SIMM to NO");
testdbPutFieldOk(nameSIMM, DBR_STRING, "NO");
testdbGetFieldEqual(nameSCAN, DBR_USHORT, 0);
testOk(*psscn == 3, "SSCN = %u == 3 (10 second)", *psscn);
*psscn = 1;
if (hasRawSimmSupport(name)) {
testDiag("set SIMM to RAW");
testdbPutFieldOk(nameSIMM, DBR_STRING, "RAW");
testdbGetFieldEqual(nameSCAN, DBR_USHORT, 1);
testOk(*psscn == 0, "SSCN = %u == 0 (Passive)", *psscn);
testDiag("set SIMM to NO");
testdbPutFieldOk(nameSIMM, DBR_STRING, "NO");
testdbGetFieldEqual(nameSCAN, DBR_USHORT, 0);
testOk(*psscn == 1, "SSCN = %u == 1 (Event)", *psscn);
} else {
testDiag("Record type %s has no support for simmRAW", name);
}
/* SIMM mode through SIML */
testdbPutFieldOk(nameSIML, DBR_STRING, nameSimmode);
testDiag("set SIMM (via SIML -> simmode) to YES");
testdbPutFieldOk(nameSimmode, DBR_USHORT, 1);
testdbPutFieldOk(namePROC, DBR_LONG, 0);
testdbGetFieldEqual(nameSIMM, DBR_USHORT, 1);
testdbGetFieldEqual(nameSCAN, DBR_USHORT, 1);
testOk(*psscn == 0, "SSCN = %u == 0 (Passive)", *psscn);
testDiag("set SIMM (via SIML -> simmode) to NO");
testdbPutFieldOk(nameSimmode, DBR_USHORT, 0);
testdbPutFieldOk(namePROC, DBR_LONG, 0);
testdbGetFieldEqual(nameSIMM, DBR_USHORT, 0);
testdbGetFieldEqual(nameSCAN, DBR_USHORT, 0);
testOk(*psscn == 1, "SSCN = %u == 1 (Event)", *psscn);
if (hasRawSimmSupport(name)) {
testDiag("set SIMM (via SIML -> simmode) to RAW");
testdbPutFieldOk(nameSimmode, DBR_USHORT, 2);
testdbPutFieldOk(namePROC, DBR_LONG, 0);
testdbGetFieldEqual(nameSIMM, DBR_USHORT, 2);
testdbGetFieldEqual(nameSCAN, DBR_USHORT, 1);
testOk(*psscn == 0, "SSCN = %u == 0 (Passive)", *psscn);
testDiag("set SIMM (via SIML -> simmode) to NO");
testdbPutFieldOk(nameSimmode, DBR_USHORT, 0);
testdbPutFieldOk(namePROC, DBR_LONG, 0);
testdbGetFieldEqual(nameSIMM, DBR_USHORT, 0);
testdbGetFieldEqual(nameSCAN, DBR_USHORT, 0);
testOk(*psscn == 1, "SSCN = %u == 1 (Event)", *psscn);
} else {
testDiag("Record type %s has no support for simmRAW", name);
}
}
/*
* Reading from SVAL (direct write or through SIOL link)
*/
static
void testSvalRead(const char *name,
const epicsTimeStamp *mytime,
const epicsTimeStamp *svtime)
{
epicsTimeStamp last;
if (strcmp(name, "histogram") == 0)
strcpy(nameVAL, nameSGNL);
if (strcmp(name, "aai") != 0 &&
strcmp(name, "waveform") != 0 &&
strcmp(name, "lsi") != 0) {
testDiag("## Reading from SVAL ##");
testDiag("in simmNO, SVAL must be ignored");
testdbPutFieldOk(nameSimmode, DBR_USHORT, 0);
testdbPutFieldOk(nameVAL, DBR_LONG, 0);
if (strcmp(name, "stringin") == 0)
testdbPutFieldOk(nameSVAL, DBR_STRING, "1");
else
testdbPutFieldOk(nameSVAL, DBR_USHORT, 1);
testdbPutFieldOk(namePROC, DBR_LONG, 0);
testdbGetFieldEqual(nameVAL, DBR_USHORT, 0);
testDiag("in simmYES, SVAL is used for VAL");
testdbPutFieldOk(nameSIMS, DBR_USHORT, 0);
testdbPutFieldOk(nameSimmode, DBR_USHORT, 1);
testdbPutFieldOk(namePROC, DBR_LONG, 0);
testdbGetFieldEqual(nameVAL, DBR_USHORT, 1);
testDiag("No SIMS setting: STAT/SEVR == NO_ALARM");
testdbGetFieldEqual(nameSTAT, DBR_STRING, "NO_ALARM");
testdbGetFieldEqual(nameSEVR, DBR_USHORT, 0);
if (hasRawSimmSupport(name)) {
testDiag("in simmRAW, SVAL is used for RVAL");
testdbPutFieldOk(nameSimmode, DBR_USHORT, 2);
testdbPutFieldOk(namePROC, DBR_LONG, 0);
testdbGetFieldEqual(nameRVAL, DBR_USHORT, 1);
} else {
testDiag("Record type %s has no support for simmRAW", name);
}
}
testDiag("## Reading from SIOL->SVAL ##");
/* Set SIOL link to simval */
testdbPutFieldOk(nameSIOL, DBR_STRING, nameSimval);
testDiag("in simmNO, SIOL->SVAL must be ignored");
testdbPutFieldOk(nameSimmode, DBR_USHORT, 0);
testdbPutFieldOk(nameVAL, DBR_LONG, 0);
testdbPutFieldOk(nameSimval, DBR_LONG, 1);
testdbPutFieldOk(namePROC, DBR_LONG, 0);
testdbGetFieldEqual(nameVAL, DBR_USHORT, 0);
testDiag("in simmYES, SIOL->SVAL is used for VAL");
testdbPutFieldOk(nameSIMS, DBR_USHORT, 3);
testdbPutFieldOk(nameSimmode, DBR_USHORT, 1);
testdbPutFieldOk(namePROC, DBR_LONG, 0);
testdbGetFieldEqual(nameVAL, DBR_USHORT, 1);
testDiag("SIMS is INVALID: STAT/SEVR == SIMM/INVALID");
testdbGetFieldEqual(nameSTAT, DBR_STRING, "SIMM");
testdbGetFieldEqual(nameSEVR, DBR_USHORT, 3);
testdbPutFieldOk(nameSIMS, DBR_USHORT, 0);
if (hasRawSimmSupport(name)) {
testDiag("in simmRAW, SIOL->SVAL is used for RVAL");
testdbPutFieldOk(nameSimmode, DBR_USHORT, 2);
testdbPutFieldOk(namePROC, DBR_LONG, 0);
testdbGetFieldEqual(nameRVAL, DBR_USHORT, 1);
} else {
testDiag("Record type %s has no support for simmRAW", name);
}
/* My timestamp must be later than simval's */
testOk(epicsTimeLessThan(svtime, mytime), "simval time < my time [TSE = 0]");
testDiag("for TSE=-2 (from device) and simmYES, take time stamp from IOC or input link");
/* Set simmYES */
testdbPutFieldOk(nameSimmode, DBR_USHORT, 1);
/* Set TSE to -2 (from device) and reprocess: timestamps is taken through SIOL from simval */
testdbPutFieldOk(nameTSE, DBR_SHORT, -2);
testdbPutFieldOk(namePROC, DBR_LONG, 0);
testOk(epicsTimeEqual(svtime, mytime), "simval time == my time [TSE = -2]");
last = *mytime;
/* With TSE=-2 and no SIOL, timestamp is taken from IOC */
testdbPutFieldOk(nameSIOL, DBR_STRING, "");
testdbPutFieldOk(namePROC, DBR_LONG, 0);
testOk(epicsTimeLessThan(&last, mytime), "new time stamp from IOC [TSE = -2, no SIOL]");
/* Reset TSE */
testdbPutFieldOk(nameTSE, DBR_SHORT, 0);
}
/*
* Writing through SIOL link
*/
static
void testSiolWrite(const char *name,
const epicsTimeStamp *mytime)
{
epicsTimeStamp now;
testDiag("## Writing through SIOL ##");
/* Set SIOL link to simval */
testdbPutFieldOk(nameSIOL, DBR_STRING, nameSimval);
testDiag("in simmNO, SIOL must be ignored");
testdbPutFieldOk(nameSimmode, DBR_USHORT, 0);
if (strcmp(name, "mbboDirect") == 0)
testdbPutFieldOk(nameB0, DBR_LONG, 1);
else
testdbPutFieldOk(nameVAL, DBR_LONG, 1);
if (strcmp(name, "aao") == 0)
testdbGetFieldEqual(nameSimvalNORD, DBR_USHORT, 0);
else if (strcmp(name, "lso") == 0)
testdbGetFieldEqual(nameSimvalLEN, DBR_USHORT, 0);
else
testdbGetFieldEqual(nameSimval, DBR_USHORT, 0);
testDiag("in simmYES, SIOL is used to write VAL");
testdbPutFieldOk(nameSimmode, DBR_USHORT, 1);
if (strcmp(name, "mbboDirect") == 0)
testdbPutFieldOk(nameB0, DBR_LONG, 1);
else
testdbPutFieldOk(nameVAL, DBR_LONG, 1);
testdbGetFieldEqual(nameSimval, DBR_USHORT, 1);
/* Set TSE to -2 (from device) and reprocess: timestamp is taken from IOC */
epicsTimeGetCurrent(&now);
testdbPutFieldOk(nameTSE, DBR_SHORT, -2);
testdbPutFieldOk(namePROC, DBR_LONG, 0);
testOk(epicsTimeLessThan(&now, mytime), "new time stamp from IOC [TSE = -2]");
/* Reset TSE */
testdbPutFieldOk(nameTSE, DBR_SHORT, 0);
}
/*
* Asynchronous processing using simm:DELAY
*/
static
void testSimmDelay(const char *name,
epicsFloat64 *psdly,
const epicsTimeStamp *mytime)
{
epicsTimeStamp now;
const double delay = 0.01; /* 10 ms */
testDiag("## Asynchronous processing with simm:DELAY ##");
/* Set delay to something just long enough */
*psdly = delay;
/* Process in simmNO: synchronous */
testDiag("simm:DELAY and simmNO processes synchronously");
testdbPutFieldOk(nameSimmode, DBR_USHORT, 0);
epicsTimeGetCurrent(&now);
testdbPutFieldOk(namePROC, DBR_LONG, 0);
testdbGetFieldEqual(namePACT, DBR_USHORT, 0);
testOk(epicsTimeLessThan(&now, mytime), "time stamp is recent");
/* Process in simmYES: asynchronous */
testDiag("simm:DELAY and simmYES processes asynchronously");
testdbPutFieldOk(nameSimmode, DBR_USHORT, 1);
testdbPutFieldOk(namePROC, DBR_LONG, 0);
testdbGetFieldEqual(namePACT, DBR_USHORT, 1);
epicsTimeGetCurrent(&now);
epicsThreadSleep(1.75*delay);
testdbGetFieldEqual(namePACT, DBR_USHORT, 0);
testOk(epicsTimeLessThan(&now, mytime), "time stamp taken from second pass processing");
/* Reset delay */
*psdly = -1.;
}
#define RUNALLTESTSREAD(type) \
testDiag("################################################### Record Type " #type); \
setNames(#type); \
testSimmToggle(#type, &((type ## Record*)testdbRecordPtr(#type))->sscn); \
testSvalRead(#type, &((type ## Record*)testdbRecordPtr(#type))->time, \
&((type ## Record*)testdbRecordPtr(#type ":simval"))->time); \
testSimmDelay(#type, &((type ## Record*)testdbRecordPtr(#type))->sdly, \
&((type ## Record*)testdbRecordPtr(#type))->time)
#define RUNALLTESTSWRITE(type) \
testDiag("################################################### Record Type " #type); \
setNames(#type); \
testSimmToggle(#type, &((type ## Record*)testdbRecordPtr(#type))->sscn); \
testSiolWrite(#type, &((type ## Record*)testdbRecordPtr(#type))->time); \
testSimmDelay(#type, &((type ## Record*)testdbRecordPtr(#type))->sdly, \
&((type ## Record*)testdbRecordPtr(#type))->time)
static
void testAllRecTypes(void)
{
RUNALLTESTSREAD(ai);
RUNALLTESTSWRITE(ao);
RUNALLTESTSREAD(aai);
RUNALLTESTSWRITE(aao);
RUNALLTESTSREAD(bi);
RUNALLTESTSWRITE(bo);
RUNALLTESTSREAD(mbbi);
RUNALLTESTSWRITE(mbbo);
RUNALLTESTSREAD(mbbiDirect);
RUNALLTESTSWRITE(mbboDirect);
RUNALLTESTSREAD(longin);
RUNALLTESTSWRITE(longout);
RUNALLTESTSREAD(int64in);
RUNALLTESTSWRITE(int64out);
RUNALLTESTSREAD(stringin);
RUNALLTESTSWRITE(stringout);
RUNALLTESTSREAD(lsi);
RUNALLTESTSWRITE(lso);
RUNALLTESTSREAD(event);
RUNALLTESTSREAD(waveform);
RUNALLTESTSREAD(histogram);
}
MAIN(simmTest)
{
testPlan(1176);
startSimmTestIoc("simmTest.db");
testSimmSetup();
testSimlFail();
testAllRecTypes();
testIocShutdownOk();
testdbCleanup();
return testDone();
}
@@ -0,0 +1,32 @@
file "simmTestSimple.template" {
{ TYPE="ai" }
{ TYPE="ao" }
{ TYPE="bi" }
{ TYPE="bo" }
{ TYPE="mbbi" }
{ TYPE="mbbo" }
{ TYPE="mbbiDirect" }
{ TYPE="mbboDirect" }
{ TYPE="longin" }
{ TYPE="longout" }
{ TYPE="int64in" }
{ TYPE="int64out" }
{ TYPE="stringin" }
{ TYPE="stringout" }
{ TYPE="lsi" }
{ TYPE="lso" }
{ TYPE="event" }
}
file "simmTestArray.template" {
{ TYPE="aai" }
{ TYPE="aao" }
{ TYPE="waveform" }
}
file "simmTestHistogram.template" {
{ TYPE="histogram" }
}
file "simmSetup.db" {
{}
file "simmSimlFail.db" {
{}
}
@@ -0,0 +1,15 @@
# Array type records
# Regular simulation mode and simm:SCAN tests
record($(TYPE), "$(TYPE)") {
field(SSCN,"Event")
field(FTVL,"SHORT")
field(NELM,"2")
}
record($(TYPE), "$(TYPE):simval") {
field(FTVL,"SHORT")
field(NELM,"2")
}
record(bo, "$(TYPE):simmode") {
field(ZNAM,"off")
field(ONAM,"on")
}
@@ -0,0 +1,12 @@
# Array type records
# Regular simulation mode and simm:SCAN tests
record($(TYPE), "$(TYPE)") {
field(SSCN,"Event")
field(NELM,"2")
}
record(ai, "$(TYPE):simval") {
}
record(bo, "$(TYPE):simmode") {
field(ZNAM,"off")
field(ONAM,"on")
}
@@ -0,0 +1,10 @@
# Regular simulation mode and simm:SCAN tests
record($(TYPE), "$(TYPE)") {
field(SSCN,"Event")
}
record($(TYPE), "$(TYPE):simval") {
}
record(bo, "$(TYPE):simmode") {
field(ZNAM,"off")
field(ONAM,"on")
}
+251
View File
@@ -0,0 +1,251 @@
/*************************************************************************\
* Copyright (c) 2017 UChicago Argonne LLC, as operator of Argonne
* National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include <string.h>
#include "dbAccess.h"
#include "dbStaticLib.h"
#include "dbTest.h"
#include "dbUnitTest.h"
#include "epicsThread.h"
#include "epicsEvent.h"
#include "errlog.h"
#include "registryFunction.h"
#include "subRecord.h"
#include "testMain.h"
static
void checkDtyp(const char *rec)
{
char dtyp[16];
strcpy(dtyp, rec);
strcat(dtyp, ".DTYP");
testdbGetFieldEqual(dtyp, DBR_LONG, 0);
testdbGetFieldEqual(dtyp, DBR_STRING, "Soft Channel");
}
static
void doProcess(const char *rec)
{
char proc[16];
strcpy(proc, rec);
strcat(proc, ".PROC");
testdbPutFieldOk(proc, DBR_CHAR, 1);
}
/* Group 0 are soft-channel input records with INP being a DB or CA link
* to the PV 'source'. Their VAL fields all start out with the default
* value for the type, i.e. 0 or an empty string. Triggering record
* processing reads the value from the 'source' PV.
*/
static
void testGroup0(void)
{
const char ** rec;
const char * records[] = {
"ai0", "bi0", "di0", "ii0", "li0", "lsi0", "mi0", "si0",
"ai0c", "bi0c", "di0c", "ii0c", "li0c", "lsi0c", "mi0c", "si0c",
NULL
};
testDiag("============ Starting %s ============", EPICS_FUNCTION);
testdbPutFieldOk("source", DBR_LONG, 1);
/* The above put sends CA monitors to all of the CA links, but
* doesn't trigger record processing (the links are not CP/CPP).
* How could we wait until all of those monitors have arrived,
* instead of just waiting for an arbitrary time period?
*/
epicsThreadSleep(1.0); /* FIXME: Wait here? */
for (rec = records; *rec; rec++) {
if (strncmp(*rec, "lsi0", 4) != 0)
testdbGetFieldEqual(*rec, DBR_LONG, 0);
checkDtyp(*rec);
doProcess(*rec);
testdbGetFieldEqual(*rec, DBR_LONG, 1);
}
testdbPutFieldOk("source", DBR_LONG, 0);
epicsThreadSleep(1.0); /* FIXME: Wait here as above */
for (rec = records; *rec; rec++) {
doProcess(*rec);
testdbGetFieldEqual(*rec, DBR_LONG, 0);
}
}
/* Group 1 are all soft-channel input records with INP being a non-zero
* "const" JSON-link, 9 for most records, 1 for the binary. Their VAL
* fields should all be initialized to that constant value. Triggering
* record processing should succeed, but shouldn't change VAL.
*/
static
void testGroup1(void)
{
const char ** rec;
const char * records[] = {
"bi1",
"ai1", "di1", "ii1", "li1", "lsi1", "mi1", "si1", NULL
};
int init = 1; /* bi1 initializes to 1 */
testDiag("============ Starting %s ============", EPICS_FUNCTION);
for (rec = records; *rec; rec++) {
testdbGetFieldEqual(*rec, DBR_LONG, init);
doProcess(*rec);
testdbGetFieldEqual(*rec, DBR_LONG, init);
init = 9; /* other records initialize to 9 */
}
}
/* Group 2 are all soft-channel input records with INP being a CONSTANT
* link with value 9 for most records, 1 for the binary. Their VAL
* fields should all be initialized to that constant value. Triggering
* record processing should succeed, but shouldn't change VAL.
*/
static
void testGroup2(void)
{
const char ** rec;
const char * records[] = {
"bi2",
"ai2", "di2", "ii2", "li2", "mi2", NULL
};
int init = 1; /* bi1 initializes to 1 */
testDiag("============ Starting %s ============", EPICS_FUNCTION);
for (rec = records; *rec; rec++) {
testdbGetFieldEqual(*rec, DBR_LONG, init);
doProcess(*rec);
testdbGetFieldEqual(*rec, DBR_LONG, init);
init = 9; /* other records initialize to 9 */
}
}
int dest;
epicsEventId destEvent;
static
long destSubr(subRecord *prec)
{
dest = prec->val;
prec->val = -1;
epicsEventMustTrigger(destEvent);
return 0;
}
static
void checkOutput3(const char *rec, int value)
{
testDiag("Checking record '%s'", rec);
testdbPutFieldOk(rec, DBR_LONG, value);
epicsEventMustWait(destEvent);
testOk(dest == value, "value %d output -> %d", value, dest);
}
/* Group 3 are all soft-channel output records with OUT being a DB or
* local CA link to the subRecord 'dest'; DB links have the PP flag,
* for CA links the VAL field is marked PP. Putting a value to the
* output record writes that value to 'dest'.
*/
static
void testGroup3(void)
{
const char ** rec;
const char * records[] = {
"ao3", "bo3", "io3", "lo3", "lso3", "mo3", "so3",
"ao3c", "bo3c", "io3c", "lo3c", "lso3c", "mo3c", "so3c",
NULL,
};
destEvent = epicsEventMustCreate(epicsEventEmpty);
testDiag("============ Starting %s ============", EPICS_FUNCTION);
for (rec = records; *rec; rec++) {
checkOutput3(*rec, 1);
checkDtyp(*rec);
}
checkOutput3("do3.B0", 1);
checkDtyp("do3");
checkOutput3("do3c.B0", 1);
checkDtyp("do3c");
for (rec = records; *rec; rec++) {
checkOutput3(*rec, 0);
}
checkOutput3("do3.B0", 0);
checkOutput3("do3c.B0", 0);
}
static
void checkOutput4(const char *rec, int value)
{
testDiag("Checking record '%s'", rec);
testdbPutFieldOk(rec, DBR_LONG, value);
}
/* Group 4 are all soft-channel output records with OUT being empty
* (i.e. a CONSTANT link). Putting a value to the record must succeed.
*/
static
void testGroup4(void)
{
const char ** rec;
const char * records[] = {
"ao4", "bo4", "do4.B0", "io4", "lo4", "lso4", "mo4", "so4", NULL,
};
testDiag("============ Starting %s ============", EPICS_FUNCTION);
for (rec = records; *rec; rec++) {
checkOutput4(*rec, 0);
}
}
void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
MAIN(softTest)
{
testPlan(258);
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
recTestIoc_registerRecordDeviceDriver(pdbbase);
registryFunctionAdd("destSubr", (REGISTRYFUNCTION) destSubr);
testdbReadDatabase("softTest.db", NULL, NULL);
eltc(0);
testIocInitOk();
eltc(1);
testGroup0();
testGroup1();
testGroup2();
testGroup3();
testGroup4();
testIocShutdownOk();
testdbCleanup();
return testDone();
}
+393
View File
@@ -0,0 +1,393 @@
# Group 0 are all soft-channel input records with INP being a DB link
# to the PV 'source'. Their VAL fields all start out with the default
# value for the type, i.e. 0 or an empty string. Triggering record
# processing should read the integer value from the 'source' PV.
record(longin, "source") {}
record(ai, "ai0") {
field(DTYP, "Soft Channel")
field(INP, "source")
}
record(bi, "bi0") {
field(DTYP, "Soft Channel")
field(INP, "source")
field(ZNAM, "Zero")
field(ONAM, "One")
}
record(int64in, "ii0") {
field(DTYP, "Soft Channel")
field(INP, "source")
}
record(longin, "li0") {
field(DTYP, "Soft Channel")
field(INP, "source")
}
record(mbbiDirect, "di0") {
field(DTYP, "Soft Channel")
field(NOBT, 4)
field(INP, "source")
}
record(mbbi, "mi0") {
field(DTYP, "Soft Channel")
field(NOBT, 4)
field(INP, "source")
field(ZRST, "Zero")
field(ONST, "One")
field(TWST, "Two")
field(THST, "Three")
field(FRST, "Four")
field(FVST, "Five")
field(SXST, "Six")
field(SVST, "Seven")
field(EIST, "Eight")
field(NIST, "Nine")
field(TEST, "Ten")
field(ELST, "Eleven")
field(TWST, "Twelve")
field(TTST, "Thirteen")
field(FTST, "Fourteen")
field(FFST, "Fifteen")
}
record(lsi, "lsi0") {
field(DTYP, "Soft Channel")
field(SIZV, 40)
field(INP, "source")
}
record(stringin, "si0") {
field(DTYP, "Soft Channel")
field(INP, "source")
}
record(ai, "ai0c") {
field(DTYP, "Soft Channel")
field(INP, "source CA")
}
record(bi, "bi0c") {
field(DTYP, "Soft Channel")
field(INP, "source CA")
field(ZNAM, "Zero")
field(ONAM, "One")
}
record(int64in, "ii0c") {
field(DTYP, "Soft Channel")
field(INP, "source CA")
}
record(longin, "li0c") {
field(DTYP, "Soft Channel")
field(INP, "source CA")
}
record(mbbiDirect, "di0c") {
field(DTYP, "Soft Channel")
field(NOBT, 4)
field(INP, "source CA")
}
record(mbbi, "mi0c") {
field(DTYP, "Soft Channel")
field(NOBT, 4)
field(INP, "source CA")
field(ZRST, "Zero")
field(ONST, "One")
field(TWST, "Two")
field(THST, "Three")
field(FRST, "Four")
field(FVST, "Five")
field(SXST, "Six")
field(SVST, "Seven")
field(EIST, "Eight")
field(NIST, "Nine")
field(TEST, "Ten")
field(ELST, "Eleven")
field(TWST, "Twelve")
field(TTST, "Thirteen")
field(FTST, "Fourteen")
field(FFST, "Fifteen")
}
record(lsi, "lsi0c") {
field(DTYP, "Soft Channel")
field(SIZV, 40)
field(INP, "source CA")
}
record(stringin, "si0c") {
field(DTYP, "Soft Channel")
field(INP, "source CA")
}
# Group 1 are all soft-channel input records with INP being a non-zero
# "const" JSON-link, 9 for most records, 1 for the binary. Their VAL
# fields should all be initialized to that constant value. Triggering
# record processing should succeed, but shouldn't change VAL.
record(ai, "ai1") {
field(DTYP, "Soft Channel")
field(INP, {const:9})
}
record(bi, "bi1") {
field(DTYP, "Soft Channel")
field(INP, {const:1})
field(ZNAM, "Zero")
field(ONAM, "One")
}
record(int64in, "ii1") {
field(DTYP, "Soft Channel")
field(INP, {const:9})
}
record(longin, "li1") {
field(DTYP, "Soft Channel")
field(INP, {const:9})
}
record(mbbiDirect, "di1") {
field(DTYP, "Soft Channel")
field(NOBT, 4)
field(INP, {const:9})
}
record(mbbi, "mi1") {
field(DTYP, "Soft Channel")
field(NOBT, 4)
field(INP, {const:9})
field(ZRST, "Zero")
field(ONST, "One")
field(TWST, "Two")
field(THST, "Three")
field(FRST, "Four")
field(FVST, "Five")
field(SXST, "Six")
field(SVST, "Seven")
field(EIST, "Eight")
field(NIST, "Nine")
field(TEST, "Ten")
field(ELST, "Eleven")
field(TWST, "Twelve")
field(TTST, "Thirteen")
field(FTST, "Fourteen")
field(FFST, "Fifteen")
}
record(lsi, "lsi1") {
field(DTYP, "Soft Channel")
field(SIZV, 40)
field(INP, {const:"9"})
}
record(stringin, "si1") {
field(DTYP, "Soft Channel")
field(INP, {const:"9"})
}
# Group 2 are all soft-channel input records with INP being a CONSTANT
# link with value 9 for most records, 1 for the binary. Their VAL
# fields should all be initialized to that constant value. Triggering
# record processing should succeed, but shouldn't change VAL.
record(ai, "ai2") {
field(DTYP, "Soft Channel")
field(INP, 9)
}
record(bi, "bi2") {
field(DTYP, "Soft Channel")
field(INP, 1)
field(ZNAM, "Zero")
field(ONAM, "One")
}
record(int64in, "ii2") {
field(DTYP, "Soft Channel")
field(INP, 9)
}
record(longin, "li2") {
field(DTYP, "Soft Channel")
field(INP, 9)
}
record(mbbiDirect, "di2") {
field(DTYP, "Soft Channel")
field(NOBT, 4)
field(INP, 9)
}
record(mbbi, "mi2") {
field(DTYP, "Soft Channel")
field(NOBT, 4)
field(INP, 9)
field(ZRST, "Zero")
field(ONST, "One")
field(TWST, "Two")
field(THST, "Three")
field(FRST, "Four")
field(FVST, "Five")
field(SXST, "Six")
field(SVST, "Seven")
field(EIST, "Eight")
field(NIST, "Nine")
field(TEST, "Ten")
field(ELST, "Eleven")
field(TWST, "Twelve")
field(TTST, "Thirteen")
field(FTST, "Fourteen")
field(FFST, "Fifteen")
}
# Group 3 are all soft-channel output records with OUT being a DB or
# CA link to the PV 'dest' with PP. Putting a value to the record
# under test writes the value to 'dest' and processes it.
record(sub, "dest") {
field(SNAM, "destSubr")
field(VAL, -1)
}
record(ao, "ao3") {
field(DTYP, "Soft Channel")
field(OUT, "dest PP")
}
record(bo, "bo3") {
field(DTYP, "Soft Channel")
field(OUT, "dest PP")
field(ZNAM, "Zero")
field(ONAM, "One")
}
record(int64out, "io3") {
field(DTYP, "Soft Channel")
field(OUT, "dest PP")
}
record(longout, "lo3") {
field(DTYP, "Soft Channel")
field(OUT, "dest PP")
}
record(mbboDirect, "do3") {
field(DTYP, "Soft Channel")
field(NOBT, 4)
field(OUT, "dest PP")
}
record(mbbo, "mo3") {
field(DTYP, "Soft Channel")
field(NOBT, 4)
field(OUT, "dest PP")
field(ZRST, "Zero")
field(ONST, "One")
field(TWST, "Two")
field(THST, "Three")
field(FRST, "Four")
field(FVST, "Five")
field(SXST, "Six")
field(SVST, "Seven")
field(EIST, "Eight")
field(NIST, "Nine")
field(TEST, "Ten")
field(ELST, "Eleven")
field(TWST, "Twelve")
field(TTST, "Thirteen")
field(FTST, "Fourteen")
field(FFST, "Fifteen")
}
record(lso, "lso3") {
field(DTYP, "Soft Channel")
field(OUT, "dest PP")
field(SIZV, 40)
}
record(stringout, "so3") {
field(DTYP, "Soft Channel")
field(OUT, "dest PP")
}
record(ao, "ao3c") {
field(DTYP, "Soft Channel")
field(OUT, "dest CA")
}
record(bo, "bo3c") {
field(DTYP, "Soft Channel")
field(OUT, "dest CA")
field(ZNAM, "Zero")
field(ONAM, "One")
}
record(int64out, "io3c") {
field(DTYP, "Soft Channel")
field(OUT, "dest CA")
}
record(longout, "lo3c") {
field(DTYP, "Soft Channel")
field(OUT, "dest CA")
}
record(mbboDirect, "do3c") {
field(DTYP, "Soft Channel")
field(NOBT, 4)
field(OUT, "dest CA")
}
record(mbbo, "mo3c") {
field(DTYP, "Soft Channel")
field(NOBT, 4)
field(OUT, "dest CA")
field(ZRST, "Zero")
field(ONST, "One")
field(TWST, "Two")
field(THST, "Three")
field(FRST, "Four")
field(FVST, "Five")
field(SXST, "Six")
field(SVST, "Seven")
field(EIST, "Eight")
field(NIST, "Nine")
field(TEST, "Ten")
field(ELST, "Eleven")
field(TWST, "Twelve")
field(TTST, "Thirteen")
field(FTST, "Fourteen")
field(FFST, "Fifteen")
}
record(lso, "lso3c") {
field(DTYP, "Soft Channel")
field(OUT, "dest CA")
field(SIZV, 40)
}
record(stringout, "so3c") {
field(DTYP, "Soft Channel")
field(OUT, "dest CA")
}
# Group 4 are all soft-channel output records with OUT being empty
# (i.e. a CONSTANT link). Putting a value to the record must succeed.
record(ao, "ao4") {
field(DTYP, "Soft Channel")
}
record(bo, "bo4") {
field(DTYP, "Soft Channel")
field(ZNAM, "Zero")
field(ONAM, "One")
}
record(int64out, "io4") {
field(DTYP, "Soft Channel")
}
record(longout, "lo4") {
field(DTYP, "Soft Channel")
}
record(mbboDirect, "do4") {
field(DTYP, "Soft Channel")
field(NOBT, 4)
}
record(mbbo, "mo4") {
field(DTYP, "Soft Channel")
field(NOBT, 4)
field(ZRST, "Zero")
field(ONST, "One")
field(TWST, "Two")
field(THST, "Three")
field(FRST, "Four")
field(FVST, "Five")
field(SXST, "Six")
field(SVST, "Seven")
field(EIST, "Eight")
field(NIST, "Nine")
field(TEST, "Ten")
field(ELST, "Eleven")
field(TWST, "Twelve")
field(TTST, "Thirteen")
field(FTST, "Fourteen")
field(FFST, "Fifteen")
}
record(lso, "lso4") {
field(DTYP, "Soft Channel")
field(SIZV, 40)
}
record(stringout, "so4") {
field(DTYP, "Soft Channel")
}