Merge lp:~epics-core/epics-gateway/dbe_props into lp:~epics-core/epics-gateway/original-trunk

Proposed by Ralph Lange on 2015-06-24
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
Reviewer Review Type Date Requested Status
Andrew Johnson 2015-07-02 Approve on 2015-07-07
Murali Shankar (community) 2015-06-24 Approve on 2015-07-01
Review via email: mp+262832@code.launchpad.net

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.

To post a comment you must log in.
Andrew Johnson (anj) wrote :

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
-------------------
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/anj/base-3.14/configure/RULES_BUILD:341: recipe for target 'runtests' failed
make[3]: [runtests] Error 1 (ignored)
make[3]: Leaving directory '/local/anj/dbe_props/testTop/pyTestsApp/O.linux-x86_64'

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

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/linux-x86_64/gateway

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/phoebus3/ANJ/epics/base/3-14-dev"
filename="../dbLexRoutines.c" line number=240
No such file or directory dbRead opening file test.db
Starting the CA Gateway using
../../../bin/linux-x86_64/gateway -sip localhost -sport 12783 -cip localhost -cport 12782 -access access.txt -pvlist pvlist.txt -archive -prefix gwtest
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/anj/dbe_props/testTop/pyTestsApp/O.linux-x86_64/DBEValueTest.py", line 19, in setUp
    os.environ["EPICS_CA_ADDR_LIST"] =
        "localhost:{} localhost:{}".format(gwtests.iocPort,gwtests.gwPort)
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 on 2015-06-29

test: add README

235. By Ralph Lange on 2015-06-29

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 on 2015-06-30

test: rename test classes to match unittest conventions

237. By Ralph Lange on 2015-06-30

test: remove runner script, move configuration setup to gwstats, make single tests executable

238. By Ralph Lange on 2015-06-30

test: update README

239. By Ralph Lange on 2015-06-30

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 on 2015-06-30

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.

review: Approve
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/anj/dbe_props/testTop/pyTestsApp/TestDBEAlarm.py", line 20, in setUp
    os.environ["EPICS_CA_ADDR_LIST"] = "localhost:{} localhost:{}".format(gwtests.iocPort,gwtests.gwPort)
ValueError: zero length field name in format
-------------------- >> begin captured stdout << ---------------------
Warning: TOP not set. Using default value of '..'
Warning: EPICS_BASE not set. Running 'softIoc' executable in PATH

--------------------- >> end captured stdout << ----------------------

======================================================================

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/anj/dbe_props/testTop/pyTestsApp/TestDBEAlarm.py", line 20, in setUp
    os.environ["EPICS_CA_ADDR_LIST"] = "localhost:{} localhost:{}".format(gwtests.iocPort,gwtests.gwPort)
ValueError: zero length field name in format
-------------------- >> begin captured stdout << ---------------------
Warning: TOP not set. Using default value of '..'
Warning: EPICS_BASE not set. Running 'softIoc' executable in PATH

--------------------- >> end captured stdout << ----------------------

======================================================================

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://code.launchpad.net/~epics-core/epics-gateway/dbe_props/+merge/262832
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),$(EPICS_HOST_ARCH))" instead of "ifdef 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 on 2015-07-02

test: fix formatter parameters to work with python < 2.7

242. By Ralph Lange on 2015-07-02

test: add environment overrides for IOC

243. By Ralph Lange on 2015-07-02

test: always write directly to IOC, prefer pyepics wait instead of time.sleep()

244. By Ralph Lange on 2015-07-02

test: fix make rules to build, run host-only and clean up properly with 3.14 and 3.15

245. By Ralph Lange on 2015-07-02

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!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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+

Subscribers

People subscribed via source and target branches