Merge ~dirk.zimoch/epics-base:FilterForInfoFields into ~epics-core/epics-base/+git/epics-base:7.0
- Git
- lp:~dirk.zimoch/epics-base
- FilterForInfoFields
- Merge into 7.0
Status: | Needs review |
---|---|
Proposed branch: | ~dirk.zimoch/epics-base:FilterForInfoFields |
Merge into: | ~epics-core/epics-base/+git/epics-base:7.0 |
Diff against target: |
735 lines (+448/-55) 7 files modified
documentation/RELEASE_NOTES.md (+9/-0) modules/database/src/std/filters/Makefile (+1/-0) modules/database/src/std/filters/filters.dbd.pod (+108/-55) modules/database/src/std/filters/info.c (+189/-0) modules/database/test/std/filters/Makefile (+7/-0) modules/database/test/std/filters/epicsRunFilterTests.c (+2/-0) modules/database/test/std/filters/infoTest.cpp (+132/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andrew Johnson | Approve | ||
Dirk Zimoch (community) | Needs Information | ||
Review via email: mp+399414@code.launchpad.net |
Commit message
Add filter to read info fields
Description of the change
Dirk Zimoch (dirk.zimoch) wrote : | # |
The breaking change is in dbAccess.c line 918.
- 73fcbb5... by Dirk Zimoch
-
fail early for info field names that are empty or over the implementation limit of 50 chars
- a461993... by Simon
-
Initial test
Ben Franksen (bfrk) wrote : | # |
In case you haven't solved it yet: what exactly breaks?
Ben Franksen (bfrk) wrote : | # |
Your branch compiles and tests fine on my machine.
Ben Franksen (bfrk) wrote : | # |
Hm, I think my copy of your branch doesn't have the latest changes. I am on your branch but when I
git pull https:/
it wants me to add a merge commit. I don't want to merge I want your branch as is. How do I do that?
Ben Franksen (bfrk) wrote : | # |
And when I checkout the latest commit 663e6442e47ea73
- cfd0ea6... by Simon
-
Added more tests
- bffda44... by Simon
-
Minor cleanup
- 1abb9c3... by Simon
-
Added one more type check test
- 74a3fa6... by Simon
-
Slight cleanup
Dirk Zimoch (dirk.zimoch) wrote : | # |
I am at commit 663e6442e47ea73
I have not pushed the re-base on top of f571c5950bfe476
What fails: I get garbage instead of the correct data. Don't know yet where it came from. I suspect it has to do with the new dbfl_has_copy() logic.
I could fix my code by adding a dummy pfl->u.r.dtor in order to claim "ownership" of the data for the logic in dbAccess.c. The desctructor does nothing, as the data does not need to be destroyed, but otherwise dbAccess.c takes a few wrong branches, it seems.
I have not analyzed the exact path of execution before and after. I have checked which your commits (cherry-picked onto my branch) broke my code and then which modified line. I got the first problem with commit 85822f in dbAccess.c line 918. Reverting that line restored my functionality. But later commits broke it again. So I checked what changed there.
I found out that adding a dtor seems to fix everything. I will commit than now and rebase this branch.
Dirk Zimoch (dirk.zimoch) wrote : | # |
With the current 7.0 head, the function dbGet() in dbAccess.c branches 3 times on a test of dbfl_has_copy().
With a dummy dtor, the program flow it takes the else path all three times.
Without a dtor, it takes the first else path but the other two times it takes the if path.
In the old version of dbAccess.c, it took the else path all three times.
- c5fe434... by Dirk Zimoch
-
fix for recent changes in dbGet()
Ben Franksen (bfrk) wrote : | # |
I see the problem. What you are doing was not anticipated. You do not make a copy, the data is owned by the record all the time, so dbfl_has_copy correctly returns false. But then the code in dbAccess.c assumes that it has to ask the record for things like the actual number of elements etc, so it calls get_array_info for the dbAddr. For actual record fields this is precisely what we want. In your case I guess this is where things start to go wrong. Indeed you have set no_elements and field_type in the pfl. I am not sure how to fix this properly. Adding a dummy dtor means we lie to dbAccess, telling it we have made a copy in order to make it use the meta data in the pfl instead of trying to query the record about it. While this works I find it a bit obscure...
Dirk Zimoch (dirk.zimoch) wrote : | # |
Yes, it is unexpected as I completely throw away the original data and replace it with a constant string. I am not happy to use such a lie, but it probably does no harm. It basically says: "It is my data, I care about the memory." Once we have code that modifies info fields at run time, I probably need a copy and a real descructor. And locks.
Ben Franksen (bfrk) wrote : | # |
Here is what I think we should do. We add another member (one bit, no extra storage needed) to allow the filter to override the ownership decision made by dbfl_has_copy. We should also rename dbfl_has_copy to dbfl_is_owner to make this clearer. Then, instead of defining a dummy dtor, you can set pfl->is_owner = 1 and all is well. Like this:
diff --git a/modules/
index e517d529f.
--- a/modules/
+++ b/modules/
@@ -107,6 +107,7 @@ struct dbfl_ref {
*/
typedef struct db_field_log {
unsigned int type:1; /* type (union) selector */
+ unsigned int is_owner:1; /* whether we own the data */
/* ctx is used for all types */
unsigned int ctx:1; /* context (operation type) */
/* the following are used for value and reference types */
@@ -131,8 +132,9 @@ typedef struct db_field_log {
* this case, making a copy is redundant, so we have no dtor. But conceptually
* the db_field_log still owns the (empty) data.
*/
-#define dbfl_has_copy(p)\
- ((p) && ((p)->type=
+#define dbfl_is_owner(p)\
+ ((p) && ((p)->type=
+ || (p)->is_owner))
#define dbfl_pfield(p)\
((p)-
Ben Franksen (bfrk) wrote : | # |
Sorry, I sent this before I saw your reply. If you anticipate that you may need an actual dtor at some later point this may not be needed. Still, I like the idea to allow a filter to explicitly claim ownership, independent of the presence of a dtor. And renaming dbfl_has_copy to dbfl_is_owner is also probably a good idea.
Ben Franksen (bfrk) wrote : | # |
I made a PR: https:/
mdavidsaver (mdavidsaver) wrote : | # |
Adding another member to db_field_log seems like the right approach. I'm wondering whether 'is_owner' is the right distinction to draw. While reviewing, I gave some consideration to adding 'must_lock' to indicate when dbScanLock is needed to for access. This avoids overloading the meaning of '!dtor'.
Dirk Zimoch (dirk.zimoch) wrote : | # |
Your change looks backward compatible. Anyway, I have modified my code and re-based it.
I will force-push now...
mdavidsaver (mdavidsaver) wrote : | # |
As I see it, there previously were three states for a db_field_log to be in:
1. Simple value type==dbfl_type_val
2. Owned value type==dbfl_type_ref
3. Borrowed value type==dbfl_type_rec
I think these would now need to be represented as:
1. type==dbfl_type_val && !dtor
2. type==dbfl_type_ref && dtor
3. type==dbfl_type_ref && dtor
Where 2 and 3 are ambiguous, and handling #2 would unnecessarily require dbScanLock()
Do I have this correct?
- b7d2a43... by Simon
-
Refactor to reduce DRY
Dirk Zimoch (dirk.zimoch) wrote : | # |
I had type==dbfl_type_ref && !dtor and own the value.
Dirk Zimoch (dirk.zimoch) wrote : | # |
Ben wrote inline:
> + if (pfl->type == dbfl_type_ref && pfl->u.r.dtor)
> + pfl->u.r.dtor(pfl);
>
> These two lines seem to be unnecessary, since you always have type and dtor set to fixed values
What if two filters with dtors are chained?
mdavidsaver (mdavidsaver) wrote : | # |
Maybe the assumption is that a value borrowed from a record will never need cleanup? Then:
1. type==dbfl_type_val && !dtor
2. type==dbfl_type_ref && dtor
3. type==dbfl_type_ref && !dtor
I'm not sure this is necessarily always true. I've been toying with the idea of involving db_field_log in reference counting where a record and and some db_field_log would share ownership. I haven't completely worked this through.
Ben Franksen (bfrk) wrote : | # |
Am 10.03.21 um 17:49 schrieb Dirk Zimoch via Core-talk:
> Ben wrote inline:
>> + if (pfl->type == dbfl_type_ref && pfl->u.r.dtor)
>> + pfl->u.r.dtor(pfl);
>>
>> These two lines seem to be unnecessary, since you always have type and dtor set to fixed values
>
> What if two filters with dtors are chained?
Do they share the same pfl in this case? How would that work?
--
I would rather have questions that cannot be answered, than answers that
cannot be questioned. -- Richard Feynman
Ben Franksen (bfrk) wrote : | # |
I am not sure I understand what you mean with "borrowed". IIUC there is nothing like that going on. You can have the value as a copy (dbfl_type_val) or as a reference (dbfl_type_ref). As I see it, the reference can be
1. owned by the record
2. owned the db_field_log
Originally I thought that 2 coincides with db_field_log having made a copy and thus having a dtor. For normal fields this is true. But info items are immutable, so there is no need to make a copy. There is also no need to to lock the record. And there is no need to query teh record for things like the current number of elements.
If there were guaranteed immutable record fields, the same would hold.
Ben Franksen (bfrk) wrote : | # |
What happens when the record owns a reference and we lock it and then access the data can be called "borrowing". This is a temporary state and not a property of the db_field_log.
Ben Franksen (bfrk) wrote : | # |
> I'm not sure this is necessarily always true. I've been toying with the idea of involving db_field_log in reference counting where a record and and some db_field_log would share ownership. I haven't completely worked this through.
I think I agree. This is why I proposed to allow the filter to override the conditional with an explicit "this is mine, keep off" bit.
Ben Franksen (bfrk) wrote : | # |
Last remark for the moment. What we have with the info items here is that there is no actual owner. The decisions made in dbAccess when it calls dbfl_has_copy is about whether the meta data in the db_field_log is valid. Until now this happened to coincide with the data being owned by the db_field_log. It is clear that for a mutable array value the owner of the data is also the owner of the meta data (size, type, etc). But for immutable data that has no proper owner this is not obvious. So an alternative view would be to say that dbfl_has_copy == dbfl_is_owner stays as is and we add another property dbfl_has_meta_data that explicitly answers that particular question.
You alluded to possible shared ownership using reference counting. Let's see if I understand what you mean. So the actual data is immutable, but may be deallocated when the ref count goes to zero. The producer (normally the record) would create a fresh array but promise not to change the data after "publishing" it i.e. making it available as a field, nor the meta data. Then one could reference that data w/o locking. And since the db_field_log already has room for the meta data, the record (producer) could immediately create immutable reference counted db_field_logs. Was that the idea?
mdavidsaver (mdavidsaver) wrote : | # |
> ... What if two filters with dtors are chained?
Since the value is being replaced, the original dtor needs to be called. So the basic process would be something like:
1. inspect previous value. eg. copy/convert etc.
2. dtor previous value
3. copy in new value/pointer and install new dtor
mdavidsaver (mdavidsaver) wrote : | # |
> What happens when the record owns a reference and we lock it and then access the data can be called "borrowing". This is a temporary state and not a property of the db_field_log.
By "borrowed" I was meaning that a db_field_log which references a buffer which may only be accessed with a dbScanLock() of the associated record. Indeed struct db_field_log does not current encode this associate fully.
I think that you and I (Ben) probably have the same idea with only a sign flip in the name. Would you agree that "is_owner=
Ben Franksen (bfrk) wrote : | # |
I don't think we disagree. I am fine with your definition "is_owner=
Immutable data does not have an owner: there is no need for any locking. Nevertheless (in the absence of garbage collection or ref counting) we must assign responsibility for deallocating the memory (unless the data remains life forever). This is signalled by a non-NULL dtor.
The other question is who has the relevant meta data? Again, for mutable data this is clearly the owner. For immutable data it could be the record or the field_log.
My refactor conflated all three notions: presence of a dtor == db_field_log is owner == db_field_log has the meta data. This is okay for mutable data but not for immutable.
The problem at hand, filters that allow to access info items, has these features: the data is immutable, so no owner; it has infinite life time, so there is no dtor; yet the meta data is held by the field_log, not the record.
This means we need another "degree of freedom": at least one extra bit, perhaps two. The design question is: precisely what meaning shall these bits have?
- c99ede6... by Dirk Zimoch <email address hidden>
-
Merge pull request #1 from dirk-zimoch/FIFtest
Fi ftest
Dirk Zimoch (dirk.zimoch) wrote : | # |
> > ... What if two filters with dtors are chained?
>
> Since the value is being replaced, the original dtor needs to be called. So
> the basic process would be something like:
>
> 1. inspect previous value. eg. copy/convert etc.
> 2. dtor previous value
> 3. copy in new value/pointer and install new dtor
Ok. That is what I'm doing:
1. Ignore the previous value
2. dtor it
3. copy new pointer and install new dtor.
That means those two line in question implement item 2 and need to stay.
- 6beea52... by Simon
-
'fixed' failing test
Ben Franksen (bfrk) wrote : | # |
> 1. Ignore the previous value
> 2. dtor it
> 3. copy new pointer and install new dtor.
>
> That means those two line in question implement item 2 and need to stay.
I see. So this guards against a configuration error: someone applies the info item filter on top of some other filter. Such a configuration doesn't make sense, but we have to do something and I guess your solution is acceptable.
I would want to get a warning in this case, but certainly not every time the filter gets processed, rather only once when the filter is set up. Do you think that could be done?
- b224fcf... by Simon
-
Using dbGet to fetch value
- 8a92bef... by Simon
-
Cleaned up source code and comments
Dirk Zimoch (dirk.zimoch) wrote : | # |
Even though it makes no sense, it is totally leagal to do so. The two lines normally cost nothing because in the typical case dtor is NULL. Actually type is typically not even ref. But they prevent a memory leak in case someone does stack info ontop of another filter.
mdavidsaver (mdavidsaver) wrote : | # |
> ... I would want to get a warning in this case ...
I might agree if such a warning would be seen by a remote client. However, there isn't a mechanism for this. So rather, the IOC console would be cluttered up with messages saying the equivalent of "someone did this wrong". This doesn't seem so useful to me.
Dirk Zimoch (dirk.zimoch) wrote : | # |
You mean like those:
dbChannelCreate: parse error: premature EOF
mdavidsaver (mdavidsaver) wrote : | # |
Precisely. They are annoying, but in the case of errors acceptable as there is no alternative. I don't like the idea of extending this to warnings, where from the client prospective everything is working just fine.
Andrew Johnson (anj) wrote : | # |
Some comments and questions inline; this also needs an entry in the documentation/
Dirk Zimoch (dirk.zimoch) wrote : | # |
Simon, please comment on the compiler flags.
- 5ef70f7... by Dirk Zimoch
-
revert link fixes
- 0d4063f... by Dirk Zimoch
-
info filter described in RELEASE_NOTES
- f4b4a36... by Dirk Zimoch
-
debugging flags removed
- 50c9d70... by Dirk Zimoch
-
cleanup unneeded includes
Dirk Zimoch (dirk.zimoch) wrote : | # |
I would like to have asyntax like TEST.{info:
Ben Franksen (bfrk) wrote : | # |
> Precisely. They are annoying, but in the case of errors acceptable as there
> is no alternative. I don't like the idea of extending this to warnings, where
> from the client prospective everything is working just fine.
Hm, yes. A warning that's not seen by the one who causes it is perhaps not worth the hassle. So we have to be defensive here even though I don't like it.
Dirk Zimoch (dirk.zimoch) wrote : | # |
Best I can achieve to have a long string "shortcut" is at the moment:
a) {info$:
b) {info:{
c) {info:{
mdavidsaver (mdavidsaver) wrote : | # |
The chfPlugin code would need to be taught about shortcuts. Maybe indicated for one parameter with a new bit flag in chfPluginArgDef?
Dirk Zimoch (dirk.zimoch) wrote : | # |
First thing I changed in chfPlugin is to support arbitratily long string arguments, so that I do not need to limit the length of the tag name.
Should that change go to a different merge request?
Should it completely replace the fixed length "chfString" implementation? (currently only used in the "sync" filter)
Andrew Johnson (anj) wrote : | # |
Can we keep any chfPlugin enhancements on a separate branch and as a separate PR please. If you mix them up you would be delaying the merge of the new filter and also complicating the review process, so separate is better. It would be okay to include modifications to streamline the existing filters in the PR that enhances the chfPlugin code.
You could include that suggested shortcut to the filter params here though, it looks much better than having the separate longstr parameter. I would also maybe use the shortcut in the Release Notes example, and even drop the quotes there so users who see it there start to recognize that the quotes aren't needed.
Dirk Zimoch (dirk.zimoch) wrote : | # |
OK, I will create a separate branch for chfPlugin improvements.
Adding the shortcuts now and removeing the quotes from all examples.
Dirk Zimoch (dirk.zimoch) wrote : | # |
Which shortcurt would you prefer for long string? {info:{
As I am working on a change in chfPlugin to allow the even shorter {info:'name'}, info$ would probably be the better way to signal long string.
Should I drop the 'auto' mode?
Ralph Lange (ralph-lange) wrote : | # |
+1 for info$
Which closely follows the initial idea behind the $ shortcut (cf. programming language Basic):
"Adding a $ makes it a string."
- 31b88eb... by Dirk Zimoch
-
cleanup quotes according to new JSON5 parser
- ece87ca... by Dirk Zimoch
-
implement several shortcut alternatives
Dirk Zimoch (dirk.zimoch) wrote : | # |
chfPlugin changes taken out and moved then to merge request 399636.
I fixed the usage of quotes in the documentation. I now suggest to use single quotes for string arguments and double quotes around the whole expression. (Basically because that allows to use shell features like $macros to be used within the double quotes).
I have added implementations for info$:{name:...} as well as for info:{name$:...}.
For me info$ looks nicer than name$ but name$ is simpler to implement.
But if I can implement the chfPlugin shortcut for 1 argument, then using info$ makes more sense.
Dirk Zimoch (dirk.zimoch) wrote : | # |
If https:/
In that case, {info:'name'} for STRING and {info$:'name'} for CHAR array makes sense (no auto switching, no switching options).
Opinions?
Andrew Johnson (anj) wrote : | # |
Core Group 8/11: Approve to be merged (without the chfPlugin changes which need more review).
Andrew Johnson (anj) wrote : | # |
This Launchpad PR was approved last August buf "without chfPlugin changes", it may need rebasing in which case moving it over to GitHub would be worth doing first.
Unmerged commits
- ece87ca... by Dirk Zimoch
-
implement several shortcut alternatives
- 31b88eb... by Dirk Zimoch
-
cleanup quotes according to new JSON5 parser
- 50c9d70... by Dirk Zimoch
-
cleanup unneeded includes
- f4b4a36... by Dirk Zimoch
-
debugging flags removed
- 0d4063f... by Dirk Zimoch
-
info filter described in RELEASE_NOTES
- 5ef70f7... by Dirk Zimoch
-
revert link fixes
- 8a92bef... by Simon
-
Cleaned up source code and comments
- b224fcf... by Simon
-
Using dbGet to fetch value
- 6beea52... by Simon
-
'fixed' failing test
- c99ede6... by Dirk Zimoch <email address hidden>
-
Merge pull request #1 from dirk-zimoch/FIFtest
Fi ftest
Preview Diff
1 | diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md |
2 | index 49cdd74..789a2ea 100644 |
3 | --- a/documentation/RELEASE_NOTES.md |
4 | +++ b/documentation/RELEASE_NOTES.md |
5 | @@ -17,6 +17,15 @@ should also be read to understand what has changed since earlier releases. |
6 | |
7 | <!-- Insert new items immediately below here ... --> |
8 | |
9 | +### New channel filter for info fields |
10 | + |
11 | +A new filter `info` is available to access info fields of a record |
12 | +using Channel Access or links. |
13 | +The text value can be read as a `DBF_STRING` or an array of `DBF_CHAR`. |
14 | + |
15 | +``` |
16 | +caget 'recordname.{info:{name="infotag"}}' |
17 | +``` |
18 | |
19 | ----- |
20 | |
21 | diff --git a/modules/database/src/std/filters/Makefile b/modules/database/src/std/filters/Makefile |
22 | index ee6a0ae..16e8171 100644 |
23 | --- a/modules/database/src/std/filters/Makefile |
24 | +++ b/modules/database/src/std/filters/Makefile |
25 | @@ -16,6 +16,7 @@ dbRecStd_SRCS += dbnd.c |
26 | dbRecStd_SRCS += arr.c |
27 | dbRecStd_SRCS += sync.c |
28 | dbRecStd_SRCS += decimate.c |
29 | +dbRecStd_SRCS += info.c |
30 | |
31 | HTMLS += filters.html |
32 | |
33 | diff --git a/modules/database/src/std/filters/filters.dbd.pod b/modules/database/src/std/filters/filters.dbd.pod |
34 | index 27e356f..d171cda 100644 |
35 | --- a/modules/database/src/std/filters/filters.dbd.pod |
36 | +++ b/modules/database/src/std/filters/filters.dbd.pod |
37 | @@ -16,6 +16,8 @@ The following filters are available in this release: |
38 | |
39 | =item * L<Decimation|/"Decimation Filter dec"> |
40 | |
41 | +=item * L<Info Field|/"Info Field Filter info"> |
42 | + |
43 | =back |
44 | |
45 | =head2 Using Filters |
46 | @@ -27,13 +29,11 @@ to the resulting data-stream. |
47 | The filter specification must appear after the field name, or if the default |
48 | (VAL) field is used after a dot C<.> appended to the record name. |
49 | With the exception of the array short-hand which is described below, all filters |
50 | -must appear inside a pair of braces C< {} > after the dot expressed as a JSON |
51 | -(L<JavaScript Object Notation|http://www.json.org/>) object, which allows filter |
52 | -parameters to be included as needed. |
53 | +must appear inside a pair of braces C< {} > after the dot expressed as a JSON5 |
54 | +(L<JavaScript Object Notation|https://spec.json5.org/>) object, which allows |
55 | +filter parameters to be included as needed. |
56 | |
57 | -Each filter is given as a name/value pair. The filter name (given in parentheses |
58 | -in the titles below) is a string, and must be enclosed inside double-quotes C<"> |
59 | -characters as per the JSON specification. |
60 | +Each filter is given as a name/value pair. |
61 | Parameters to that filter are provided as the value part of the name/value pair, |
62 | and will normally appear as a child JSON object consisting of name/value pairs |
63 | inside a nested pair of braces C< {} >. |
64 | @@ -42,14 +42,18 @@ inside a nested pair of braces C< {} >. |
65 | |
66 | Given a record called C<test:channel> the following would apply a filter C<f> to |
67 | the VAL field of that record, giving the filter two numeric parameters named |
68 | -C<lo> and C<hi>: |
69 | +C<lo> and C<hi> and a string parameter C<s>: |
70 | + |
71 | + "test:channel.{f:{lo:0,hi:10,s:'X'}}" |
72 | |
73 | - test:channel.{"f":{"lo":0,"hi":10}} |
74 | +Note that due to the required presence of quotes around string values, and |
75 | +the way Unix shells expand braces, it is recommended to enclose a filtered name |
76 | +within either double quotes C<< "..." >> or single quotes C<< '...' >>. |
77 | +When using double quotes around the filter, use single quotes around string |
78 | +parameters and vice versa. |
79 | |
80 | -Note that due to the required presence of the double-quote characters in the |
81 | -JSON strings in the name string, it will usually be necessary to enclose a |
82 | -filtered name within single-quotes C<< ' ... ' >> when typing it as an |
83 | -argument to a Unix shell command. |
84 | +Thanks to the new JSON5 parser, member names like the filter names and parameter |
85 | +names do not require quotes any longer. |
86 | |
87 | =head2 Filter Reference |
88 | |
89 | @@ -57,7 +61,7 @@ argument to a Unix shell command. |
90 | |
91 | registrar(tsInitialize) |
92 | |
93 | -=head3 TimeStamp Filter C<"ts"> |
94 | +=head3 TimeStamp Filter C<ts> |
95 | |
96 | This filter is used to set the timestamp of the value fetched through |
97 | the channel to the time the value was fetched (or an update was sent), |
98 | @@ -71,16 +75,16 @@ None, use an empty pair of braces. |
99 | |
100 | =head4 Example |
101 | |
102 | - Hal$ caget -a 'test:channel.{"ts":{}}' |
103 | - test:channel.{"ts":{}} 2012-08-28 22:10:31.192547 0 UDF INVALID |
104 | - Hal$ caget -a 'test:channel' |
105 | + Hal$ caget -a "test:channel.{ts:{}}" |
106 | + test:channel.{ts:{}} 2012-08-28 22:10:31.192547 0 UDF INVALID |
107 | + Hal$ caget -a "test:channel" |
108 | test:channel <undefined> 0 UDF INVALID |
109 | |
110 | =cut |
111 | |
112 | registrar(dbndInitialize) |
113 | |
114 | -=head3 Deadband Filter C<"dbnd"> |
115 | +=head3 Deadband Filter C<dbnd> |
116 | |
117 | This filter implements a channel-specific monitor deadband, which is applied |
118 | after any deadbands implemented by the record itself (it can only drop updates |
119 | @@ -93,21 +97,21 @@ percentage. |
120 | |
121 | =over |
122 | |
123 | -=item Mode+Deadband C<"abs">/C<"rel"> (shorthand) |
124 | +=item Mode+Deadband C<abs>/C<rel> (shorthand) |
125 | |
126 | Mode and deadband can be specified in one definition (shorthand). |
127 | -The desired mode is given as parameter name (C<"abs"> or C<"rel">), with the |
128 | +The desired mode is given as parameter name (C<abs> or C<rel>), with the |
129 | numeric size of the deadband (absolute value or numeric percentage) as value. |
130 | |
131 | -=item Deadband C<"d"> |
132 | +=item Deadband C<d> |
133 | |
134 | The size of the deadband to use. |
135 | Relative deadband values are given as a numeric percentage, but without any |
136 | trailing percent character. |
137 | |
138 | -=item Mode C<"m"> (optional) |
139 | +=item Mode C<m> (optional) |
140 | |
141 | -A string (enclosed in double-quotes C<">), which should contain either |
142 | +A string (enclosed in quotes), which should contain either |
143 | C<abs> or C<rel>. |
144 | The default mode is C<abs> if no mode parameter is included. |
145 | |
146 | @@ -115,7 +119,7 @@ The default mode is C<abs> if no mode parameter is included. |
147 | |
148 | =head4 Example |
149 | |
150 | - Hal$ camonitor 'test:channel' |
151 | + Hal$ camonitor test:channel |
152 | test:channel 2012-09-01 22:10:19.600595 1 LOLO MAJOR |
153 | test:channel 2012-09-01 22:10:20.600661 2 LOLO MAJOR |
154 | test:channel 2012-09-01 22:10:21.600819 3 LOW MINOR |
155 | @@ -123,19 +127,19 @@ The default mode is C<abs> if no mode parameter is included. |
156 | test:channel 2012-09-01 22:10:23.601023 5 |
157 | test:channel 2012-09-01 22:10:24.601136 6 HIGH MINOR |
158 | ^C |
159 | - Hal$ camonitor 'test:channel.{"dbnd":{"abs":1.5}}' |
160 | - test:channel.{"dbnd":{"d":1.5}} 2012-09-01 22:11:49.613341 1 LOLO MAJOR |
161 | - test:channel.{"dbnd":{"d":1.5}} 2012-09-01 22:11:51.613615 3 LOW MINOR |
162 | - test:channel.{"dbnd":{"d":1.5}} 2012-09-01 22:11:53.613804 5 |
163 | - test:channel.{"dbnd":{"d":1.5}} 2012-09-01 22:11:55.614074 7 HIGH MINOR |
164 | - test:channel.{"dbnd":{"d":1.5}} 2012-09-01 22:11:57.614305 9 HIHI MAJOR |
165 | + Hal$ camonitor "test:channel.{dbnd:{abs:1.5}}" |
166 | + test:channel.{dbnd:{d:1.5}} 2012-09-01 22:11:49.613341 1 LOLO MAJOR |
167 | + test:channel.{dbnd:{d:1.5}} 2012-09-01 22:11:51.613615 3 LOW MINOR |
168 | + test:channel.{dbnd:{d:1.5}} 2012-09-01 22:11:53.613804 5 |
169 | + test:channel.{dbnd:{d:1.5}} 2012-09-01 22:11:55.614074 7 HIGH MINOR |
170 | + test:channel.{dbnd:{d:1.5}} 2012-09-01 22:11:57.614305 9 HIHI MAJOR |
171 | ^C |
172 | |
173 | =cut |
174 | |
175 | registrar(arrInitialize) |
176 | |
177 | -=head3 Array Filter C<"arr"> |
178 | +=head3 Array Filter C<arr> |
179 | |
180 | This filter is used to retrieve parts of an array (subarrays and strided |
181 | subarrays). |
182 | @@ -153,16 +157,16 @@ Any parameter may be omitted (keeping the colons) to use the default value. |
183 | If only one colon is included, this means C<[start:end]> with an increment of 1. |
184 | If only a single parameter is used C<[index]> the filter returns one element. |
185 | |
186 | -=item Start index C<"s"> |
187 | +=item Start index C<s> |
188 | |
189 | Index of the first original array element to retrieve. |
190 | |
191 | -=item Increment C<"i"> |
192 | +=item Increment C<i> |
193 | |
194 | Index increment between retrieved elements of the original array; must be |
195 | a positive number. |
196 | |
197 | -=item End index C<"e"> |
198 | +=item End index C<e> |
199 | |
200 | Index of the last original array element to retrieve. |
201 | |
202 | @@ -174,9 +178,9 @@ C<s=0> (first element), C<i=1> (fetch all elements), C<e=-1> |
203 | |
204 | =head4 Example |
205 | |
206 | - Hal$ caget test:channel 'test:channel.{"arr":{"s":2,"i":2,"e":8}}' test:channel.[3:5] test:channel.[3:2:-3] |
207 | + Hal$ caget test:channel "test:channel.{arr:{s:2,i:2,e:8}}" test:channel.[3:5] test:channel.[3:2:-3] |
208 | test:channel 10 0 1 2 3 4 5 6 7 8 9 |
209 | - test:channel.{"arr":{"s":2,"i":2,"e":8}} 4 2 4 6 8 |
210 | + test:channel.{arr:{s:2,i:2,e:8}} 4 2 4 6 8 |
211 | test:channel.[3:5] 3 3 4 5 |
212 | test:channel.[3:2:-3] 3 3 5 7 |
213 | |
214 | @@ -184,7 +188,7 @@ C<s=0> (first element), C<i=1> (fetch all elements), C<e=-1> |
215 | |
216 | registrar(syncInitialize) |
217 | |
218 | -=head3 Synchronize Filter C<"sync"> |
219 | +=head3 Synchronize Filter C<sync> |
220 | |
221 | This filter is used to dynamically enable or disable monitors according |
222 | to a condition and a state variable declared by the IOC. |
223 | @@ -200,57 +204,57 @@ C<dbStateSet()>. |
224 | =item Mode+State |
225 | |
226 | Mode and state can be specified in one definition (shorthand). |
227 | -The desired mode is given as parameter name (C<"before"> / C<"first"> / |
228 | -C<"while"> / C<"last"> / C<"after"> / C<"unless">), with the state name |
229 | -(enclosed in double quotes C<">) as value. |
230 | +The desired mode is given as parameter name (C<before> / C<first> / |
231 | +C<while> / C<last> / C<after> / C<unless>), with the state name |
232 | +(enclosed in quotes) as value. |
233 | |
234 | -=item Mode C<"m"> |
235 | +=item Mode C<m> |
236 | |
237 | -A single word from the list below, enclosed in double quotes C<">. |
238 | +A single word from the list below, enclosed in quotes. |
239 | This controls how the state value should affect the monitor stream. |
240 | |
241 | =over |
242 | |
243 | -=item C<"before"> E<mdash> only the last value received before the state |
244 | +=item C<before> E<mdash> only the last value received before the state |
245 | changes from false to true is forwarded to the client. |
246 | |
247 | -=item C<"first"> E<mdash> only the first value received after the state |
248 | +=item C<first> E<mdash> only the first value received after the state |
249 | changes from true to false is forwarded to the client. |
250 | |
251 | -=item C<"while"> E<mdash> values are forwarded to the client as long as |
252 | +=item C<while> E<mdash> values are forwarded to the client as long as |
253 | the state is true. |
254 | |
255 | -=item C<"last"> E<mdash> only the last value received before the state |
256 | +=item C<last> E<mdash> only the last value received before the state |
257 | changes from true to false is forwarded to the client. |
258 | |
259 | -=item C<"after"> E<mdash> only the first value received after the state |
260 | +=item C<after> E<mdash> only the first value received after the state |
261 | changes from true to false is forwarded to the client. |
262 | |
263 | -=item C<"unless"> E<mdash> values are forwarded to the client as long |
264 | +=item C<unless> E<mdash> values are forwarded to the client as long |
265 | as the state is false. |
266 | |
267 | =back |
268 | |
269 | -=item State C<"s"> |
270 | +=item State C<s> |
271 | |
272 | -The name of a state variable, enclosed in double quotes C<">. |
273 | +The name of a state variable, enclosed in quotes. |
274 | |
275 | =back |
276 | |
277 | =head4 Example |
278 | |
279 | -Assuming there is a system state called "blue", that is being controlled by |
280 | +Assuming there is a system state called C<blue>, that is being controlled by |
281 | some other facility such as a timing system, updates could be restricted to |
282 | -periods only when "blue" is true by using |
283 | +periods only when C<blue> is true by using |
284 | |
285 | - Hal$ camonitor 'test:channel' 'test:channel.{"sync":{"while":"blue"}}' |
286 | + Hal$ camonitor test:channel "test:channel.{sync:{while:'blue'}}" |
287 | ... |
288 | |
289 | =cut |
290 | |
291 | registrar(decInitialize) |
292 | |
293 | -=head3 Decimation Filter C<"dec"> |
294 | +=head3 Decimation Filter C<dec> |
295 | |
296 | This filter is used to reduce the number or rate of monitor updates from a |
297 | channel by an integer factor C<n> that is provided as a filter argument, |
298 | @@ -262,7 +266,7 @@ update through. |
299 | |
300 | =over |
301 | |
302 | -=item Number C<"n"> |
303 | +=item Number C<n> |
304 | |
305 | The decimation factor, a positive integer. Giving n=1 is equivalent to a no-op |
306 | that allows all updates to be passed to the client. |
307 | @@ -281,7 +285,56 @@ client connects. |
308 | To sample a 60Hz channel at 1Hz, a 10Hz channel every 6 seconds or a 1Hz channel |
309 | once every minute: |
310 | |
311 | - Hal$ camonitor 'test:channel' 'test:channel.{"dec":{"n":60}}' |
312 | + Hal$ camonitor test:channel "test:channel.{dec:{n:60}}" |
313 | ... |
314 | |
315 | =cut |
316 | + |
317 | +registrar(infoInitialize) |
318 | + |
319 | +=head3 Info Field Filter C<info> |
320 | + |
321 | +This filter gives access to the info fields of a record. |
322 | + |
323 | +=head4 Parameters |
324 | + |
325 | +=over |
326 | + |
327 | +=item Name C<name> or C<n> |
328 | + |
329 | +The name of the info field. |
330 | +Currently, there is an implementation limit of 50 chars |
331 | +for the name length. |
332 | + |
333 | +=item Long String Flag C<longstr> or C<l> |
334 | + |
335 | +If this optional flag is C<'yes'>, C<'on'> or C<1>, the info field is |
336 | +returned as an array of C<CHAR>. |
337 | +If it is C<'no'>, C<'off'> or C<0>, the info field is returned as a |
338 | +C<STRING>, truncated to 40 characters if necessary. |
339 | +The default is C<'auto'>, which will use C<STRING> for sufficiently short values |
340 | +and array of C<CHAR> for long values. |
341 | + |
342 | +Alternatively, long strings can be requested by adding a C<$> to the filter |
343 | +name or the name parameter. |
344 | + |
345 | +=back |
346 | + |
347 | +=head4 Example |
348 | + |
349 | +Assuming the record C<test:channel> has: |
350 | + |
351 | + info(infofieldname, "value of the info field") |
352 | + |
353 | +To read the value of the info field: |
354 | + |
355 | + $ caget "test:channel.{info:{name:'infofieldname'}}" |
356 | + test:channel.{info:{name:'infofieldname'}} value of the info field |
357 | + $ caget -S "test:channel.{info:{name:'infofieldname',l:'yes'}}" |
358 | + test:channel.{info:{name:'infofieldname'}} value of the info field |
359 | + $ caget -S "test:channel.{info$:{name:'infofieldname'}}" |
360 | + test:channel.{info$:{name:'infofieldname'}} value of the info field |
361 | + $ caget -S "test:channel.{info:{n$:'infofieldname'}}" |
362 | + test:channel.{info:{n$:'infofieldname'}} value of the info field |
363 | + |
364 | +=cut |
365 | diff --git a/modules/database/src/std/filters/info.c b/modules/database/src/std/filters/info.c |
366 | new file mode 100644 |
367 | index 0000000..b14d7ed |
368 | --- /dev/null |
369 | +++ b/modules/database/src/std/filters/info.c |
370 | @@ -0,0 +1,189 @@ |
371 | +/*************************************************************************\ |
372 | +* Copyright (c) 2021 Paul Scherrer Institute |
373 | +* SPDX-License-Identifier: EPICS |
374 | +* EPICS BASE is distributed subject to a Software License Agreement found |
375 | +* in file LICENSE that is included with this distribution. |
376 | +\*************************************************************************/ |
377 | + |
378 | +/* |
379 | + * Author: Dirk Zimoch <dirk.zimoch@psi.ch> |
380 | + */ |
381 | + |
382 | +#include <stdio.h> |
383 | +#include <string.h> |
384 | + |
385 | +#include "chfPlugin.h" |
386 | +#include "dbStaticLib.h" |
387 | +#include "dbAccessDefs.h" |
388 | +#include "epicsExit.h" |
389 | +#include "freeList.h" |
390 | +#include "epicsExport.h" |
391 | + |
392 | +typedef struct myStruct { |
393 | + char name[52]; /* arbitrary size, we better had dynamic strings */ |
394 | + DBENTRY dbentry; |
395 | + int longstr; |
396 | +} myStruct; |
397 | + |
398 | +static void *myStructFreeList; |
399 | + |
400 | +static const |
401 | +chfPluginEnumType longstrEnum[] = {{"no",0}, {"off",0}, {"yes",1}, {"on",1}, {"auto",2}, {NULL, 0}}; |
402 | + |
403 | +static const chfPluginArgDef opts[] = { |
404 | +/* member, name, [tagmember, tagval,] required, convert, [enums] */ |
405 | + chfString (myStruct, name, "name", 1, 0), |
406 | + chfString (myStruct, name, "n", 1, 0), |
407 | + chfTagString (myStruct, name, "name$", longstr, 1, 1, 0), |
408 | + chfTagString (myStruct, name, "n$", longstr, 1, 1, 0), |
409 | + chfTagString (myStruct, name, "$", longstr, 1, 1, 0), |
410 | + chfEnum (myStruct, longstr, "longstr", 0, 1, longstrEnum), |
411 | + chfEnum (myStruct, longstr, "l", 0, 1, longstrEnum), |
412 | + chfPluginArgEnd |
413 | +}; |
414 | + |
415 | +static void * allocPvt(void) |
416 | +{ |
417 | + myStruct *my = (myStruct*) freeListCalloc(myStructFreeList); |
418 | + if (!my) return NULL; |
419 | + my->longstr = 2; |
420 | + return (void *) my; |
421 | +} |
422 | + |
423 | +static void freePvt(void *pvt) |
424 | +{ |
425 | + freeListFree(myStructFreeList, pvt); |
426 | +} |
427 | + |
428 | +static int parse_ok(void *pvt) |
429 | +{ |
430 | + myStruct *my = (myStruct*) pvt; |
431 | + if (my->name[0] == 0) /* empty name */ |
432 | + return -1; |
433 | + if (my->name[sizeof(my->name)-2] != 0) /* name buffer overrun */ |
434 | + return -1; |
435 | + return 0; |
436 | +} |
437 | + |
438 | +static int parse_ok_long(void *pvt) |
439 | +{ |
440 | + myStruct *my = (myStruct*) pvt; |
441 | + my->longstr = 1; |
442 | + return parse_ok(pvt); |
443 | +} |
444 | + |
445 | +static void dbfl_free(struct db_field_log *pfl) |
446 | +{ |
447 | + /* dummy needed for dbGet() to work correctly */ |
448 | +} |
449 | + |
450 | +static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) |
451 | +{ |
452 | + myStruct *my = (myStruct*) pvt; |
453 | + |
454 | + if (pfl->type == dbfl_type_ref && pfl->u.r.dtor) |
455 | + pfl->u.r.dtor(pfl); |
456 | + pfl->type = dbfl_type_ref; |
457 | + pfl->u.r.dtor = dbfl_free; |
458 | + pfl->u.r.field = (void*)dbGetInfoString(&my->dbentry); |
459 | + |
460 | + if (my->longstr) { |
461 | + pfl->field_size = 1; |
462 | + pfl->field_type = DBF_CHAR; |
463 | + pfl->no_elements = strlen((char*)pfl->u.r.field)+1; |
464 | + } else { |
465 | + pfl->field_size = MAX_STRING_SIZE; |
466 | + pfl->field_type = DBF_STRING; |
467 | + pfl->no_elements = 1; |
468 | + } |
469 | + return pfl; |
470 | +} |
471 | + |
472 | +static long channel_open(dbChannel *chan, void *pvt) |
473 | +{ |
474 | + myStruct *my = (myStruct*) pvt; |
475 | + DBENTRY* pdbe = &my->dbentry; |
476 | + int status; |
477 | + |
478 | + dbInitEntryFromAddr(&chan->addr, pdbe); |
479 | + for (status = dbFirstInfo(pdbe); !status; status = dbNextInfo(pdbe)) |
480 | + if (strcmp(dbGetInfoName(pdbe), my->name) == 0) |
481 | + return 0; |
482 | + return -1; |
483 | +} |
484 | + |
485 | +static void channelRegisterPre(dbChannel *chan, void *pvt, |
486 | + chPostEventFunc **cb_out, void **arg_out, db_field_log *pfl) |
487 | +{ |
488 | + myStruct *my = (myStruct*) pvt; |
489 | + size_t len = strlen(dbGetInfoString(&my->dbentry)) + 1; |
490 | + if (my->longstr == 2) { |
491 | + my->longstr = len > MAX_STRING_SIZE; |
492 | + } |
493 | + if (my->longstr) { |
494 | + pfl->field_size = 1; |
495 | + pfl->field_type = DBF_CHAR; |
496 | + pfl->no_elements = len; |
497 | + } else { |
498 | + pfl->field_size = MAX_STRING_SIZE; |
499 | + pfl->field_type = DBF_STRING; |
500 | + pfl->no_elements = 1; |
501 | + } |
502 | + *cb_out = filter; |
503 | + *arg_out = pvt; |
504 | +} |
505 | + |
506 | +static void channel_report(dbChannel *chan, void *pvt, int level, |
507 | + const unsigned short indent) |
508 | +{ |
509 | + myStruct *my = (myStruct*) pvt; |
510 | + printf("%*sInfo: name=%s\n", indent, "", |
511 | + my->name); |
512 | +} |
513 | + |
514 | +static chfPluginIf pif = { |
515 | + allocPvt, |
516 | + freePvt, |
517 | + |
518 | + NULL, /* parse_error, */ |
519 | + parse_ok, |
520 | + |
521 | + channel_open, |
522 | + channelRegisterPre, |
523 | + NULL, /* channelRegisterPost, */ |
524 | + channel_report, |
525 | + NULL /* channel_close */ |
526 | +}; |
527 | + |
528 | +static chfPluginIf piflong = { |
529 | + allocPvt, |
530 | + freePvt, |
531 | + |
532 | + NULL, /* parse_error */ |
533 | + parse_ok_long, |
534 | + |
535 | + channel_open, |
536 | + channelRegisterPre, |
537 | + NULL, /* channelRegisterPost, */ |
538 | + channel_report, |
539 | + NULL /* channel_close */ |
540 | +}; |
541 | + |
542 | +static void infoShutdown(void* ignore) |
543 | +{ |
544 | + if(myStructFreeList) |
545 | + freeListCleanup(myStructFreeList); |
546 | + myStructFreeList = NULL; |
547 | +} |
548 | + |
549 | +static void infoInitialize(void) |
550 | +{ |
551 | + if (!myStructFreeList) |
552 | + freeListInitPvt(&myStructFreeList, sizeof(myStruct), 64); |
553 | + |
554 | + chfPluginRegister("info", &pif, opts); |
555 | + chfPluginRegister("info$", &piflong, opts); |
556 | + epicsAtExit(infoShutdown, NULL); |
557 | +} |
558 | + |
559 | +epicsExportRegistrar(infoInitialize); |
560 | diff --git a/modules/database/test/std/filters/Makefile b/modules/database/test/std/filters/Makefile |
561 | index 2576ce4..190f406 100644 |
562 | --- a/modules/database/test/std/filters/Makefile |
563 | +++ b/modules/database/test/std/filters/Makefile |
564 | @@ -65,6 +65,13 @@ decTest_SRCS += filterTest_registerRecordDeviceDriver.cpp |
565 | testHarness_SRCS += decTest.c |
566 | TESTS += decTest |
567 | |
568 | +TESTPROD_HOST += infoTest |
569 | +infoTest_SRCS += infoTest.cpp |
570 | +infoTest_SRCS += filterTest_registerRecordDeviceDriver.cpp |
571 | +testHarness_SRCS += infoTest.cpp |
572 | +TESTFILES += ../infoTest.db |
573 | +TESTS += infoTest |
574 | + |
575 | # epicsRunFilterTests runs all the test programs in a known working order. |
576 | testHarness_SRCS += epicsRunFilterTests.c |
577 | |
578 | diff --git a/modules/database/test/std/filters/epicsRunFilterTests.c b/modules/database/test/std/filters/epicsRunFilterTests.c |
579 | index 2b92b53..19952c1 100644 |
580 | --- a/modules/database/test/std/filters/epicsRunFilterTests.c |
581 | +++ b/modules/database/test/std/filters/epicsRunFilterTests.c |
582 | @@ -19,6 +19,7 @@ int dbndTest(void); |
583 | int syncTest(void); |
584 | int arrTest(void); |
585 | int decTest(void); |
586 | +int infoTest(void); |
587 | |
588 | void epicsRunFilterTests(void) |
589 | { |
590 | @@ -29,6 +30,7 @@ void epicsRunFilterTests(void) |
591 | runTest(syncTest); |
592 | runTest(arrTest); |
593 | runTest(decTest); |
594 | + runTest(infoTest); |
595 | |
596 | dbmfFreeChunks(); |
597 | |
598 | diff --git a/modules/database/test/std/filters/infoTest.cpp b/modules/database/test/std/filters/infoTest.cpp |
599 | new file mode 100644 |
600 | index 0000000..070d23d |
601 | --- /dev/null |
602 | +++ b/modules/database/test/std/filters/infoTest.cpp |
603 | @@ -0,0 +1,132 @@ |
604 | +/*************************************************************************\ |
605 | +* Copyright (c) 2021 European Spallation Source (ERIC) |
606 | +* SPDX-License-Identifier: EPICS |
607 | +* EPICS BASE is distributed subject to the Software License Agreement |
608 | +* found in the file LICENSE that is included with this distribution. |
609 | +\*************************************************************************/ |
610 | + |
611 | +/* |
612 | + * Author: Simon Rose <simon.rose@ess.eu> |
613 | + */ |
614 | + |
615 | +#include <string.h> |
616 | + |
617 | +#include "epicsStdio.h" |
618 | +#include "dbStaticLib.h" |
619 | +#include "errlog.h" |
620 | +#include "dbAccess.h" |
621 | +#include "dbChannel.h" |
622 | +#include "dbUnitTest.h" |
623 | +#include "testMain.h" |
624 | + |
625 | +extern "C" { |
626 | + void filterTest_registerRecordDeviceDriver(struct dbBase *); |
627 | +} |
628 | + |
629 | +#define DEBUG(fmt, x) printf("DEBUG (%d): %s: " fmt "\n", __LINE__, #x, x) |
630 | + |
631 | +static void runSingleTest(const char *json, dbfType dbf_type, const char *type, const char *expected) { |
632 | + dbChannel *pch; |
633 | + db_field_log fl; |
634 | + db_field_log *pfl; |
635 | + DBADDR addr; |
636 | + char data[80]; |
637 | + long nreq; |
638 | + char name[80] = "x."; |
639 | + |
640 | + strncat(name, json, sizeof(name)-strlen(name)-1); |
641 | + |
642 | + testOk(!!(pch = dbChannelCreate(name)), "dbChannel with plugin %s successful", name); |
643 | + testOk((ellCount(&pch->filters) == 1), "Channel has one plugin"); |
644 | + |
645 | + testOk(!(dbChannelOpen(pch)), "dbChannel with plugin info opened"); |
646 | + testOk(pch->final_type == dbf_type, "Channel type should be %s", type); |
647 | + |
648 | + memset(&fl, 0, sizeof(fl)); |
649 | + pfl = dbChannelRunPreChain(pch, &fl); |
650 | + testOk(pfl->field_type == dbf_type, "Field type should be %s", type); |
651 | + |
652 | + dbNameToAddr(name, &addr); |
653 | + dbScanLock(addr.precord); |
654 | + nreq = strlen(expected) + 1; |
655 | + dbGet(&addr, dbf_type, &data, NULL, &nreq, pfl); |
656 | + dbScanUnlock(addr.precord); |
657 | + |
658 | + testOk(strcmp(expected, data) == 0, "Info string matches expected '%s'", expected); |
659 | + |
660 | + dbChannelDelete(pch); |
661 | +} |
662 | + |
663 | +MAIN(arrTest) |
664 | +{ |
665 | + dbChannel *pch; |
666 | + const chFilterPlugin *plug; |
667 | + char info[] = "info"; |
668 | + dbEventCtx evtctx; |
669 | + DBENTRY dbentry; |
670 | + const char test_info_str[] = "xxxxxxxxxxxx"; |
671 | + const char test_info_str_long[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; |
672 | + const char test_info_str_truncated[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; |
673 | + |
674 | + testPlan(30); |
675 | + |
676 | + /* Prepare the IOC */ |
677 | + |
678 | + testdbPrepare(); |
679 | + |
680 | + testdbReadDatabase("filterTest.dbd", NULL, NULL); |
681 | + |
682 | + filterTest_registerRecordDeviceDriver(pdbbase); |
683 | + |
684 | + testdbReadDatabase("xRecord.db", NULL, NULL); |
685 | + |
686 | + eltc(0); |
687 | + testIocInitOk(); |
688 | + eltc(1); |
689 | + |
690 | + /* Start the IOC */ |
691 | + |
692 | + evtctx = db_init_events(); |
693 | + |
694 | + testOk(!!(plug = dbFindFilter(info, strlen(info))), "plugin 'info' registered correctly"); |
695 | + |
696 | + |
697 | + testDiag("Testing failing info tag reads"); |
698 | + testOk(!dbChannelCreate("x.{info:{}}"), "dbChannel with plugin {info:{}} failed"); |
699 | + testOk(!dbChannelCreate("x.{info:{name:\"\"}}"), "dbChannel with plugin {info:{name:\"\"}} failed"); |
700 | + testOk(!dbChannelCreate("x.{info:{name:\"0123456789a123456789b123456789c123456789d123456789e\"}}"), "dbChannel with plugin {info:{name:\"<too long name>\"}} failed"); |
701 | + |
702 | + |
703 | + testDiag("Testing non-existent tags"); |
704 | + testOk(!!(pch = dbChannelCreate("x.{info:{name:\"non-existent\"}}")), "dbChannel with plugin {info:{name:\"non-existent\"}} created"); |
705 | + testOk(!!(dbChannelOpen(pch)), "dbChannel with non-existent info tag did not open"); |
706 | + |
707 | + /* |
708 | + * Update the record to add the correct info record |
709 | + */ |
710 | + dbInitEntry(pdbbase, &dbentry); |
711 | + dbFindRecord(&dbentry,"x"); |
712 | + dbPutInfo(&dbentry, "a", test_info_str); |
713 | + dbPutInfo(&dbentry, "b", test_info_str_long); |
714 | + |
715 | + |
716 | + testDiag("Testing valid info tag"); |
717 | + runSingleTest("{info:{name:\"a\"}}", DBF_STRING, "DBF_STRING", test_info_str); |
718 | + |
719 | + testDiag("Testing long string"); |
720 | + runSingleTest("{info:{name:\"a\", l:\"on\"}}", DBF_CHAR, "DBF_CHAR", test_info_str); |
721 | + |
722 | + testDiag("Testing long string, full"); |
723 | + runSingleTest("{info:{name:\"b\", l:\"auto\"}}", DBF_CHAR, "DBF_CHAR", test_info_str_long); |
724 | + |
725 | + testDiag("Testing long string, truncated"); |
726 | + runSingleTest("{info:{name:\"b\", l:\"off\"}}", DBF_STRING, "DBF_STRING", test_info_str_truncated); |
727 | + |
728 | + db_close_events(evtctx); |
729 | + |
730 | + testIocShutdownOk(); |
731 | + |
732 | + testdbCleanup(); |
733 | + |
734 | + return testDone(); |
735 | +} |
Works with R7.0.5 but stopped working after the merge with Ben's dbChannel refactoring). I am investigating why. It seems commit 85822f broke it.