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:
@@ -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
613
src/ioc/db/chfPlugin.c
Normal 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
223
src/ioc/db/chfPlugin.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
388
src/ioc/db/test/chfPluginTest.c
Normal file
388
src/ioc/db/test/chfPluginTest.c
Normal 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();
|
||||
}
|
||||
Reference in New Issue
Block a user