Added chfPlugin, a simple interface for channel filter plugins.

- chfPlugin.h contains the simple jump table and doxygen documentation
  - chfPlugin.c is a wrapper library around the yajl callbacks that parses
    any client-supplied configuration into the user's structure
  - test/chfPluginTest.c tests the library and the data conversion
This commit is contained in:
Michael Davidsaver
2012-04-27 13:21:51 -04:00
parent d22b719e32
commit de134ea72d
5 changed files with 1231 additions and 0 deletions

View File

@@ -32,6 +32,7 @@ INC += db_field_log.h
INC += initHooks.h
INC += recGbl.h
INC += dbIocRegister.h
INC += chfPlugin.h
INC += db_access_routines.h
INC += db_convert.h
@@ -78,4 +79,5 @@ dbCore_SRCS += dbPutNotifyBlocker.cpp
dbCore_SRCS += dbContextReadNotifyCache.cpp
dbCore_SRCS += templateInstances.cpp
dbCore_SRCS += dbIocRegister.c
dbCore_SRCS += chfPlugin.c

613
src/ioc/db/chfPlugin.c Normal file
View File

@@ -0,0 +1,613 @@
/*************************************************************************\
* 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.
\*************************************************************************/
/* Based on the linkoptions utility by Michael Davidsaver (BNL) */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <math.h>
#include <dbDefs.h>
#include <dbStaticLib.h>
#include <epicsTypes.h>
#include <epicsString.h>
#include <errlog.h>
#define epicsExportSharedSymbols
#include "chfPlugin.h"
#ifndef HUGE_VALF
# define HUGE_VALF HUGE_VAL
#endif
#ifndef HUGE_VALL
# define HUGE_VALL (-(HUGE_VAL))
#endif
/*
* Data for a chfPlugin
*/
typedef struct chfPluginCtx {
const chfPluginArgDef *opts;
size_t nopts;
const chfPluginIf *pif;
} chfPluginCtx;
/*
* Parser state data for a chfFilter (chfPlugin instance)
*/
typedef struct chfFilterCtx {
const chfPluginCtx *plugin;
epicsUInt32 *found;
void *puser;
epicsInt16 nextParam;
} chfFilterCtx;
/* Data types we get from the parser */
typedef enum chfPluginType {
chfPluginTypeBool,
chfPluginTypeInt,
chfPluginTypeDouble,
chfPluginTypeString
} chfPluginType;
/*
* Convert the (long) integer value 'val' to the type named in 'opt->optType'
* and store the result at 'user + opt->offset'.
*/
static
int
store_integer_value(const chfPluginArgDef *opt, void *user, long val)
{
long *ival;
int *eval;
const chfPluginEnumType *emap;
double *dval;
char *sval;
char buff[22]; /* 2^64 = 1.8e+19, so 20 digits plus sign max */
/* printf("Got an integer for %s (type %d): %ld\n", opt->name, opt->optType, val); */
if (!opt->convert && opt->optType != chfPluginArgInt32) {
return -1;
}
switch (opt->optType) {
case chfPluginArgInt32:
ival = (long*) ((char*)user + opt->offset);
*ival = val;
break;
case chfPluginArgBoolean:
eval = (int*) ((char*)user + opt->offset);
*eval = !!val;
break;
case chfPluginArgDouble:
dval = (double*) ((char*)user + opt->offset);
*dval = (double) val;
break;
case chfPluginArgString:
sval = ((char*)user + opt->offset);
sprintf(buff, "%ld", val);
if (strlen(buff) > opt->size-1) {
return -1;
}
strncpy(sval, buff, opt->size-1);
sval[opt->size-1]='\0';
break;
case chfPluginArgEnum:
eval = (int*) ((char*)user + opt->offset);
for (emap = opt->enums; emap && emap->name; emap++) {
if (val == emap->value) {
*eval = val;
break;
}
}
if (!emap || !emap->name) {
return -1;
}
break;
case chfPluginArgInvalid:
return -1;
}
return 0;
}
/*
* Convert the (int) boolean value 'val' to the type named in 'opt->optType'
* and store the result at 'user + opt->offset'.
*/
static
int
store_boolean_value(const chfPluginArgDef *opt, void *user, int val)
{
long *ival;
int *eval;
double *dval;
char *sval;
/* printf("Got a boolean for %s (type %d): %d\n", opt->name, opt->optType, val); */
if (!opt->convert && opt->optType != chfPluginArgBoolean) {
return -1;
}
switch (opt->optType) {
case chfPluginArgInt32:
ival = (long*) ((char*)user + opt->offset);
*ival = val;
break;
case chfPluginArgBoolean:
eval = (int*) ((char*)user + opt->offset);
*eval = val;
break;
case chfPluginArgDouble:
dval = (double*) ((char*)user + opt->offset);
*dval = val ? 1. : 0.;
break;
case chfPluginArgString:
sval = ((char*)user + opt->offset);
if ((val ? 4 : 5) > opt->size-1) {
return -1;
}
strncpy(sval, (val ? "true" : "false"), opt->size-1);
sval[opt->size-1]='\0';
break;
case chfPluginArgEnum:
case chfPluginArgInvalid:
return -1;
}
return 0;
}
/*
* Convert the double value 'val' to the type named in 'opt->optType'
* and store the result at 'user + opt->offset'.
*/
static
int
store_double_value(const chfPluginArgDef *opt, void *user, double val)
{
long *ival;
int *eval;
double *dval;
char *sval;
int i;
/*
printf("Got a double for %s (type %d, convert: %s): %g\n",
opt->name, opt->optType, opt->convert?"yes":"no", val);
*/
if (!opt->convert && opt->optType != chfPluginArgDouble) {
return -1;
}
switch (opt->optType) {
case chfPluginArgInt32:
if (val < LONG_MIN || val > LONG_MAX) {
return -1;
}
ival = (long*) ((char*)user + opt->offset);
*ival = (long) val;
break;
case chfPluginArgBoolean:
eval = (int*) ((char*)user + opt->offset);
*eval = (val != 0.) ? 1 : 0;
break;
case chfPluginArgDouble:
dval = (double*) ((char*)user + opt->offset);
*dval = val;
break;
case chfPluginArgString:
sval = ((char*)user + opt->offset);
if (opt->size <= 8) { /* Play it safe: 3 exp + 2 sign + 'e' + '.' */
return -1;
}
i = snprintf(sval, opt->size, "%.*g", opt->size - 7, val);
if (i >= opt->size) {
return -1;
}
break;
case chfPluginArgEnum:
case chfPluginArgInvalid:
return -1;
}
return 0;
}
/*
* Convert the (char*) string value 'val' to the type named in 'opt->optType'
* and store the result at 'user + opt->offset'.
*/
static
int
store_string_value(const chfPluginArgDef *opt, void *user, const char *val, size_t len)
{
long *ival;
int *eval;
const chfPluginEnumType *emap;
double *dval;
char *sval;
char *end;
int i;
/* printf("Got a string for %s (type %d): %.*s\n", opt->name, opt->optType, len, val); */
if (!opt->convert && opt->optType != chfPluginArgString && opt->optType != chfPluginArgEnum) {
return -1;
}
switch (opt->optType) {
case chfPluginArgInt32:
ival = (long*) ((char*)user + opt->offset);
*ival = strtol(val, &end, 0);
/* test for the myriad error conditions which strtol may use */
if (*ival == LONG_MAX || *ival == LONG_MIN || end == val) {
return -1;
}
break;
case chfPluginArgBoolean:
eval = (int*) ((char*)user + opt->offset);
if (strncasecmp(val, "true", len) == 0) {
*eval = 1;
} else if (strncasecmp(val, "false", len) == 0) {
*eval = 0;
} else {
i = strtol(val, &end, 0);
if (i > INT_MAX || i < INT_MIN || end == val) {
return -1;
}
*eval = !!i;
}
break;
case chfPluginArgDouble:
dval = (double*) ((char*)user + opt->offset);
*dval = strtod(val, &end);
/* Indicates errors in the same manner as strtol */
if (*dval==HUGE_VALF||*dval==HUGE_VALL||end==val )
{
return -1;
}
break;
case chfPluginArgString:
i = opt->size-1 < len ? opt->size-1 : len;
sval = ((char*)user + opt->offset);
strncpy(sval, val, i);
sval[i] = '\0';
break;
case chfPluginArgEnum:
eval = (int*) ((char*)user + opt->offset);
for (emap = opt->enums; emap && emap->name; emap++) {
if (strncmp(emap->name, val, len) == 0) {
*eval = emap->value;
break;
}
}
if( !emap || !emap->name ) {
return -1;
}
break;
case chfPluginArgInvalid:
return -1;
}
return 0;
}
static void freeInstanceData(chfFilterCtx *fctx)
{
free(fctx->found); /* FIXME: Use a free-list */
free(fctx); /* FIXME: Use a free-list */
}
/*
* chFilterIf callbacks
*/
/* First entry point when a new filter instance is created.
* All per-instance allocations happen here.
*/
static parse_result parse_start(chFilter *filter)
{
chfPluginCtx *pctx = (chfPluginCtx*) filter->plug->puser;
chfFilterCtx *fctx;
/* Filter context */
/* FIXME: Use a free-list */
fctx = calloc(1, sizeof(chfFilterCtx));
if (!fctx) {
fprintf(stderr,"chfFilterCtx calloc failed\n");
goto errfctx;
}
fctx->nextParam = -1;
/* Bit array to find missing required keys */
/* FIXME: Use a free-list */
fctx->found = calloc( (pctx->nopts/32)+1, sizeof(epicsUInt32) );
if (!fctx->found) {
fprintf(stderr,"chfConfigParseStart: bit array calloc failed\n");
goto errbitarray;
}
/* Call the plugin to allocate its structure, it returns NULL on error */
if (pctx->pif->allocPvt) {
if ((fctx->puser = pctx->pif->allocPvt()) == NULL)
goto errplugin;
}
filter->puser = (void*) fctx;
return parse_continue;
errplugin:
free(fctx->found); /* FIXME: Use a free-list */
errbitarray:
free(fctx); /* FIXME: Use a free-list */
errfctx:
return parse_stop;
}
static void parse_abort(chFilter *filter) {
chfPluginCtx *pctx = (chfPluginCtx*) filter->plug->puser;
chfFilterCtx *fctx = (chfFilterCtx*) filter->puser;
/* Call the plugin to tell it we're aborting */
if (pctx->pif->parse_error) pctx->pif->parse_error();
if (pctx->pif->freePvt) pctx->pif->freePvt(fctx->puser);
freeInstanceData(fctx);
}
static parse_result parse_end(chFilter *filter)
{
chfPluginCtx *pctx = (chfPluginCtx*) filter->plug->puser;
chfFilterCtx *fctx = (chfFilterCtx*) filter->puser;
const chfPluginArgDef *cur;
int i;
for(cur = pctx->opts, i = 0; cur && cur->name; cur++, i++) {
if ( !(fctx->found[i/32] & (1 << (i%32))) && cur->required ) {
if (pctx->pif->parse_error) pctx->pif->parse_error();
if (pctx->pif->freePvt) pctx->pif->freePvt(fctx->puser);
freeInstanceData(fctx);
return parse_stop;
}
}
/* Call the plugin to tell it we're done */
if (pctx->pif->parse_ok) {
if (pctx->pif->parse_ok(fctx->puser)) {
if (pctx->pif->freePvt) pctx->pif->freePvt(fctx->puser);
freeInstanceData(fctx);
return parse_stop;
}
}
return parse_continue;
}
static parse_result parse_boolean(chFilter *filter, int boolVal)
{
const chfPluginArgDef *opts = ((chfPluginCtx*)filter->plug->puser)->opts;
chfFilterCtx *ctx = (chfFilterCtx*)filter->puser;
if (ctx->nextParam < 0 || store_boolean_value(&opts[ctx->nextParam], ctx->puser, boolVal)) {
return parse_stop;
} else {
return parse_continue;
}
}
static parse_result parse_integer(chFilter *filter, long integerVal)
{
const chfPluginArgDef *opts = ((chfPluginCtx*)filter->plug->puser)->opts;
chfFilterCtx *ctx = (chfFilterCtx*)filter->puser;
if (ctx->nextParam < 0 || store_integer_value(&opts[ctx->nextParam], ctx->puser, integerVal)) {
return parse_stop;
} else {
return parse_continue;
}
}
static parse_result parse_double(chFilter *filter, double doubleVal)
{
const chfPluginArgDef *opts = ((chfPluginCtx*)filter->plug->puser)->opts;
chfFilterCtx *ctx = (chfFilterCtx*)filter->puser;
if (ctx->nextParam < 0 || store_double_value(&opts[ctx->nextParam], ctx->puser, doubleVal)) {
return parse_stop;
} else {
return parse_continue;
}
}
static parse_result parse_string(chFilter *filter, const char *stringVal, size_t stringLen)
{
const chfPluginArgDef *opts = ((chfPluginCtx*)filter->plug->puser)->opts;
chfFilterCtx *ctx = (chfFilterCtx*)filter->puser;
if (ctx->nextParam < 0 || store_string_value(&opts[ctx->nextParam], ctx->puser, stringVal, stringLen)) {
return parse_stop;
} else {
return parse_continue;
}
}
static parse_result parse_start_map(chFilter *filter)
{
return parse_continue;
}
static parse_result parse_map_key(chFilter *filter, const char *key, size_t stringLen)
{
const chfPluginArgDef *cur;
const chfPluginArgDef *opts = ((chfPluginCtx*)filter->plug->puser)->opts;
chfFilterCtx *ctx = (chfFilterCtx*)filter->puser;
int i;
ctx->nextParam = -1;
for(cur = opts, i = 0; cur && cur->name; cur++, i++) {
if (strncmp(key, cur->name, stringLen) == 0) {
ctx->nextParam = i;
break;
}
}
if (ctx->nextParam == -1) {
return parse_stop;
}
ctx->found[i/32] |= 1<<(i%32);
return parse_continue;
}
static parse_result parse_end_map(chFilter *filter)
{
return parse_continue;
}
static long channel_open(chFilter *filter)
{
chfPluginCtx *pctx = (chfPluginCtx*) filter->plug->puser;
chfFilterCtx *fctx = (chfFilterCtx*) filter->puser;
if (pctx->pif->channel_open) return pctx->pif->channel_open(filter->chan, fctx->puser);
else return 0;
}
static void channel_report(chFilter *filter, const char *intro, int level)
{
chfPluginCtx *pctx = (chfPluginCtx*) filter->plug->puser;
chfFilterCtx *fctx = (chfFilterCtx*) filter->puser;
if (pctx->pif->channel_report) pctx->pif->channel_report(filter->chan, fctx->puser, intro, level);
}
static void channel_close(chFilter *filter)
{
chfPluginCtx *pctx = (chfPluginCtx*) filter->plug->puser;
chfFilterCtx *fctx = (chfFilterCtx*) filter->puser;
if (pctx->pif->channel_close) pctx->pif->channel_close(filter->chan, fctx->puser);
if (pctx->pif->freePvt) pctx->pif->freePvt(fctx->puser);
free(fctx->found); /* FIXME: Use a free-list */
free(fctx); /* FIXME: Use a free-list */
}
/*
* chFilterIf for the wrapper
* we just support a simple one-level map, and no arrays
*/
static chFilterIf wrapper_fif = {
parse_start,
parse_abort,
parse_end,
NULL, /* parse_null, */
parse_boolean,
parse_integer,
parse_double,
parse_string,
parse_start_map,
parse_map_key,
parse_end_map,
NULL, /* parse_start_array, */
NULL, /* parse_end_array, */
channel_open,
channel_report,
channel_close
};
epicsShareFunc
const char*
epicsShareAPI
chfPluginEnumString(const chfPluginEnumType *emap, int i, const char* def)
{
for(; emap && emap->name; emap++) {
if ( i == emap->value ) {
return emap->name;
}
}
return def;
}
epicsShareFunc
int
epicsShareAPI
chfPluginRegister(const char* key, const chfPluginIf *pif, const chfPluginArgDef* opts)
{
chfPluginCtx *pctx;
size_t i;
const chfPluginArgDef *cur;
/* Check and count options */
for (i = 0, cur = opts; cur && cur->name; i++, cur++) {
switch(cur->optType) {
case chfPluginArgInt32:
if (cur->size < sizeof(long)) {
errlogPrintf("Plugin %s: provided storage (%d bytes) for %s is too small for long (%lu)\n",
key, cur->size, cur->name,
(unsigned long) sizeof(long));
return -1;
}
break;
case chfPluginArgBoolean:
if (cur->size < 1) {
errlogPrintf("Plugin %s: provided storage (%d bytes) for %s is too small for boolean (%lu)\n",
key, cur->size, cur->name,
(unsigned long) sizeof(char));
return -1;
}
break;
case chfPluginArgDouble:
if (cur->size < sizeof(double)) {
errlogPrintf("Plugin %s: provided storage (%d bytes) for %s is too small for double (%lu)\n",
key, cur->size, cur->name,
(unsigned long) sizeof(double));
return -1;
}
break;
case chfPluginArgString:
if (cur->size < sizeof(char*)) {
/* Catch if someone has given us a char* instead of a char[]
* Also means that char buffers must be >=4.
*/
errlogPrintf("Plugin %s: provided storage (%d bytes) for %s is too small for string (>= %lu)\n",
key, cur->size, cur->name,
(unsigned long) sizeof(char*));
return -1;
}
break;
case chfPluginArgEnum:
if (cur->size < sizeof(int)) {
errlogPrintf("Plugin %s: provided storage (%d bytes) for %s is too small for enum (%lu)\n",
key, cur->size, cur->name,
(unsigned long) sizeof(int));
return -1;
}
break;
case chfPluginArgInvalid:
errlogPrintf("Plugin %s: storage type for %s is not defined\n",
key, cur->name);
return -1;
break;
}
}
/* Plugin context */
pctx = dbCalloc(1, sizeof(chfPluginCtx));
pctx->pif = pif;
pctx->opts = opts;
pctx->nopts = i;
dbRegisterFilter(key, &wrapper_fif, pctx);
return 0;
}

