Merge ~dirk.zimoch/epics-base:chfPluginImprovements into epics-base:7.0

Proposed by Dirk Zimoch
Status: Needs review
Proposed branch: ~dirk.zimoch/epics-base:chfPluginImprovements
Merge into: epics-base:7.0
Diff against target: 1138 lines (+412/-142)
9 files modified
documentation/RELEASE_NOTES.md (+16/-1)
modules/database/src/ioc/db/chfPlugin.c (+97/-25)
modules/database/src/ioc/db/chfPlugin.h (+12/-1)
modules/database/src/ioc/db/dbChannel.c (+26/-0)
modules/database/src/std/filters/filters.dbd.pod (+13/-5)
modules/database/src/std/filters/sync.c (+9/-11)
modules/database/test/ioc/db/chfPluginTest.c (+236/-96)
modules/database/test/std/filters/decTest.c (+2/-2)
modules/database/test/std/filters/tsTest.c (+1/-1)
Reviewer Review Type Date Requested Status
EPICS Core Developers Pending
Review via email: mp+399636@code.launchpad.net

Commit message

Improved channel filter plugin interface

Description of the change

1. Allow string arguments with arbitrarily long strings.
In addition to the chfPluginArgString argument type which works on fixed size char array members, chfPluginArgStringAlloc works on char* members. It performs the following actions:
* If a chfPluginArgStringAlloc is provided by the user, the char* member is first NULLed, then filled with a dynamically allocated string (which may fail due to memory shortage)
* If the same char* member may be provided multiple times (maybe under different names), the old value is freed before a new value is written.
* When the structure is released (before freePvt is called), the allocated strings are automatically freed and the char* member NULLed.
* If the parameter is optional and not provided my the user, the pointer is left untouched, allowing to initialize the char* member with a static string as a default value.
2. Allow short forms for filters with 1 or 0 parameters
* If only 1 parameter is required, {filter:value} can be used instead of {filter:{parameter:value}}
* If no parameter is requires, {filter} can be used instead of {filter:{}}
3. Documentarion changed: Use JSON5 features in examples.

To post a comment you must log in.
Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Question: Should the old chfPluginArgString be dropped and be replaced by the new chfPluginArgStringAlloc support (redefining the meaning of chfPluginArgString)?

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Additional plan: Implement shortcut for one parameter.

If there is exacly one mandatory parameter, allow a shortcut without using an object.
Instead of: {filter:{onlyparameter:value}}
allow: {filter:value}

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Now the short form works if there is only one parameter or only one parameter is mandatory (which means it is clear which parameter is meant).
Example: The "dec" filter:
Instead of: record.{dec:{n:5}} one can use record.{dec:5}

ab85813... by Dirk Zimoch on 2021-03-17

typo fixed

c1b22d0... by Dirk Zimoch on 2021-03-18

allow arbritrary state name length

06f8a37... by Dirk Zimoch on 2021-03-18

simplified filter syntax for only one argument

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Re-pushed, this time with tests.

4395cf4... by Dirk Zimoch on 2021-03-19

simplified filter syntax for no argument

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Another short form: If no argument at all is needed then just the filter name is sufficient (no :{} needed). Example: caget -a "record.{ts}"
I know this is not valid JSON5, but it is convenient and intuitive and works.
Can be stacked with other filters without problems: caget -a "record.{ts,arr:{i:3},dec:1}"
The filter is created as if called with {}.

Revision history for this message
Andrew Johnson (anj) wrote :

Most of this looks great (although I haven't actually tried it).

To answer your first question, answer me this: If someone wrote an out-of-tree filter that has a chfPluginArgString parameter and they compiled their code unmodified after you had redefined the meaning of that parameter, would they find out at compile-time that their code needs to be fixed? If they would, the redefinition is probably fine; if not, you'd need to make it so that their code would not compile.

I'm not sure yet whether I like the extra-short form, although it certainly seems clever. Please convince me that this extra code won't cause any problems, it can't cause crashes if someone feeds it some other non-JSON syntax, what error messages does it generate when fed weird stuff etc. I'm not asking you to take it out, but to justify this approach.

db29bb0... by Dirk Zimoch on 2021-04-09

check that filter without value is not followed by garbage

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

I have added a check that the "extra-short form" is not followed by garbage. (And wrote tests for it.) It only accepts , or } now. It never crashed before but had accepted any separator like ; or # and did not check for closing }. That is fixed now.

The error message if } is missing is:
dbChannelCreate: parse error: premature EOF

If garbarge follows instead or a comma, the error is:
dbChannelCreate: lexical error: invalid char in json text.

These are exactly the same error one gets with the "long form" using :{}

Whitespace around the comma are possible just as usual.

---

I could not find a clever way to properly type check chfPluginArgString for char* vs char[] (without writing a new API in C++). Thus I will keep old chfPluginArgString and new chfPluginArgStringAlloc.

1f76799... by Dirk Zimoch on 2021-04-09

check for out of memory

Unmerged commits

1f76799... by Dirk Zimoch on 2021-04-09

check for out of memory

db29bb0... by Dirk Zimoch on 2021-04-09

check that filter without value is not followed by garbage

4395cf4... by Dirk Zimoch on 2021-03-19

simplified filter syntax for no argument

06f8a37... by Dirk Zimoch on 2021-03-18

simplified filter syntax for only one argument

c1b22d0... by Dirk Zimoch on 2021-03-18

allow arbritrary state name length

24da53b... by Dirk Zimoch on 2021-03-12

allow strings with arbitrary length

ab85813... by Dirk Zimoch on 2021-03-17

