Merge lp:~epics-core/epics-gateway/dbe_props into lp:~epics-core/epics-gateway/original-trunk
- dbe_props
- Merge into original-trunk
Status: | Merged |
---|---|
Merged at revision: | 208 |
Proposed branch: | lp:~epics-core/epics-gateway/dbe_props |
Merge into: | lp:~epics-core/epics-gateway/original-trunk |
Diff against target: |
1633 lines (+1179/-97) 21 files modified
.bzrignore (+3/-0) src/gatePv.cc (+260/-93) src/gatePv.h (+16/-0) src/gateResources.h (+1/-0) src/gateServer.cc (+1/-1) src/gateVc.cc (+13/-3) src/gateway.cc (+4/-0) testTop/README (+56/-0) testTop/configure/RULES (+2/-0) testTop/pyTestsApp/GatewayControl.py (+43/-0) testTop/pyTestsApp/IOCControl.py (+52/-0) testTop/pyTestsApp/Makefile (+43/-0) testTop/pyTestsApp/TestDBEAlarm.py (+58/-0) testTop/pyTestsApp/TestDBELog.py (+57/-0) testTop/pyTestsApp/TestDBEProp.py (+75/-0) testTop/pyTestsApp/TestDBEValue.py (+52/-0) testTop/pyTestsApp/TestPropertyCache.py (+289/-0) testTop/pyTestsApp/access.txt (+3/-0) testTop/pyTestsApp/gwtests.py (+67/-0) testTop/pyTestsApp/pvlist.txt (+8/-0) testTop/pyTestsApp/test.db (+76/-0) |
To merge this branch: | bzr merge lp:~epics-core/epics-gateway/dbe_props |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andrew Johnson | Approve | ||
Murali Shankar (community) | Approve | ||
Review via email: mp+262832@code.launchpad.net |
Commit message
Description of the change
Adding support for DBE_PROPERTY and the first handful of tests.
Based on Murali's branch of the same name, with some massaging of the test setup, and some changes to the property update implementation.
Andrew Johnson (anj) wrote : | # |
Ralph Lange (ralph-lange) wrote : | # |
That's tappy that you suggested to me.
Andrew Johnson (anj) wrote : | # |
Thanks. The python prerequisites are thus argparse (RHEL6's RPM python-argparse) and tappy (PyPI package tap.py).
The tests all fail for me, but they do now run:
tux% cat runTests.py.tap
# TAP results for DBEValueTest
not ok 1 - DBE_VALUE monitor on an ai - value changes generate events.
# TAP results for DBELogTest
not ok 2 - DBE_LOG monitor on an ai with an ADEL - leaving the deadband generates events.
# TAP results for DBEAlarmTest
not ok 3 - DBE_ALARM monitor on an ai with two alarm levels - crossing the level generates updates
# TAP results for DBEPropTest
not ok 4 - DBE_PROPERTY monitor on an ai - value changes generate no events; property changes generate events.
# TAP results for PropertyCacheTest
not ok 5 - Get PV (value) through GW - change HIGH directly - get the DBR_CTRL of the PV through GW
not ok 6 - Get PV (value) through GW - disconnect client - change HIGH directly - get the DBR_CTRL of the PV through GW
not ok 7 - Monitor PV (value events) through GW - change HIGH directly - get the DBR_CTRL of the PV through GW
Ralph Lange (ralph-lange) wrote : | # |
Encouraging.
I will add pyepics3 to the prerequisites.
Andrew Johnson (anj) wrote : | # |
Ok. That should actually by pyepics (version 3). I installed it, but don't see any difference in the .tap output yet, all tests still fail. Also when I try to run the pyTest.py program on its own or through the runTests.py.t script it fails:
tux% ./runTests.py
Warning: TOP not set. Using default value of '..'
Cannot find the gateway executable to test ../../bin/
I think that error message is wrong, it doesn't actually set a default value at all (and if it did it should be '../..' when running from the O.<host> directory).
Now executing runTests.py with --verbose I get this to start with:
tux$ TOP=../.. ./runTests.py --verbose
DBE_VALUE monitor on an ai - value changes generate events. ... Starting the IOC using
softIoc -d test.db
Warning: IOC is booting with TOP = "../.."
but was built with TOP = "/home/
filename=
No such file or directory dbRead opening file test.db
Starting the CA Gateway using
../../.
ERROR
...
I copied the test.db file into the O.<host> directory (see my previous warning about the circular dependency for why it wasn't being installed) and now it gets further, but still fails everything:
tux$ TOP=../.. ./runTests.py
EEEEEEE
=======
ERROR: DBE_VALUE monitor on an ai - value changes generate events.
-------
Traceback (most recent call last):
File "/local/
os.
ValueError: zero length field name in format
...
I don't know how to go about fixing that, my python-foo is not strong enough.
It also left my terminal in a bad state; I had to run 'stty sane' to restore it. That's because your stop() routine is terminating the IOC process that in my case is waiting inside readline() (which wants to do key echoing itself). You could avoid that problem by giving the IOC a private stream for stdin and just closing that stream when you want the IOC to exit (or send it an 'exit' command and then close the stream) and waiting for the process to die.
Alternatively about a year ago I modified the base command-line history stuff to allow readline to be disabled using an environment variable, but I haven't merged my ioc-test-module branch that its in yet. I could pull out and merge that particular change if you thing it would be worthwhile.
Ralph Lange (ralph-lange) wrote : | # |
Thanks for the hint with the private streams - I had to 'reset' my terminal countless times, and wondered what to do about it.
The tests were originally intended to be run on the pyTestsApp directory (note that the runTests.py script takes arguments and thus depends on the arg parser, needed EPICS_HOST_ARCH to be set and was making a bunch of assumptions.
For the time being, I left the ability to run from above the O.<T_A> directory (because it is very handy when developing), and distinguish two cases:
1. running from O.<T_A>, as part of an EPICS build. TOP and T_A are set and being used. If '-tap' is found on the command line (set by the perl wrapper), TAP output is being generated on stdout.
2. running manually from pyTestsApp. TOP defaults to '..', and EPICS_HOST_ARCH must be set.
I should probably remove the command line args to runTests.py and pass the verbosity in the environment. Then a manual run can use one of the standard test runners (preferably nosetests), which automagically finds all test classes and allows selecting the tests to be run.
On a related issue: I plan to add proper support for standard python unit tests using nosetests to EPICS Base. The perl wrapper to run a python script is very awkward.
Stay around for an update of this branch next week.
- 234. By Ralph Lange
-
test: add README
- 235. By Ralph Lange
-
test: fix Makefile to work with 3.14 and 3.15
Ralph Lange (ralph-lange) wrote : | # |
The circular dependency was a 3.14 issue (overuse of VPATH). Added a workaround to get things installed in the O.<arch> directory under 3.14 and 3.15.
Added a README.
Now the tests run for me ('make runtests') on 3.14 and 3.15.
On 3.15, all tests pass. On 3.14, some are failing. Will investigate further.
- 236. By Ralph Lange
-
test: rename test classes to match unittest conventions
- 237. By Ralph Lange
-
test: remove runner script, move configuration setup to gwstats, make single tests executable
- 238. By Ralph Lange
-
test: update README
- 239. By Ralph Lange
-
test: stop IOC via stdin pipe to avoid leaving terminal in bad state
Ralph Lange (ralph-lange) wrote : | # |
All issues have been taken care of. Please retry.
- Added README.
- Removed runner script (and argparse dependency).
- Renamed test files/classes to match unittest conventions
- Stopping IOC with input stream to avoid terminal screwup
Under plain 3.14, some tests are failing, as 3.14 does not fully support DBE_PROPERTY.
Under the Debian packaged version (called 3.14), the necessary patches have been backported.
- 240. By Ralph Lange
-
test: fix wrong indention for main script bodies
Murali Shankar (mshankar) wrote : | # |
I downloaded and tested against base-3.15.2 on RHEL6 64 bit box with pyepics-3.2.4. I was able to get the tests running with the EPICS build system and also under nosetests.
Andrew Johnson (anj) wrote : | # |
I still get all tests failing, whether I use use 'make runtests' or 'nosetests --exe' in the pyTestsApp directory. This is the output to the end of the first test, the others give exactly the same traceback:
tux% nosetests --exe
EEEEEEE
=======
ERROR: DBE_ALARM monitor on an ai with two alarm levels - crossing the level generates updates
-------
Traceback (most recent call last):
File "/local/
os.
ValueError: zero length field name in format
-------
Warning: TOP not set. Using default value of '..'
Warning: EPICS_BASE not set. Running 'softIoc' executable in PATH
-------
=======
Setting VERBOSE=YES shows the IOC starting and stopping (it looks like it could be starting the IOCs all at once, but that's probably just the order of capturing and displaying the stdout streams).
What could I be doing wrong?
Murali Shankar (mshankar) wrote : | # |
Would you know the version of Python being used? I'm using 2.7.6 here.
Regards,
Murali
_______
From: <email address hidden> <email address hidden> on behalf of Andrew Johnson <email address hidden>
Sent: Wednesday, July 1, 2015 10:27 AM
To: Ralph Lange
Subject: Re: [Merge] lp:~epics-core/epics-gateway/dbe_props into lp:epics-gateway
I still get all tests failing, whether I use use 'make runtests' or 'nosetests --exe' in the pyTestsApp directory. This is the output to the end of the first test, the others give exactly the same traceback:
tux% nosetests --exe
EEEEEEE
=======
ERROR: DBE_ALARM monitor on an ai with two alarm levels - crossing the level generates updates
-------
Traceback (most recent call last):
File "/local/
os.
ValueError: zero length field name in format
-------
Warning: TOP not set. Using default value of '..'
Warning: EPICS_BASE not set. Running 'softIoc' executable in PATH
-------
=======
Setting VERBOSE=YES shows the IOC starting and stopping (it looks like it could be starting the IOCs all at once, but that's probably just the order of capturing and displaying the stdout streams).
What could I be doing wrong?
--
https:/
You are reviewing the proposed merge of lp:~epics-core/epics-gateway/dbe_props into lp:epics-gateway.
Ralph Lange (ralph-lange) wrote : | # |
Ah, found it!
Omitting the positional parameters in the format string is allowed starting with Python 2.7...
Please replace the two empty squirly brackets in the statement with their explicit variants:
'{} {}' -> '{0} {1}'
Sorry for the inconvenience!
Andrew Johnson (anj) wrote : | # |
RHEL 6.6 ships with Python 2.6.6 so that made a major difference, would you like me to commit those changes?
I still have test failures under 3.14, but not 3.15 — could you maybe check for the Base version and disable these tests under 3.14? With epicsUnitTest I would mark these tests as SKIP:
FAIL: DBE_PROPERTY monitor on an ai - value changes generate no events; property changes generate events.
FAIL: Get PV (value) through GW - change HIGH directly - get the DBR_CTRL of the PV through GW
FAIL: Monitor PV (value events) through GW - change HIGH directly - get the DBR_CTRL of the PV through GW
Hmm, when I run nosetests manually in the pyTestsApp directory, pyepics can't find libca.so at all. I think make is setting the EPICS_BASE environment variable which is sufficient for pyepics to work. You should probably mention the need to set EPICS_BASE in the testTop/README.
Makefile issues:
If I use 'make runtest' it re-runs the tests (on the host, using the host binaries) for every cross-target architecture I have Base configured to build. The Makefile should probably use "ifeq ($(T_A)
Please use $(INSTALL) or even $(CP) instead of install, which is not portable. The final argument to $(INSTALL) must be the directory name to install into though, not the target filename.
Your build is doing installations of the .py files at 'make runtests' time, not during the initial 'make'. That works, but you might want to put them in TESTSCRIPTS_HOST instead of your own PYTESTS variable and then they will be installed automatically at 'make' time (you do still need the %.py: pattern rule though). That seems to partially fix the issue that 'make clean' doesn't work from inside the O.<host> directory.
I don't understand your comment in the Makefile about making stuff "go through common dir".
Ralph Lange (ralph-lange) wrote : | # |
> I don't understand your comment in the Makefile about making stuff "go through
> common dir".
In EPICS 3.15, I can do
%.db: ../%.db
$(INSTALL) -m 644 $< .
myTest.py: test.db
and the test database will be copied from .. to the O.<arch> where it is needed.
The EPICS 3.14 build system extensively uses VPATH, and the above simple rule does not work. Make creates circular dependencies (you experienced that, see your above comments) because .. being in the VPATH makes %.db equivalent to ../%.db - no matter how you try, make will never do the obvious.
The only ways out are either installing in a non-VPATH directory first, then into O.<arch> - that's what I meant by "making stuff go through common dir" - or renaming the files in-between (so that VPATH does not apply).
- 241. By Ralph Lange
-
test: fix formatter parameters to work with python < 2.7
- 242. By Ralph Lange
-
test: add environment overrides for IOC
- 243. By Ralph Lange
-
test: always write directly to IOC, prefer pyepics wait instead of time.sleep()
- 244. By Ralph Lange
-
test: fix make rules to build, run host-only and clean up properly with 3.14 and 3.15
- 245. By Ralph Lange
-
test: make property tests work with 3.14 and 3.15 IOCs
Ralph Lange (ralph-lange) wrote : | # |
All taken care of:
I did find a slightly easier way to have make install the extra files.
All commands in recipes use the EPICS build system variables.
Tests are run only on EPICS_HOST_ARCH.
The failing property tests needed a broader fix: the expected results depend on what Base version the IOC is built with, and are independent from the Gateway's Base version (which is the current value of EPICS_BASE when tests are run). I had to change the tests and have them probe the IOC for DBE_PROPERTY support as part of the test setup. A Gateway compiled against 3.14 perfectly passes the full tests if run against a 3.15 IOC.
Thanks a lot for your help and reviewing effort!
One more time, please....
Andrew Johnson (anj) wrote : | # |
Works for me now, thanks!
Preview Diff
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2014-07-30 13:33:03 +0000 |
3 | +++ .bzrignore 2015-07-02 13:38:28 +0000 |
4 | @@ -7,3 +7,6 @@ |
5 | ./templates |
6 | **/O.* |
7 | ./QtC-* |
8 | +# Gateway generated scriptlets |
9 | +gateway.killer |
10 | +gateway.restart |
11 | |
12 | === modified file 'src/gatePv.cc' |
13 | --- src/gatePv.cc 2015-06-19 13:37:26 +0000 |
14 | +++ src/gatePv.cc 2015-07-02 13:38:28 +0000 |
15 | @@ -96,6 +96,9 @@ |
16 | extern void logEventCB(EVENT_ARGS args) { // log event callback |
17 | gatePvData::logEventCB(args); |
18 | } |
19 | + extern void propEventCB(EVENT_ARGS args) { // prop event callback |
20 | + gatePvData::propEventCB(args); |
21 | + } |
22 | } |
23 | |
24 | // quick access to global_resources |
25 | @@ -195,6 +198,9 @@ |
26 | value_alarm_mask|=(mrg->valueEventMask()|mrg->alarmEventMask()); |
27 | value_log_mask|=(mrg->valueEventMask()|mrg->logEventMask()); |
28 | value_mask|=mrg->valueEventMask(); |
29 | + |
30 | + |
31 | + prop_get_state = 1; |
32 | } |
33 | |
34 | gatePvData::~gatePvData(void) |
35 | @@ -236,6 +242,7 @@ |
36 | } |
37 | unmonitor(); |
38 | logUnmonitor(); |
39 | + propUnmonitor(); |
40 | alhUnmonitor(); |
41 | status=ca_clear_channel(chID); |
42 | if(status != ECA_NORMAL) { |
43 | @@ -267,6 +274,7 @@ |
44 | status=0; |
45 | markNotMonitored(); |
46 | markLogNotMonitored(); |
47 | + markPropNotMonitored(); |
48 | markNoCtrlGetPending(); |
49 | markNoTimeGetPending(); |
50 | markAlhNotMonitored(); |
51 | @@ -396,8 +404,8 @@ |
52 | } |
53 | else{ |
54 | markAddRemoveNeeded(); |
55 | - if(ca_read_access(chID)) rc=get(ctrlType); |
56 | - else rc=0; |
57 | + if (ca_read_access(chID)) rc = get(ctrlType); |
58 | + else rc = 0; |
59 | } |
60 | break; |
61 | case gatePvDisconnect: |
62 | @@ -431,6 +439,7 @@ |
63 | gateDebug1(10,"gatePvData::deactivate() %s PV\n",getStateName()); |
64 | unmonitor(); |
65 | logUnmonitor(); |
66 | + propUnmonitor(); |
67 | alhUnmonitor(); |
68 | setState(gatePvInactive); |
69 | #ifdef STAT_PVS |
70 | @@ -653,6 +662,7 @@ |
71 | markNoTimeGetPending(); |
72 | unmonitor(); |
73 | logUnmonitor(); |
74 | + propUnmonitor(); |
75 | alhUnmonitor(); |
76 | |
77 | return rc; |
78 | @@ -724,6 +734,41 @@ |
79 | return rc; |
80 | } |
81 | |
82 | +int gatePvData::propUnmonitor(void) |
83 | +{ |
84 | + gateDebug1(5,"gatePvData::propUnmonitor() name=%s\n",name()); |
85 | + int rc=0; |
86 | + |
87 | + if(propMonitored()) |
88 | + { |
89 | +#ifdef USE_313 |
90 | + rc=ca_clear_event(propID); |
91 | + if(rc != ECA_NORMAL) { |
92 | + fprintf(stderr,"%s gatePvData::propUnmonitor: ca_clear_event failed " |
93 | + "for %s:\n" |
94 | + " %s\n", |
95 | + timeStamp(),name()?name():"Unknown",ca_message(rc)); |
96 | + } else { |
97 | + rc=0; |
98 | + } |
99 | +#else |
100 | + rc=ca_clear_subscription(propID); |
101 | + if(rc != ECA_NORMAL) { |
102 | + fprintf(stderr,"%s gatePvData::propUnmonitor: ca_clear_subscription failed " |
103 | + "for %s:\n" |
104 | + " %s\n", |
105 | + timeStamp(),name()?name():"Unknown",ca_message(rc)); |
106 | + } else { |
107 | + rc=0; |
108 | + } |
109 | +#endif |
110 | + markPropNotMonitored(); |
111 | + } |
112 | + return rc; |
113 | +} |
114 | + |
115 | + |
116 | + |
117 | int gatePvData::alhUnmonitor(void) |
118 | { |
119 | gateDebug1(5,"gatePvData::alhUnmonitor() name=%s\n",name()); |
120 | @@ -881,6 +926,68 @@ |
121 | return rc; |
122 | } |
123 | |
124 | +int gatePvData::propMonitor(void) |
125 | +{ |
126 | + gateDebug1(5,"gatePvData::propMonitor() name=%s\n",name()); |
127 | + int rc=0; |
128 | + |
129 | +#if DEBUG_DELAY |
130 | + if(!strncmp("Xorbit",name(),6)) { |
131 | + printf("%s gatePvData::propMonitor: %s state=%d\n",timeStamp(),name(), |
132 | + getState()); |
133 | + } |
134 | +#endif |
135 | + |
136 | + if(!propMonitored()) |
137 | + { |
138 | + // gets only 1 element: |
139 | + // rc=ca_add_event(eventType(),chID,eventCB,this,&event); |
140 | + // gets native element count number of elements: |
141 | + |
142 | + if(ca_read_access(chID)) { |
143 | + gateDebug1(5,"gatePvData::propMonitor() type=%ld\n",dataType()); |
144 | +#ifdef USE_313 |
145 | + rc=ca_add_masked_array_event(dataType(),0,chID,::propEventCB,this, |
146 | + 0.0,0.0,0.0,&propID,DBE_PROPERTY); |
147 | + if(rc != ECA_NORMAL) { |
148 | + fprintf(stderr,"%s gatePvData::propMonitor: " |
149 | + "ca_add_masked_array_event failed for %s:\n" |
150 | + " %s\n", |
151 | + timeStamp(),name()?name():"Unknown",ca_message(rc)); |
152 | + rc=-1; |
153 | + } else { |
154 | + rc=0; |
155 | + markpropMonitored(); |
156 | +#if OMIT_CHECK_EVENT |
157 | +#else |
158 | + checkEvent(); |
159 | +#endif |
160 | + } |
161 | +#else |
162 | + rc=ca_create_subscription(dataType(),0,chID,DBE_PROPERTY, |
163 | + ::propEventCB,this,&propID); |
164 | + if(rc != ECA_NORMAL) { |
165 | + fprintf(stderr,"%s gatePvData::propMonitor: " |
166 | + "ca_create_subscription failed for %s:\n" |
167 | + " %s\n", |
168 | + timeStamp(),name()?name():"Unknown",ca_message(rc)); |
169 | + rc=-1; |
170 | + } else { |
171 | + rc=0; |
172 | + markPropMonitored(); |
173 | +#if OMIT_CHECK_EVENT |
174 | +#else |
175 | + checkEvent(); |
176 | +#endif |
177 | + } |
178 | +#endif |
179 | + } else { |
180 | + rc=-1; |
181 | + } |
182 | + } |
183 | + return rc; |
184 | +} |
185 | + |
186 | int gatePvData::alhMonitor(void) |
187 | { |
188 | gateDebug1(5,"gatePvData::alhMonitor() name=%s\n",name()); |
189 | @@ -948,7 +1055,7 @@ |
190 | if(global_resources->getCacheMode()) /* caching enabled */ |
191 | { |
192 | if(!pendingCtrlGet()) { |
193 | - gateDebug0(3,"gatePvData::get() doing ca_array_get_callback\n"); |
194 | + gateDebug1(3,"gatePvData::get() CACHE doing ca_array_get_callback of type CTRL (%ld)\n",dataType()); |
195 | setTransTime(); |
196 | markCtrlGetPending(); |
197 | rc=ca_array_get_callback(dataType(), 1/*totalElements()*/, |
198 | @@ -969,7 +1076,7 @@ |
199 | if(!pendingCtrlGet()) { |
200 | /*check if array is longer than available memory*/ |
201 | if(global_resources->getMaxBytes() >= (unsigned long)(bytes*totalElements()+sizeof(caHdr) + 2 * sizeof ( ca_uint32_t ))){ |
202 | - gateDebug0(3,"gatePvData::get() doing ca_array_get_callback of type CTRL\n"); |
203 | + gateDebug1(3,"gatePvData::get() NO_CACHE doing ca_array_get_callback of type CTRL (%ld)\n",dataType()); |
204 | setTransTime(); |
205 | markCtrlGetPending(); |
206 | rc = ca_array_get_callback(dataType(), 0, chID, ::getCB, this); |
207 | @@ -993,7 +1100,7 @@ |
208 | if(!pendingTimeGet()) { |
209 | /*check if array is longer than available memory*/ |
210 | if(global_resources->getMaxBytes() >= (unsigned long)(bytes*totalElements()+sizeof(caHdr) + 2 * sizeof ( ca_uint32_t ))){ |
211 | - gateDebug0(3,"gatePvData::get() doing ca_array_get_callback of type TIME\n"); |
212 | + gateDebug1(3,"gatePvData::get() NO_CACHE doing ca_array_get_callback of type TIME (%ld)\n", eventType()); |
213 | setTransTime(); |
214 | markTimeGetPending(); |
215 | rc = ca_array_get_callback(eventType(), 0, chID, ::getTimeCB, this); |
216 | @@ -1063,7 +1170,7 @@ |
217 | |
218 | #if DEBUG_GDD |
219 | printf("gatePvData::put(%s): at=%d pt=%d dbr=%ld ft=%ld[%s] name=%s\n", |
220 | - sPutCallback?"callback":"nocallback", |
221 | + pWIO?"callback":"nocallback", |
222 | dd.applicationType(), |
223 | dd.primitiveType(), |
224 | cht, |
225 | @@ -1544,6 +1651,75 @@ |
226 | } |
227 | } |
228 | |
229 | +void gatePvData::propEventCB(EVENT_ARGS args) |
230 | +{ |
231 | + gatePvData* pv=(gatePvData*)ca_puser(args.chid); |
232 | + gateDebug3(5,"gatePvData::propEventCB(gatePvData=%p)(gateVCData=%p) type=%d\n", |
233 | + (void *)pv, (void*)pv->vc, (unsigned int)args.type); |
234 | + gdd* dd; |
235 | + |
236 | +#ifdef RATE_STATS |
237 | + ++pv->mrg->client_event_count; |
238 | +#endif |
239 | + |
240 | +#if DEBUG_BEAM |
241 | + printf("gatePvData::propEventCB(): status=%d %s\n", |
242 | + args.status, |
243 | + pv->name()); |
244 | +#endif |
245 | + |
246 | +#if DEBUG_DELAY |
247 | + if(!strncmp("Xorbit",pv->name(),6)) { |
248 | + printf("%s gatePvData::propEventCB: %s state=%d\n",timeStamp(),pv->name(), |
249 | + pv->getState()); |
250 | + } |
251 | +#endif |
252 | + |
253 | + if(args.status==ECA_NORMAL) |
254 | + { |
255 | + // only sends event_data and does ADD transactions |
256 | + if(pv->active()) |
257 | + { |
258 | + gateDebug2(5,"gatePvData::propEventCB() %s PV %d\n",pv->getStateName(), pv->propGetPending()); |
259 | + if(pv->propGetPending()) { |
260 | + gateDebug1(5,"gatePvData::propEventCB() Ignore first event %s PV\n",pv->getStateName()); |
261 | + pv->markPropNoGetPending(); |
262 | + return; |
263 | + } |
264 | + |
265 | + if ((dd = pv->runDataCB(&args))) // Create the attributes gdd |
266 | + { |
267 | +#if DEBUG_BEAM |
268 | + printf(" dd=%p needAddRemove=%d\n", dd, pv->needAddRemove()); |
269 | +#endif |
270 | + // Update attribute cache |
271 | + pv->vc->setPvData(dd); |
272 | + } |
273 | + |
274 | + if ((dd = pv->runValueDataCB(&args))) // Create the value gdd |
275 | + { |
276 | +#if DEBUG_BEAM |
277 | + printf(" dd=%p needAddRemove=%d\n", dd, pv->needAddRemove()); |
278 | +#endif |
279 | + pv->vc->setEventData(dd); |
280 | + |
281 | + if (pv->needAddRemove()) |
282 | + { |
283 | + gateDebug0(5,"gatePvData::propEventCB() need add/remove\n"); |
284 | + pv->markAddRemoveNotNeeded(); |
285 | + pv->vc->vcAdd(ctrlType); |
286 | + } |
287 | + else |
288 | + { |
289 | + // Post the event |
290 | + pv->vc->vcPostEvent(pv->mrg->propertyEventMask()); |
291 | + } |
292 | + } |
293 | + } |
294 | + ++(pv->event_count); |
295 | + } |
296 | +} |
297 | + |
298 | |
299 | // This is the callback registered with ca_add_subscription in the |
300 | // alhMonitor routine. If conditions are right, it calls the routines |
301 | @@ -1595,109 +1771,91 @@ |
302 | |
303 | void gatePvData::getCB(EVENT_ARGS args) |
304 | { |
305 | - gatePvData* pv=(gatePvData*)ca_puser(args.chid); |
306 | - gateDebug1(5,"gatePvData::getCB(gatePvData=%p)\n",(void *)pv); |
307 | - gdd* dd; |
308 | - readType read_type = ctrlType; |
309 | + gatePvData* pv=(gatePvData*)ca_puser(args.chid); |
310 | + gateDebug1(5,"gatePvData::getCB(gatePvData=%p)\n",(void *)pv); |
311 | + gdd* dd; |
312 | + readType read_type = ctrlType; |
313 | |
314 | |
315 | #ifdef RATE_STATS |
316 | - ++pv->mrg->client_event_count; |
317 | + ++pv->mrg->client_event_count; |
318 | #endif |
319 | |
320 | #if DEBUG_ENUM |
321 | - printf("gatePvData::getCB\n"); |
322 | + printf("gatePvData::getCB\n"); |
323 | #endif |
324 | |
325 | #if DEBUG_DELAY |
326 | - if(!strncmp("Xorbit",pv->name(),6)) { |
327 | - printf("%s gatePvData::getCB: %s state=%d\n",timeStamp(),pv->name(), |
328 | - pv->getState()); |
329 | - } |
330 | + if(!strncmp("Xorbit",pv->name(),6)) { |
331 | + printf("%s gatePvData::getCB: %s state=%d\n",timeStamp(),pv->name(), |
332 | + pv->getState()); |
333 | + } |
334 | #endif |
335 | |
336 | |
337 | - pv->markNoCtrlGetPending(); |
338 | - if(args.status==ECA_NORMAL) |
339 | - { |
340 | - |
341 | - if(pv->active()) |
342 | - { |
343 | - gateDebug1(5,"gatePvData::getCB() %s PV\n",pv->getStateName()); |
344 | + pv->markNoCtrlGetPending(); |
345 | + if (pv->active()) { |
346 | + if (args.status == ECA_NORMAL) { |
347 | + gateDebug1(5,"gatePvData::getCB() %s PV\n",pv->getStateName()); |
348 | + // Update property cache with received property data |
349 | dd = pv->runDataCB(&args); |
350 | if (dd) |
351 | pv->vc->setPvData(dd); |
352 | - |
353 | - if(!global_resources->getCacheMode()) //we must set also value if only ctrl was requested from client |
354 | - { |
355 | + } |
356 | + |
357 | + if (global_resources->getCacheMode()) { |
358 | + |
359 | + // CACHING mode |
360 | + |
361 | + gateDebug0(5,"gatePvData::getCB() [CACHE] Enable value monitor\n"); |
362 | + pv->monitor(); |
363 | + gateDebug0(5,"gatePvData::getCB() [CACHE] Enable property monitor\n"); |
364 | + pv->propMonitor(); |
365 | + |
366 | + if (pv->vc->needPosting() && // enable archive monitor only if in archive mode and requested |
367 | + global_resources->getArchiveMode() && |
368 | + (pv->vc->client_mask == DBE_LOG)) { |
369 | + gateDebug0(5,"gatePvData::getCB() [CACHE] Enable log monitor\n"); |
370 | + pv->logMonitor(); |
371 | + } |
372 | + |
373 | + } else { |
374 | + |
375 | + // NO CACHING mode |
376 | + |
377 | + if (args.status == ECA_NORMAL) { |
378 | + // Update value cache with received data |
379 | dd = pv->runValueDataCB(&args); |
380 | if (dd) |
381 | pv->vc->setEventData(dd); |
382 | - |
383 | - if(pv->needAddRemove() && !pv->vc->needPosting()) |
384 | - { |
385 | - gateDebug0(5,"gatePvData::getCB() need add/remove\n"); |
386 | - pv->markAddRemoveNotNeeded(); |
387 | - pv->vc->vcAdd(read_type); |
388 | - } |
389 | - else |
390 | - pv->vc->vcData(read_type); |
391 | - |
392 | - if(pv->vc->needPosting() && !pv->monitored()) // do monitor only if requested |
393 | - { |
394 | - pv->monitor(); |
395 | - |
396 | - } |
397 | - |
398 | - if(pv->vc->needPosting() && // do archive monitor only if requested |
399 | - global_resources->getArchiveMode() && |
400 | - !pv->logMonitored() && |
401 | - (pv->vc->client_mask == DBE_LOG)) pv->logMonitor(); |
402 | - |
403 | - } |
404 | - else |
405 | - { |
406 | - pv->monitor();//chacing enabled |
407 | - |
408 | - if(pv->vc->needPosting() && // do archive monitor only if requested |
409 | - global_resources->getArchiveMode() && |
410 | - !pv->logMonitored() && |
411 | - (pv->vc->client_mask == DBE_LOG)) pv->logMonitor(); |
412 | - } |
413 | - |
414 | - } |
415 | - } |
416 | - else |
417 | - { |
418 | - // problems with the PV if status code not normal - attempt monitor |
419 | - // should check if Monitor() fails and send remove trans if |
420 | - // needed |
421 | - if(pv->active()) |
422 | - { |
423 | - if(global_resources->getCacheMode())//chacing enabled |
424 | - { |
425 | - pv->monitor(); |
426 | - if(pv->vc->needPosting() && // do archive monitor only if requested |
427 | - global_resources->getArchiveMode() && |
428 | - !pv->logMonitored() && |
429 | - (pv->vc->client_mask == DBE_LOG)) pv->logMonitor(); |
430 | - } |
431 | - else |
432 | - { |
433 | - if(pv->vc->needPosting()) |
434 | - { |
435 | - pv->monitor(); |
436 | - } |
437 | - |
438 | - if(pv->vc->needPosting() && // do archive monitor only if requested |
439 | - global_resources->getArchiveMode() && |
440 | - !pv->logMonitored() && |
441 | - (pv->vc->client_mask == DBE_LOG)) pv->logMonitor(); |
442 | - |
443 | - } |
444 | - } |
445 | - |
446 | - } |
447 | + } |
448 | + |
449 | + if (pv->needAddRemove() && !pv->vc->needPosting()) { |
450 | + gateDebug0(5,"gatePvData::getCB() need add/remove\n"); |
451 | + pv->markAddRemoveNotNeeded(); |
452 | + pv->vc->vcAdd(read_type); |
453 | + } else |
454 | + pv->vc->vcData(read_type); |
455 | + |
456 | + if (pv->vc->needPosting()) { // enable monitor only if requested |
457 | + gateDebug0(5,"gatePvData::getCB() [NO_CACHE] Enable value monitor\n"); |
458 | + pv->monitor(); |
459 | + } |
460 | + |
461 | + if (pv->vc->needPosting() && // enable archive monitor only if in achive mode and requested |
462 | + global_resources->getArchiveMode() && |
463 | + (pv->vc->client_mask == DBE_LOG)) { |
464 | + gateDebug0(5,"gatePvData::getCB() [NO_CACHE] Enable log monitor\n"); |
465 | + pv->logMonitor(); |
466 | + } |
467 | + |
468 | + if (pv->vc->needPosting() && // enable property monitor only if requested |
469 | + (pv->vc->client_mask == DBE_PROPERTY)) { |
470 | + gateDebug0(5,"gatePvData::getCB() [NO_CACHE] Enable property monitor\n"); |
471 | + pv->propMonitor(); |
472 | + } |
473 | + } |
474 | + } |
475 | } |
476 | |
477 | void gatePvData::getTimeCB(EVENT_ARGS args) |
478 | @@ -1753,8 +1911,17 @@ |
479 | if(pv->vc->needPosting() && // do archive monitor only if requested |
480 | global_resources->getArchiveMode() && |
481 | !pv->logMonitored() && |
482 | - (pv->vc->client_mask == DBE_LOG)) pv->logMonitor(); |
483 | + (pv->vc->client_mask == DBE_LOG)) { |
484 | + gateDebug0(5,"gatePvData::getCB() Starting log monitor timecb\n"); |
485 | + pv->logMonitor(); |
486 | + } |
487 | |
488 | + if(pv->vc->needPosting() && // do property monitor only if requested |
489 | + !pv->propMonitored() && |
490 | + (pv->vc->client_mask == DBE_PROPERTY)) { |
491 | + gateDebug0(5,"gatePvData::getCB() Starting prop monitor timecb\n"); |
492 | + pv->propMonitor(); |
493 | + } |
494 | |
495 | } |
496 | } |
497 | @@ -2132,7 +2299,7 @@ |
498 | aitIndex maxCount = totalElements(); |
499 | gdd* value; |
500 | |
501 | - // DBR_TIME_FLOAT response |
502 | + // DBR_TIME_DOUBLE response |
503 | // set up the value |
504 | if (maxCount > 1) |
505 | { |
506 | |
507 | === modified file 'src/gatePv.h' |
508 | --- src/gatePv.h 2015-01-29 14:37:40 +0000 |
509 | +++ src/gatePv.h 2015-07-02 13:38:28 +0000 |
510 | @@ -117,8 +117,11 @@ |
511 | int pendingTimeGet(void) const { return (time_get_state)?1:0; } |
512 | int monitored(void) const { return (mon_state)?1:0; } |
513 | int logMonitored(void) const { return (log_mon_state)?1:0; } |
514 | + int propMonitored(void) const { return (prop_mon_state)?1:0; } |
515 | int alhMonitored(void) const { return (alh_mon_state)?1:0; } |
516 | int alhGetPending(void) const { return (alh_get_state)?1:0; } |
517 | + int propGetPending(void) const { return (prop_get_state)?1:0; } |
518 | + int logGetPending(void) const { return (log_get_state)?1:0; } |
519 | int needAddRemove(void) const { return (complete_flag)?1:0; } |
520 | int abort(void) const { return (abort_flag)?1:0; } |
521 | |
522 | @@ -148,8 +151,10 @@ |
523 | int life(void); // set to connected (CAC connect) |
524 | int monitor(void); // add monitor |
525 | int logMonitor(void); // add log monitor |
526 | + int propMonitor(void); // add prop monitor |
527 | int unmonitor(void); // delete monitor |
528 | int logUnmonitor(void); // delete log monitor |
529 | + int propUnmonitor(void); // delete prop monitor |
530 | int alhMonitor(void); // add alh info monitor |
531 | int alhUnmonitor(void); // delete alh info monitor |
532 | int get(readType read_type); // get callback |
533 | @@ -174,6 +179,10 @@ |
534 | |
535 | void markAlhGetPending(void) { alh_get_state=1; } |
536 | void markAlhNoGetPending(void) { alh_get_state=0; } |
537 | + void markPropGetPending(void) { prop_get_state=1; } |
538 | + void markPropNoGetPending(void) { prop_get_state=0; } |
539 | + void markLogGetPending(void) { log_get_state=1; } |
540 | + void markLogNoGetPending(void) { log_get_state=0; } |
541 | |
542 | |
543 | protected: |
544 | @@ -191,7 +200,9 @@ |
545 | void markMonitored(void) { mon_state=1; } |
546 | void markNotMonitored(void) { mon_state=0; } |
547 | void markLogMonitored(void) { log_mon_state=1; } |
548 | + void markPropMonitored(void) { prop_mon_state=1; } |
549 | void markLogNotMonitored(void) { log_mon_state=0; } |
550 | + void markPropNotMonitored(void) { prop_mon_state=0; } |
551 | void markCtrlGetPending(void) { ctrl_get_state=1; } |
552 | void markNoCtrlGetPending(void) { ctrl_get_state=0; } |
553 | void markTimeGetPending(void) { time_get_state=1; } |
554 | @@ -221,6 +232,7 @@ |
555 | chid chID; // Channel access ID |
556 | evid evID; // Channel access event id |
557 | evid logID; // Channel access event id |
558 | + evid propID; // Channel access event id |
559 | evid alhID; // Channel access alh info event id |
560 | chtype event_type; // DBR type associated with eventCB (event_data) |
561 | chtype data_type; // DBR type associated with getCB (pv_data) |
562 | @@ -234,10 +246,13 @@ |
563 | |
564 | int mon_state; // 0=not monitored, 1=is monitored |
565 | int log_mon_state; // 0=not log monitored, 1=is log monitored |
566 | + int prop_mon_state; // 0=not prop monitored, 1=is prop monitored |
567 | int ctrl_get_state; // 0=no ctrl get pending, 1=ctrl get pending |
568 | int time_get_state; // 0=no time get pending, 1=time get pending |
569 | int alh_mon_state; // 0=alh info not monitored, 1=alh info is monitored |
570 | int alh_get_state; // 0=no alh info get pending, 1=alh info get pending |
571 | + int prop_get_state; // 0=no prop info get pending, 1=prop info get pending |
572 | + int log_get_state; // 0=no log info get pending, 1=log info get pending |
573 | int abort_flag; // true if activate-connect sequence should be aborted |
574 | int complete_flag; // true if ADD/REMOVE required after completion |
575 | |
576 | @@ -287,6 +302,7 @@ |
577 | static void accessCB(ACCESS_ARGS args); // access security callback |
578 | static void eventCB(EVENT_ARGS args); // value-changed callback |
579 | static void logEventCB(EVENT_ARGS args); // value-changed callback |
580 | + static void propEventCB(EVENT_ARGS args); // value-changed callback |
581 | static void alhCB(EVENT_ARGS args); // alh info value-changed callback |
582 | static void putCB(EVENT_ARGS args); // put callback |
583 | static void getCB(EVENT_ARGS args); // get callback |
584 | |
585 | === modified file 'src/gateResources.h' |
586 | --- src/gateResources.h 2015-01-23 15:27:48 +0000 |
587 | +++ src/gateResources.h 2015-07-02 13:38:28 +0000 |
588 | @@ -86,6 +86,7 @@ |
589 | if (event_mask & DBE_VALUE) strcat(event_mask_string, "v"); |
590 | if (event_mask & DBE_ALARM) strcat(event_mask_string, "a"); |
591 | if (event_mask & DBE_LOG) strcat(event_mask_string, "l"); |
592 | + if (event_mask & DBE_PROPERTY) strcat(event_mask_string, "p"); |
593 | } |
594 | unsigned long eventMask(void) const { return event_mask; } |
595 | const char* eventMaskString(void) const { return event_mask_string; } |
596 | |
597 | === modified file 'src/gateServer.cc' |
598 | --- src/gateServer.cc 2015-06-19 13:38:25 +0000 |
599 | +++ src/gateServer.cc 2015-07-02 13:38:28 +0000 |
600 | @@ -1098,7 +1098,7 @@ |
601 | " %s\n",timeStamp(),ca_message(status)); |
602 | } |
603 | |
604 | - select_mask|=(alarmEventMask()|valueEventMask()|logEventMask()); |
605 | + select_mask|=(alarmEventMask()|valueEventMask()|logEventMask()|propertyEventMask()); |
606 | alh_mask|=alarmEventMask(); |
607 | |
608 | exist_count=0; |
609 | |
610 | === modified file 'src/gateVc.cc' |
611 | --- src/gateVc.cc 2015-01-23 15:27:48 +0000 |
612 | +++ src/gateVc.cc 2015-07-02 13:38:28 +0000 |
613 | @@ -357,7 +357,8 @@ |
614 | |
615 | select_mask|=(mrg->alarmEventMask()| |
616 | mrg->valueEventMask()| |
617 | - mrg->logEventMask()); |
618 | + mrg->logEventMask()| |
619 | + mrg->propertyEventMask()); |
620 | alh_mask|=mrg->alarmEventMask(); |
621 | client_mask=0u; |
622 | |
623 | @@ -562,7 +563,7 @@ |
624 | |
625 | #if DEBUG_GDD |
626 | heading("gateVcData::setEventData",name()); |
627 | - dumpdd(1,"dd (incoming)",name(),dd); |
628 | + dumpdd(1,"dd (new value data)",name(),dd); |
629 | #endif |
630 | |
631 | #if DEBUG_DELAY |
632 | @@ -751,6 +752,11 @@ |
633 | // currently |
634 | gateDebug2(2,"gateVcData::setPvData(gdd=%p) name=%s\n",(void *)dd,name()); |
635 | |
636 | +#if DEBUG_GDD |
637 | + heading("gateVcData::setPvData",name()); |
638 | + dumpdd(1,"dd (new property data)",name(),dd); |
639 | +#endif |
640 | + |
641 | if(pv_data) pv_data->unreference(); |
642 | pv_data=dd; |
643 | |
644 | @@ -1084,6 +1090,9 @@ |
645 | if (caProtoMask & DBE_LOG) { |
646 | client_mask = DBE_LOG; |
647 | } |
648 | + if (caProtoMask & DBE_PROPERTY) { |
649 | + client_mask = DBE_PROPERTY; |
650 | + } |
651 | } |
652 | |
653 | #if DEBUG_GDD || DEBUG_ENUM |
654 | @@ -1168,7 +1177,8 @@ |
655 | return S_casApp_postponeAsyncIO; |
656 | } else { |
657 | // Copy the current state into the dd |
658 | - copyState(dd); |
659 | + gateDebug1(10,"gateVcData::read() %s using cached pv data for reply\n",name()); |
660 | + copyState(dd); |
661 | } |
662 | return S_casApp_success; |
663 | case gddAppType_value: if(!global_resources->getCacheMode()) read_type = timeType; |
664 | |
665 | === modified file 'src/gateway.cc' |
666 | --- src/gateway.cc 2015-01-29 15:25:02 +0000 |
667 | +++ src/gateway.cc 2015-07-02 13:38:28 +0000 |
668 | @@ -760,6 +760,10 @@ |
669 | case 'L' : |
670 | mask |= DBE_LOG; |
671 | break; |
672 | + case 'p' : |
673 | + case 'P' : |
674 | + mask |= DBE_PROPERTY; |
675 | + break; |
676 | default : |
677 | break; |
678 | } |
679 | |
680 | === added file 'testTop/README' |
681 | --- testTop/README 1970-01-01 00:00:00 +0000 |
682 | +++ testTop/README 2015-07-02 13:38:28 +0000 |
683 | @@ -0,0 +1,56 @@ |
684 | +README for CA Gateway Tests |
685 | +=========================== |
686 | + |
687 | +This is an embedded TOP structure. |
688 | +By default, it is being used as part of the CA Gateway sources. |
689 | +You can also copy it out of the CA Gateway source tree and use it |
690 | +separately. In that case, you will have to set the pointers in |
691 | +.../configure/RELEASE appropriately. |
692 | + |
693 | +pyTestsApp |
694 | +---------- |
695 | + |
696 | +Python unit tests. |
697 | +Default settings and configuration in the gwtests.py file. |
698 | + |
699 | +Most of the tests start an EPICS (soft) IOC and a CA Gateway, |
700 | +working on different non-standard ports on the 'localhost' loopback interface, |
701 | +The Gateway uses an ALIAS directive to export the channels on the IOC using |
702 | +a different prefix. |
703 | +The test routines, using the pyepics binding, have access to both the IOC and |
704 | +the Gateway channels to set up subscriptions, trigger events on the IOC and |
705 | +check the resulting event streams through the Gateway. |
706 | + |
707 | +There are two main ways to run the tests: |
708 | +1. As part of the EPICS Build system 'make runtests' and 'make tapfiles' |
709 | + targets: |
710 | + Test files and scripts are copied to the O.<arch> directory, |
711 | + then 'nosetests' is used to run the tests there. |
712 | + The build system sets all necessary environment variables. |
713 | + For 'make tapfiles' the nosetests tap plugin from tap.py is used |
714 | + to create TAP output. |
715 | +2. Manually: |
716 | + Test can be run in the pyTestApp directory using the unittest or nosetests |
717 | + wrapper. (E.g. run 'nosetests --exe'.) |
718 | + These wrappers also allow selecting specific tests to run. |
719 | + You might have to set EPICS_BASE, EPICS_HOST_ARCH and TOP (to find the |
720 | + gateway executable under test and the CA libraries). |
721 | + |
722 | +Environment variables |
723 | +VERBOSE - YES to make tests more verbose |
724 | +VERBOSE_GATEWAY - YES to run the gateway with default debuglevel 10 |
725 | + <debuglevel> for setting a specific debuglevel |
726 | + |
727 | +IOC environment overrides |
728 | +To allow control over the IOC that is being used independently from any |
729 | +settings for the Gateway under test, you can specify environment variables |
730 | +with an "IOC_" prefix that will be set for the IOC (with the prefix removed). |
731 | +E.g. |
732 | +IOC_EPICS_BASE=<location> will set EPICS_BASE=<location> for the IOC. |
733 | +As this setting is also used to determine the location of the 'softIoc' |
734 | +executable, it allows testing against IOCs from different versions of base. |
735 | + |
736 | +Prerequisite python packages: |
737 | + |
738 | +tap.py - Creating TAP output (for 'make tapfiles') |
739 | +pyepics (v3) - Python CA client library |
740 | |
741 | === modified file 'testTop/configure/RULES' |
742 | --- testTop/configure/RULES 2015-01-26 15:44:55 +0000 |
743 | +++ testTop/configure/RULES 2015-07-02 13:38:28 +0000 |
744 | @@ -4,3 +4,5 @@ |
745 | |
746 | # Library should be rebuilt because LIBOBJS may have changed. |
747 | $(LIBNAME): ../Makefile |
748 | + |
749 | +TAPFILES += $(TESTSCRIPTS:.py=.tap) |
750 | |
751 | === added directory 'testTop/pyTestsApp' |
752 | === added file 'testTop/pyTestsApp/GatewayControl.py' |
753 | --- testTop/pyTestsApp/GatewayControl.py 1970-01-01 00:00:00 +0000 |
754 | +++ testTop/pyTestsApp/GatewayControl.py 2015-07-02 13:38:28 +0000 |
755 | @@ -0,0 +1,43 @@ |
756 | +#!/usr/bin/env python |
757 | +'''Controls the CA Gateway''' |
758 | +import subprocess |
759 | +import time |
760 | +import os |
761 | +import gwtests |
762 | + |
763 | +class GatewayControl: |
764 | + gatewayProcess = None |
765 | + DEVNULL = None |
766 | + |
767 | + def startGateway(self): |
768 | + '''Starts the CA Gateway''' |
769 | + gateway_commands = [gwtests.gwExecutable] |
770 | + gateway_commands.extend(["-sip", "localhost", "-sport", str(gwtests.gwPort)]) |
771 | + gateway_commands.extend(["-cip", "localhost", "-cport", str(gwtests.iocPort)]) |
772 | + gateway_commands.extend(["-access", "access.txt", "-pvlist", "pvlist.txt"]) |
773 | + gateway_commands.extend(["-archive", "-prefix", gwtests.gwStatsPrefix]) |
774 | + |
775 | + if gwtests.verboseGateway: |
776 | + gateway_commands.extend(["-debug", str(gwtests.gwDebug)]); |
777 | + if gwtests.verbose: |
778 | + print "Starting the CA Gateway using\n", " ".join(gateway_commands) |
779 | + if not gwtests.verboseGateway and not gwtests.verbose: |
780 | + self.DEVNULL = open(os.devnull, 'wb') |
781 | + self.gatewayProcess = subprocess.Popen(gateway_commands, stdout=self.DEVNULL, stderr=subprocess.STDOUT) |
782 | + |
783 | + def stop(self): |
784 | + '''Stops the CA Gateway''' |
785 | + self.gatewayProcess.terminate() |
786 | + if self.DEVNULL: |
787 | + self.DEVNULL.close() |
788 | + |
789 | + |
790 | +if __name__ == "__main__": |
791 | + gwtests.setup() |
792 | + print "Running the test CA Gateway in verbose mode for {0} seconds".format(gwtests.gwRunDuration) |
793 | + gwtests.verbose = True |
794 | + gwtests.verboseGateway = True |
795 | + gatewayControl = GatewayControl() |
796 | + gatewayControl.startGateway() |
797 | + time.sleep(gwtests.gwRunDuration) |
798 | + gatewayControl.stop() |
799 | |
800 | === added file 'testTop/pyTestsApp/IOCControl.py' |
801 | --- testTop/pyTestsApp/IOCControl.py 1970-01-01 00:00:00 +0000 |
802 | +++ testTop/pyTestsApp/IOCControl.py 2015-07-02 13:38:28 +0000 |
803 | @@ -0,0 +1,52 @@ |
804 | +#!/usr/bin/env python |
805 | +'''Controls the test IOC''' |
806 | +import os |
807 | +import subprocess |
808 | +import time |
809 | +import gwtests |
810 | + |
811 | +class IOCControl: |
812 | + iocProcess = None |
813 | + DEVNULL = None |
814 | + |
815 | + def startIOC(self, arglist=None): |
816 | + '''Starts the test IOC''' |
817 | + childEnviron = os.environ.copy() |
818 | + childEnviron['EPICS_CA_SERVER_PORT'] = str(gwtests.iocPort) |
819 | + childEnviron['EPICS_CA_ADDR_LIST'] = "localhost" |
820 | + childEnviron['EPICS_CA_AUTO_ADDR_LIST'] = "NO" |
821 | + # IOC_ environment overrides |
822 | + for v in os.environ.keys(): |
823 | + if v.startswith("IOC_"): |
824 | + childEnviron[v.replace("IOC_", "", 1)] = os.environ[v] |
825 | + |
826 | + if not gwtests.verbose: |
827 | + self.DEVNULL = open(os.devnull, 'wb') |
828 | + |
829 | + if arglist is None: |
830 | + iocCommand = [gwtests.iocExecutable, '-d', 'test.db'] |
831 | + else: |
832 | + iocCommand = [gwtests.iocExecutable] |
833 | + iocCommand.extend(arglist) |
834 | + |
835 | + if gwtests.verbose: |
836 | + print "Starting the IOC using\n", " ".join(iocCommand) |
837 | + self.iocProcess = subprocess.Popen(iocCommand, env=childEnviron, |
838 | + stdin=subprocess.PIPE, stdout=self.DEVNULL, stderr=subprocess.STDOUT) |
839 | + time.sleep(.5) |
840 | + |
841 | + def stop(self): |
842 | + '''Stops the test IOC''' |
843 | + self.iocProcess.stdin.close() |
844 | + if self.DEVNULL: |
845 | + self.DEVNULL.close() |
846 | + |
847 | + |
848 | +if __name__ == "__main__": |
849 | + gwtests.setup() |
850 | + print "Running the test IOC in verbose mode for {0} seconds".format(gwtests.gwRunDuration) |
851 | + gwtests.verbose = True |
852 | + iocControl = IOCControl() |
853 | + iocControl.startIOC() |
854 | + time.sleep(gwtests.iocRunDuration) |
855 | + iocControl.stop() |
856 | |
857 | === added file 'testTop/pyTestsApp/Makefile' |
858 | --- testTop/pyTestsApp/Makefile 1970-01-01 00:00:00 +0000 |
859 | +++ testTop/pyTestsApp/Makefile 2015-07-02 13:38:28 +0000 |
860 | @@ -0,0 +1,43 @@ |
861 | +TOP=.. |
862 | + |
863 | +include $(TOP)/configure/CONFIG |
864 | +#---------------------------------------- |
865 | +# ADD MACRO DEFINITIONS AFTER THIS LINE |
866 | +#============================= |
867 | + |
868 | +TESTSCRIPTS_HOST += gwtests.py TestDBEValue.py TestDBEAlarm.py TestDBELog.py TestDBEProp.py |
869 | +TESTSCRIPTS_HOST += TestPropertyCache.py |
870 | +TESTSCRIPTS_HOST += IOCControl.py GatewayControl.py |
871 | + |
872 | +TESTFILES += test.db |
873 | +TESTFILES += pvlist.txt access.txt |
874 | + |
875 | +CLEANS += *.pyc $(TESTFILES) gateway.* |
876 | + |
877 | +include $(TOP)/configure/RULES |
878 | +#---------------------------------------- |
879 | +# ADD RULES AFTER THIS LINE |
880 | + |
881 | +ifeq ($(T_A),$(EPICS_HOST_ARCH)) |
882 | + |
883 | +ifeq ($(BASE_3_14),YES) |
884 | +clean:: |
885 | + @$(RM) $(CLEANS) |
886 | +endif |
887 | + |
888 | +runtests: |
889 | + nosetests |
890 | + |
891 | +tapfiles: |
892 | + nosetests --with-tap |
893 | + |
894 | +%.py: ../%.py |
895 | + $(ECHO) "Copying test script $@" |
896 | + @$(INSTALL) -m $(INSTALL_PERMISSIONS) $< . |
897 | + |
898 | +# Workaround for 3.14 make rules: special target to avoid circular dependencies |
899 | +build runtests: testfiles |
900 | +testfiles: $(TESTFILES:%=../%) |
901 | + $(ECHO) "Copying test files $(TESTFILES)" |
902 | + @$(CP) $^ . |
903 | +endif |
904 | |
905 | === added file 'testTop/pyTestsApp/TestDBEAlarm.py' |
906 | --- testTop/pyTestsApp/TestDBEAlarm.py 1970-01-01 00:00:00 +0000 |
907 | +++ testTop/pyTestsApp/TestDBEAlarm.py 2015-07-02 13:38:28 +0000 |
908 | @@ -0,0 +1,58 @@ |
909 | +#!/usr/bin/env python |
910 | +import os |
911 | +import unittest |
912 | +import epics |
913 | +import IOCControl |
914 | +import GatewayControl |
915 | +import gwtests |
916 | +import time |
917 | + |
918 | +class TestDBEAlarm(unittest.TestCase): |
919 | + '''Test alarm updates (client using DBE_ALARM flag) through the Gateway''' |
920 | + |
921 | + def setUp(self): |
922 | + gwtests.setup() |
923 | + self.iocControl = IOCControl.IOCControl() |
924 | + self.gatewayControl = GatewayControl.GatewayControl() |
925 | + self.iocControl.startIOC() |
926 | + self.gatewayControl.startGateway() |
927 | + os.environ["EPICS_CA_AUTO_ADDR_LIST"] = "NO" |
928 | + os.environ["EPICS_CA_ADDR_LIST"] = "localhost:{0} localhost:{1}".format(gwtests.iocPort,gwtests.gwPort) |
929 | + epics.ca.initialize_libca() |
930 | + self.eventsReceived = 0 |
931 | + self.severityUnchanged = 0 |
932 | + self.lastSeverity = 4 |
933 | + |
934 | + def tearDown(self): |
935 | + epics.ca.finalize_libca() |
936 | + self.gatewayControl.stop() |
937 | + self.iocControl.stop() |
938 | + |
939 | + def onChange(self, pvname=None, **kws): |
940 | + self.eventsReceived += 1 |
941 | + if gwtests.verbose: |
942 | + print pvname, " changed to ", kws['value'], kws['severity'] |
943 | + if self.lastSeverity == kws['severity']: |
944 | + self.severityUnchanged += 1 |
945 | + self.lastSeverity = kws['severity'] |
946 | + |
947 | + def testAlarmLevel(self): |
948 | + '''DBE_ALARM monitor on an ai with two alarm levels - crossing the level generates updates''' |
949 | + # gateway:passiveALRM has HIGH=5 (MINOR) and HIHI=10 (MAJOR) |
950 | + ioc = epics.PV("ioc:passiveALRM", auto_monitor=epics.dbr.DBE_ALARM) |
951 | + gw = epics.PV("gateway:passiveALRM", auto_monitor=epics.dbr.DBE_ALARM) |
952 | + gw.add_callback(self.onChange) |
953 | + ioc.get() |
954 | + gw.get() |
955 | + for val in [0,1,2,3,4,5,6,7,8,9,10,9,8,7,6,5,4,3,2,1,0]: |
956 | + ioc.put(val, wait=True) |
957 | + time.sleep(.05) |
958 | + # We get 6 events: at connection (INVALID), at first write (NO_ALARM), |
959 | + # and at the level crossings MINOR-MAJOR-MINOR-NO_ALARM. |
960 | + self.assertTrue(self.eventsReceived == 6, 'events expected: 6; events received: ' + str(self.eventsReceived)) |
961 | + # Any updates with unchanged severity are an error |
962 | + self.assertTrue(self.severityUnchanged == 0, str(self.severityUnchanged) + ' events with no severity changes received') |
963 | + |
964 | + |
965 | +if __name__ == '__main__': |
966 | + unittest.main(verbosity=2) |
967 | |
968 | === added file 'testTop/pyTestsApp/TestDBELog.py' |
969 | --- testTop/pyTestsApp/TestDBELog.py 1970-01-01 00:00:00 +0000 |
970 | +++ testTop/pyTestsApp/TestDBELog.py 2015-07-02 13:38:28 +0000 |
971 | @@ -0,0 +1,57 @@ |
972 | +#!/usr/bin/env python |
973 | +import os |
974 | +import unittest |
975 | +import epics |
976 | +import IOCControl |
977 | +import GatewayControl |
978 | +import gwtests |
979 | +import time |
980 | + |
981 | +class TestDBELog(unittest.TestCase): |
982 | + '''Test log/archive updates (client using DBE_LOG flag) through the Gateway''' |
983 | + |
984 | + def setUp(self): |
985 | + gwtests.setup() |
986 | + self.iocControl = IOCControl.IOCControl() |
987 | + self.gatewayControl = GatewayControl.GatewayControl() |
988 | + self.iocControl.startIOC() |
989 | + self.gatewayControl.startGateway() |
990 | + os.environ["EPICS_CA_AUTO_ADDR_LIST"] = "NO" |
991 | + os.environ["EPICS_CA_ADDR_LIST"] = "localhost:{0} localhost:{1}".format(gwtests.iocPort,gwtests.gwPort) |
992 | + epics.ca.initialize_libca() |
993 | + self.eventsReceived = 0 |
994 | + self.diffInsideDeadband = 0 |
995 | + self.lastValue = -99.9 |
996 | + |
997 | + def tearDown(self): |
998 | + epics.ca.finalize_libca() |
999 | + self.gatewayControl.stop() |
1000 | + self.iocControl.stop() |
1001 | + |
1002 | + def onChange(self, pvname=None, **kws): |
1003 | + self.eventsReceived += 1 |
1004 | + if gwtests.verbose: |
1005 | + print pvname, " changed to ", kws['value'], kws['severity'] |
1006 | + if (kws['value'] != 0.0) and (abs(self.lastValue - kws['value']) <= 10.0): |
1007 | + self.diffInsideDeadband += 1 |
1008 | + self.lastValue = kws['value'] |
1009 | + |
1010 | + def testLogDeadband(self): |
1011 | + '''DBE_LOG monitor on an ai with an ADEL - leaving the deadband generates events.''' |
1012 | + # gateway:passiveADEL has ADEL=10 |
1013 | + ioc = epics.PV("ioc:passiveADEL", auto_monitor=epics.dbr.DBE_LOG) |
1014 | + gw = epics.PV("gateway:passiveADEL", auto_monitor=epics.dbr.DBE_LOG) |
1015 | + gw.add_callback(self.onChange) |
1016 | + ioc.get() |
1017 | + gw.get() |
1018 | + for val in range(35): |
1019 | + ioc.put(val, wait=True) |
1020 | + time.sleep(.05) |
1021 | + # We get 5 events: at connection, first put, then at 11 22 33 |
1022 | + self.assertTrue(self.eventsReceived == 5, 'events expected: 5; events received: ' + str(self.eventsReceived)) |
1023 | + # Any updates inside deadband are an error |
1024 | + self.assertTrue(self.diffInsideDeadband == 0, str(self.diffInsideDeadband) + ' events with change <= deadband received') |
1025 | + |
1026 | + |
1027 | +if __name__ == '__main__': |
1028 | + unittest.main(verbosity=2) |
1029 | |
1030 | === added file 'testTop/pyTestsApp/TestDBEProp.py' |
1031 | --- testTop/pyTestsApp/TestDBEProp.py 1970-01-01 00:00:00 +0000 |
1032 | +++ testTop/pyTestsApp/TestDBEProp.py 2015-07-02 13:38:28 +0000 |
1033 | @@ -0,0 +1,75 @@ |
1034 | +#!/usr/bin/env python |
1035 | +import os |
1036 | +import unittest |
1037 | +import epics |
1038 | +import IOCControl |
1039 | +import GatewayControl |
1040 | +import gwtests |
1041 | +import time |
1042 | + |
1043 | +class TestDBEProp(unittest.TestCase): |
1044 | + '''Test property updates (client using DBE_PROPERTY flag) direct and through the Gateway''' |
1045 | + |
1046 | + def setUp(self): |
1047 | + gwtests.setup() |
1048 | + self.iocControl = IOCControl.IOCControl() |
1049 | + self.gatewayControl = GatewayControl.GatewayControl() |
1050 | + self.iocControl.startIOC() |
1051 | + self.gatewayControl.startGateway() |
1052 | + os.environ["EPICS_CA_AUTO_ADDR_LIST"] = "NO" |
1053 | + os.environ["EPICS_CA_ADDR_LIST"] = "localhost:{0} localhost:{1}".format(gwtests.iocPort,gwtests.gwPort) |
1054 | + epics.ca.initialize_libca() |
1055 | + self.eventsReceivedGW = 0 |
1056 | + self.eventsReceivedIOC = 0 |
1057 | + |
1058 | + def tearDown(self): |
1059 | + epics.ca.finalize_libca() |
1060 | + self.gatewayControl.stop() |
1061 | + self.iocControl.stop() |
1062 | + |
1063 | + def onChangeGW(self, pvname=None, **kws): |
1064 | + self.eventsReceivedGW += 1 |
1065 | + if gwtests.verbose: |
1066 | + print " GW update: ", pvname, " changed to ", kws['value'] |
1067 | + |
1068 | + def onChangeIOC(self, pvname=None, **kws): |
1069 | + self.eventsReceivedIOC += 1 |
1070 | + if gwtests.verbose: |
1071 | + print "IOC update: ", pvname, " changed to ", kws['value'] |
1072 | + |
1073 | + def testPropAlarmLevels(self): |
1074 | + '''DBE_PROPERTY monitor on an ai - value changes generate no events; property changes generate events.''' |
1075 | + # gateway:passive0 is a blank ai record |
1076 | + ioc = epics.PV("ioc:passive0", auto_monitor=epics.dbr.DBE_PROPERTY) |
1077 | + ioc.add_callback(self.onChangeIOC) |
1078 | + gw = epics.PV("gateway:passive0", auto_monitor=epics.dbr.DBE_PROPERTY) |
1079 | + gw.add_callback(self.onChangeGW) |
1080 | + pvhihi = epics.PV("ioc:passive0.HIHI", auto_monitor=None) |
1081 | + pvlolo = epics.PV("ioc:passive0.LOLO", auto_monitor=None) |
1082 | + pvhigh = epics.PV("ioc:passive0.HIGH", auto_monitor=None) |
1083 | + pvlow = epics.PV("ioc:passive0.LOW", auto_monitor=None) |
1084 | + ioc.get() |
1085 | + gw.get() |
1086 | + |
1087 | + for val in range(10): |
1088 | + ioc.put(val, wait=True) |
1089 | + time.sleep(.05) |
1090 | + # We get 1 event: at connection |
1091 | + self.assertTrue(self.eventsReceivedGW == 1, 'GW events expected: 1; received: ' + str(self.eventsReceivedGW)) |
1092 | + self.assertTrue(self.eventsReceivedIOC == 1, 'IOC events expected: 1; received: ' + str(self.eventsReceivedIOC)) |
1093 | + |
1094 | + self.eventsReceived = 0 |
1095 | + pvhihi.put(20.0, wait=True) |
1096 | + pvhigh.put(18.0, wait=True) |
1097 | + pvlolo.put(10.0, wait=True) |
1098 | + pvlow.put(12.0, wait=True) |
1099 | + time.sleep(.05) |
1100 | + |
1101 | + # Depending on the IOC (supporting PROPERTY changes on limits or not) we get 0 or 4 events. |
1102 | + # Pass test if updates from IOC act the same as updates from GW |
1103 | + self.assertTrue(self.eventsReceivedGW == self.eventsReceivedIOC, |
1104 | + "Expected equal number of updates; received {0} from GW and {1} from IOC".format(self.eventsReceivedGW, self.eventsReceivedIOC)) |
1105 | + |
1106 | + |
1107 | +if __name__ == '__main__': |
1108 | + unittest.main(verbosity=2) |
1109 | |
1110 | === added file 'testTop/pyTestsApp/TestDBEValue.py' |
1111 | --- testTop/pyTestsApp/TestDBEValue.py 1970-01-01 00:00:00 +0000 |
1112 | +++ testTop/pyTestsApp/TestDBEValue.py 2015-07-02 13:38:28 +0000 |
1113 | @@ -0,0 +1,52 @@ |
1114 | +#!/usr/bin/env python |
1115 | +import os |
1116 | +import unittest |
1117 | +import epics |
1118 | +import IOCControl |
1119 | +import GatewayControl |
1120 | +import gwtests |
1121 | +import time |
1122 | + |
1123 | +class TestDBEValue(unittest.TestCase): |
1124 | + '''Test value updates (client using DBE_VALUE flag) through the Gateway''' |
1125 | + |
1126 | + def setUp(self): |
1127 | + gwtests.setup() |
1128 | + self.iocControl = IOCControl.IOCControl() |
1129 | + self.gatewayControl = GatewayControl.GatewayControl() |
1130 | + self.iocControl.startIOC() |
1131 | + self.gatewayControl.startGateway() |
1132 | + os.environ["EPICS_CA_AUTO_ADDR_LIST"] = "NO" |
1133 | + os.environ["EPICS_CA_ADDR_LIST"] = "localhost:{0} localhost:{1}".format(gwtests.iocPort,gwtests.gwPort) |
1134 | + epics.ca.initialize_libca() |
1135 | + self.eventsReceived = 0 |
1136 | + |
1137 | + def tearDown(self): |
1138 | + epics.ca.finalize_libca() |
1139 | + self.gatewayControl.stop() |
1140 | + self.iocControl.stop() |
1141 | + |
1142 | + def onChange(self, pvname=None, **kws): |
1143 | + self.eventsReceived += 1 |
1144 | + if gwtests.verbose: |
1145 | + print pvname, " changed to ", kws['value'] |
1146 | + |
1147 | + def testValueNoDeadband(self): |
1148 | + '''DBE_VALUE monitor on an ai - value changes generate events.''' |
1149 | + # gateway:passive0 is a blank ai record |
1150 | + ioc = epics.PV("ioc:passive0", auto_monitor=epics.dbr.DBE_VALUE) |
1151 | + gw = epics.PV("gateway:passive0", auto_monitor=epics.dbr.DBE_VALUE) |
1152 | + gw.add_callback(self.onChange) |
1153 | + ioc.get() |
1154 | + gw.get() |
1155 | + |
1156 | + for val in range(10): |
1157 | + ioc.put(val, wait=True) |
1158 | + time.sleep(.05) |
1159 | + |
1160 | + # We get 11 events: at connection, then at 10 value changes (puts) |
1161 | + self.assertTrue(self.eventsReceived == 11, 'events expected: 11; events received: ' + str(self.eventsReceived)) |
1162 | + |
1163 | + |
1164 | +if __name__ == '__main__': |
1165 | + unittest.main(verbosity=2) |
1166 | |
1167 | === added file 'testTop/pyTestsApp/TestPropertyCache.py' |
1168 | --- testTop/pyTestsApp/TestPropertyCache.py 1970-01-01 00:00:00 +0000 |
1169 | +++ testTop/pyTestsApp/TestPropertyCache.py 2015-07-02 13:38:28 +0000 |
1170 | @@ -0,0 +1,289 @@ |
1171 | +#!/usr/bin/env python |
1172 | +import os |
1173 | +import unittest |
1174 | +import epics |
1175 | +from epics import ca, dbr |
1176 | +import IOCControl |
1177 | +import GatewayControl |
1178 | +import gwtests |
1179 | +import time |
1180 | +import subprocess |
1181 | + |
1182 | +class TestPropertyCache(unittest.TestCase): |
1183 | + '''Testing the Gateway PV property cache |
1184 | + Set up a connection through the Gateway - change a property externally - check if Gateway cache was updated |
1185 | + ''' |
1186 | + |
1187 | + def setUp(self): |
1188 | + gwtests.setup() |
1189 | + self.iocControl = IOCControl.IOCControl() |
1190 | + self.gatewayControl = GatewayControl.GatewayControl() |
1191 | + self.iocControl.startIOC() |
1192 | + self.gatewayControl.startGateway() |
1193 | + self.propSupported = False |
1194 | + os.environ["EPICS_CA_AUTO_ADDR_LIST"] = "NO" |
1195 | + os.environ["EPICS_CA_ADDR_LIST"] = "localhost:{0} localhost:{1}".format(gwtests.iocPort,gwtests.gwPort) |
1196 | + ca.initialize_libca() |
1197 | + |
1198 | + # Check if IOC supports DBE_PROPERTY |
1199 | + self.eventsReceivedIOC = 0 |
1200 | + ioc = epics.PV("ioc:passive0", auto_monitor=epics.dbr.DBE_PROPERTY) |
1201 | + ioc.add_callback(self.onChangeIOC) |
1202 | + ioc.get() |
1203 | + pvhigh = epics.PV("ioc:passive0.HIGH", auto_monitor=None) |
1204 | + pvhigh.put(18.0, wait=True) |
1205 | + time.sleep(.05) |
1206 | + if self.eventsReceivedIOC == 2: |
1207 | + self.propSupported = True |
1208 | + |
1209 | + def tearDown(self): |
1210 | + ca.finalize_libca() |
1211 | + self.gatewayControl.stop() |
1212 | + self.iocControl.stop() |
1213 | + |
1214 | + def onChangeIOC(self, pvname=None, **kws): |
1215 | + self.eventsReceivedIOC += 1 |
1216 | + |
1217 | + def onChange(self, pvname=None, **kws): |
1218 | + a = 1 |
1219 | + |
1220 | + |
1221 | + def testPropCache_ValueMonitorCTRLget(self): |
1222 | + '''Monitor PV (value events) through GW - change HIGH directly - get the DBR_CTRL of the PV through GW''' |
1223 | + gw_vctotal = ca.create_channel("gwtest:vctotal") |
1224 | + gw_pvtotal = ca.create_channel("gwtest:pvtotal") |
1225 | + gw_connected = ca.create_channel("gwtest:connected") |
1226 | + gw_active = ca.create_channel("gwtest:active") |
1227 | + gw_inactive = ca.create_channel("gwtest:inactive") |
1228 | + |
1229 | + # gateway should show no VC (client side connection) and no PV (IOC side connection) |
1230 | + count = ca.get(gw_vctotal) |
1231 | + self.assertTrue(count == 0, "Expected GW VC total count: 0, actual: " + str(count)) |
1232 | + count = ca.get(gw_pvtotal) |
1233 | + self.assertTrue(count == 0, "Expected GW PV total count: 0, actual: " + str(count)) |
1234 | + count = ca.get(gw_connected) |
1235 | + self.assertTrue(count == 0, "Expected GW connected PV count: 0, actual: " + str(count)) |
1236 | + count = ca.get(gw_active) |
1237 | + self.assertTrue(count == 0, "Expected GW active PV count: 0, actual: " + str(count)) |
1238 | + count = ca.get(gw_inactive) |
1239 | + self.assertTrue(count == 0, "Expected GW inactive PV count: 0, actual: " + str(count)) |
1240 | + |
1241 | + # gwcachetest is an ai record with full set of alarm limits: -100 -10 10 100 |
1242 | + gw = ca.create_channel("gateway:gwcachetest") |
1243 | + connected = ca.connect_channel(gw, timeout=.5) |
1244 | + self.assertTrue(connected, "Could not connect to gateway channel " + ca.name(gw)) |
1245 | + (gw_cbref, gw_uaref, gw_eventid) = ca.create_subscription(gw, mask=dbr.DBE_VALUE, callback=self.onChange) |
1246 | + ioc = ca.create_channel("ioc:gwcachetest") |
1247 | + connected = ca.connect_channel(ioc, timeout=.5) |
1248 | + self.assertTrue(connected, "Could not connect to ioc channel " + ca.name(gw)) |
1249 | + (ioc_cbref, ioc_uaref, ioc_eventid) = ca.create_subscription(ioc, mask=dbr.DBE_VALUE, callback=self.onChange) |
1250 | + |
1251 | + # gateway should show one VC and one connected active PV |
1252 | + count = ca.get(gw_vctotal) |
1253 | + self.assertTrue(count == 1, "Expected GW VC total count: 1, actual: " + str(count)) |
1254 | + count = ca.get(gw_pvtotal) |
1255 | + self.assertTrue(count == 1, "Expected GW PV total count: 1, actual: " + str(count)) |
1256 | + count = ca.get(gw_connected) |
1257 | + self.assertTrue(count == 1, "Expected GW connected PV count: 1, actual: " + str(count)) |
1258 | + count = ca.get(gw_active) |
1259 | + self.assertTrue(count == 1, "Expected GW active PV count: 1, actual: " + str(count)) |
1260 | + count = ca.get(gw_inactive) |
1261 | + self.assertTrue(count == 0, "Expected GW inactive PV count: 0, actual: " + str(count)) |
1262 | + |
1263 | + # limit should not have been updated |
1264 | + ioc_ctrl = ca.get_ctrlvars(ioc) |
1265 | + highVal = ioc_ctrl['upper_warning_limit'] |
1266 | + self.assertTrue(highVal == 10.0, "Expected IOC warning_limit: 10; actual limit: "+ str(highVal)) |
1267 | + gw_ctrl = ca.get_ctrlvars(gw) |
1268 | + highVal = gw_ctrl['upper_warning_limit'] |
1269 | + self.assertTrue(highVal == 10.0, "Expected GW warning_limit: 10; actual limit: "+ str(highVal)) |
1270 | + |
1271 | + # set warning limit on IOC |
1272 | + ioc_high = ca.create_channel("ioc:gwcachetest.HIGH") |
1273 | + ca.put(ioc_high, 20.0, wait=True) |
1274 | + time.sleep(.1) |
1275 | + |
1276 | + # Now the limit should have been updated (if IOC supports DBE_PROPERTY) |
1277 | + ioc_ctrl = ca.get_ctrlvars(ioc) |
1278 | + highVal = ioc_ctrl['upper_warning_limit'] |
1279 | + self.assertTrue(highVal == 20.0, "Expected IOC warning_limit: 20; actual limit: "+ str(highVal)) |
1280 | + if self.propSupported: |
1281 | + gw_expected = 20.0 |
1282 | + else: |
1283 | + gw_expected = 10.0 |
1284 | + gw_ctrl = ca.get_ctrlvars(gw) |
1285 | + highVal = gw_ctrl['upper_warning_limit'] |
1286 | + self.assertTrue(highVal == gw_expected, "Expected GW warning_limit: {0}; actual limit: {1}".format(gw_expected, highVal)) |
1287 | + |
1288 | + |
1289 | + def testPropCache_ValueGetCTRLGet(self): |
1290 | + '''Get PV (value) through GW - change HIGH directly - get the DBR_CTRL of the PV through GW''' |
1291 | + gw_vctotal = ca.create_channel("gwtest:vctotal") |
1292 | + gw_pvtotal = ca.create_channel("gwtest:pvtotal") |
1293 | + gw_connected = ca.create_channel("gwtest:connected") |
1294 | + gw_active = ca.create_channel("gwtest:active") |
1295 | + gw_inactive = ca.create_channel("gwtest:inactive") |
1296 | + |
1297 | + # gateway should show no VC (client side connection) and no PV (IOC side connection) |
1298 | + count = ca.get(gw_vctotal) |
1299 | + self.assertTrue(count == 0, "Expected GW VC total count: 0, actual: " + str(count)) |
1300 | + count = ca.get(gw_pvtotal) |
1301 | + self.assertTrue(count == 0, "Expected GW PV total count: 0, actual: " + str(count)) |
1302 | + count = ca.get(gw_connected) |
1303 | + self.assertTrue(count == 0, "Expected GW connected PV count: 0, actual: " + str(count)) |
1304 | + count = ca.get(gw_active) |
1305 | + self.assertTrue(count == 0, "Expected GW active PV count: 0, actual: " + str(count)) |
1306 | + count = ca.get(gw_inactive) |
1307 | + self.assertTrue(count == 0, "Expected GW inactive PV count: 0, actual: " + str(count)) |
1308 | + |
1309 | + # gwcachetest is an ai record with full set of alarm limits: -100 -10 10 100 |
1310 | + gw = ca.create_channel("gateway:gwcachetest") |
1311 | + connected = ca.connect_channel(gw, timeout=.5) |
1312 | + self.assertTrue(connected, "Could not connect to gateway channel " + ca.name(gw)) |
1313 | + ioc = ca.create_channel("ioc:gwcachetest") |
1314 | + connected = ca.connect_channel(ioc, timeout=.5) |
1315 | + self.assertTrue(connected, "Could not connect to ioc channel " + ca.name(gw)) |
1316 | + |
1317 | + # gateway should show one VC and one connected active PV |
1318 | + count = ca.get(gw_vctotal) |
1319 | + self.assertTrue(count == 1, "Expected GW VC total count: 0, actual: " + str(count)) |
1320 | + count = ca.get(gw_pvtotal) |
1321 | + self.assertTrue(count == 1, "Expected GW PV total count: 1, actual: " + str(count)) |
1322 | + count = ca.get(gw_connected) |
1323 | + self.assertTrue(count == 1, "Expected GW connected PV count: 1, actual: " + str(count)) |
1324 | + count = ca.get(gw_active) |
1325 | + self.assertTrue(count == 1, "Expected GW active PV count: 1, actual: " + str(count)) |
1326 | + count = ca.get(gw_inactive) |
1327 | + self.assertTrue(count == 0, "Expected GW inactive PV count: 0, actual: " + str(count)) |
1328 | + |
1329 | + # limit should not have been updated |
1330 | + ioc_ctrl = ca.get_ctrlvars(ioc) |
1331 | + highVal = ioc_ctrl['upper_warning_limit'] |
1332 | + self.assertTrue(highVal == 10.0, "Expected IOC warning_limit: 10; actual limit: "+ str(highVal)) |
1333 | + gw_ctrl = ca.get_ctrlvars(gw) |
1334 | + highVal = gw_ctrl['upper_warning_limit'] |
1335 | + self.assertTrue(highVal == 10.0, "Expected GW warning_limit: 10; actual limit: "+ str(highVal)) |
1336 | + |
1337 | + # set warning limit on IOC |
1338 | + ioc_high = ca.create_channel("ioc:gwcachetest.HIGH") |
1339 | + ca.put(ioc_high, 20.0, wait=True) |
1340 | + time.sleep(.1) |
1341 | + |
1342 | + # Now the limit should have been updated (if IOC supports DBE_PROPERTY) |
1343 | + ioc_ctrl = ca.get_ctrlvars(ioc) |
1344 | + highVal = ioc_ctrl['upper_warning_limit'] |
1345 | + self.assertTrue(highVal == 20.0, "Expected IOC warning_limit: 20; actual limit: "+ str(highVal)) |
1346 | + if self.propSupported: |
1347 | + gw_expected = 20.0 |
1348 | + else: |
1349 | + gw_expected = 10.0 |
1350 | + gw_ctrl = ca.get_ctrlvars(gw) |
1351 | + highVal = gw_ctrl['upper_warning_limit'] |
1352 | + self.assertTrue(highVal == gw_expected, "Expected GW warning_limit: {0}; actual limit: {1}".format(gw_expected, highVal)) |
1353 | + |
1354 | + |
1355 | + def testPropCache_ValueGetDisconnectCTRLGet(self): |
1356 | + '''Get PV (value) through GW - disconnect client - change HIGH directly - get the DBR_CTRL of the PV through GW''' |
1357 | + gw_vctotal = ca.create_channel("gwtest:vctotal") |
1358 | + gw_pvtotal = ca.create_channel("gwtest:pvtotal") |
1359 | + gw_connected = ca.create_channel("gwtest:connected") |
1360 | + gw_active = ca.create_channel("gwtest:active") |
1361 | + gw_inactive = ca.create_channel("gwtest:inactive") |
1362 | + |
1363 | + # gateway should show no VC (client side connection) and no PV (IOC side connection) |
1364 | + count = ca.get(gw_vctotal) |
1365 | + self.assertTrue(count == 0, "Expected GW VC total count: 0, actual: " + str(count)) |
1366 | + count = ca.get(gw_pvtotal) |
1367 | + self.assertTrue(count == 0, "Expected GW PV total count: 0, actual: " + str(count)) |
1368 | + count = ca.get(gw_connected) |
1369 | + self.assertTrue(count == 0, "Expected GW connected PV count: 0, actual: " + str(count)) |
1370 | + count = ca.get(gw_active) |
1371 | + self.assertTrue(count == 0, "Expected GW active PV count: 0, actual: " + str(count)) |
1372 | + count = ca.get(gw_inactive) |
1373 | + self.assertTrue(count == 0, "Expected GW inactive PV count: 0, actual: " + str(count)) |
1374 | + |
1375 | + # gwcachetest is an ai record with full set of alarm limits: -100 -10 10 100 |
1376 | + gw = ca.create_channel("gateway:gwcachetest") |
1377 | + connected = ca.connect_channel(gw, timeout=.5) |
1378 | + self.assertTrue(connected, "Could not connect to gateway channel " + ca.name(gw)) |
1379 | + ioc = ca.create_channel("ioc:gwcachetest") |
1380 | + connected = ca.connect_channel(ioc, timeout=.5) |
1381 | + self.assertTrue(connected, "Could not connect to ioc channel " + ca.name(gw)) |
1382 | + |
1383 | + # gateway should show one VC and one connected active PV |
1384 | + count = ca.get(gw_vctotal) |
1385 | + self.assertTrue(count == 1, "Expected GW VC total count: 1, actual: " + str(count)) |
1386 | + count = ca.get(gw_pvtotal) |
1387 | + self.assertTrue(count == 1, "Expected GW PV total count: 1, actual: " + str(count)) |
1388 | + count = ca.get(gw_connected) |
1389 | + self.assertTrue(count == 1, "Expected GW connected PV count: 1, actual: " + str(count)) |
1390 | + count = ca.get(gw_active) |
1391 | + self.assertTrue(count == 1, "Expected GW active PV count: 1, actual: " + str(count)) |
1392 | + count = ca.get(gw_inactive) |
1393 | + self.assertTrue(count == 0, "Expected GW inactive PV count: 0, actual: " + str(count)) |
1394 | + |
1395 | + # limit should not have been updated |
1396 | + ioc_ctrl = ca.get_ctrlvars(ioc) |
1397 | + highVal = ioc_ctrl['upper_warning_limit'] |
1398 | + self.assertTrue(highVal == 10.0, "Expected IOC warning_limit: 10; actual limit: "+ str(highVal)) |
1399 | + gw_ctrl = ca.get_ctrlvars(gw) |
1400 | + highVal = gw_ctrl['upper_warning_limit'] |
1401 | + self.assertTrue(highVal == 10.0, "Expected GW warning_limit: 10; actual limit: "+ str(highVal)) |
1402 | + |
1403 | + # disconnect Channel Access, reconnect Gateway stats |
1404 | + ca.finalize_libca() |
1405 | + ca.initialize_libca() |
1406 | + gw_vctotal = ca.create_channel("gwtest:vctotal") |
1407 | + gw_pvtotal = ca.create_channel("gwtest:pvtotal") |
1408 | + gw_connected = ca.create_channel("gwtest:connected") |
1409 | + gw_active = ca.create_channel("gwtest:active") |
1410 | + gw_inactive = ca.create_channel("gwtest:inactive") |
1411 | + |
1412 | + # gateway should show no VC and 1 connected inactive PV |
1413 | + count = ca.get(gw_vctotal) |
1414 | + self.assertTrue(count == 0, "Expected GW VC total count: 0, actual: " + str(count)) |
1415 | + count = ca.get(gw_pvtotal) |
1416 | + self.assertTrue(count == 1, "Expected GW PV total count: 1, actual: " + str(count)) |
1417 | + count = ca.get(gw_connected) |
1418 | + self.assertTrue(count == 1, "Expected GW connected PV count: 1, actual: " + str(count)) |
1419 | + count = ca.get(gw_active) |
1420 | + self.assertTrue(count == 0, "Expected GW active PV count: 0, actual: " + str(count)) |
1421 | + count = ca.get(gw_inactive) |
1422 | + self.assertTrue(count == 1, "Expected GW inactive PV count: 1, actual: " + str(count)) |
1423 | + |
1424 | + # set warning limit on IOC |
1425 | + ioc_high = ca.create_channel("ioc:gwcachetest.HIGH") |
1426 | + ca.put(ioc_high, 20.0, wait=True) |
1427 | + time.sleep(.1) |
1428 | + |
1429 | + # reconnect Gateway and IOC |
1430 | + gw = ca.create_channel("gateway:gwcachetest") |
1431 | + connected = ca.connect_channel(gw, timeout=.5) |
1432 | + self.assertTrue(connected, "Could not connect to gateway channel " + ca.name(gw)) |
1433 | + ioc = ca.create_channel("ioc:gwcachetest") |
1434 | + connected = ca.connect_channel(ioc, timeout=.5) |
1435 | + self.assertTrue(connected, "Could not connect to ioc channel " + ca.name(gw)) |
1436 | + |
1437 | + # gateway should show one VC and one connected active PV |
1438 | + count = ca.get(gw_vctotal) |
1439 | + self.assertTrue(count == 1, "Expected GW VC total count: 1, actual: " + str(count)) |
1440 | + count = ca.get(gw_pvtotal) |
1441 | + self.assertTrue(count == 1, "Expected GW PV total count: 1, actual: " + str(count)) |
1442 | + count = ca.get(gw_connected) |
1443 | + self.assertTrue(count == 1, "Expected GW connected PV count: 1, actual: " + str(count)) |
1444 | + count = ca.get(gw_active) |
1445 | + self.assertTrue(count == 1, "Expected GW active PV count: 1, actual: " + str(count)) |
1446 | + count = ca.get(gw_inactive) |
1447 | + self.assertTrue(count == 0, "Expected GW inactive PV count: 0, actual: " + str(count)) |
1448 | + |
1449 | + # now the limit should have been updated |
1450 | + ioc_ctrl = ca.get_ctrlvars(ioc) |
1451 | + highVal = ioc_ctrl['upper_warning_limit'] |
1452 | + self.assertTrue(highVal == 20.0, "Expected IOC warning_limit: 20; actual limit: "+ str(highVal)) |
1453 | + gw_ctrl = ca.get_ctrlvars(gw) |
1454 | + highVal = gw_ctrl['upper_warning_limit'] |
1455 | + self.assertTrue(highVal == 20.0, "Expected GW warning_limit: 20; actual limit: "+ str(highVal)) |
1456 | + |
1457 | + |
1458 | +if __name__ == '__main__': |
1459 | + unittest.main(verbosity=2) |
1460 | |
1461 | === added file 'testTop/pyTestsApp/access.txt' |
1462 | --- testTop/pyTestsApp/access.txt 1970-01-01 00:00:00 +0000 |
1463 | +++ testTop/pyTestsApp/access.txt 2015-07-02 13:38:28 +0000 |
1464 | @@ -0,0 +1,3 @@ |
1465 | +ASG(DEFAULT) { |
1466 | + RULE(1,WRITE) |
1467 | +} |
1468 | |
1469 | === added file 'testTop/pyTestsApp/gwtests.py' |
1470 | --- testTop/pyTestsApp/gwtests.py 1970-01-01 00:00:00 +0000 |
1471 | +++ testTop/pyTestsApp/gwtests.py 2015-07-02 13:38:28 +0000 |
1472 | @@ -0,0 +1,67 @@ |
1473 | +#!/usr/bin/env python |
1474 | +'''CA Gateway test configuration''' |
1475 | + |
1476 | +import os |
1477 | +import sys |
1478 | + |
1479 | +# Do we want verbose logging |
1480 | +verbose = False |
1481 | +# Do we want debug logging from the gateway |
1482 | +verboseGateway = False |
1483 | + |
1484 | +# CA ports to use |
1485 | +iocPort = 12782 |
1486 | +gwPort = 12783 |
1487 | + |
1488 | +# Duration of standalong runs |
1489 | +gwRunDuration = 300 |
1490 | +iocRunDuration = 300 |
1491 | + |
1492 | +# Gateway attributes |
1493 | +gwStatsPrefix = "gwtest" |
1494 | + |
1495 | +# Defaults for EPICS properties: |
1496 | +gwLocation = "../.." |
1497 | +hostArch = "linux-x86_64" |
1498 | + |
1499 | +iocExecutable = "softIoc" |
1500 | +gwExecutable = "" |
1501 | +gwDebug = 10 |
1502 | + |
1503 | +def setup(): |
1504 | + '''Sets up test parameters for CA Gateway tests |
1505 | + ''' |
1506 | + global verbose, verboseGateway, gwLocation, hostArch |
1507 | + global iocExecutable, gwExecutable, gwDebug |
1508 | + |
1509 | + if 'VERBOSE' in os.environ and os.environ['VERBOSE'].lower().startswith('y'): |
1510 | + verbose = True |
1511 | + |
1512 | + if 'VERBOSE_GATEWAY' in os.environ: |
1513 | + verboseGateway = True |
1514 | + if os.environ['VERBOSE_GATEWAY'].isdigit(): |
1515 | + gwDebug = os.environ['VERBOSE_GATEWAY'] |
1516 | + |
1517 | + if 'EPICS_HOST_ARCH' in os.environ: |
1518 | + hostArch = os.environ['EPICS_HOST_ARCH'] |
1519 | + elif 'T_A' in os.environ: |
1520 | + hostArch = os.environ['T_A'] |
1521 | + else: |
1522 | + print "Warning: EPICS_HOST_ARCH not set. Using default value of '{0}'".format(hostArch) |
1523 | + |
1524 | + if 'TOP' in os.environ: |
1525 | + gwLocation = os.path.join(os.environ['TOP'], '..') |
1526 | + else: |
1527 | + print "Warning: TOP not set. Using default value of '..'" |
1528 | + |
1529 | + gwExecutable = os.path.join(gwLocation, 'bin', hostArch, 'gateway') |
1530 | + if not os.path.exists(gwExecutable): |
1531 | + print "Cannot find the gateway executable at", gwExecutable |
1532 | + sys.exit(1) |
1533 | + |
1534 | + if 'IOC_EPICS_BASE' in os.environ: |
1535 | + iocExecutable = os.path.join(os.environ['IOC_EPICS_BASE'], 'bin', hostArch, 'softIoc') |
1536 | + elif 'EPICS_BASE' in os.environ: |
1537 | + iocExecutable = os.path.join(os.environ['EPICS_BASE'], 'bin', hostArch, 'softIoc') |
1538 | + else: |
1539 | + print "Warning: IOC_EPICS_BASE or EPICS_BASE not set. Running 'softIoc' executable in PATH" |
1540 | |
1541 | === added file 'testTop/pyTestsApp/pvlist.txt' |
1542 | --- testTop/pyTestsApp/pvlist.txt 1970-01-01 00:00:00 +0000 |
1543 | +++ testTop/pyTestsApp/pvlist.txt 2015-07-02 13:38:28 +0000 |
1544 | @@ -0,0 +1,8 @@ |
1545 | +# gwlist file for Gateway tests |
1546 | +# |
1547 | +# Convert gateway: prefix to ioc: prefix on IOC side |
1548 | +# Do NOT serve the IOC channels under their original name |
1549 | +# |
1550 | +gateway:\(.*\) ALIAS ioc:\1 |
1551 | +ioc:.* DENY |
1552 | +gwtest:.* ALLOW |
1553 | |
1554 | === added file 'testTop/pyTestsApp/test.db' |
1555 | --- testTop/pyTestsApp/test.db 1970-01-01 00:00:00 +0000 |
1556 | +++ testTop/pyTestsApp/test.db 2015-07-02 13:38:28 +0000 |
1557 | @@ -0,0 +1,76 @@ |
1558 | +record(calcout, "ioc:auto:cnt") { |
1559 | + field(SCAN, "1 second") |
1560 | + field(CALC, "A+B") |
1561 | + field(INPA, "ioc:auto:cnt.VAL NPP") |
1562 | + field(INPB, "0.01") |
1563 | + field(OCAL, "sin(A)") |
1564 | + field(DOPT, "Use OCAL") |
1565 | + field(OUT , "ioc:auto PP") |
1566 | +} |
1567 | + |
1568 | +record(ai, "ioc:auto") { |
1569 | + field(PREC , 10) |
1570 | + field(DESC , "A PV that changes once a second") |
1571 | +} |
1572 | + |
1573 | +record(ai, "ioc:passive0") { |
1574 | + field(DESC , "No MDEL/ADEL + passive") |
1575 | + field(SCAN, "Passive") |
1576 | +} |
1577 | + |
1578 | +record(ai, "ioc:passiveADEL") { |
1579 | + field(DESC , "ADEL + passive") |
1580 | + field(ADEL , "10.0") |
1581 | + field(SCAN, "Passive") |
1582 | +} |
1583 | + |
1584 | +record(ai, "ioc:passiveALRM") { |
1585 | + field(DESC , "Alarm + passive") |
1586 | + field(SCAN, "Passive") |
1587 | + field(HIGH, "5.0") |
1588 | + field(HIHI, "10.0") |
1589 | + field(HSV, MINOR) |
1590 | + field(HHSV, MAJOR) |
1591 | +} |
1592 | + |
1593 | +record(ai, "ioc:passiveADELALRM") { |
1594 | + field(DESC , "ADEL, Alarm + passive") |
1595 | + field(SCAN, "Passive") |
1596 | + field(ADEL , "0.5") |
1597 | + field(HIGH, "5.0") |
1598 | + field(HIHI, "10.0") |
1599 | + field(HSV, MINOR) |
1600 | + field(HHSV, MAJOR) |
1601 | +} |
1602 | + |
1603 | +record(mbbi, "ioc:passiveMBBI") { |
1604 | + field(DESC , "Passive mbbi") |
1605 | + field(SCAN, "Passive") |
1606 | +} |
1607 | + |
1608 | +record(longin, "ioc:passivelongin") { |
1609 | + field(DESC , "Passive longin") |
1610 | + field(SCAN, "Passive") |
1611 | +} |
1612 | + |
1613 | +record(waveform, "ioc:passivewaveform") { |
1614 | + field(DESC , "Passive waveform") |
1615 | + field(SCAN, "Passive") |
1616 | + field(NELM, "1000") |
1617 | + field(FTVL, "DOUBLE") |
1618 | +} |
1619 | + |
1620 | +record(ai, "ioc:gwcachetest") { |
1621 | + field(PREC, "3") |
1622 | + field(EGU, "wobbles") |
1623 | + field(HIGH, "10") |
1624 | + field(HIHI, "100") |
1625 | + field(HSV , "MINOR") |
1626 | + field(HHSV , "MAJOR") |
1627 | + field(LOW , "-10") |
1628 | + field(LOLO , "-100") |
1629 | + field(LSV , "MINOR") |
1630 | + field(LLSV , "MAJOR") |
1631 | +} |
1632 | + |
1633 | + |
Can you document the python prerequisites to run the tests please. Make is also warning about a circular dependency:
make[3]: Entering directory '/local/ anj/dbe_ props/testTop/ pyTestsApp/ O.linux- x86_64'
make[3]: Circular test.db <- test.db dependency dropped.
perl -MTest::Harness -e 'runtests @ARGV if @ARGV;' runTests.py.t
runTests.py.t .. Traceback (most recent call last):
File "./runTests.py", line 4, in <module>
import argparse
ImportError: No module named argparse
runTests.py.t .. Dubious, test returned 1 (wstat 256, 0x100)
No subtests run
Test Summary Report anj/base- 3.14/configure/ RULES_BUILD: 341: recipe for target 'runtests' failed anj/dbe_ props/testTop/ pyTestsApp/ O.linux- x86_64'
-------------------
runTests.py.t (Wstat: 256 Tests: 0 Failed: 0)
Non-zero exit status: 1
Parse errors: No plan found in TAP output
Files=1, Tests=0, 0 wallclock secs ( 0.02 usr 0.01 sys + 0.03 cusr 0.02 csys = 0.08 CPU)
Result: FAIL
Failed 1/1 test programs. 0/0 subtests failed.
/local/
make[3]: [runtests] Error 1 (ignored)
make[3]: Leaving directory '/local/
On RHEL6.6 I installed python-argparse to fix the import problem above, but there is no python-tap package so I'm currently stuck here:
runTests.py.t .. Traceback (most recent call last):
File "./runTests.py", line 8, in <module>
from tap import TAPTestRunner
ImportError: No module named tap