223
src/ioc/db/chfPlugin.h Normal file
View File

@@ -0,0 +1,223 @@
/*************************************************************************\
* 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.
\*************************************************************************/
#ifndef CHFPLUGIN_H
#define CHFPLUGIN_H
#include <shareLib.h>
#include <dbDefs.h>
#include <epicsTypes.h>
#include <dbChannel.h>
/* Based on the linkoptions utility by Michael Davidsaver (BNL) */
/** @file chfPlugin.h
* @brief Channel filter simplified plugins.
*
* Utility layer to allow an easier (reduced) interface for
* channel filter plugins.
*
* Parsing the configuration arguments of a channel filter plugin
* is done according to an argument description table provided by the plugin.
* The parser stores the results directly into a user supplied structure
* after appropriate type conversion.
*
* To specify the arguments, a chfPluginArgDef table must be defined
* for the user structure. This table has to be specified when the plugin registers.
*
* The plugin is responsible to register an init function using
* epicsExportRegistrar() and the accompanying registrar() directive in the dbd,
* and call chfPluginRegister() from within the init function.
*
* For example:
*
* typedef struct myStruct {
* ... other stuff
* epicsUInt32 ival;
* double dval;
* epicsUInt32 ival2;
* int enumval;
* char strval[20];
* } myStruct;
*
* static const
* chfPluginEnumType colorEnum[] = { {"Red",1}, {"Green",2}, {"Blue",3}, {NULL,0} };
*
* static const
* chfPluginDef myStructDef[] = {
* chfInt32 (myStruct, ival, "Integer" , 0, 0),
* chfInt32 (myStruct, ival2, "Second" , 1, 0),
* chfDouble(myStruct, dval, "Double" , 1, 0),
* chfString(myStruct, strval , "String" , 1, 0),
* chfEnum (myStruct, enumval, "Color" , 1, 0, colorEnum),
* chfPluginEnd
* };
*
* Note: The 4th argument specifies the parameter to be required (1) or optional (0),
* the 5th whether converting to the required type is allowed (1), or
* type mismatches are an error (0).
*
*/
#ifdef __cplusplus
extern "C" {
#endif
/** @brief Channel filter simplified plugin interface.
*
* The routines in this structure must be implemented by each filter plugin.
*/
typedef struct chfPluginIf {
/* Memory management */
/** @brief Allocate private resources.
*
* <em>Called before parsing starts.</em>
* The plugin should allocate its per-instance structures,
* returning a pointer to them or NULL requesting an abort of the operation.
*
* allocPvt may be set to NULL, if no resource allocation is needed.
*
* @return Pointer to private structure, NULL if operation is to be aborted.
*/
void * (* allocPvt) (void);
/** @brief Free private resources.
*
* <em>Called as part of abort or shutdown.</em>
* The plugin should release any resources allocated for this filter;
* no further calls through this interface will be made.
*
* freePvt may be set to NULL, if no resources need to be released.
*
* @param pvt Pointer to private structure.
*/
void (* freePvt) (void *pvt);
/* Parameter parsing results */
/** @brief A parsing error occurred.
*
* <em>Called after parsing failed with an error.</em>
*/
void (* parse_error) (void);
/** @brief Configuration has been parsed successfully.
*
* <em>Called after parsing has finished ok.</em>
* The plugin may check the validity of the parsed data,
* returning -1 to request an abort of the operation.
*
* @param pvt Pointer to private structure.
* @return 0 for success, -1 if operation is to be aborted.
*/
int (* parse_ok) (void *pvt);
/* Channel operations */
/** @brief Open channel.
*
* <em>Called as part of the channel connection setup.</em>
* @param chan dbChannel for which the connection is being made.
* @param pvt Pointer to private structure.
* @return 0 for success, -1 if operation is to be aborted.
*/
long (* channel_open) (dbChannel *chan, void *pvt);
/** @brief Channel report request.
*
* <em>Called as part of show... routines.</em>
* @param chan dbChannel for which the report is requested.
* @param pvt Pointer to private structure.
* @param intro Line header string to be printed at the beginning of each line.
* @param level Interest level.
*/
void (* channel_report) (dbChannel *chan, void *pvt, const char *intro, int level);
/* FIXME: More filter routines here ... */
/** @brief Channel close request.
*
* <em>Called as part of connection shutdown.</em>
* @param chan dbChannel for which the connection is being shut down.
* @param pvt Pointer to private structure.
*/
void (* channel_close) (dbChannel *chan, void *pvt);
} chfPluginIf;
typedef enum chfPluginArg {
chfPluginArgInvalid=0,
chfPluginArgBoolean,
chfPluginArgInt32,
chfPluginArgDouble,
chfPluginArgString,
chfPluginArgEnum
} chfPluginArg;
typedef struct chfPluginEnumType {
const char *name;
const int value;
} chfPluginEnumType;
typedef struct chfPluginArgDef {
const char * name;
chfPluginArg optType;
int required:1;
int convert:1;
epicsUInt32 offset;
epicsUInt32 size;
const chfPluginEnumType *enums;
} chfPluginArgDef;
#define chfInt32(Struct, Member, Name, Req, Conv) \
{Name, chfPluginArgInt32, Req, Conv, OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), NULL}
#define chfBoolean(Struct, Member, Name, Req, Conv) \
{Name, chfPluginArgBoolean, Req, Conv, OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), NULL}
#define chfDouble(Struct, Member, Name, Req, Conv) \
{Name, chfPluginArgDouble, Req, Conv, OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), NULL}
#define chfString(Struct, Member, Name, Req, Conv) \
{Name, chfPluginArgString, Req, Conv, OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), NULL}
#define chfEnum(Struct, Member, Name, Req, Conv, Enums) \
{Name, chfPluginArgEnum, Req, Conv, OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), Enums}
#define chfPluginArgEnd {0}
/* Extra output when parsing and converting */
#define CHFPLUGINDEBUG 1
/** @brief Return the string associated with Enum index 'i'.
*
* @param Enums A null-terminated array of string/integer pairs.
* @param i An Enum index.
* @param def String to be returned when 'i' isn't a valid Enum index.
* @return The string associated with 'i'.
*/
epicsShareFunc
const char*
epicsShareAPI
chfPluginEnumString(const chfPluginEnumType *Enums, int i, const char* def);
/** @brief Register a plugin.
*
* @param key The plugin name key that clients will use.
* @param pif Pointer to the plugin's interface.
* @param opts Pointer to the configuration argument description table.
*/
epicsShareFunc
int
epicsShareAPI
chfPluginRegister(const char* key, const chfPluginIf *pif, const chfPluginArgDef* opts);
#ifdef __cplusplus
}
#endif
#endif // CHFPLUGIN_H