typo fixed

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md
2index 17b039c..b79e5f1 100644
3--- a/documentation/RELEASE_NOTES.md
4+++ b/documentation/RELEASE_NOTES.md
5@@ -17,6 +17,22 @@ should also be read to understand what has changed since earlier releases.
6
7 <!-- Insert new items immediately below here ... -->
8
9+## Channel filter improvements
10+
11+The new chfStringAlloc and chfTagStringAlloc macros allows to store string
12+arguments with arbitrary length in char* members instead of fixed size arrays.
13+Memory is automaticaly allocated and released.
14+The "sync" filter has has been updated to allow arbitrarily long state names.
15+
16+Channel filters that require only one parameter (either they have only one
17+parameter or only one parameter is required) can be used with a simplified
18+syntax where only the value of that parameter is given instead of an object
19+with one member.
20+
21+Channel filters that require no parameter at all can be used with a simplified
22+syntax where only the filter name is given but not even an empty parameter set.
23+This extends the JSON5 syntax.
24+
25 ### IOCsh sets `${PWD}`
26
27 IOC shell will now ensure `${PWD}` is set on startup,
28@@ -105,7 +121,6 @@ that the variables referenced by output pointers are initialized.
29 #endif
30 ```
31
32-
33 -----
34
35 ## EPICS Release 7.0.5
36diff --git a/modules/database/src/ioc/db/chfPlugin.c b/modules/database/src/ioc/db/chfPlugin.c
37index c84e086..4f103e3 100644
38--- a/modules/database/src/ioc/db/chfPlugin.c
39+++ b/modules/database/src/ioc/db/chfPlugin.c
40@@ -70,6 +70,7 @@ store_integer_value(const chfPluginArgDef *opt, char *user, epicsInt32 val)
41 const chfPluginEnumType *emap;
42 double *dval;
43 char *sval;
44+ char **psval;
45 int ret;
46 char buff[22]; /* 2^64 = 1.8e+19, so 20 digits plus sign max */
47
48@@ -104,6 +105,11 @@ store_integer_value(const chfPluginArgDef *opt, char *user, epicsInt32 val)
49 strncpy(sval, buff, opt->size-1);
50 sval[opt->size-1]='\0';
51 break;
52+ case chfPluginArgStringAlloc:
53+ psval = (char **) (user + opt->dataOffset);
54+ *psval = realloc(*psval, 22);
55+ if (*psval) sprintf(*psval, "%ld", (long)val);
56+ break;
57 case chfPluginArgEnum:
58 eval = (int*) (user + opt->dataOffset);
59 for (emap = opt->enums; emap && emap->name; emap++) {
60@@ -116,7 +122,7 @@ store_integer_value(const chfPluginArgDef *opt, char *user, epicsInt32 val)
61 return -1;
62 }
63 break;
64- case chfPluginArgInvalid:
65+ default:
66 return -1;
67 }
68 return 0;
69@@ -131,6 +137,7 @@ static int store_boolean_value(const chfPluginArgDef *opt, char *user, int val)
70 epicsInt32 *ival;
71 double *dval;
72 char *sval;
73+ char **psval;
74
75 #ifdef DEBUG_CHF
76 printf("Got a boolean for %s (type %d): %d\n",
77@@ -162,8 +169,12 @@ static int store_boolean_value(const chfPluginArgDef *opt, char *user, int val)
78 strncpy(sval, val ? "true" : "false", opt->size - 1);
79 sval[opt->size - 1] = '\0';
80 break;
81- case chfPluginArgEnum:
82- case chfPluginArgInvalid:
83+ case chfPluginArgStringAlloc:
84+ psval = (char **) (user + opt->dataOffset);
85+ *psval = realloc(*psval, 6);
86+ if (*psval) strcpy(*psval, val ? "true" : "false");
87+ break;
88+ default:
89 return -1;
90 }
91 return 0;
92@@ -180,6 +191,7 @@ store_double_value(const chfPluginArgDef *opt, void *vuser, double val)
93 epicsInt32 *ival;
94 double *dval;
95 char *sval;
96+ char **psval;
97 int i;
98
99 #ifdef DEBUG_CHF
100@@ -217,8 +229,12 @@ store_double_value(const chfPluginArgDef *opt, void *vuser, double val)
101 return -1;
102 }
103 break;
104- case chfPluginArgEnum:
105- case chfPluginArgInvalid:
106+ case chfPluginArgStringAlloc:
107+ psval = (char **) (user + opt->dataOffset);
108+ *psval = realloc(*psval, 25);
109+ if (*psval) epicsSnprintf(*psval, 24, "%.17g", val);
110+ break;
111+ default:
112 return -1;
113 }
114 return 0;
115@@ -237,6 +253,7 @@ store_string_value(const chfPluginArgDef *opt, char *user, const char *val,
116 const chfPluginEnumType *emap;
117 double *dval;
118 char *sval;
119+ char **psval;
120 char *end;
121 size_t i;
122
123@@ -246,6 +263,7 @@ store_string_value(const chfPluginArgDef *opt, char *user, const char *val,
124 #endif
125
126 if (!opt->convert && opt->optType != chfPluginArgString &&
127+ opt->optType != chfPluginArgStringAlloc &&
128 opt->optType != chfPluginArgEnum) {
129 return -1;
130 }
131@@ -279,6 +297,11 @@ store_string_value(const chfPluginArgDef *opt, char *user, const char *val,
132 strncpy(sval, val, i);
133 sval[i] = '\0';
134 break;
135+ case chfPluginArgStringAlloc:
136+ psval = (char **) (user + opt->dataOffset);
137+ free(*psval);
138+ *psval = epicsStrnDup(val, len);
139+ break;
140 case chfPluginArgEnum:
141 eval = (int*) (user + opt->dataOffset);
142 for (emap = opt->enums; emap && emap->name; emap++) {
143@@ -291,7 +314,7 @@ store_string_value(const chfPluginArgDef *opt, char *user, const char *val,
144 return -1;
145 }
146 break;
147- case chfPluginArgInvalid:
148+ default:
149 return -1;
150 }
151 return 0;
152@@ -314,6 +337,7 @@ static parse_result parse_start(chFilter *filter)
153 {
154 chfPlugin *p = (chfPlugin*) filter->plug->puser;
155 chfFilter *f;
156+ size_t i;
157
158 /* Filter context */
159 /* FIXME: Use a free-list */
160@@ -339,25 +363,61 @@ static parse_result parse_start(chFilter *filter)
161 }
162 }
163
164+ /* If there is only one argument or only one required argument,
165+ allow shortcut without map */
166+ if (p->nopts == 1)
167+ f->nextParam = 0;
168+ else
169+ for (i = 0; i < p->nopts; i++) {
170+ if (p->opts[i].required) {
171+ if (f->nextParam == -1) {
172+ f->nextParam = i;
173+ } else {
174+ f->nextParam = -1;
175+ break;
176+ }
177+ }
178+ }
179+ if (f->nextParam >= 0 &&
180+ p->opts[f->nextParam].optType == chfPluginArgStringAlloc) {
181+ *(char **) ((char*) f->puser + p->opts[f->nextParam].dataOffset) = NULL;
182+ }
183+
184 filter->puser = (void*) f;
185
186 return parse_continue;
187
188 errplugin:
189- free(f->found);
190 errbitarray:
191- free(f); /* FIXME: Use a free-list */
192+ freeInstanceData(f);
193 errfctx:
194 return parse_stop;
195 }
196
197+static void freePvt(chFilter *filter) {
198+ chfPlugin *p = (chfPlugin*) filter->plug->puser;
199+ chfFilter *f = (chfFilter*) filter->puser;
200+ int i;
201+
202+ /* free all chfPluginArgStringAlloc options we had allocated */
203+ for(i = 0; i < p->nopts; i++) {
204+ if (p->opts[i].optType == chfPluginArgStringAlloc &&
205+ (f->found[i/32] & (1<<(i%32)))) {
206+ char **psval = (char **) ((char*) f->puser + p->opts[i].dataOffset);
207+ free(*psval);
208+ *psval = NULL;
209+ }
210+ }
211+ if (p->pif->freePvt) p->pif->freePvt(f->puser);
212+}
213+
214 static void parse_abort(chFilter *filter) {
215 chfPlugin *p = (chfPlugin*) filter->plug->puser;
216 chfFilter *f = (chfFilter*) filter->puser;
217
218 /* Call the plugin to tell it we're aborting */
219 if (p->pif->parse_error) p->pif->parse_error(f->puser);
220- if (p->pif->freePvt) p->pif->freePvt(f->puser);
221+ freePvt(filter);
222 freeInstanceData(f);
223 }
224
225@@ -365,22 +425,11 @@ static parse_result parse_end(chFilter *filter)
226 {
227 chfPlugin *p = (chfPlugin*) filter->plug->puser;
228 chfFilter *f = (chfFilter*) filter->puser;
229- int i;
230-
231- /* Check if all required arguments were supplied */
232- for(i = 0; i < (p->nopts/32)+1; i++) {
233- if ((f->found[i] & p->required[i]) != p->required[i]) {
234- if (p->pif->parse_error) p->pif->parse_error(f->puser);
235- if (p->pif->freePvt) p->pif->freePvt(f->puser);
236- freeInstanceData(f);
237- return parse_stop;
238- }
239- }
240
241 /* Call the plugin to tell it we're done */
242 if (p->pif->parse_ok) {
243 if (p->pif->parse_ok(f->puser)) {
244- if (p->pif->freePvt) p->pif->freePvt(f->puser);
245+ freePvt(filter);
246 freeInstanceData(f);
247 return parse_stop;
248 }
249@@ -478,6 +527,12 @@ parse_map_key(chFilter *filter, const char *key, size_t stringLen)
250 *tag = opts[i].choice;
251 }
252
253+ /* NULL all found chfPluginArgStringAlloc options */
254+ if (opts[i].optType == chfPluginArgStringAlloc &&
255+ !(f->found[i/32] & 1<<(i%32))) {
256+ char **psval = (char **) ((char*) f->puser + opts[i].dataOffset);
257+ *psval = NULL;
258+ }
259 f->found[i/32] |= 1<<(i%32);
260 /* Mark tag and all other options pointing to the same data as found */
261 for (cur = opts, j = 0; cur && cur->name; cur++, j++) {
262@@ -491,6 +546,15 @@ parse_map_key(chFilter *filter, const char *key, size_t stringLen)
263
264 static parse_result parse_end_map(chFilter *filter)
265 {
266+ chfPlugin *p = (chfPlugin*) filter->plug->puser;
267+ chfFilter *f = (chfFilter*) filter->puser;
268+ int i;
269+
270+ /* Check if all required arguments were supplied */
271+ for(i = 0; i < (p->nopts/32)+1; i++) {
272+ if ((f->found[i] & p->required[i]) != p->required[i])
273+ return parse_stop;
274+ }
275 return parse_continue;
276 }
277
278@@ -545,9 +609,8 @@ static void channel_close(chFilter *filter)
279 chfFilter *f = (chfFilter*) filter->puser;
280
281 if (p->pif->channel_close) p->pif->channel_close(filter->chan, f->puser);
282- if (p->pif->freePvt) p->pif->freePvt(f->puser);
283- free(f->found);
284- free(f); /* FIXME: Use a free-list */
285+ freePvt(filter);
286+ freeInstanceData(f);
287 }
288
289 static void plugin_free(void* puser)
290@@ -649,7 +712,16 @@ chfPluginRegister(const char* key, const chfPluginIf *pif,
291 return -1;
292 }
293 break;
294- case chfPluginArgInvalid:
295+ case chfPluginArgStringAlloc:
296+ if (cur->size != sizeof(char*)) {
297+ /* Catch if someone has given us a something else than a pointer
298+ */
299+ errlogPrintf("Plugin %s: %d bytes not a pointer type for string %s\n",
300+ key, cur->size, cur->name);
301+ return -1;
302+ }
303+ break;
304+ default:
305 errlogPrintf("Plugin %s: storage type for %s is not defined\n",
306 key, cur->name);
307 return -1;
308diff --git a/modules/database/src/ioc/db/chfPlugin.h b/modules/database/src/ioc/db/chfPlugin.h
309index 5867a66..eed218d 100644
310--- a/modules/database/src/ioc/db/chfPlugin.h
311+++ b/modules/database/src/ioc/db/chfPlugin.h
312@@ -52,6 +52,7 @@ struct db_field_log;
313 * epicsInt32 ival2;
314 * int enumval;
315 * char strval[20];
316+ * char* str;
317 * char boolval;
318 * } myStruct;
319 *
320@@ -64,6 +65,7 @@ struct db_field_log;
321 * chfInt32 (myStruct, ival2, "Second" , 1, 0),
322 * chfDouble (myStruct, dval, "Double" , 1, 0),
323 * chfString (myStruct, strval , "String" , 1, 0),
324+ * chfStringAlloc(myStruct, str , "String" , 1, 0),
325 * chfEnum (myStruct, enumval, "Color" , 1, 0, colorEnum),
326 * chfBoolean (myStruct, boolval, "Bool" , 1, 0),
327 * chfPluginEnd
328@@ -225,7 +227,8 @@ typedef enum chfPluginArg {
329 chfPluginArgInt32,
330 chfPluginArgDouble,
331 chfPluginArgString,
332- chfPluginArgEnum
333+ chfPluginArgEnum,
334+ chfPluginArgStringAlloc
335 } chfPluginArg;
336
337 typedef struct chfPluginEnumType {
338@@ -268,6 +271,10 @@ typedef struct chfPluginArgDef {
339 {Name, chfPluginArgEnum, Req, Conv, 0, 0, 0, \
340 OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), Enums}
341
342+#define chfStringAlloc(Struct, Member, Name, Req, Conv) \
343+ {Name, chfPluginArgStringAlloc, Req, Conv, 0, 0, 0, \
344+ OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), NULL}
345+
346 /* Tagged arguments */
347
348 #define chfTagInt32(Struct, Member, Name, Tag, Choice, Req, Conv) \
349@@ -290,6 +297,10 @@ typedef struct chfPluginArgDef {
350 {Name, chfPluginArgEnum, Req, Conv, 1, OFFSET(Struct, Tag), Choice, \
351 OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), Enums}
352
353+#define chfTagStringAlloc(Struct, Member, Name, Tag, Choice, Req, Conv) \
354+ {Name, chfPluginArgStringAlloc, Req, Conv, 1, OFFSET(Struct, Tag), Choice, \
355+ OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), NULL}
356+
357 #define chfPluginArgEnd {0}
358
359 /* Extra output when parsing and converting */
360diff --git a/modules/database/src/ioc/db/dbChannel.c b/modules/database/src/ioc/db/dbChannel.c
361index a806f3c..edf2124 100644
362--- a/modules/database/src/ioc/db/dbChannel.c
363+++ b/modules/database/src/ioc/db/dbChannel.c
364@@ -291,6 +291,32 @@ static long chf_parse(dbChannel *chan, const char **pjson)
365 case yajl_status_error: {
366 unsigned char *err;
367
368+ if (parser.depth == 0 && parser.filter &&
369+ (json[ylen-1] == ',' || json[ylen-1] == '}')) {
370+ /* We get here only if filter is called without arguments. */
371+ /* Let's fake ":{}" */
372+ if (chf_start_map(&parser) == parse_continue &&
373+ chf_end_map(&parser) == parse_continue) {
374+ *pjson += ylen;
375+ status = 0;
376+ if (json[ylen]) {
377+ /* some filters left, let's parse them too */
378+ char *jsoncopy = epicsStrDup(json+ylen-1);
379+ if (!jsoncopy) {
380+ printf("dbChannelCreate: out of memory\n");
381+ break;
382+ }
383+ jsoncopy[0] = '{';
384+ json = jsoncopy;
385+ status = chf_parse(chan, &json);
386+ ylen = json-jsoncopy-1;
387+ *pjson += ylen;
388+ free(jsoncopy);
389+ }
390+ break;
391+ }
392+ }
393+
394 err = yajl_get_error(yh, 1, (const unsigned char *) json, jlen);
395 printf("dbChannelCreate: %s\n", err);
396 yajl_free_error(yh, err);
397diff --git a/modules/database/src/std/filters/filters.dbd.pod b/modules/database/src/std/filters/filters.dbd.pod
398index 7b31466..5c1219d 100644
399--- a/modules/database/src/std/filters/filters.dbd.pod
400+++ b/modules/database/src/std/filters/filters.dbd.pod
401@@ -40,6 +40,13 @@ Parameters to that filter are provided as the value part of the name/value pair,
402 and will normally appear as a child JSON object consisting of name/value pairs
403 inside a nested pair of braces C< {} >.
404
405+If a filter takes only one parameter or only one parameter is required, a short
406+form may be used where the parameter vallue is passed directly instead of
407+an object in braces C< {} >.
408+
409+If a filter does not need any parameter, the value part can be skipped
410+completely. Only the filter name is needed in this case.
411+
412 =head4 Example Filter
413
414 Given a record called C<test:channel> the following would apply a filter C<f> to
415@@ -69,12 +76,12 @@ to the EPICS epoch if the record has never processed.
416
417 =head4 Parameters
418
419-None, use an empty pair of braces.
420+None.
421
422 =head4 Example
423
424- Hal$ caget -a 'test:channel.{"ts":{}}'
425- test:channel.{"ts":{}} 2012-08-28 22:10:31.192547 0 UDF INVALID
426+ Hal$ caget -a 'test:channel.{"ts"}'
427+ test:channel.{"ts"} 2012-08-28 22:10:31.192547 0 UDF INVALID
428 Hal$ caget -a 'test:channel'
429 test:channel <undefined> 0 UDF INVALID
430
431@@ -281,9 +288,10 @@ client connects.
432 =head4 Example
433
434 To sample a 60Hz channel at 1Hz, a 10Hz channel every 6 seconds or a 1Hz channel
435-once every minute:
436+once every minute. As the "dec" filter requires only one parameter, the short
437+syntax may be used:
438
439- Hal$ camonitor 'test:channel' 'test:channel.{"dec":{"n":60}}'
440+ Hal$ camonitor 'test:channel' 'test:channel.{"dec":60}'
441 ...
442
443 =cut
444diff --git a/modules/database/src/std/filters/sync.c b/modules/database/src/std/filters/sync.c
445index 8140c36..f31f677 100644
446--- a/modules/database/src/std/filters/sync.c
447+++ b/modules/database/src/std/filters/sync.c
448@@ -21,8 +21,6 @@
449 #include "epicsAssert.h"
450 #include "epicsExport.h"
451
452-#define STATE_NAME_LENGTH 20
453-
454 typedef enum syncMode {
455 syncModeBefore=0,
456 syncModeFirst=1,
457@@ -45,7 +43,7 @@ chfPluginEnumType modeEnum[] = {
458
459 typedef struct myStruct {
460 syncMode mode;
461- char state[STATE_NAME_LENGTH];
462+ char* state;
463 dbStateId id;
464 db_field_log *lastfl;
465 int laststate:1;
466@@ -55,14 +53,14 @@ static void *myStructFreeList;
467
468 static const
469 chfPluginArgDef opts[] = {
470- chfEnum (myStruct, mode, "m", 1, 1, modeEnum),
471- chfString (myStruct, state, "s", 1, 0),
472- chfTagString (myStruct, state, "before", mode, 0, 1, 0),
473- chfTagString (myStruct, state, "first", mode, 1, 1, 0),
474- chfTagString (myStruct, state, "last", mode, 2, 1, 0),
475- chfTagString (myStruct, state, "after", mode, 3, 1, 0),
476- chfTagString (myStruct, state, "while", mode, 4, 1, 0),
477- chfTagString (myStruct, state, "unless", mode, 5, 1, 0),
478+ chfEnum (myStruct, mode, "m", 1, 1, modeEnum),
479+ chfStringAlloc (myStruct, state, "s", 1, 0),
480+ chfTagStringAlloc (myStruct, state, "before", mode, 0, 1, 0),
481+ chfTagStringAlloc (myStruct, state, "first", mode, 1, 1, 0),
482+ chfTagStringAlloc (myStruct, state, "last", mode, 2, 1, 0),
483+ chfTagStringAlloc (myStruct, state, "after", mode, 3, 1, 0),
484+ chfTagStringAlloc (myStruct, state, "while", mode, 4, 1, 0),
485+ chfTagStringAlloc (myStruct, state, "unless", mode, 5, 1, 0),
486 chfPluginArgEnd
487 };
488
489diff --git a/modules/database/test/ioc/db/chfPluginTest.c b/modules/database/test/ioc/db/chfPluginTest.c
490index 4a1667c..6d281fc 100644
491--- a/modules/database/test/ioc/db/chfPluginTest.c
492+++ b/modules/database/test/ioc/db/chfPluginTest.c
493@@ -68,6 +68,7 @@ typedef struct myStruct {
494 char c1[2];
495 int offpre;
496 int offpost;
497+ char* astr;
498 } myStruct;
499
500 static const
501@@ -75,54 +76,76 @@ chfPluginEnumType colorEnum[] = { {"R", 1}, {"G", 2}, {"B", 4}, {NULL,0} };
502
503 static const
504 chfPluginArgDef sloppyTaggedOpts[] = {
505- chfInt32 (myStruct, tval, "t" , 0, 0),
506- chfTagInt32 (myStruct, ival, "I" , tval, 1, 0, 0),
507- chfTagBoolean(myStruct, flag, "F" , tval, 2, 0, 0),
508- chfTagDouble (myStruct, dval, "D" , tval, 3, 0, 0),
509- chfTagString (myStruct, str, "S" , tval, 4, 0, 0),
510- chfTagEnum (myStruct, enumval, "C" , tval, 5, 0, 0, colorEnum),
511+ chfInt32 (myStruct, tval, "t" , 0, 0),
512+ chfTagInt32 (myStruct, ival, "I" , tval, 1, 0, 0),
513+ chfTagBoolean (myStruct, flag, "F" , tval, 2, 0, 0),
514+ chfTagDouble (myStruct, dval, "D" , tval, 3, 0, 0),
515+ chfTagString (myStruct, str, "S" , tval, 4, 0, 0),
516+ chfTagEnum (myStruct, enumval, "C" , tval, 5, 0, 0, colorEnum),
517+ chfTagStringAlloc(myStruct, astr, "A" , tval, 6, 0, 0),
518 chfPluginArgEnd
519 };
520
521 static const
522 chfPluginArgDef strictTaggedOpts[] = {
523- chfInt32 (myStruct, tval, "t" , 1, 0),
524- chfBoolean (myStruct, flag, "f" , 1, 0),
525- chfTagInt32 (myStruct, ival, "I" , tval, 1, 0, 0),
526- chfTagBoolean(myStruct, flag, "F" , tval, 2, 0, 0),
527- chfTagDouble (myStruct, dval, "D" , tval, 3, 1, 0),
528- chfTagDouble (myStruct, dval, "D2", tval, 4, 1, 0),
529- chfTagEnum (myStruct, enumval, "C" , tval, 5, 0, 0, colorEnum),
530+ chfInt32 (myStruct, tval, "t" , 1, 0),
531+ chfBoolean (myStruct, flag, "f" , 1, 0),
532+ chfTagInt32 (myStruct, ival, "I" , tval, 1, 0, 0),
533+ chfTagBoolean (myStruct, flag, "F" , tval, 2, 0, 0),
534+ chfTagDouble (myStruct, dval, "D" , tval, 3, 1, 0),
535+ chfTagDouble (myStruct, dval, "D2", tval, 4, 1, 0),
536+ chfTagEnum (myStruct, enumval, "C" , tval, 5, 0, 0, colorEnum),
537+ chfTagStringAlloc(myStruct, astr, "A", tval, 6, 0, 0),
538 chfPluginArgEnd
539 };
540
541 static const
542 chfPluginArgDef strictOpts[] = {
543- chfInt32 (myStruct, ival, "i" , 1, 0),
544- chfBoolean(myStruct, flag, "f" , 1, 0),
545- chfDouble (myStruct, dval, "d" , 1, 0),
546- chfString (myStruct, str, "s" , 1, 0),
547- chfEnum (myStruct, enumval, "c" , 1, 0, colorEnum),
548+ chfInt32 (myStruct, ival, "i" , 1, 0),
549+ chfBoolean (myStruct, flag, "f" , 1, 0),
550+ chfDouble (myStruct, dval, "d" , 1, 0),
551+ chfString (myStruct, str, "s" , 1, 0),
552+ chfEnum (myStruct, enumval, "c" , 1, 0, colorEnum),
553+ chfStringAlloc(myStruct, astr, "a", 1, 0),
554 chfPluginArgEnd
555 };
556
557 static const
558 chfPluginArgDef noconvOpts[] = {
559- chfInt32 (myStruct, ival, "i" , 0, 0),
560- chfBoolean(myStruct, flag, "f" , 0, 0),
561- chfDouble (myStruct, dval, "d" , 0, 0),
562- chfString (myStruct, str, "s" , 0, 0),
563- chfEnum (myStruct, enumval, "c" , 0, 0, colorEnum),
564+ chfInt32 (myStruct, ival, "i" , 0, 0),
565+ chfBoolean (myStruct, flag, "f" , 0, 0),
566+ chfDouble (myStruct, dval, "d" , 0, 0),
567+ chfString (myStruct, str, "s" , 0, 0),
568+ chfEnum (myStruct, enumval, "c" , 0, 0, colorEnum),
569+ chfStringAlloc(myStruct, astr, "a", 0, 0),
570 chfPluginArgEnd
571 };
572
573 static const
574 chfPluginArgDef sloppyOpts[] = {
575- chfInt32 (myStruct, ival, "i" , 0, 1),
576- chfBoolean(myStruct, flag, "f" , 0, 1),
577- chfDouble (myStruct, dval, "d" , 0, 1),
578- chfString (myStruct, str, "s" , 0, 1),
579- chfEnum (myStruct, enumval, "c" , 0, 1, colorEnum),
580+ chfInt32 (myStruct, ival, "i" , 0, 1),
581+ chfBoolean (myStruct, flag, "f" , 0, 1),
582+ chfDouble (myStruct, dval, "d" , 0, 1),
583+ chfString (myStruct, str, "s" , 0, 1),
584+ chfEnum (myStruct, enumval, "c" , 0, 1, colorEnum),
585+ chfStringAlloc(myStruct, astr, "a", 0, 1),
586+ chfPluginArgEnd
587+};
588+
589+static const
590+chfPluginArgDef onlyOneOpts[] = {
591+ chfInt32 (myStruct, ival, "i" , 0, 0),
592+ chfPluginArgEnd
593+};
594+
595+static const
596+chfPluginArgDef onlyOneRequiredOpts[] = {
597+ chfInt32 (myStruct, ival, "i" , 0, 0),
598+ chfBoolean (myStruct, flag, "f" , 0, 0),
599+ chfDouble (myStruct, dval, "d" , 1, 0),
600+ chfString (myStruct, str, "s" , 0, 0),
601+ chfEnum (myStruct, enumval, "c" , 0, 0, colorEnum),
602+ chfStringAlloc(myStruct, astr, "a", 0, 0),
603 chfPluginArgEnd
604 };
605
606@@ -154,6 +177,7 @@ chfPluginArgDef brokenOpts4[] = {
607 int p_ok_return = 0;
608 int c_open_return = 0;
609 void *puser1, *puser2;
610+char defaultstring[] = "default";
611
612 static void clearStruct(void *p) {
613 myStruct *my = (myStruct*) p;
614@@ -168,6 +192,7 @@ static void clearStruct(void *p) {
615 my->dval = 1.234e5;
616 strcpy(my->str, "hello");
617 my->enumval = 4;
618+ my->astr = defaultstring;
619 }
620
621 static char inst(void* user) {
622@@ -177,7 +202,6 @@ static char inst(void* user) {
623 static void * allocPvt(void)
624 {
625 myStruct *my = (myStruct*) calloc(1, sizeof(myStruct));
626-
627 if (!puser1) {
628 puser1 = my;
629 testOk(e1 & e_alloc, "allocPvt (1) called");
630@@ -194,7 +218,7 @@ static void * allocPvt(void)
631 static void * allocPvtFail(void)
632 {
633 if (!puser1) {
634- testOk(e1 & e_alloc, "allocPvt (1) called");
635+ testOk(e1 & e_alloc, "allocPvtFail (1) called");
636 c1 |= e_alloc;
637 }
638 return NULL;
639@@ -202,7 +226,10 @@ static void * allocPvtFail(void)
640
641 static void freePvt(void *user)
642 {
643- if (user == puser1) {
644+ myStruct *my = (myStruct*) user;
645+ if ((user == puser1 || user == puser2) && (my->astr && my->astr != defaultstring)) {
646+ testFail("freePvt: allocated string not free'd");
647+ } else if (user == puser1) {
648 testOk(e1 & e_free, "freePvt (1) called");
649 c1 |= e_free;
650 free(user);
651@@ -499,9 +526,9 @@ static chfPluginIf allocFailPif = {
652 };
653
654 static int checkValues(myStruct *my,
655- char t, epicsUInt32 i, int f, double d, char *s1, char *s2, int c) {
656+ char t, epicsUInt32 i, int f, double d, char *s1, char *s2, int c, char *s3) {
657 int ret = 1;
658- int s1fail, s2fail;
659+ int s1fail, s2fail, s3fail;
660 int s2valid = (s2 && s2[0] != '\0');
661
662 if (!my) return 0;
663@@ -526,6 +553,11 @@ static int checkValues(myStruct *my,
664 if (s2valid && s2fail) testDiag("Fail: my->str (%s) != s (%s)", my->str, s2);
665 ret = 0;
666 }
667+ s3fail = (!s3 && my->astr) || (s3 && !my->astr) || (s3 && strcmp(s3, my->astr));
668+ if (s3fail) {
669+ testDiag("Fail: my->str (%s) != s (%s)", my->astr, s3);
670+ ret = 0;
671+ }
672 return ret;
673 }
674
675@@ -549,7 +581,7 @@ MAIN(chfPluginTest)
676 #endif
677 #endif
678
679- testPlan(1433);
680+ testPlan(1612);
681
682 dbChannelInit();
683 db_init_events();
684@@ -599,6 +631,10 @@ MAIN(chfPluginTest)
685 "register plugin noconv");
686 testOk(!chfPluginRegister("sloppy", &myPif, sloppyOpts),
687 "register plugin sloppy");
688+ testOk(!chfPluginRegister("onlyOne", &myPif, onlyOneOpts),
689+ "register plugin onlyOne");
690+ testOk(!chfPluginRegister("onlyOneRequired", &myPif, onlyOneRequiredOpts),
691+ "register plugin onlyOneRequired");
692 testOk(!chfPluginRegister("pre", &prePif, sloppyOpts),
693 "register plugin pre");
694 testOk(!chfPluginRegister("post", &postPif, sloppyOpts),
695@@ -629,7 +665,7 @@ MAIN(chfPluginTest)
696 testOk(!!(pch = dbChannelCreate(
697 "x.{'strict-tagged':{D:1.2e15,f:false}}")),
698 "create channel for strict-tagged parsing: D (t and d) and f");
699- testOk(checkValues(puser1, 3, 12, 0, 1.2e15, "hello", 0, 4),
700+ testOk(checkValues(puser1, 3, 12, 0, 1.2e15, "hello", 0, 4, "default"),
701 "guards intact, values correct");
702 if (!testOk(c1 == e1, "all expected calls happened"))
703 testDiag("expected %#x - called %#x", e1, c1);
704@@ -643,7 +679,7 @@ MAIN(chfPluginTest)
705 testOk(!!(pch = dbChannelCreate(
706 "x.{'strict-tagged':{D2:1.2e15,f:false}}")),
707 "create channel for strict-tagged parsing: D2 (t and d) and f");
708- testOk(checkValues(puser1, 4, 12, 0, 1.2e15, "hello", 0, 4),
709+ testOk(checkValues(puser1, 4, 12, 0, 1.2e15, "hello", 0, 4, "default"),
710 "guards intact, values correct");
711 if (!testOk(c1 == e1, "all expected calls happened"))
712 testDiag("expected %#x - called %#x", e1, c1);
713@@ -673,12 +709,26 @@ MAIN(chfPluginTest)
714
715 testHead("SLOPPY TAGGED parsing: all ok");
716
717+ /* no tag */
718+ e1 = e_alloc | e_ok; c1 = 0;
719+ testOk(!!(pch = dbChannelCreate(
720+ "x.{'sloppy-tagged':{}}")),
721+ "create channel for sloppy-tagged parsing: no argument");
722+ testOk(checkValues(puser1, 99, 12, 1, 1.234e5, "hello", 0, 4, "default"),
723+ "guards intact, values correct");
724+ if (!testOk(c1 == e1, "all expected calls happened"))
725+ testDiag("expected %#x - called %#x", e1, c1);
726+ e1 = e_close | e_free; c1 = 0;
727+ if (pch) dbChannelDelete(pch);
728+ testOk(!puser1, "user part cleaned up");
729+ if (!testOk(c1 == e1, "all expected calls happened"))
730+ testDiag("expected %#x - called %#x", e1, c1);
731 /* tag i */
732 e1 = e_alloc | e_ok; c1 = 0;
733 testOk(!!(pch = dbChannelCreate(
734 "x.{'sloppy-tagged':{I:1}}")),
735 "create channel for sloppy-tagged parsing: I");
736- testOk(checkValues(puser1, 1, 1, 1, 1.234e5, "hello", 0, 4),
737+ testOk(checkValues(puser1, 1, 1, 1, 1.234e5, "hello", 0, 4, "default"),
738 "guards intact, values correct");
739 if (!testOk(c1 == e1, "all expected calls happened"))
740 testDiag("expected %#x - called %#x", e1, c1);
741@@ -692,7 +742,7 @@ MAIN(chfPluginTest)
742 testOk(!!(pch = dbChannelCreate(
743 "x.{'sloppy-tagged':{F:false}}")),
744 "create channel for sloppy-tagged parsing: F");
745- testOk(checkValues(puser1, 2, 12, 0, 1.234e5, "hello", 0, 4),
746+ testOk(checkValues(puser1, 2, 12, 0, 1.234e5, "hello", 0, 4, "default"),
747 "guards intact, values correct");
748 if (!testOk(c1 == e1, "all expected calls happened"))
749 testDiag("expected %#x - called %#x", e1, c1);
750@@ -706,7 +756,7 @@ MAIN(chfPluginTest)
751 testOk(!!(pch = dbChannelCreate(
752 "x.{'sloppy-tagged':{D:1.2e15}}")),
753 "create channel for sloppy-tagged parsing: D");
754- testOk(checkValues(puser1, 3, 12, 1, 1.2e15, "hello", 0, 4),
755+ testOk(checkValues(puser1, 3, 12, 1, 1.2e15, "hello", 0, 4, "default"),
756 "guards intact, values correct");
757 if (!testOk(c1 == e1, "all expected calls happened"))
758 testDiag("expected %#x - called %#x", e1, c1);
759@@ -720,7 +770,7 @@ MAIN(chfPluginTest)
760 testOk(!!(pch = dbChannelCreate(
761 "x.{'sloppy-tagged':{S:'bar'}}")),
762 "create channel for sloppy-tagged parsing: S");
763- testOk(checkValues(puser1, 4, 12, 1, 1.234e5, "bar", 0, 4),
764+ testOk(checkValues(puser1, 4, 12, 1, 1.234e5, "bar", 0, 4, "default"),
765 "guards intact, values correct");
766 if (!testOk(c1 == e1, "all expected calls happened"))
767 testDiag("expected %#x - called %#x", e1, c1);
768@@ -734,7 +784,21 @@ MAIN(chfPluginTest)
769 testOk(!!(pch = dbChannelCreate(
770 "x.{'sloppy-tagged':{C:'R'}}")),
771 "create channel for sloppy-tagged parsing: C");
772- testOk(checkValues(puser1, 5, 12, 1, 1.234e5, "hello", 0, 1),
773+ testOk(checkValues(puser1, 5, 12, 1, 1.234e5, "hello", 0, 1, "default"),
774+ "guards intact, values correct");
775+ if (!testOk(c1 == e1, "all expected calls happened"))
776+ testDiag("expected %#x - called %#x", e1, c1);
777+ e1 = e_close | e_free; c1 = 0;
778+ if (pch) dbChannelDelete(pch);
779+ testOk(!puser1, "user part cleaned up");
780+ if (!testOk(c1 == e1, "all expected calls happened"))
781+ testDiag("expected %#x - called %#x", e1, c1);
782+ /* tag A */
783+ e1 = e_alloc | e_ok; c1 = 0;
784+ testOk(!!(pch = dbChannelCreate(
785+ "x.{'sloppy-tagged':{A:'bar'}}")),
786+ "create channel for sloppy-tagged parsing: A");
787+ testOk(checkValues(puser1, 6, 12, 1, 1.234e5, "hello", 0, 4, "bar"),
788 "guards intact, values correct");
789 if (!testOk(c1 == e1, "all expected calls happened"))
790 testDiag("expected %#x - called %#x", e1, c1);
791@@ -749,9 +813,9 @@ MAIN(chfPluginTest)
792 /* All perfect */
793 testHead("STRICT parsing: all ok");
794 e1 = e_alloc | e_ok; c1 = 0;
795- testOk(!!(pch = dbChannelCreate("x.{strict:{i:1,f:false,d:1.2e15,s:'bar',c:'R'}}")),
796+ testOk(!!(pch = dbChannelCreate("x.{strict:{i:1,f:false,d:1.2e15,s:'bar',c:'R',a:'foo'}}")),
797 "create channel for strict parsing: JSON correct");
798- testOk(checkValues(puser1, 99, 1, 0, 1.2e15, "bar", 0, 1),
799+ testOk(checkValues(puser1, 99, 1, 0, 1.2e15, "bar", 0, 1, "foo"),
800 "guards intact, values correct");
801 if (!testOk(c1 == e1, "all expected calls happened"))
802 testDiag("expected %#x - called %#x", e1, c1);
803@@ -765,39 +829,46 @@ MAIN(chfPluginTest)
804 testHead("STRICT parsing: any missing parameter must fail");
805 e1 = e_alloc | e_error | e_free; c1 = 0;
806 testOk(!(pch = dbChannelCreate(
807- "x.{strict:{i:1,f:false,d:1.2e15,s:'bar'}}")),
808+ "x.{strict:{i:1,f:false,d:1.2e15,s:'bar',a:'foo'}}")),
809 "create channel for strict parsing: c missing");
810 testOk(!puser1, "user part cleaned up");
811 if (!testOk(c1 == e1, "all expected calls happened"))
812 testDiag("expected %#x - called %#x", e1, c1);
813 e1 = e_alloc | e_error | e_free; c1 = 0;
814 testOk(!(pch = dbChannelCreate(
815- "x.{strict:{f:false,i:1,d:1.2e15,c:'R'}}")),
816+ "x.{strict:{f:false,i:1,d:1.2e15,c:'R',a:'foo'}}")),
817 "create channel for strict parsing: s missing");
818 testOk(!puser1, "user part cleaned up");
819 if (!testOk(c1 == e1, "all expected calls happened"))
820 testDiag("expected %#x - called %#x", e1, c1);
821 e1 = e_alloc | e_error | e_free; c1 = 0;
822 testOk(!(pch = dbChannelCreate(
823- "x.{strict:{i:1,c:'R',f:false,s:'bar'}}")),
824+ "x.{strict:{i:1,c:'R',f:false,s:'bar',a:'foo'}}")),
825 "create channel for strict parsing: d missing");
826 testOk(!puser1, "user part cleaned up");
827 if (!testOk(c1 == e1, "all expected calls happened"))
828 testDiag("expected %#x - called %#x", e1, c1);
829 e1 = e_alloc | e_error | e_free; c1 = 0;
830 testOk(!(pch = dbChannelCreate(
831- "x.{strict:{d:1.2e15,c:'R',i:1,s:'bar'}}")),
832+ "x.{strict:{d:1.2e15,c:'R',i:1,s:'bar',a:'foo'}}")),
833 "create channel for strict parsing: f missing");
834 testOk(!puser1, "user part cleaned up");
835 if (!testOk(c1 == e1, "all expected calls happened"))
836 testDiag("expected %#x - called %#x", e1, c1);
837 e1 = e_alloc | e_error | e_free; c1 = 0;
838 testOk(!(pch = dbChannelCreate(
839- "x.{strict:{c:'R',s:'bar',f:false,d:1.2e15}}")),
840+ "x.{strict:{c:'R',s:'bar',f:false,d:1.2e15,a:'foo'}}")),
841 "create channel for strict parsing: i missing");
842 testOk(!puser1, "user part cleaned up");
843 if (!testOk(c1 == e1, "all expected calls happened"))
844 testDiag("expected %#x - called %#x", e1, c1);
845+ e1 = e_alloc | e_error | e_free; c1 = 0;
846+ testOk(!(pch = dbChannelCreate(
847+ "x.{strict:{i:1,f:false,d:1.2e15,s:'bar',c:'R'}}")),
848+ "create channel for strict parsing: a missing");
849+ testOk(!puser1, "user part cleaned up");
850+ if (!testOk(c1 == e1, "all expected calls happened"))
851+ testDiag("expected %#x - called %#x", e1, c1);
852
853 /* NOCONV parsing: optional, no conversion */
854
855@@ -805,9 +876,9 @@ MAIN(chfPluginTest)
856 testHead("NOCONV parsing: missing parameters get default value");
857 e1 = e_alloc | e_ok; c1 = 0;
858 testOk(!!(pch = dbChannelCreate(
859- "x.{noconv:{i:1,f:false,d:1.2e15,s:'bar'}}")),
860+ "x.{noconv:{i:1,f:false,d:1.2e15,s:'bar',a:'foo'}}")),
861 "create channel for noconv parsing: c missing");
862- testOk(checkValues(puser1, 99, 1, 0, 1.2e15, "bar", 0, 4),
863+ testOk(checkValues(puser1, 99, 1, 0, 1.2e15, "bar", 0, 4, "foo"),
864 "guards intact, values correct");
865 if (!testOk(c1 == e1, "all expected calls happened"))
866 testDiag("expected %#x - called %#x", e1, c1);
867@@ -819,36 +890,90 @@ MAIN(chfPluginTest)
868
869 e1 = e_any;
870 testOk(!!(pch = dbChannelCreate(
871- "x.{noconv:{i:1,f:false,d:1.2e15,c:'R'}}")),
872+ "x.{noconv:{i:1,f:false,d:1.2e15,c:'R',a:'foo'}}")),
873 "create channel for noconv parsing: s missing");
874- testOk(checkValues(puser1, 99, 1, 0, 1.2e15, "hello", 0, 1),
875+ testOk(checkValues(puser1, 99, 1, 0, 1.2e15, "hello", 0, 1, "foo"),
876 "guards intact, values correct");
877 if (pch) dbChannelDelete(pch);
878
879 testOk(!!(pch = dbChannelCreate(
880- "x.{noconv:{i:1,f:false,s:'bar',c:'R'}}")),
881+ "x.{noconv:{i:1,f:false,s:'bar',c:'R',a:'foo'}}")),
882 "create channel for noconv parsing: d missing");
883- testOk(checkValues(puser1, 99, 1, 0, 1.234e5, "bar", 0, 1),
884+ testOk(checkValues(puser1, 99, 1, 0, 1.234e5, "bar", 0, 1, "foo"),
885 "guards intact, values correct");
886 if (pch) dbChannelDelete(pch);
887
888 testOk(!!(pch = dbChannelCreate(
889- "x.{noconv:{i:1,d:1.2e15,s:'bar',c:'R'}}")),
890+ "x.{noconv:{i:1,d:1.2e15,s:'bar',c:'R',a:'foo'}}")),
891 "create channel for noconv parsing: f missing");
892- testOk(checkValues(puser1, 99, 1, 1, 1.2e15, "bar", 0, 1),
893+ testOk(checkValues(puser1, 99, 1, 1, 1.2e15, "bar", 0, 1, "foo"),
894 "guards intact, values correct");
895 if (pch) dbChannelDelete(pch);
896
897 testOk(!!(pch = dbChannelCreate(
898- "x.{noconv:{f:false,d:1.2e15,s:'bar',c:'R'}}")),
899+ "x.{noconv:{f:false,d:1.2e15,s:'bar',c:'R',a:'foo'}}")),
900 "create channel for noconv parsing: i missing");
901- testOk(checkValues(puser1, 99, 12, 0, 1.2e15, "bar", 0, 1),
902+ testOk(checkValues(puser1, 99, 12, 0, 1.2e15, "bar", 0, 1, "foo"),
903+ "guards intact, values correct");
904+ if (pch) dbChannelDelete(pch);
905+
906+ testOk(!!(pch = dbChannelCreate(
907+ "x.{noconv:{i:1,f:false,d:1.2e15,s:'bar',c:'R'}}")),
908+ "create channel for noconv parsing: a missing");
909+ testOk(checkValues(puser1, 99, 1, 0, 1.2e15, "bar", 0, 1, "default"),
910+ "guards intact, values correct");
911+ if (pch) dbChannelDelete(pch);
912+
913+ /* SIMPLIFIED parsing: only one argument */
914+
915+ /* */
916+ testHead("SIMPLIFIED parsing: all ok");
917+
918+ /* only one arg (i) defined */
919+ testOk(!!(pch = dbChannelCreate("x.{onlyOne:1}")),
920+ "create channel for onlyOne parsing: single arg");
921+ testOk(checkValues(puser1, 99, 1, 1, 1.234e5, "hello", 0, 4, "default"),
922+ "guards intact, values correct");
923+ if (pch) dbChannelDelete(pch);
924+
925+ /* only one arg (d) required */
926+ testOk(!!(pch = dbChannelCreate("x.{onlyOneRequired:1.23}")),
927+ "create channel for onlyOneRequired parsing: single arg");
928+ testOk(checkValues(puser1, 99, 12, 1, 1.23, "hello", 0, 4, "default"),
929+ "guards intact, values correct");
930+ if (pch) dbChannelDelete(pch);
931+
932+ /* ambigous arg fails */
933+ testOk(!(pch = dbChannelCreate("x.{sloppy:1}")),
934+ "create channel for sloppy parsing: ambigous arg");
935+ if (pch) dbChannelDelete(pch);
936+
937+ /* No arg at all */
938+ testOk(!!(pch = dbChannelCreate("x.{sloppy}")),
939+ "create channel for sloppy parsing: no arg at all");
940+ testOk(checkValues(puser1, 99, 12, 1, 1.234e5, "hello", 0, 4, "default"),
941 "guards intact, values correct");
942 if (pch) dbChannelDelete(pch);
943
944+ /* No arg at all followed by another filter */
945+ testHead("Chains of filters without args"); \
946+ e1 = e_any; e2 = e_any;
947+ testOk(!!(pch = dbChannelCreate("x.{sloppy,sloppy}")),
948+ "create channel for chained argless filters: comma separated");
949+ if (pch) dbChannelDelete(pch);
950+ testOk(!!(pch = dbChannelCreate("x.{sloppy , sloppy}")),
951+ "create channel for chained argless filters: comma and space separated");
952+ if (pch) dbChannelDelete(pch);
953+ testOk(!(pch = dbChannelCreate("x.{sloppy#sloppy}")),
954+ "create channel for chained argless filters fail: garbage separator");
955+ if (pch) dbChannelDelete(pch);
956+ testOk(!(pch = dbChannelCreate("x.{sloppy")),
957+ "create channel for chained argless filters fail: missing end");
958+ if (pch) dbChannelDelete(pch);
959+
960 /* Reject wrong types */
961 #define WRONGTYPETEST(Var, Val, Typ) \
962- e1 = e_alloc | e_error | e_free; c1 = 0; \
963+ e1 = e_alloc | e_error | e_free; c1 = 0; e2 = 0; \
964 testOk(!(pch = dbChannelCreate("x.{noconv:{'"#Var"':"#Val"}}")), \
965 "create channel for noconv parsing: wrong type "#Typ" for "#Var); \
966 testOk(!puser1, "user part cleaned up"); \
967@@ -872,15 +997,18 @@ MAIN(chfPluginTest)
968 WRONGTYPETEST(c, 1.23, double);
969 WRONGTYPETEST(c, true, boolean);
970 WRONGTYPETEST(c, 2, integer);
971+ WRONGTYPETEST(a, 1.23, double);
972+ WRONGTYPETEST(a, true, boolean);
973+ WRONGTYPETEST(a, 123, integer);
974
975 /* SLOPPY parsing: optional, with conversion */
976
977-#define CONVTESTGOOD(Var, Val, Typ, Ival, Fval, Dval, Sval1, Sval2, Cval) \
978- e1 = e_alloc | e_ok; c1 = 0; \
979+#define CONVTESTGOOD(Var, Val, Typ, Ival, Fval, Dval, Sval1, Sval2, Cval, Dstr) \
980+ e1 = e_alloc | e_ok; c1 = 0; e2 = 0; \
981 testDiag("Calling dbChannelCreate x.{sloppy:{"#Var":"#Val"}}"); \
982 testOk(!!(pch = dbChannelCreate("x.{sloppy:{"#Var":"#Val"}}")), \
983 "create channel for sloppy parsing: "#Typ" (good) for "#Var); \
984- testOk(checkValues(puser1, 99, Ival, Fval, Dval, Sval1, Sval2, Cval), \
985+ testOk(checkValues(puser1, 99, Ival, Fval, Dval, Sval1, Sval2, Cval, Dstr), \
986 "guards intact, values correct"); \
987 if (!testOk(c1 == e1, "create channel: all expected calls happened")) \
988 testDiag("expected %#x - called %#x", e1, c1); \
989@@ -891,7 +1019,7 @@ MAIN(chfPluginTest)
990 testDiag("expected %#x - called %#x", e1, c1);
991
992 #define CONVTESTBAD(Var, Val, Typ) \
993- e1 = e_alloc | e_error | e_free; c1 = 0; \
994+ e1 = e_alloc | e_error | e_free; c1 = 0; e2 = 0; \
995 testDiag("Calling dbChannelCreate x.{sloppy:{"#Var":"#Val"}}"); \
996 testOk(!(pch = dbChannelCreate("x.{sloppy:{"#Var":"#Val"}}")), \
997 "create channel for sloppy parsing: "#Typ" (bad) for "#Var); \
998@@ -901,67 +1029,79 @@ MAIN(chfPluginTest)
999
1000 /* To integer */
1001 testHead("SLOPPY parsing: conversion to integer");
1002- CONVTESTGOOD(i, "123e4", positive string, 123, 1, 1.234e5, "hello", 0, 4);
1003- CONVTESTGOOD(i, "-12345", negative string, -12345, 1, 1.234e5, "hello", 0, 4);
1004+ CONVTESTGOOD(i, "123e4", positive string, 123, 1, 1.234e5, "hello", 0, 4, "default");
1005+ CONVTESTGOOD(i, "-12345", negative string, -12345, 1, 1.234e5, "hello", 0, 4, "default");
1006 CONVTESTBAD(i, "9234567890", out-of-range string);
1007 CONVTESTBAD(i, ".4", invalid string);
1008- CONVTESTGOOD(i, false, valid boolean, 0, 1, 1.234e5, "hello", 0, 4);
1009- CONVTESTGOOD(i, 3456.789, valid double, 3456, 1, 1.234e5, "hello", 0, 4);
1010+ CONVTESTGOOD(i, false, valid boolean, 0, 1, 1.234e5, "hello", 0, 4, "default");
1011+ CONVTESTGOOD(i, 3456.789, valid double, 3456, 1, 1.234e5, "hello", 0, 4, "default");
1012 CONVTESTBAD(i, 34.7e14, out-of-range double);
1013
1014 /* To boolean */
1015 testHead("SLOPPY parsing: conversion to boolean");
1016- CONVTESTGOOD(f, "false", valid string, 12, 0, 1.234e5, "hello", 0, 4);
1017- CONVTESTGOOD(f, "False", capital valid string, 12, 0, 1.234e5, "hello", 0, 4);
1018- CONVTESTGOOD(f, "0", 0 string, 12, 0, 1.234e5, "hello", 0, 4);
1019- CONVTESTGOOD(f, "15", 15 string, 12, 1, 1.234e5, "hello", 0, 4);
1020+ CONVTESTGOOD(f, "false", valid string, 12, 0, 1.234e5, "hello", 0, 4, "default");
1021+ CONVTESTGOOD(f, "False", capital valid string, 12, 0, 1.234e5, "hello", 0, 4, "default");
1022+ CONVTESTGOOD(f, "0", 0 string, 12, 0, 1.234e5, "hello", 0, 4, "default");
1023+ CONVTESTGOOD(f, "15", 15 string, 12, 1, 1.234e5, "hello", 0, 4, "default");
1024 CONVTESTBAD(f, ".4", invalid .4 string);
1025 CONVTESTBAD(f, "Flase", misspelled invalid string);
1026- CONVTESTGOOD(f, 0, zero integer, 12, 0, 1.234e5, "hello", 0, 4);
1027- CONVTESTGOOD(f, 12, positive integer, 12, 1, 1.234e5, "hello", 0, 4);
1028- CONVTESTGOOD(f, -1234, negative integer, 12, 1, 1.234e5, "hello", 0, 4);
1029- CONVTESTGOOD(f, 0.4, positive non-zero double, 12, 1, 1.234e5, "hello", 0, 4);
1030- CONVTESTGOOD(f, 0.0, zero double, 12, 0, 1.234e5, "hello", 0, 4);
1031- CONVTESTGOOD(f, -0.0, minus-zero double, 12, 0, 1.234e5, "hello", 0, 4);
1032- CONVTESTGOOD(f, -1.24e14, negative double, 12, 1, 1.234e5, "hello", 0, 4);
1033+ CONVTESTGOOD(f, 0, zero integer, 12, 0, 1.234e5, "hello", 0, 4, "default");
1034+ CONVTESTGOOD(f, 12, positive integer, 12, 1, 1.234e5, "hello", 0, 4, "default");
1035+ CONVTESTGOOD(f, -1234, negative integer, 12, 1, 1.234e5, "hello", 0, 4, "default");
1036+ CONVTESTGOOD(f, 0.4, positive non-zero double, 12, 1, 1.234e5, "hello", 0, 4, "default");
1037+ CONVTESTGOOD(f, 0.0, zero double, 12, 0, 1.234e5, "hello", 0, 4, "default");
1038+ CONVTESTGOOD(f, -0.0, minus-zero double, 12, 0, 1.234e5, "hello", 0, 4, "default");
1039+ CONVTESTGOOD(f, -1.24e14, negative double, 12, 1, 1.234e5, "hello", 0, 4, "default");
1040
1041 /* To double */
1042 testHead("SLOPPY parsing: conversion to double");
1043- CONVTESTGOOD(d, "123e4", positive double string, 12, 1, 1.23e6, "hello", 0, 4);
1044- CONVTESTGOOD(d, "-7.89e-14", negative double string, 12, 1, -7.89e-14, "hello", 0, 4);
1045- CONVTESTGOOD(d, "123", positive integer string, 12, 1, 123.0, "hello", 0, 4);
1046- CONVTESTGOOD(d, "-1234567", negative integer string, 12, 1, -1.234567e6, "hello", 0, 4);
1047+ CONVTESTGOOD(d, "123e4", positive double string, 12, 1, 1.23e6, "hello", 0, 4, "default");
1048+ CONVTESTGOOD(d, "-7.89e-14", negative double string, 12, 1, -7.89e-14, "hello", 0, 4, "default");
1049+ CONVTESTGOOD(d, "123", positive integer string, 12, 1, 123.0, "hello", 0, 4, "default");
1050+ CONVTESTGOOD(d, "-1234567", negative integer string, 12, 1, -1.234567e6, "hello", 0, 4, "default");
1051 CONVTESTBAD(d, "1.67e407", out-of-range double string);
1052 CONVTESTBAD(d, "blubb", invalid blubb string);
1053- CONVTESTGOOD(d, 123, positive integer, 12, 1, 123.0, "hello", 0, 4);
1054- CONVTESTGOOD(d, -12345, negative integer, 12, 1, -1.2345e4, "hello", 0, 4);
1055- CONVTESTGOOD(d, true, true boolean, 12, 1, 1.0, "hello", 0, 4);
1056- CONVTESTGOOD(d, false, false boolean, 12, 1, 0.0, "hello", 0, 4);
1057+ CONVTESTGOOD(d, 123, positive integer, 12, 1, 123.0, "hello", 0, 4, "default");
1058+ CONVTESTGOOD(d, -12345, negative integer, 12, 1, -1.2345e4, "hello", 0, 4, "default");
1059+ CONVTESTGOOD(d, true, true boolean, 12, 1, 1.0, "hello", 0, 4, "default");
1060+ CONVTESTGOOD(d, false, false boolean, 12, 1, 0.0, "hello", 0, 4, "default");
1061
1062 /* To string */
1063 testHead("SLOPPY parsing: conversion to string");
1064- CONVTESTGOOD(s, 12345, positive integer, 12, 1, 1.234e5, "12345", 0, 4);
1065- CONVTESTGOOD(s, -1234567891, negative integer, 12, 1, 1.234e5, "-1234567891", 0, 4);
1066- CONVTESTGOOD(s, true, true boolean, 12, 1, 1.234e5, "true", 0, 4);
1067- CONVTESTGOOD(s, false, false boolean, 12, 1, 1.234e5, "false", 0, 4);
1068- CONVTESTGOOD(s, 123e4, small positive double, 12, 1, 1.234e5, "1230000", 0, 4);
1069- CONVTESTGOOD(s, -123e24, negative double, 12, 1, 1.234e5, "-1.23e+26", "-1.23e+026", 4);
1070- CONVTESTGOOD(s, -1.23456789123e26, large negative double, 12, 1, 1.234e5, "-1.23456789123e+26", "-1.23456789123e+026", 4);
1071+ CONVTESTGOOD(s, 12345, positive integer, 12, 1, 1.234e5, "12345", 0, 4, "default");
1072+ CONVTESTGOOD(s, -1234567891, negative integer, 12, 1, 1.234e5, "-1234567891", 0, 4, "default");
1073+ CONVTESTGOOD(s, true, true boolean, 12, 1, 1.234e5, "true", 0, 4, "default");
1074+ CONVTESTGOOD(s, false, false boolean, 12, 1, 1.234e5, "false", 0, 4, "default");
1075+ CONVTESTGOOD(s, 123e4, small positive double, 12, 1, 1.234e5, "1230000", 0, 4, "default");
1076+ CONVTESTGOOD(s, -123e24, negative double, 12, 1, 1.234e5, "-1.23e+26", "-1.23e+026", 4, "default");
1077+ CONVTESTGOOD(s, -1.23456789123e26, large negative double, 12, 1, 1.234e5, "-1.23456789123e+26", "-1.23456789123e+026", 4, "default");
1078
1079 /* To Enum */
1080 testHead("SLOPPY parsing: conversion to enum");
1081- CONVTESTGOOD(c, 2, valid integer choice, 12, 1, 1.234e5, "hello", 0, 2);
1082+ CONVTESTGOOD(c, 2, valid integer choice, 12, 1, 1.234e5, "hello", 0, 2, "default");
1083 CONVTESTBAD(c, 3, invalid integer choice);
1084 CONVTESTBAD(c, 3.2, double);
1085- CONVTESTGOOD(c, "R", valid string choice, 12, 1, 1.234e5, "hello", 0, 1);
1086+ CONVTESTGOOD(c, "R", valid string choice, 12, 1, 1.234e5, "hello", 0, 1, "default");
1087 CONVTESTBAD(c, "blubb", invalid string choice);
1088
1089+ /* To allocated string */
1090+ testHead("SLOPPY parsing: conversion to string");
1091+ CONVTESTGOOD(a, 12345, positive integer, 12, 1, 1.234e5, "hello", 0, 4, "12345");
1092+ CONVTESTGOOD(a, -1234567891, negative integer, 12, 1, 1.234e5, "hello", 0, 4, "-1234567891");
1093+ CONVTESTGOOD(a, true, true boolean, 12, 1, 1.234e5, "hello", 0, 4, "true");
1094+ CONVTESTGOOD(a, false, false boolean, 12, 1, 1.234e5, "hello", 0, 4, "false");
1095+ CONVTESTGOOD(a, 123e4, small positive double, 12, 1, 1.234e5, "hello", 0, 4, "1230000");
1096+ CONVTESTGOOD(a, -123e24, negative double, 12, 1, 1.234e5, "hello", 0, 4, "-1.23e+26");
1097+ CONVTESTGOOD(a, -1.23456789123e26, large negative double, 12, 1, 1.234e5, "hello", 0, 4, "-1.23456789123e+26");
1098+ CONVTESTGOOD(a, {}, empty argument, 12, 1, 1.234e5, "hello", 0, 4, 0);
1099+ CONVTESTGOOD(a, '', empty string, 12, 1, 1.234e5, "hello", 0, 4, "");
1100+
1101 /* Registering and running filter callbacks */
1102
1103 #define CHAINTEST1(Type, Json, ExpReg, ExpRun, DType) \
1104 testHead("Filter chain test, "Type" filter"); \
1105 offset = 0; \
1106- e1 = e_alloc | e_ok; c1 = 0; \
1107+ e1 = e_alloc | e_ok; c1 = 0; e2 = 0; \
1108 testOk(!!(pch = dbChannelCreate("x."Json)), "filter chains: create channel with "Type" filter"); \
1109 if (!testOk(c1 == e1, "create channel: all expected calls happened")) testDiag("expected %#x - called %#x", e1, c1); \
1110 e1 = e_open | ExpReg; c1 = 0; \
1111diff --git a/modules/database/test/std/filters/decTest.c b/modules/database/test/std/filters/decTest.c
1112index e0961af..1eefe5d 100644
1113--- a/modules/database/test/std/filters/decTest.c
1114+++ b/modules/database/test/std/filters/decTest.c
1115@@ -251,8 +251,8 @@ MAIN(decTest)
1116
1117 /* Decimation (N=4) */
1118
1119- testHead("Decimation (n=4)");
1120- testOk(!!(pch = dbChannelCreate("x.VAL{dec:{n:4}}")),
1121+ testHead("Decimation (n=4) with simplified syntax");
1122+ testOk(!!(pch = dbChannelCreate("x.VAL{dec:4}")),
1123 "dbChannel with plugin dec (n=4) created");
1124
1125 checkAndOpenChannel(pch, plug);
1126diff --git a/modules/database/test/std/filters/tsTest.c b/modules/database/test/std/filters/tsTest.c
1127index bd0b799..31ceb99 100644
1128--- a/modules/database/test/std/filters/tsTest.c
1129+++ b/modules/database/test/std/filters/tsTest.c
1130@@ -74,7 +74,7 @@ MAIN(tsTest)
1131
1132 testOk(!!(plug = dbFindFilter(ts, strlen(ts))), "plugin ts registered correctly");
1133
1134- testOk(!!(pch = dbChannelCreate("x.VAL{ts:{}}")), "dbChannel with plugin ts created");
1135+ testOk(!!(pch = dbChannelCreate("x.{ts}")), "dbChannel with plugin ts created");
1136 testOk((ellCount(&pch->filters) == 1), "channel has one plugin");
1137
1138 memset(&fl, PATTERN, sizeof(fl));

Subscribers

People subscribed via source and target branches