View File

@@ -25,6 +25,11 @@ dbChannelTest_SRCS += dbChannelTest.c
OBJS_IOC_vxWorks += dbChannelTest
TESTS += dbChannelTest
TESTPROD_HOST += chfPluginTest
chfPluginTest_SRCS += chfPluginTest.c
OBJS_IOC_vxWorks += chfPluginTest
TESTS += chfPluginTest
TESTSCRIPTS_HOST += $(TESTS:%=%.t)
include $(TOP)/configure/RULES

View File

@@ -0,0 +1,388 @@
/*************************************************************************\
* 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.
\*************************************************************************/
#include <string.h>
#include "chfPlugin.h"
#include "dbStaticLib.h"
#include "dbAccessDefs.h"
#include "epicsUnitTest.h"
#include "testMain.h"
#define PATTERN 0x55555555
/* Expected call bit definitions */
#define e_alloc 0x00000001
#define e_free 0x00000002
#define e_error 0x00000004
#define e_ok 0x00000008
#define e_open 0x00000010
#define e_report 0x00000020
#define e_close 0x00000040
unsigned int e, c;
#define e_any (e_alloc | e_free | e_error | e_ok | e_open | e_report | e_close)
typedef struct myStruct {
int sent1;
char flag;
int sent2;
epicsUInt32 ival;
int sent3;
double dval;
int sent4;
int enumval;
int sent5;
char str[20];
int sent6;
char c;
char c1[2];
} myStruct;
static const
chfPluginEnumType colorEnum[] = { {"R", 1}, {"G", 2}, {"B", 4}, {NULL,0} };
static const
chfPluginArgDef strictOpts[] = {
chfInt32 (myStruct, ival, "i" , 1, 0),
chfBoolean(myStruct, flag, "f" , 1, 0),
chfDouble (myStruct, dval, "d" , 1, 0),
chfString (myStruct, str, "s" , 1, 0),
chfEnum (myStruct, enumval, "c" , 1, 0, colorEnum),
chfPluginArgEnd
};
static const
chfPluginArgDef noconvOpts[] = {
chfInt32 (myStruct, ival, "i" , 0, 0),
chfBoolean(myStruct, flag, "f" , 0, 0),
chfDouble (myStruct, dval, "d" , 0, 0),
chfString (myStruct, str, "s" , 0, 0),
chfEnum (myStruct, enumval, "c" , 0, 0, colorEnum),
chfPluginArgEnd
};
static const
chfPluginArgDef sloppyOpts[] = {
chfInt32 (myStruct, ival, "i" , 0, 1),
chfBoolean(myStruct, flag, "f" , 0, 1),
chfDouble (myStruct, dval, "d" , 0, 1),
chfString (myStruct, str, "s" , 0, 1),
chfEnum (myStruct, enumval, "c" , 0, 1, colorEnum),
chfPluginArgEnd
};
/* Options defs with not enough room provided */
static const
chfPluginArgDef brokenOpts1[] = {
chfInt32 (myStruct, c, "i" , 0, 1),
chfPluginArgEnd
};
static const
chfPluginArgDef brokenOpts2[] = {
chfDouble(myStruct, c, "d" , 0, 1),
chfPluginArgEnd
};
static const
chfPluginArgDef brokenOpts3[] = {
chfString(myStruct, c1, "s" , 0, 1),
chfPluginArgEnd
};
static const
chfPluginArgDef brokenOpts4[] = {
chfEnum (myStruct, c, "c" , 0, 1, colorEnum),
chfPluginArgEnd
};
int p_ok_return = 0;
int c_open_return = 0;
void *puser;
static void clearStruct(void *p) {
myStruct *my = (myStruct*) p;
if (!my) return;
memset(my, 0, sizeof(myStruct));
my->sent1 = my->sent2 = my->sent3 = my->sent4 = my->sent5 = my->sent6 = PATTERN;
my->ival = 12;
my->flag = 1;
my->dval = 1.234e5;
strcpy(my->str, "hello");
my->enumval = 4;
}
static void * allocPvt(void)
{
testOk(e & e_alloc, "allocPvt called");
c |= e_alloc;
myStruct *my = puser = (myStruct*) calloc(1, sizeof(myStruct));
clearStruct (my);
return my;
}
static void freePvt(void *user)
{
testOk(e & e_free, "freePvt called");
c |= e_free;
testOk(user == puser, "user pointer correct");
free(user);
puser = NULL;
}
static void parse_error(void)
{
testOk(e & e_error, "parse_error called");
c |= e_error;
}
static int parse_ok(void *user)
{
testOk(e & e_ok, "parse_ok called");
c |= e_ok;
testOk(user == puser, "user pointer correct");
return p_ok_return;
}
static long channel_open(dbChannel *chan, void *user)
{
testOk(e & e_open, "channel_open called");
c |= e_open;
testOk(user == puser, "user pointer correct");
return c_open_return;
}
static void channel_report(dbChannel *chan, void *user, const char *intro, int level)
{
testOk(e & e_report, "channel_report called");
c |= e_report;
testOk(user == puser, "user pointer correct");
}
static void channel_close(dbChannel *chan, void *user)
{
testOk(e & e_close, "channel_close called");
c |= e_close;
testOk(user == puser, "user pointer correct");
}
static chfPluginIf myPif = {
allocPvt,
freePvt,
parse_error,
parse_ok,
channel_open,
channel_report,
channel_close
};
static int checkValues(myStruct *my, epicsUInt32 i, int f, double d, char *s, int c) {
if (!my) return 0;
if (my->sent1 == PATTERN && my->sent2 == PATTERN && my->sent3 == PATTERN
&& my->sent4 == PATTERN && my->sent5 == PATTERN && my->sent6 == PATTERN
&& my->ival == i && my->flag == f && my->dval == d && my->enumval == c
&& (strcmp(s, my->str) == 0)) {
return 1;
} else {
return 0;
}
}
MAIN(chfPluginTest)
{
dbChannel *pch;
testPlan(667);
/* Enum to string conversion */
testOk(strcmp(chfPluginEnumString(colorEnum, 1, "-"), "R") == 0, "Enum to string: R");
testOk(strcmp(chfPluginEnumString(colorEnum, 2, "-"), "G") == 0, "Enum to string: G");
testOk(strcmp(chfPluginEnumString(colorEnum, 4, "-"), "B") == 0, "Enum to string: B");
testOk(strcmp(chfPluginEnumString(colorEnum, 3, "-"), "-") == 0, "Enum to string: invalid index");
testOk1(!dbReadDatabase(&pdbbase, "dbChannelTest.dbx", ".:..", NULL));
testOk(!!pdbbase, "pdbbase was set");
testOk(chfPluginRegister("buggy", &myPif, brokenOpts1), "not enough storage for integer");
testOk(chfPluginRegister("buggy", &myPif, brokenOpts2), "not enough storage for double");
testOk(chfPluginRegister("buggy", &myPif, brokenOpts3), "not enough storage for string");
testOk(chfPluginRegister("buggy", &myPif, brokenOpts4), "not enough storage for enum");
testOk(!chfPluginRegister("strict", &myPif, strictOpts), "register plugin strict");
testOk(!chfPluginRegister("noconv", &myPif, noconvOpts), "register plugin noconv");
testOk(!chfPluginRegister("sloppy", &myPif, sloppyOpts), "register plugin sloppy");
/* STRICT parsing: mandatory, no conversion */
/* All perfect */
e = e_alloc | e_ok; c = 0;
testOk(!!(pch = dbChannelCreate("x.{\"strict\":{\"i\":1,\"f\":false,\"d\":1.2e15,\"s\":\"bar\",\"c\":\"R\"}}")), "strict parsing: JSON correct");
testOk(checkValues(puser, 1, 0, 1.2e15, "bar", 1), "guards intact, values correct");
if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c);
e = e_close | e_free; c = 0;
if (pch) dbChannelDelete(pch);
testOk(!puser, "user part cleaned up");
if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c);
/* Any one missing must fail */
e = e_alloc | e_error | e_free; c = 0;
testOk(!(pch = dbChannelCreate("x.{\"strict\":{\"i\":1,\"f\":false,\"d\":1.2e15,\"s\":\"bar\"}}")), "strict parsing: c missing");
testOk(!puser, "user part cleaned up");
if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c);
e = e_alloc | e_error | e_free; c = 0;
testOk(!(pch = dbChannelCreate("x.{\"strict\":{\"f\":false,\"i\":1,\"d\":1.2e15,\"c\":\"R\"}}")), "strict parsing: s missing");
testOk(!puser, "user part cleaned up");
if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c);
e = e_alloc | e_error | e_free; c = 0;
testOk(!(pch = dbChannelCreate("x.{\"strict\":{\"i\":1,\"c\":\"R\",\"f\":false,\"s\":\"bar\"}}")), "strict parsing: d missing");
testOk(!puser, "user part cleaned up");
if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c);
e = e_alloc | e_error | e_free; c = 0;
testOk(!(pch = dbChannelCreate("x.{\"strict\":{\"d\":1.2e15,\"c\":\"R\",\"i\":1,\"s\":\"bar\"}}")), "strict parsing: f missing");
testOk(!puser, "user part cleaned up");
if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c);
e = e_alloc | e_error | e_free; c = 0;
testOk(!(pch = dbChannelCreate("x.{\"strict\":{\"c\":\"R\",\"s\":\"bar\",\"f\":false,\"d\":1.2e15}}")), "strict parsing: i missing");
testOk(!puser, "user part cleaned up");
if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c);
/* NOCONV parsing: optional, no conversion */
/* Any one missing must leave the default intact */
e = e_alloc | e_ok; c = 0;
testOk(!!(pch = dbChannelCreate("x.{\"noconv\":{\"i\":1,\"f\":false,\"d\":1.2e15,\"s\":\"bar\"}}")), "noconv parsing: c missing");
testOk(checkValues(puser, 1, 0, 1.2e15, "bar", 4), "guards intact, values correct");
if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c);
e = e_close | e_free; c = 0;
if (pch) dbChannelDelete(pch);
testOk(!puser, "user part cleaned up");
if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c);
e = e_any;
testOk(!!(pch = dbChannelCreate("x.{\"noconv\":{\"i\":1,\"f\":false,\"d\":1.2e15,\"c\":\"R\"}}")), "noconv parsing: s missing");
testOk(checkValues(puser, 1, 0, 1.2e15, "hello", 1), "guards intact, values correct");
if (pch) dbChannelDelete(pch);
testOk(!!(pch = dbChannelCreate("x.{\"noconv\":{\"i\":1,\"f\":false,\"s\":\"bar\",\"c\":\"R\"}}")), "noconv parsing: d missing");
testOk(checkValues(puser, 1, 0, 1.234e5, "bar", 1), "guards intact, values correct");
if (pch) dbChannelDelete(pch);
testOk(!!(pch = dbChannelCreate("x.{\"noconv\":{\"i\":1,\"d\":1.2e15,\"s\":\"bar\",\"c\":\"R\"}}")), "noconv parsing: f missing");
testOk(checkValues(puser, 1, 1, 1.2e15, "bar", 1), "guards intact, values correct");
if (pch) dbChannelDelete(pch);
testOk(!!(pch = dbChannelCreate("x.{\"noconv\":{\"f\":false,\"d\":1.2e15,\"s\":\"bar\",\"c\":\"R\"}}")), "noconv parsing: i missing");
testOk(checkValues(puser, 12, 0, 1.2e15, "bar", 1), "guards intact, values correct");
if (pch) dbChannelDelete(pch);
/* Reject wrong types */
#define WRONGTYPETEST(Var, Val, Typ) \
e = e_alloc | e_error | e_free; c = 0; \
testOk(!(pch = dbChannelCreate("x.{\"noconv\":{\""#Var"\":"#Val"}}")), "noconv parsing: wrong type "#Typ" for "#Var); \
testOk(!puser, "user part cleaned up"); \
if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c);
WRONGTYPETEST(i, 123.0, double);
WRONGTYPETEST(i, true, boolean);
WRONGTYPETEST(i, "1", string);
WRONGTYPETEST(f, "false", string);
WRONGTYPETEST(f, 0.0, double);
WRONGTYPETEST(f, 1, integer);
WRONGTYPETEST(d, "1.2", string);
WRONGTYPETEST(d, true, boolean);
WRONGTYPETEST(d, 123, integer);
WRONGTYPETEST(s, 1.23, double);
WRONGTYPETEST(s, true, boolean);
WRONGTYPETEST(s, 123, integer);
WRONGTYPETEST(c, 1.23, double);
WRONGTYPETEST(c, true, boolean);
WRONGTYPETEST(c, 2, integer);
/* SLOPPY parsing: optional, with conversion */
#define CONVTESTGOOD(Var, Val, Typ, Ival, Fval, Dval, Sval, Cval) \
e = e_alloc | e_ok; c = 0; \
testOk(!!(pch = dbChannelCreate("x.{\"sloppy\":{\""#Var"\":"#Val"}}")), "sloppy parsing: "#Typ" (good) for "#Var); \
testOk(checkValues(puser, Ival, Fval, Dval, Sval, Cval), "guards intact, values correct"); \
if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c); \
e = e_close | e_free; c = 0; \
if (pch) dbChannelDelete(pch); \
testOk(!puser, "user part cleaned up"); \
if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c);
#define CONVTESTBAD(Var, Val, Typ) \
e = e_alloc | e_error | e_free; c = 0; \
testOk(!(pch = dbChannelCreate("x.{\"sloppy\":{\""#Var"\":"#Val"}}")), "sloppy parsing: "#Typ" (bad) for "#Var); \
testOk(!puser, "user part cleaned up"); \
if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c);
/* To integer */
CONVTESTGOOD(i, "123e4", positive string, 123, 1, 1.234e5, "hello", 4);
CONVTESTGOOD(i, "-12345", negative string, -12345, 1, 1.234e5, "hello", 4);
CONVTESTBAD(i, "9234567890", out-of-range string);
CONVTESTBAD(i, ".4", invalid string);
CONVTESTGOOD(i, false, valid boolean, 0, 1, 1.234e5, "hello", 4);
CONVTESTGOOD(i, 3456.789, valid double, 3456, 1, 1.234e5, "hello", 4);
CONVTESTBAD(i, 34.7e14, out-of-range double);
/* To boolean */
CONVTESTGOOD(f, "false", valid string, 12, 0, 1.234e5, "hello", 4);
CONVTESTGOOD(f, "False", capital valid string, 12, 0, 1.234e5, "hello", 4);
CONVTESTGOOD(f, "0", 0 string, 12, 0, 1.234e5, "hello", 4);
CONVTESTGOOD(f, "15", 15 string, 12, 1, 1.234e5, "hello", 4);
CONVTESTBAD(f, ".4", invalid .4 string);
CONVTESTBAD(f, "Flase", misspelled invalid string);
CONVTESTGOOD(f, 0, zero integer, 12, 0, 1.234e5, "hello", 4);
CONVTESTGOOD(f, 12, positive integer, 12, 1, 1.234e5, "hello", 4);
CONVTESTGOOD(f, -1234, negative integer, 12, 1, 1.234e5, "hello", 4);
CONVTESTGOOD(f, 0.4, positive non-zero double, 12, 1, 1.234e5, "hello", 4);
CONVTESTGOOD(f, 0.0, zero double, 12, 0, 1.234e5, "hello", 4);
CONVTESTGOOD(f, -0.0, minus-zero double, 12, 0, 1.234e5, "hello", 4);
CONVTESTGOOD(f, -1.24e14, negative double, 12, 1, 1.234e5, "hello", 4);
/* To double */
CONVTESTGOOD(d, "123e4", positive double string, 12, 1, 1.23e6, "hello", 4);
CONVTESTGOOD(d, "-7.89e-14", negative double string, 12, 1, -7.89e-14, "hello", 4);
CONVTESTGOOD(d, "123", positive integer string, 12, 1, 123.0, "hello", 4);
CONVTESTGOOD(d, "-1234567", negative integer string, 12, 1, -1.234567e6, "hello", 4);
CONVTESTBAD(d, "1.67e407", out-of-range double string);
CONVTESTBAD(d, "blubb", invalid blubb string);
CONVTESTGOOD(d, 123, positive integer, 12, 1, 123.0, "hello", 4);
CONVTESTGOOD(d, -12345, negative integer, 12, 1, -1.2345e4, "hello", 4);
CONVTESTGOOD(d, true, true boolean, 12, 1, 1.0, "hello", 4);
CONVTESTGOOD(d, false, false boolean, 12, 1, 0.0, "hello", 4);
/* To string */
CONVTESTGOOD(s, 12345, positive integer, 12, 1, 1.234e5, "12345", 4);
CONVTESTGOOD(s, -1234567891, negative integer, 12, 1, 1.234e5, "-1234567891", 4);
CONVTESTGOOD(s, true, true boolean, 12, 1, 1.234e5, "true", 4);
CONVTESTGOOD(s, false, false boolean, 12, 1, 1.234e5, "false", 4);
CONVTESTGOOD(s, 123e4, small positive double, 12, 1, 1.234e5, "1230000", 4);
CONVTESTGOOD(s, -123e24, negative double, 12, 1, 1.234e5, "-1.23e+26", 4);
CONVTESTGOOD(s, -1.23456789123456789e26, large rounded negative double, 12, 1, 1.234e5, "-1.234567891235e+26", 4);
/* To Enum */
CONVTESTGOOD(c, 2, valid integer choice, 12, 1, 1.234e5, "hello", 2);
CONVTESTBAD(c, 3, invalid integer choice);
CONVTESTBAD(c, 3.2, double);
CONVTESTGOOD(c, "R", valid string choice, 12, 1, 1.234e5, "hello", 1);
CONVTESTBAD(c, "blubb", invalid string choice);
dbFreeBase(pdbbase);
return testDone();
}