more merges from gerrit
Change-Id: I13441cd8889dd39f74a2dd1a85e75a1b76bb93c8
This commit is contained in:
parent
10018b8cad
commit
34b93adef0
@ -30,8 +30,8 @@ from os import path
|
|||||||
# Add import path for inplace usage
|
# Add import path for inplace usage
|
||||||
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
||||||
|
|
||||||
from secop.lib import getGeneralConfig
|
from secop.lib import generalConfig
|
||||||
from secop.logging import initLogging
|
from secop.logging import logger
|
||||||
from secop.server import Server
|
from secop.server import Server
|
||||||
|
|
||||||
|
|
||||||
@ -75,6 +75,11 @@ def parseArgv(argv):
|
|||||||
action='store_true',
|
action='store_true',
|
||||||
help='check cfg files only',
|
help='check cfg files only',
|
||||||
default=False)
|
default=False)
|
||||||
|
parser.add_argument('-r',
|
||||||
|
'--relaxed',
|
||||||
|
action='store_true',
|
||||||
|
help='no checking of problematic behaviour',
|
||||||
|
default=False)
|
||||||
return parser.parse_args(argv)
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
@ -85,9 +90,15 @@ def main(argv=None):
|
|||||||
args = parseArgv(argv[1:])
|
args = parseArgv(argv[1:])
|
||||||
|
|
||||||
loglevel = 'debug' if args.verbose else ('error' if args.quiet else 'info')
|
loglevel = 'debug' if args.verbose else ('error' if args.quiet else 'info')
|
||||||
getGeneralConfig(args.gencfg)
|
if args.relaxed:
|
||||||
log = initLogging(loglevel)
|
generalConfig.defaults['lazy_number_validation'] = True
|
||||||
srv = Server(args.name, log, cfgfiles=args.cfgfiles, interface=args.port, testonly=args.test)
|
generalConfig.defaults['disable_value_range_check'] = True
|
||||||
|
generalConfig.defaults['legacy_hasiodev'] = True
|
||||||
|
generalConfig.defaults['tolerate_poll_property'] = True
|
||||||
|
generalConfig.init(args.gencfg)
|
||||||
|
logger.init(loglevel)
|
||||||
|
|
||||||
|
srv = Server(args.name, logger.log, cfgfiles=args.cfgfiles, interface=args.port, testonly=args.test)
|
||||||
|
|
||||||
if args.daemonize:
|
if args.daemonize:
|
||||||
srv.start()
|
srv.start()
|
||||||
|
40
ci/Jenkinsfile
vendored
40
ci/Jenkinsfile
vendored
@ -30,6 +30,8 @@ def changedFiles = '';
|
|||||||
|
|
||||||
def run_pylint(pyver) {
|
def run_pylint(pyver) {
|
||||||
stage ('pylint-' + pyver) {
|
stage ('pylint-' + pyver) {
|
||||||
|
def cpylint = "RUNNING"
|
||||||
|
gerritPostCheck(["jenkins:pylint_${pyver}": cpylint])
|
||||||
def status = 'OK'
|
def status = 'OK'
|
||||||
changedFiles = sh returnStdout: true, script: '''\
|
changedFiles = sh returnStdout: true, script: '''\
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
@ -56,8 +58,8 @@ fi
|
|||||||
withCredentials([string(credentialsId: 'GERRITHTTP',
|
withCredentials([string(credentialsId: 'GERRITHTTP',
|
||||||
variable: 'GERRITHTTP')]) {
|
variable: 'GERRITHTTP')]) {
|
||||||
sh """\
|
sh """\
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
if [ -f pylint_results.txt ] ; then
|
if [ -f pylint_results.txt ] ; then
|
||||||
/home/jenkins/tools2/bin/pylint2gerrit
|
/home/jenkins/tools2/bin/pylint2gerrit
|
||||||
mv pylint_results.txt pylint-${pyver}.txt
|
mv pylint_results.txt pylint-${pyver}.txt
|
||||||
else
|
else
|
||||||
@ -68,18 +70,15 @@ fi
|
|||||||
|
|
||||||
echo "pylint result: $res"
|
echo "pylint result: $res"
|
||||||
this.verifyresult.put('pylint'+pyver, 1)
|
this.verifyresult.put('pylint'+pyver, 1)
|
||||||
|
cpylint = "SUCCESSFUL"
|
||||||
if ( res != 0 ) {
|
if ( res != 0 ) {
|
||||||
currentBuild.result='FAILURE'
|
currentBuild.result='FAILURE'
|
||||||
this.verifyresult.put('pylint'+ pyver, -1)
|
this.verifyresult.put('pylint'+ pyver, -1)
|
||||||
status = 'FAILURE'
|
status = 'FAILURE'
|
||||||
|
cpylint = "FAILED"
|
||||||
}
|
}
|
||||||
|
|
||||||
gerritverificationpublisher([
|
gerritPostCheck(["jenkins:pylint_${pyver}": cpylint])
|
||||||
verifyStatusValue: this.verifyresult['pylint'+pyver],
|
|
||||||
verifyStatusCategory: 'pylint ',
|
|
||||||
verifyStatusName: 'pylint-'+pyver,
|
|
||||||
verifyStatusReporter: 'jenkins',
|
|
||||||
verifyStatusRerun: '!recheck'])
|
|
||||||
archiveArtifacts([allowEmptyArchive: true,
|
archiveArtifacts([allowEmptyArchive: true,
|
||||||
artifacts: 'pylint-*.txt'])
|
artifacts: 'pylint-*.txt'])
|
||||||
recordIssues([enabledForFailure: true,
|
recordIssues([enabledForFailure: true,
|
||||||
@ -99,7 +98,9 @@ fi
|
|||||||
|
|
||||||
def run_tests(pyver) {
|
def run_tests(pyver) {
|
||||||
stage('Test:' + pyver) {
|
stage('Test:' + pyver) {
|
||||||
writeFile file: 'setup.cfg', text: '''
|
def cpytest = "RUNNING"
|
||||||
|
gerritPostCheck(["jenkins:pytest_${pyver}":"RUNNING"])
|
||||||
|
writeFile file: 'setup.cfg', text: '''
|
||||||
[tool:pytest]
|
[tool:pytest]
|
||||||
addopts = --junit-xml=pytest.xml --junit-prefix=''' + pyver
|
addopts = --junit-xml=pytest.xml --junit-prefix=''' + pyver
|
||||||
|
|
||||||
@ -116,18 +117,15 @@ python3 setup.py develop
|
|||||||
make test
|
make test
|
||||||
'''
|
'''
|
||||||
verifyresult.put(pyver, 1)
|
verifyresult.put(pyver, 1)
|
||||||
|
cpytest = "SUCCESSFUL"
|
||||||
}
|
}
|
||||||
} catch (all) {
|
} catch (all) {
|
||||||
currentBuild.result = 'FAILURE'
|
currentBuild.result = 'FAILURE'
|
||||||
status = 'FAILURE'
|
status = 'FAILURE'
|
||||||
|
cpytest= "FAILED"
|
||||||
verifyresult.put(pyver, -1)
|
verifyresult.put(pyver, -1)
|
||||||
}
|
}
|
||||||
gerritverificationpublisher([
|
gerritPostCheck(["jenkins:pytest_${pyver}":cpytest])
|
||||||
verifyStatusValue: verifyresult[pyver],
|
|
||||||
verifyStatusCategory: 'test ',
|
|
||||||
verifyStatusName: 'pytest-'+pyver,
|
|
||||||
verifyStatusReporter: 'jenkins',
|
|
||||||
verifyStatusRerun: '!recheck'])
|
|
||||||
|
|
||||||
step([$class: 'JUnitResultArchiver', allowEmptyResults: true,
|
step([$class: 'JUnitResultArchiver', allowEmptyResults: true,
|
||||||
keepLongStdio: true, testResults: 'pytest.xml'])
|
keepLongStdio: true, testResults: 'pytest.xml'])
|
||||||
@ -138,6 +136,8 @@ make test
|
|||||||
}
|
}
|
||||||
|
|
||||||
def run_docs() {
|
def run_docs() {
|
||||||
|
def cdocs = "RUNNING"
|
||||||
|
gerritPostCheck(["jenkins:docs":cdocs])
|
||||||
stage('prepare') {
|
stage('prepare') {
|
||||||
sh '''
|
sh '''
|
||||||
. /home/jenkins/secopvenv/bin/activate
|
. /home/jenkins/secopvenv/bin/activate
|
||||||
@ -185,15 +185,9 @@ def run_docs() {
|
|||||||
|
|
||||||
stage('store html doc for build') {
|
stage('store html doc for build') {
|
||||||
publishHTML([allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'doc/_build/html', reportFiles: 'index.html', reportName: 'Built documentation', reportTitles: ''])
|
publishHTML([allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'doc/_build/html', reportFiles: 'index.html', reportName: 'Built documentation', reportTitles: ''])
|
||||||
gerritverificationpublisher([
|
cdocs = "SUCCESSFUL"
|
||||||
verifyStatusValue: 1,
|
|
||||||
verifyStatusCategory: 'test ',
|
|
||||||
verifyStatusName: 'doc',
|
|
||||||
verifyStatusReporter: 'jenkins',
|
|
||||||
verifyStatusRerun: '@recheck'
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
gerritPostCheck(["jenkins:docs":cdocs])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
170
debian/changelog
vendored
170
debian/changelog
vendored
@ -1,3 +1,111 @@
|
|||||||
|
secop-core (0.12.4) focal; urgency=medium
|
||||||
|
|
||||||
|
* fix command inheritance
|
||||||
|
|
||||||
|
-- Markus Zolliker <jenkins@jenkins01.admin.frm2.tum.de> Thu, 11 Nov 2021 16:21:19 +0100
|
||||||
|
|
||||||
|
secop-core (0.12.3) focal; urgency=medium
|
||||||
|
|
||||||
|
[ Georg Brandl ]
|
||||||
|
* Makefile: fix docker image
|
||||||
|
|
||||||
|
[ Markus Zolliker ]
|
||||||
|
* various fixes
|
||||||
|
* remove irrelevant comments
|
||||||
|
* introduce BytesIO
|
||||||
|
* GUI fixes
|
||||||
|
* persistent params / trinamic motor
|
||||||
|
* fix Parameter/Command copy method
|
||||||
|
* show first instead of last traceback on multiple errors
|
||||||
|
* fix parameter inheritance
|
||||||
|
* fix property inheritance
|
||||||
|
* fix python 3.5 compatibility
|
||||||
|
* omit updates of unchanged values within short time
|
||||||
|
* improve simulation
|
||||||
|
* automatically register subclasses of AsynConn
|
||||||
|
* fix feature for removing commands
|
||||||
|
|
||||||
|
-- Georg Brandl <jenkins@jenkins01.admin.frm2.tum.de> Wed, 10 Nov 2021 16:33:19 +0100
|
||||||
|
|
||||||
|
secop-core (0.12.2) focal; urgency=medium
|
||||||
|
|
||||||
|
[ Markus Zolliker ]
|
||||||
|
* fix issue with new syntax in simulation
|
||||||
|
* treat specifier of describe message
|
||||||
|
* allow to remove accessibles
|
||||||
|
|
||||||
|
[ Enrico Faulhaber ]
|
||||||
|
* secop_mlz: small fixes
|
||||||
|
|
||||||
|
-- Markus Zolliker <jenkins@jenkins01.admin.frm2.tum.de> Tue, 18 May 2021 10:29:17 +0200
|
||||||
|
|
||||||
|
secop-core (0.12.1) focal; urgency=medium
|
||||||
|
|
||||||
|
* remove secop-console from debian *.install file
|
||||||
|
|
||||||
|
-- Enrico Faulhaber <jenkins@jenkins02.admin.frm2.tum.de> Tue, 04 May 2021 09:42:53 +0200
|
||||||
|
|
||||||
|
secop-core (0.12.0) focal; urgency=medium
|
||||||
|
|
||||||
|
[ Markus Zolliker ]
|
||||||
|
* make datatypes immutable
|
||||||
|
* customizable general config
|
||||||
|
* support for multiple secop servers
|
||||||
|
* secop.asynconn without pyserial
|
||||||
|
* change cfg file format
|
||||||
|
* fix bug in secop.gui.valuewidgets
|
||||||
|
* fix deadlock when reconnecting client
|
||||||
|
* allow class instead of class name in proxy_class
|
||||||
|
* fix pylint command in Makefile
|
||||||
|
* change arguments of stringio-server
|
||||||
|
* router bug fix
|
||||||
|
* introduce update callbacks
|
||||||
|
* improve error handling on client connections
|
||||||
|
* ppms: improve status and temperature
|
||||||
|
* rework tcp server
|
||||||
|
* cosmetics on datatypes.TextType
|
||||||
|
* improve error handling in SecopClient
|
||||||
|
* improve HasIodev
|
||||||
|
* HasIodev bug fix
|
||||||
|
* fix handling of StructOf datatype
|
||||||
|
* more flexible end_of_line in stringio
|
||||||
|
* improvements on PPMS and LS370
|
||||||
|
* add readbytes method to AsynConn
|
||||||
|
* Param(..., initwrite=True) works only with poll=True
|
||||||
|
* fix initwrite behaviour
|
||||||
|
* make order of accessibles work again
|
||||||
|
* main module of LS370 is now drivable
|
||||||
|
* improve softcal
|
||||||
|
* make arguments of Parameter and Override consistent
|
||||||
|
* new syntax for parameter/commands/properties
|
||||||
|
* enhance documentation
|
||||||
|
* removed old style syntax
|
||||||
|
* after running isort
|
||||||
|
* try to follow PEP8
|
||||||
|
* fix inheritance order
|
||||||
|
* remove obsolete code
|
||||||
|
* lookup cfg files in a list of directories
|
||||||
|
* added hook for optional history writer
|
||||||
|
* fixed errors during migration
|
||||||
|
* move historywriter to secop_psi
|
||||||
|
* fix autoscan behaviour in ls370res
|
||||||
|
|
||||||
|
[ l_samenv ]
|
||||||
|
* improve tutorial_helevel
|
||||||
|
* fixed bugs from syntax migration
|
||||||
|
|
||||||
|
[ Markus Zolliker ]
|
||||||
|
* user friendly reporting of config errors
|
||||||
|
|
||||||
|
[ Bjoern Pedersen ]
|
||||||
|
* Jenkisfile: verification
|
||||||
|
* Fixes to Jenkinsfile
|
||||||
|
* No pull for images, they are recreated in the job
|
||||||
|
* Another Jenkisfile error
|
||||||
|
* Correct checks enum
|
||||||
|
|
||||||
|
-- Markus Zolliker <jenkins@jenkins02.admin.frm2.tum.de> Tue, 04 May 2021 08:49:57 +0200
|
||||||
|
|
||||||
secop-core (0.11.6) unstable; urgency=medium
|
secop-core (0.11.6) unstable; urgency=medium
|
||||||
|
|
||||||
* fix secop-generator
|
* fix secop-generator
|
||||||
@ -133,7 +241,7 @@ secop-core (0.10.5) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Tue, 29 Oct 2019 16:33:18 +0100
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Tue, 29 Oct 2019 16:33:18 +0100
|
||||||
|
|
||||||
secop-core (0.10.3) unstable; urgency=low
|
secop-core (0.10.3) unstable; urgency=low
|
||||||
|
|
||||||
@ -142,7 +250,7 @@ secop-core (0.10.3) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Fri, 11 Oct 2019 10:49:43 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Fri, 11 Oct 2019 10:49:43 +0200
|
||||||
|
|
||||||
secop-core (0.10.2) unstable; urgency=low
|
secop-core (0.10.2) unstable; urgency=low
|
||||||
|
|
||||||
@ -153,7 +261,7 @@ secop-core (0.10.2) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Fri, 11 Oct 2019 10:42:58 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Fri, 11 Oct 2019 10:42:58 +0200
|
||||||
|
|
||||||
secop-core (0.10.1) unstable; urgency=low
|
secop-core (0.10.1) unstable; urgency=low
|
||||||
|
|
||||||
@ -162,7 +270,7 @@ secop-core (0.10.1) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 26 Sep 2019 16:41:10 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 26 Sep 2019 16:41:10 +0200
|
||||||
|
|
||||||
secop-core (0.10.0) unstable; urgency=low
|
secop-core (0.10.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -171,7 +279,7 @@ secop-core (0.10.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 26 Sep 2019 16:31:14 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 26 Sep 2019 16:31:14 +0200
|
||||||
|
|
||||||
secop-core (0.9.0) unstable; urgency=low
|
secop-core (0.9.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -198,7 +306,7 @@ secop-core (0.9.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 26 Sep 2019 16:26:07 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 26 Sep 2019 16:26:07 +0200
|
||||||
|
|
||||||
secop-core (0.8.1) unstable; urgency=low
|
secop-core (0.8.1) unstable; urgency=low
|
||||||
|
|
||||||
@ -207,7 +315,7 @@ secop-core (0.8.1) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Wed, 25 Sep 2019 15:40:44 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Wed, 25 Sep 2019 15:40:44 +0200
|
||||||
|
|
||||||
secop-core (0.8.0) unstable; urgency=low
|
secop-core (0.8.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -275,7 +383,7 @@ secop-core (0.8.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Wed, 25 Sep 2019 10:27:51 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Wed, 25 Sep 2019 10:27:51 +0200
|
||||||
|
|
||||||
secop-core (0.7.0) unstable; urgency=low
|
secop-core (0.7.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -311,7 +419,7 @@ secop-core (0.7.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 28 Mar 2019 13:46:08 +0100
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 28 Mar 2019 13:46:08 +0100
|
||||||
|
|
||||||
secop-core (0.6.4) unstable; urgency=low
|
secop-core (0.6.4) unstable; urgency=low
|
||||||
|
|
||||||
@ -376,7 +484,7 @@ secop-core (0.6.4) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 20 Dec 2018 16:44:03 +0100
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 20 Dec 2018 16:44:03 +0100
|
||||||
|
|
||||||
secop-core (0.6.3) unstable; urgency=low
|
secop-core (0.6.3) unstable; urgency=low
|
||||||
|
|
||||||
@ -390,7 +498,7 @@ secop-core (0.6.3) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Fri, 27 Jul 2018 09:31:59 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Fri, 27 Jul 2018 09:31:59 +0200
|
||||||
|
|
||||||
secop-core (0.6.2) unstable; urgency=low
|
secop-core (0.6.2) unstable; urgency=low
|
||||||
|
|
||||||
@ -429,7 +537,7 @@ secop-core (0.6.2) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Wed, 18 Jul 2018 12:06:57 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Wed, 18 Jul 2018 12:06:57 +0200
|
||||||
|
|
||||||
secop-core (0.6.1) unstable; urgency=low
|
secop-core (0.6.1) unstable; urgency=low
|
||||||
|
|
||||||
@ -438,7 +546,7 @@ secop-core (0.6.1) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 19 Apr 2018 10:24:44 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 19 Apr 2018 10:24:44 +0200
|
||||||
|
|
||||||
secop-core (0.6.0) unstable; urgency=low
|
secop-core (0.6.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -458,7 +566,7 @@ secop-core (0.6.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Tue, 17 Apr 2018 17:38:52 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Tue, 17 Apr 2018 17:38:52 +0200
|
||||||
|
|
||||||
secop-core (0.5.0) unstable; urgency=low
|
secop-core (0.5.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -521,7 +629,7 @@ secop-core (0.5.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Tue, 17 Apr 2018 12:45:58 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Tue, 17 Apr 2018 12:45:58 +0200
|
||||||
|
|
||||||
secop-core (0.4.4) unstable; urgency=low
|
secop-core (0.4.4) unstable; urgency=low
|
||||||
|
|
||||||
@ -530,7 +638,7 @@ secop-core (0.4.4) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Sun, 24 Sep 2017 22:25:01 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Sun, 24 Sep 2017 22:25:01 +0200
|
||||||
|
|
||||||
secop-core (0.4.3) unstable; urgency=low
|
secop-core (0.4.3) unstable; urgency=low
|
||||||
|
|
||||||
@ -539,7 +647,7 @@ secop-core (0.4.3) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Fri, 22 Sep 2017 17:29:46 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Fri, 22 Sep 2017 17:29:46 +0200
|
||||||
|
|
||||||
secop-core (0.4.2) unstable; urgency=low
|
secop-core (0.4.2) unstable; urgency=low
|
||||||
|
|
||||||
@ -548,7 +656,7 @@ secop-core (0.4.2) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Fri, 22 Sep 2017 16:37:59 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Fri, 22 Sep 2017 16:37:59 +0200
|
||||||
|
|
||||||
secop-core (0.4.1) unstable; urgency=low
|
secop-core (0.4.1) unstable; urgency=low
|
||||||
|
|
||||||
@ -557,7 +665,7 @@ secop-core (0.4.1) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Fri, 22 Sep 2017 13:25:28 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Fri, 22 Sep 2017 13:25:28 +0200
|
||||||
|
|
||||||
secop-core (0.4.0) unstable; urgency=low
|
secop-core (0.4.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -567,7 +675,7 @@ secop-core (0.4.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Fri, 22 Sep 2017 10:33:04 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Fri, 22 Sep 2017 10:33:04 +0200
|
||||||
|
|
||||||
secop-core (0.3.0) unstable; urgency=low
|
secop-core (0.3.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -633,7 +741,7 @@ secop-core (0.3.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Mon, 18 Sep 2017 14:18:36 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Mon, 18 Sep 2017 14:18:36 +0200
|
||||||
|
|
||||||
secop-core (0.2.0) unstable; urgency=low
|
secop-core (0.2.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -642,7 +750,7 @@ secop-core (0.2.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 07 Sep 2017 14:55:41 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 07 Sep 2017 14:55:41 +0200
|
||||||
|
|
||||||
secop-core (0.1.1) unstable; urgency=low
|
secop-core (0.1.1) unstable; urgency=low
|
||||||
|
|
||||||
@ -651,7 +759,7 @@ secop-core (0.1.1) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 07 Sep 2017 11:02:19 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 07 Sep 2017 11:02:19 +0200
|
||||||
|
|
||||||
secop-core (0.1.0) unstable; urgency=low
|
secop-core (0.1.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -660,7 +768,7 @@ secop-core (0.1.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 07 Sep 2017 10:50:24 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 07 Sep 2017 10:50:24 +0200
|
||||||
|
|
||||||
secop-core (0.0.8) unstable; urgency=low
|
secop-core (0.0.8) unstable; urgency=low
|
||||||
|
|
||||||
@ -669,7 +777,7 @@ secop-core (0.0.8) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Tue, 01 Aug 2017 14:13:11 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Tue, 01 Aug 2017 14:13:11 +0200
|
||||||
|
|
||||||
secop-core (0.0.7) unstable; urgency=low
|
secop-core (0.0.7) unstable; urgency=low
|
||||||
|
|
||||||
@ -678,7 +786,7 @@ secop-core (0.0.7) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Tue, 01 Aug 2017 13:52:15 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Tue, 01 Aug 2017 13:52:15 +0200
|
||||||
|
|
||||||
secop-core (0.0.6) unstable; urgency=low
|
secop-core (0.0.6) unstable; urgency=low
|
||||||
|
|
||||||
@ -688,7 +796,7 @@ secop-core (0.0.6) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Tue, 01 Aug 2017 13:39:07 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Tue, 01 Aug 2017 13:39:07 +0200
|
||||||
|
|
||||||
secop-core (0.0.5) unstable; urgency=low
|
secop-core (0.0.5) unstable; urgency=low
|
||||||
|
|
||||||
@ -697,7 +805,7 @@ secop-core (0.0.5) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Tue, 01 Aug 2017 13:11:43 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Tue, 01 Aug 2017 13:11:43 +0200
|
||||||
|
|
||||||
secop-core (0.0.4) unstable; urgency=low
|
secop-core (0.0.4) unstable; urgency=low
|
||||||
|
|
||||||
@ -706,7 +814,7 @@ secop-core (0.0.4) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 27 Jul 2017 11:39:42 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 27 Jul 2017 11:39:42 +0200
|
||||||
|
|
||||||
secop-core (0.0.3) unstable; urgency=low
|
secop-core (0.0.3) unstable; urgency=low
|
||||||
|
|
||||||
@ -716,7 +824,7 @@ secop-core (0.0.3) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 27 Jul 2017 11:27:28 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 27 Jul 2017 11:27:28 +0200
|
||||||
|
|
||||||
secop-core (0.0.2) unstable; urgency=medium
|
secop-core (0.0.2) unstable; urgency=medium
|
||||||
|
|
||||||
@ -794,4 +902,4 @@ secop-core (0.0.2) unstable; urgency=medium
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Wed, 19 Jul 2017 11:44:13 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Wed, 19 Jul 2017 11:44:13 +0200
|
||||||
|
1
debian/secop-core.install
vendored
1
debian/secop-core.install
vendored
@ -1,5 +1,4 @@
|
|||||||
usr/bin/secop-server
|
usr/bin/secop-server
|
||||||
usr/bin/secop-console
|
|
||||||
usr/lib/python3.*/dist-packages/secop/*.py
|
usr/lib/python3.*/dist-packages/secop/*.py
|
||||||
usr/lib/python3.*/dist-packages/secop/lib
|
usr/lib/python3.*/dist-packages/secop/lib
|
||||||
usr/lib/python3.*/dist-packages/secop/client
|
usr/lib/python3.*/dist-packages/secop/client
|
||||||
|
@ -4,6 +4,8 @@ Reference
|
|||||||
Module Base Classes
|
Module Base Classes
|
||||||
...................
|
...................
|
||||||
|
|
||||||
|
.. autodata:: secop.modules.Done
|
||||||
|
|
||||||
.. autoclass:: secop.modules.Module
|
.. autoclass:: secop.modules.Module
|
||||||
:members: earlyInit, initModule, startModule, pollerClass
|
:members: earlyInit, initModule, startModule, pollerClass
|
||||||
|
|
||||||
@ -49,11 +51,15 @@ Communication
|
|||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
:members: communicate
|
:members: communicate
|
||||||
|
|
||||||
.. autoclass:: secop.stringio.StringIO
|
.. autoclass:: secop.io.StringIO
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
:members: communicate, multicomm
|
:members: communicate, multicomm
|
||||||
|
|
||||||
.. autoclass:: secop.stringio.HasIodev
|
.. autoclass:: secop.io.BytesIO
|
||||||
|
:show-inheritance:
|
||||||
|
:members: communicate, multicomm
|
||||||
|
|
||||||
|
.. autoclass:: secop.io.HasIO
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
.. autoclass:: secop.iohandler.IOHandlerBase
|
.. autoclass:: secop.iohandler.IOHandlerBase
|
||||||
|
@ -22,7 +22,7 @@ CCU4 luckily has a very simple and logical protocol:
|
|||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
# the most common Frappy classes can be imported from secop.core
|
# the most common Frappy classes can be imported from secop.core
|
||||||
from secop.core import Readable, Parameter, FloatRange, BoolType, StringIO, HasIodev
|
from secop.core import Readable, Parameter, FloatRange, BoolType, StringIO, HasIO
|
||||||
|
|
||||||
|
|
||||||
class CCU4IO(StringIO):
|
class CCU4IO(StringIO):
|
||||||
@ -34,14 +34,13 @@ CCU4 luckily has a very simple and logical protocol:
|
|||||||
identification = [('cid', r'CCU4.*')]
|
identification = [('cid', r'CCU4.*')]
|
||||||
|
|
||||||
|
|
||||||
# inheriting the HasIodev mixin creates us a private attribute *_iodev*
|
# inheriting HasIO allows us to use the communicate method for talking with the hardware
|
||||||
# for talking with the hardware
|
|
||||||
# Readable as a base class defines the value and status parameters
|
# Readable as a base class defines the value and status parameters
|
||||||
class HeLevel(HasIodev, Readable):
|
class HeLevel(HasIO, Readable):
|
||||||
"""He Level channel of CCU4"""
|
"""He Level channel of CCU4"""
|
||||||
|
|
||||||
# define the communication class to create the IO module
|
# define the communication class to create the IO module
|
||||||
iodevClass = CCU4IO
|
ioClass = CCU4IO
|
||||||
|
|
||||||
# define or alter the parameters
|
# define or alter the parameters
|
||||||
# as Readable.value exists already, we give only the modified property 'unit'
|
# as Readable.value exists already, we give only the modified property 'unit'
|
||||||
@ -49,7 +48,7 @@ CCU4 luckily has a very simple and logical protocol:
|
|||||||
|
|
||||||
def read_value(self):
|
def read_value(self):
|
||||||
# method for reading the main value
|
# method for reading the main value
|
||||||
reply = self._iodev.communicate('h') # send 'h\n' and get the reply 'h=<value>\n'
|
reply = self.communicate('h') # send 'h\n' and get the reply 'h=<value>\n'
|
||||||
name, txtvalue = reply.split('=')
|
name, txtvalue = reply.split('=')
|
||||||
assert name == 'h' # check that we got a reply to our command
|
assert name == 'h' # check that we got a reply to our command
|
||||||
return txtvalue # the framework will automatically convert the string to a float
|
return txtvalue # the framework will automatically convert the string to a float
|
||||||
@ -115,17 +114,17 @@ the status codes from the hardware to the standard SECoP status codes.
|
|||||||
}
|
}
|
||||||
|
|
||||||
def read_status(self):
|
def read_status(self):
|
||||||
name, txtvalue = self._iodev.communicate('hsf').split('=')
|
name, txtvalue = self.communicate('hsf').split('=')
|
||||||
assert name == 'hsf'
|
assert name == 'hsf'
|
||||||
return self.STATUS_MAP(int(txtvalue))
|
return self.STATUS_MAP(int(txtvalue))
|
||||||
|
|
||||||
def read_empty_length(self):
|
def read_empty_length(self):
|
||||||
name, txtvalue = self._iodev.communicate('hem').split('=')
|
name, txtvalue = self.communicate('hem').split('=')
|
||||||
assert name == 'hem'
|
assert name == 'hem'
|
||||||
return txtvalue
|
return txtvalue
|
||||||
|
|
||||||
def write_empty_length(self, value):
|
def write_empty_length(self, value):
|
||||||
name, txtvalue = self._iodev.communicate('hem=%g' % value).split('=')
|
name, txtvalue = self.communicate('hem=%g' % value).split('=')
|
||||||
assert name == 'hem'
|
assert name == 'hem'
|
||||||
return txtvalue
|
return txtvalue
|
||||||
|
|
||||||
@ -152,7 +151,7 @@ which means it might be worth to create a *query* method, and then the
|
|||||||
for changing a parameter
|
for changing a parameter
|
||||||
:returns: the (new) value of the parameter
|
:returns: the (new) value of the parameter
|
||||||
"""
|
"""
|
||||||
name, txtvalue = self._iodev.communicate(cmd).split('=')
|
name, txtvalue = self.communicate(cmd).split('=')
|
||||||
assert name == cmd.split('=')[0] # check that we got a reply to our command
|
assert name == cmd.split('=')[0] # check that we got a reply to our command
|
||||||
return txtvalue # Frappy will automatically convert the string to the needed data type
|
return txtvalue # Frappy will automatically convert the string to the needed data type
|
||||||
|
|
||||||
|
@ -111,6 +111,7 @@ class Cryostat(CryoBase):
|
|||||||
group='stability')
|
group='stability')
|
||||||
|
|
||||||
def initModule(self):
|
def initModule(self):
|
||||||
|
super().initModule()
|
||||||
self._stopflag = False
|
self._stopflag = False
|
||||||
self._thread = mkthread(self.thread)
|
self._thread = mkthread(self.thread)
|
||||||
|
|
||||||
|
@ -133,6 +133,7 @@ class MagneticField(Drivable):
|
|||||||
status = Parameter(datatype=TupleOf(EnumType(Status), StringType()))
|
status = Parameter(datatype=TupleOf(EnumType(Status), StringType()))
|
||||||
|
|
||||||
def initModule(self):
|
def initModule(self):
|
||||||
|
super().initModule()
|
||||||
self._state = Enum('state', idle=1, switch_on=2, switch_off=3, ramp=4).idle
|
self._state = Enum('state', idle=1, switch_on=2, switch_off=3, ramp=4).idle
|
||||||
self._heatswitch = self.DISPATCHER.get_module(self.heatswitch)
|
self._heatswitch = self.DISPATCHER.get_module(self.heatswitch)
|
||||||
_thread = threading.Thread(target=self._thread)
|
_thread = threading.Thread(target=self._thread)
|
||||||
@ -235,6 +236,7 @@ class SampleTemp(Drivable):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def initModule(self):
|
def initModule(self):
|
||||||
|
super().initModule()
|
||||||
_thread = threading.Thread(target=self._thread)
|
_thread = threading.Thread(target=self._thread)
|
||||||
_thread.daemon = True
|
_thread.daemon = True
|
||||||
_thread.start()
|
_thread.start()
|
||||||
|
@ -31,7 +31,7 @@ import math
|
|||||||
from secop.datatypes import ArrayOf, FloatRange, StringType, StructOf, TupleOf
|
from secop.datatypes import ArrayOf, FloatRange, StringType, StructOf, TupleOf
|
||||||
from secop.errors import ConfigError, DisabledError
|
from secop.errors import ConfigError, DisabledError
|
||||||
from secop.lib.sequence import SequencerMixin, Step
|
from secop.lib.sequence import SequencerMixin, Step
|
||||||
from secop.modules import BasicPoller, Drivable, Parameter
|
from secop.modules import Drivable, Parameter
|
||||||
|
|
||||||
|
|
||||||
class GarfieldMagnet(SequencerMixin, Drivable):
|
class GarfieldMagnet(SequencerMixin, Drivable):
|
||||||
@ -47,9 +47,6 @@ class GarfieldMagnet(SequencerMixin, Drivable):
|
|||||||
the symmetry setting selects which.
|
the symmetry setting selects which.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pollerClass = BasicPoller
|
|
||||||
|
|
||||||
|
|
||||||
# parameters
|
# parameters
|
||||||
subdev_currentsource = Parameter('(bipolar) Powersupply', datatype=StringType(), readonly=True, export=False)
|
subdev_currentsource = Parameter('(bipolar) Powersupply', datatype=StringType(), readonly=True, export=False)
|
||||||
subdev_enable = Parameter('Switch to set for on/off', datatype=StringType(), readonly=True, export=False)
|
subdev_enable = Parameter('Switch to set for on/off', datatype=StringType(), readonly=True, export=False)
|
||||||
@ -57,10 +54,10 @@ class GarfieldMagnet(SequencerMixin, Drivable):
|
|||||||
subdev_symmetry = Parameter('Switch to read for symmetry', datatype=StringType(), readonly=True, export=False)
|
subdev_symmetry = Parameter('Switch to read for symmetry', datatype=StringType(), readonly=True, export=False)
|
||||||
userlimits = Parameter('User defined limits of device value',
|
userlimits = Parameter('User defined limits of device value',
|
||||||
datatype=TupleOf(FloatRange(unit='$'), FloatRange(unit='$')),
|
datatype=TupleOf(FloatRange(unit='$'), FloatRange(unit='$')),
|
||||||
default=(float('-Inf'), float('+Inf')), readonly=False, poll=10)
|
default=(float('-Inf'), float('+Inf')), readonly=False)
|
||||||
abslimits = Parameter('Absolute limits of device value',
|
abslimits = Parameter('Absolute limits of device value',
|
||||||
datatype=TupleOf(FloatRange(unit='$'), FloatRange(unit='$')),
|
datatype=TupleOf(FloatRange(unit='$'), FloatRange(unit='$')),
|
||||||
default=(-0.5, 0.5), poll=True,
|
default=(-0.5, 0.5),
|
||||||
)
|
)
|
||||||
precision = Parameter('Precision of the device value (allowed deviation '
|
precision = Parameter('Precision of the device value (allowed deviation '
|
||||||
'of stable values from target)',
|
'of stable values from target)',
|
||||||
@ -71,7 +68,7 @@ class GarfieldMagnet(SequencerMixin, Drivable):
|
|||||||
calibration = Parameter('Coefficients for calibration '
|
calibration = Parameter('Coefficients for calibration '
|
||||||
'function: [c0, c1, c2, c3, c4] calculates '
|
'function: [c0, c1, c2, c3, c4] calculates '
|
||||||
'B(I) = c0*I + c1*erf(c2*I) + c3*atan(c4*I)'
|
'B(I) = c0*I + c1*erf(c2*I) + c3*atan(c4*I)'
|
||||||
' in T', poll=1,
|
' in T',
|
||||||
datatype=ArrayOf(FloatRange(), 5, 5),
|
datatype=ArrayOf(FloatRange(), 5, 5),
|
||||||
default=(1.0, 0.0, 0.0, 0.0, 0.0))
|
default=(1.0, 0.0, 0.0, 0.0, 0.0))
|
||||||
calibrationtable = Parameter('Map of Coefficients for calibration per symmetry setting',
|
calibrationtable = Parameter('Map of Coefficients for calibration per symmetry setting',
|
||||||
@ -137,7 +134,7 @@ class GarfieldMagnet(SequencerMixin, Drivable):
|
|||||||
'_current2field polynome not monotonic!')
|
'_current2field polynome not monotonic!')
|
||||||
|
|
||||||
def initModule(self):
|
def initModule(self):
|
||||||
super(GarfieldMagnet, self).initModule()
|
super().initModule()
|
||||||
self._enable = self.DISPATCHER.get_module(self.subdev_enable)
|
self._enable = self.DISPATCHER.get_module(self.subdev_enable)
|
||||||
self._symmetry = self.DISPATCHER.get_module(self.subdev_symmetry)
|
self._symmetry = self.DISPATCHER.get_module(self.subdev_symmetry)
|
||||||
self._polswitch = self.DISPATCHER.get_module(self.subdev_polswitch)
|
self._polswitch = self.DISPATCHER.get_module(self.subdev_polswitch)
|
||||||
@ -220,7 +217,7 @@ class GarfieldMagnet(SequencerMixin, Drivable):
|
|||||||
self._currentsource.read_value() *
|
self._currentsource.read_value() *
|
||||||
self._get_field_polarity())
|
self._get_field_polarity())
|
||||||
|
|
||||||
def read_hw_status(self):
|
def readHwStatus(self):
|
||||||
# called from SequencerMixin.read_status if no sequence is running
|
# called from SequencerMixin.read_status if no sequence is running
|
||||||
if self._enable.value == 'Off':
|
if self._enable.value == 'Off':
|
||||||
return self.Status.WARN, 'Disabled'
|
return self.Status.WARN, 'Disabled'
|
||||||
|
@ -39,7 +39,7 @@ from secop.datatypes import ArrayOf, EnumType, FloatRange, \
|
|||||||
from secop.errors import CommunicationFailedError, \
|
from secop.errors import CommunicationFailedError, \
|
||||||
ConfigError, HardwareError, ProgrammingError
|
ConfigError, HardwareError, ProgrammingError
|
||||||
from secop.lib import lazy_property
|
from secop.lib import lazy_property
|
||||||
from secop.modules import BasicPoller, Command, \
|
from secop.modules import Command, \
|
||||||
Drivable, Module, Parameter, Readable
|
Drivable, Module, Parameter, Readable
|
||||||
|
|
||||||
#####
|
#####
|
||||||
@ -157,8 +157,6 @@ class PyTangoDevice(Module):
|
|||||||
execution and attribute operations with logging and exception mapping.
|
execution and attribute operations with logging and exception mapping.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pollerClass = BasicPoller
|
|
||||||
|
|
||||||
# parameters
|
# parameters
|
||||||
comtries = Parameter('Maximum retries for communication',
|
comtries = Parameter('Maximum retries for communication',
|
||||||
datatype=IntRange(1, 100), default=3, readonly=False,
|
datatype=IntRange(1, 100), default=3, readonly=False,
|
||||||
@ -210,7 +208,7 @@ class PyTangoDevice(Module):
|
|||||||
# exception mapping is enabled).
|
# exception mapping is enabled).
|
||||||
self._createPyTangoDevice = self._applyGuardToFunc(
|
self._createPyTangoDevice = self._applyGuardToFunc(
|
||||||
self._createPyTangoDevice, 'constructor')
|
self._createPyTangoDevice, 'constructor')
|
||||||
super(PyTangoDevice, self).earlyInit()
|
super().earlyInit()
|
||||||
|
|
||||||
@lazy_property
|
@lazy_property
|
||||||
def _dev(self):
|
def _dev(self):
|
||||||
@ -249,10 +247,10 @@ class PyTangoDevice(Module):
|
|||||||
# otherwise would lead to attribute errors later
|
# otherwise would lead to attribute errors later
|
||||||
try:
|
try:
|
||||||
device.State
|
device.State
|
||||||
except AttributeError:
|
except AttributeError as e:
|
||||||
raise CommunicationFailedError(
|
raise CommunicationFailedError(
|
||||||
self, 'connection to Tango server failed, '
|
self, 'connection to Tango server failed, '
|
||||||
'is the server running?')
|
'is the server running?') from e
|
||||||
return self._applyGuardsToPyTangoDevice(device)
|
return self._applyGuardsToPyTangoDevice(device)
|
||||||
|
|
||||||
def _applyGuardsToPyTangoDevice(self, dev):
|
def _applyGuardsToPyTangoDevice(self, dev):
|
||||||
@ -376,14 +374,17 @@ class AnalogInput(PyTangoDevice, Readable):
|
|||||||
The AnalogInput handles all devices only delivering an analogue value.
|
The AnalogInput handles all devices only delivering an analogue value.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def startModule(self, started_callback):
|
def startModule(self, start_events):
|
||||||
super(AnalogInput, self).startModule(started_callback)
|
super().startModule(start_events)
|
||||||
# query unit from tango and update value property
|
try:
|
||||||
attrInfo = self._dev.attribute_query('value')
|
# query unit from tango and update value property
|
||||||
# prefer configured unit if nothing is set on the Tango device, else
|
attrInfo = self._dev.attribute_query('value')
|
||||||
# update
|
# prefer configured unit if nothing is set on the Tango device, else
|
||||||
if attrInfo.unit != 'No unit':
|
# update
|
||||||
self.accessibles['value'].datatype.setProperty('unit', attrInfo.unit)
|
if attrInfo.unit != 'No unit':
|
||||||
|
self.accessibles['value'].datatype.setProperty('unit', attrInfo.unit)
|
||||||
|
except Exception as e:
|
||||||
|
self.log.error(e)
|
||||||
|
|
||||||
def read_value(self):
|
def read_value(self):
|
||||||
return self._dev.value
|
return self._dev.value
|
||||||
@ -422,7 +423,7 @@ class AnalogOutput(PyTangoDevice, Drivable):
|
|||||||
userlimits = Parameter('User defined limits of device value',
|
userlimits = Parameter('User defined limits of device value',
|
||||||
datatype=LimitsType(FloatRange(unit='$')),
|
datatype=LimitsType(FloatRange(unit='$')),
|
||||||
default=(float('-Inf'), float('+Inf')),
|
default=(float('-Inf'), float('+Inf')),
|
||||||
readonly=False, poll=10,
|
readonly=False,
|
||||||
)
|
)
|
||||||
abslimits = Parameter('Absolute limits of device value',
|
abslimits = Parameter('Absolute limits of device value',
|
||||||
datatype=LimitsType(FloatRange(unit='$')),
|
datatype=LimitsType(FloatRange(unit='$')),
|
||||||
@ -446,13 +447,13 @@ class AnalogOutput(PyTangoDevice, Drivable):
|
|||||||
_moving = False
|
_moving = False
|
||||||
|
|
||||||
def initModule(self):
|
def initModule(self):
|
||||||
super(AnalogOutput, self).initModule()
|
super().initModule()
|
||||||
# init history
|
# init history
|
||||||
self._history = [] # will keep (timestamp, value) tuple
|
self._history = [] # will keep (timestamp, value) tuple
|
||||||
self._timeout = None # keeps the time at which we will timeout, or None
|
self._timeout = None # keeps the time at which we will timeout, or None
|
||||||
|
|
||||||
def startModule(self, started_callback):
|
def startModule(self, start_events):
|
||||||
super(AnalogOutput, self).startModule(started_callback)
|
super().startModule(start_events)
|
||||||
# query unit from tango and update value property
|
# query unit from tango and update value property
|
||||||
attrInfo = self._dev.attribute_query('value')
|
attrInfo = self._dev.attribute_query('value')
|
||||||
# prefer configured unit if nothing is set on the Tango device, else
|
# prefer configured unit if nothing is set on the Tango device, else
|
||||||
@ -460,8 +461,8 @@ class AnalogOutput(PyTangoDevice, Drivable):
|
|||||||
if attrInfo.unit != 'No unit':
|
if attrInfo.unit != 'No unit':
|
||||||
self.accessibles['value'].datatype.setProperty('unit', attrInfo.unit)
|
self.accessibles['value'].datatype.setProperty('unit', attrInfo.unit)
|
||||||
|
|
||||||
def pollParams(self, nr=0):
|
def doPoll(self):
|
||||||
super(AnalogOutput, self).pollParams(nr)
|
super().doPoll()
|
||||||
while len(self._history) > 2:
|
while len(self._history) > 2:
|
||||||
# if history would be too short, break
|
# if history would be too short, break
|
||||||
if self._history[-1][0] - self._history[1][0] <= self.window:
|
if self._history[-1][0] - self._history[1][0] <= self.window:
|
||||||
@ -489,8 +490,11 @@ class AnalogOutput(PyTangoDevice, Drivable):
|
|||||||
hist = self._history[:]
|
hist = self._history[:]
|
||||||
window_start = currenttime() - self.window
|
window_start = currenttime() - self.window
|
||||||
hist_in_window = [v for (t, v) in hist if t >= window_start]
|
hist_in_window = [v for (t, v) in hist if t >= window_start]
|
||||||
|
if len(hist) == len(hist_in_window):
|
||||||
|
return False # no data point before window
|
||||||
if not hist_in_window:
|
if not hist_in_window:
|
||||||
return False # no relevant history -> no knowledge
|
# window is too small -> use last point only
|
||||||
|
hist_in_window = [self.value]
|
||||||
|
|
||||||
max_in_hist = max(hist_in_window)
|
max_in_hist = max(hist_in_window)
|
||||||
min_in_hist = min(hist_in_window)
|
min_in_hist = min(hist_in_window)
|
||||||
@ -503,13 +507,14 @@ class AnalogOutput(PyTangoDevice, Drivable):
|
|||||||
if self._isAtTarget():
|
if self._isAtTarget():
|
||||||
self._timeout = None
|
self._timeout = None
|
||||||
self._moving = False
|
self._moving = False
|
||||||
return super(AnalogOutput, self).read_status()
|
status = super().read_status()
|
||||||
if self._timeout:
|
else:
|
||||||
if self._timeout < currenttime():
|
if self._timeout and self._timeout < currenttime():
|
||||||
return self.Status.UNSTABLE, 'timeout after waiting for stable value'
|
status = self.Status.UNSTABLE, 'timeout after waiting for stable value'
|
||||||
if self._moving:
|
else:
|
||||||
return (self.Status.BUSY, 'moving')
|
status = (self.Status.BUSY, 'moving') if self._moving else (self.Status.IDLE, 'stable')
|
||||||
return (self.Status.IDLE, 'stable')
|
self.setFastPoll(self.isBusy(status))
|
||||||
|
return status
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def absmin(self):
|
def absmin(self):
|
||||||
@ -571,11 +576,14 @@ class AnalogOutput(PyTangoDevice, Drivable):
|
|||||||
if not self.timeout:
|
if not self.timeout:
|
||||||
self._timeout = None
|
self._timeout = None
|
||||||
self._moving = True
|
self._moving = True
|
||||||
self._history = [] # clear history
|
# do not clear the history here:
|
||||||
self.read_status() # poll our status to keep it updated
|
# - if the target is not changed by more than precision, there is no need to wait
|
||||||
|
# self._history = []
|
||||||
|
self.read_status() # poll our status to keep it updated (this will also set fast poll)
|
||||||
|
return self.read_target()
|
||||||
|
|
||||||
def _hw_wait(self):
|
def _hw_wait(self):
|
||||||
while super(AnalogOutput, self).read_status()[0] == self.Status.BUSY:
|
while super().read_status()[0] == self.Status.BUSY:
|
||||||
sleep(0.3)
|
sleep(0.3)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
@ -597,8 +605,7 @@ class Actuator(AnalogOutput):
|
|||||||
readonly=False, datatype=FloatRange(0, unit='$/s'),
|
readonly=False, datatype=FloatRange(0, unit='$/s'),
|
||||||
)
|
)
|
||||||
ramp = Parameter('The speed of changing the value',
|
ramp = Parameter('The speed of changing the value',
|
||||||
readonly=False, datatype=FloatRange(0, unit='$/s'),
|
readonly=False, datatype=FloatRange(0, unit='$/min'),
|
||||||
poll=30,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def read_speed(self):
|
def read_speed(self):
|
||||||
@ -677,17 +684,22 @@ class TemperatureController(Actuator):
|
|||||||
)
|
)
|
||||||
pid = Parameter('pid control Parameters',
|
pid = Parameter('pid control Parameters',
|
||||||
datatype=TupleOf(FloatRange(), FloatRange(), FloatRange()),
|
datatype=TupleOf(FloatRange(), FloatRange(), FloatRange()),
|
||||||
readonly=False, group='pid', poll=30,
|
readonly=False, group='pid',
|
||||||
)
|
)
|
||||||
setpoint = Parameter('Current setpoint', datatype=FloatRange(unit='$'), poll=1,
|
setpoint = Parameter('Current setpoint', datatype=FloatRange(unit='$'),
|
||||||
)
|
)
|
||||||
heateroutput = Parameter('Heater output', datatype=FloatRange(), poll=1,
|
heateroutput = Parameter('Heater output', datatype=FloatRange(),
|
||||||
)
|
)
|
||||||
|
|
||||||
# overrides
|
# overrides
|
||||||
precision = Parameter(default=0.1)
|
precision = Parameter(default=0.1)
|
||||||
ramp = Parameter(description='Temperature ramp')
|
ramp = Parameter(description='Temperature ramp')
|
||||||
|
|
||||||
|
def doPoll(self):
|
||||||
|
super().doPoll()
|
||||||
|
self.read_setpoint()
|
||||||
|
self.read_heateroutput()
|
||||||
|
|
||||||
def read_ramp(self):
|
def read_ramp(self):
|
||||||
return self._dev.ramp
|
return self._dev.ramp
|
||||||
|
|
||||||
@ -730,6 +742,10 @@ class TemperatureController(Actuator):
|
|||||||
def read_heateroutput(self):
|
def read_heateroutput(self):
|
||||||
return self._dev.heaterOutput
|
return self._dev.heaterOutput
|
||||||
|
|
||||||
|
# remove UserCommand setposition from Actuator
|
||||||
|
# (makes no sense for a TemperatureController)
|
||||||
|
setposition = None
|
||||||
|
|
||||||
|
|
||||||
class PowerSupply(Actuator):
|
class PowerSupply(Actuator):
|
||||||
"""A power supply (voltage and current) device.
|
"""A power supply (voltage and current) device.
|
||||||
@ -737,13 +753,19 @@ class PowerSupply(Actuator):
|
|||||||
|
|
||||||
# parameters
|
# parameters
|
||||||
voltage = Parameter('Actual voltage',
|
voltage = Parameter('Actual voltage',
|
||||||
datatype=FloatRange(unit='V'), poll=-5)
|
datatype=FloatRange(unit='V'))
|
||||||
current = Parameter('Actual current',
|
current = Parameter('Actual current',
|
||||||
datatype=FloatRange(unit='A'), poll=-5)
|
datatype=FloatRange(unit='A'))
|
||||||
|
|
||||||
# overrides
|
# overrides
|
||||||
ramp = Parameter(description='Current/voltage ramp')
|
ramp = Parameter(description='Current/voltage ramp')
|
||||||
|
|
||||||
|
def doPoll(self):
|
||||||
|
super().doPoll()
|
||||||
|
# TODO: poll voltage and current faster when busy
|
||||||
|
self.read_voltage()
|
||||||
|
self.read_current()
|
||||||
|
|
||||||
def read_ramp(self):
|
def read_ramp(self):
|
||||||
return self._dev.ramp
|
return self._dev.ramp
|
||||||
|
|
||||||
@ -777,16 +799,18 @@ class NamedDigitalInput(DigitalInput):
|
|||||||
datatype=StringType(), export=False) # XXX:!!!
|
datatype=StringType(), export=False) # XXX:!!!
|
||||||
|
|
||||||
def initModule(self):
|
def initModule(self):
|
||||||
super(NamedDigitalInput, self).initModule()
|
super().initModule()
|
||||||
try:
|
try:
|
||||||
# pylint: disable=eval-used
|
mapping = self.mapping
|
||||||
mapping = eval(self.mapping.replace('\n', ' '))
|
if isinstance(mapping, str):
|
||||||
|
# pylint: disable=eval-used
|
||||||
|
mapping = eval(self.mapping.replace('\n', ' '))
|
||||||
if isinstance(mapping, str):
|
if isinstance(mapping, str):
|
||||||
# pylint: disable=eval-used
|
# pylint: disable=eval-used
|
||||||
mapping = eval(mapping)
|
mapping = eval(mapping)
|
||||||
self.accessibles['value'].setProperty('datatype', EnumType('value', **mapping))
|
self.accessibles['value'].setProperty('datatype', EnumType('value', **mapping))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError('Illegal Value for mapping: %r' % e)
|
raise ValueError('Illegal Value for mapping: %r' % self.mapping) from e
|
||||||
|
|
||||||
def read_value(self):
|
def read_value(self):
|
||||||
value = self._dev.value
|
value = self._dev.value
|
||||||
@ -805,7 +829,7 @@ class PartialDigitalInput(NamedDigitalInput):
|
|||||||
datatype=IntRange(0), default=1)
|
datatype=IntRange(0), default=1)
|
||||||
|
|
||||||
def initModule(self):
|
def initModule(self):
|
||||||
super(PartialDigitalInput, self).initModule()
|
super().initModule()
|
||||||
self._mask = (1 << self.bitwidth) - 1
|
self._mask = (1 << self.bitwidth) - 1
|
||||||
# self.accessibles['value'].datatype = IntRange(0, self._mask)
|
# self.accessibles['value'].datatype = IntRange(0, self._mask)
|
||||||
|
|
||||||
@ -827,9 +851,16 @@ class DigitalOutput(PyTangoDevice, Drivable):
|
|||||||
def read_value(self):
|
def read_value(self):
|
||||||
return self._dev.value # mapping is done by datatype upon export()
|
return self._dev.value # mapping is done by datatype upon export()
|
||||||
|
|
||||||
|
def read_status(self):
|
||||||
|
status = self.read_status()
|
||||||
|
self.setFastPoll(self.isBusy(status))
|
||||||
|
return status
|
||||||
|
|
||||||
def write_target(self, value):
|
def write_target(self, value):
|
||||||
self._dev.value = value
|
self._dev.value = value
|
||||||
self.read_value()
|
self.read_value()
|
||||||
|
self.read_status() # this will also set fast poll
|
||||||
|
return self.read_target()
|
||||||
|
|
||||||
def read_target(self):
|
def read_target(self):
|
||||||
attrObj = self._dev.read_attribute('value')
|
attrObj = self._dev.read_attribute('value')
|
||||||
@ -845,22 +876,25 @@ class NamedDigitalOutput(DigitalOutput):
|
|||||||
datatype=StringType(), export=False)
|
datatype=StringType(), export=False)
|
||||||
|
|
||||||
def initModule(self):
|
def initModule(self):
|
||||||
super(NamedDigitalOutput, self).initModule()
|
super().initModule()
|
||||||
try:
|
try:
|
||||||
# pylint: disable=eval-used
|
mapping = self.mapping
|
||||||
mapping = eval(self.mapping.replace('\n', ' '))
|
if isinstance(mapping, str):
|
||||||
|
# pylint: disable=eval-used
|
||||||
|
mapping = eval(self.mapping.replace('\n', ' '))
|
||||||
if isinstance(mapping, str):
|
if isinstance(mapping, str):
|
||||||
# pylint: disable=eval-used
|
# pylint: disable=eval-used
|
||||||
mapping = eval(mapping)
|
mapping = eval(mapping)
|
||||||
self.accessibles['value'].setProperty('datatype', EnumType('value', **mapping))
|
self.accessibles['value'].setProperty('datatype', EnumType('value', **mapping))
|
||||||
self.accessibles['target'].setProperty('datatype', EnumType('target', **mapping))
|
self.accessibles['target'].setProperty('datatype', EnumType('target', **mapping))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError('Illegal Value for mapping: %r' % e)
|
raise ValueError('Illegal Value for mapping: %r' % self.mapping) from e
|
||||||
|
|
||||||
def write_target(self, value):
|
def write_target(self, value):
|
||||||
# map from enum-str to integer value
|
# map from enum-str to integer value
|
||||||
self._dev.value = int(value)
|
self._dev.value = int(value)
|
||||||
self.read_value()
|
self.read_value()
|
||||||
|
return self.read_target()
|
||||||
|
|
||||||
|
|
||||||
class PartialDigitalOutput(NamedDigitalOutput):
|
class PartialDigitalOutput(NamedDigitalOutput):
|
||||||
@ -875,7 +909,7 @@ class PartialDigitalOutput(NamedDigitalOutput):
|
|||||||
datatype=IntRange(0), default=1)
|
datatype=IntRange(0), default=1)
|
||||||
|
|
||||||
def initModule(self):
|
def initModule(self):
|
||||||
super(PartialDigitalOutput, self).initModule()
|
super().initModule()
|
||||||
self._mask = (1 << self.bitwidth) - 1
|
self._mask = (1 << self.bitwidth) - 1
|
||||||
# self.accessibles['value'].datatype = IntRange(0, self._mask)
|
# self.accessibles['value'].datatype = IntRange(0, self._mask)
|
||||||
# self.accessibles['target'].datatype = IntRange(0, self._mask)
|
# self.accessibles['target'].datatype = IntRange(0, self._mask)
|
||||||
@ -891,6 +925,7 @@ class PartialDigitalOutput(NamedDigitalOutput):
|
|||||||
(value << self.startbit)
|
(value << self.startbit)
|
||||||
self._dev.value = newvalue
|
self._dev.value = newvalue
|
||||||
self.read_value()
|
self.read_value()
|
||||||
|
return self.read_target()
|
||||||
|
|
||||||
|
|
||||||
class StringIO(PyTangoDevice, Module):
|
class StringIO(PyTangoDevice, Module):
|
||||||
|
78
test/test_attach.py
Normal file
78
test/test_attach.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# *****************************************************************************
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Module authors:
|
||||||
|
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
|
||||||
|
from secop.modules import Module, Attached
|
||||||
|
from secop.protocol.dispatcher import Dispatcher
|
||||||
|
|
||||||
|
|
||||||
|
# class DispatcherStub:
|
||||||
|
# # omit_unchanged_within = 0
|
||||||
|
#
|
||||||
|
# # def __init__(self, updates):
|
||||||
|
# # self.updates = updates
|
||||||
|
# #
|
||||||
|
# # def announce_update(self, modulename, pname, pobj):
|
||||||
|
# # self.updates.setdefault(modulename, {})
|
||||||
|
# # if pobj.readerror:
|
||||||
|
# # self.updates[modulename]['error', pname] = str(pobj.readerror)
|
||||||
|
# # else:
|
||||||
|
# # self.updates[modulename][pname] = pobj.value
|
||||||
|
#
|
||||||
|
# def __init__(self):
|
||||||
|
# self.modules = {}
|
||||||
|
#
|
||||||
|
# def get_module(self, name):
|
||||||
|
# return self.modules[name]
|
||||||
|
#
|
||||||
|
# def register_module(self, name, module):
|
||||||
|
# self.modules[name] = module
|
||||||
|
|
||||||
|
|
||||||
|
class LoggerStub:
|
||||||
|
def debug(self, fmt, *args):
|
||||||
|
print(fmt % args)
|
||||||
|
info = warning = exception = debug
|
||||||
|
handlers = []
|
||||||
|
|
||||||
|
|
||||||
|
logger = LoggerStub()
|
||||||
|
|
||||||
|
|
||||||
|
class ServerStub:
|
||||||
|
restart = None
|
||||||
|
shutdown = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.dispatcher = Dispatcher('dispatcher', logger, {}, self)
|
||||||
|
|
||||||
|
|
||||||
|
def test_attach():
|
||||||
|
class Mod(Module):
|
||||||
|
att = Attached()
|
||||||
|
|
||||||
|
srv = ServerStub()
|
||||||
|
a = Module('a', logger, {'description': ''}, srv)
|
||||||
|
m = Mod('m', logger, {'description': '', 'att': 'a'}, srv)
|
||||||
|
assert m.propertyValues['att'] == 'a'
|
||||||
|
srv.dispatcher.register_module(a, 'a')
|
||||||
|
srv.dispatcher.register_module(m, 'm')
|
||||||
|
assert m.att == a
|
@ -28,7 +28,9 @@ import pytest
|
|||||||
from secop.datatypes import ArrayOf, BLOBType, BoolType, \
|
from secop.datatypes import ArrayOf, BLOBType, BoolType, \
|
||||||
CommandType, ConfigError, DataType, Enum, EnumType, FloatRange, \
|
CommandType, ConfigError, DataType, Enum, EnumType, FloatRange, \
|
||||||
IntRange, ProgrammingError, ScaledInteger, StatusType, \
|
IntRange, ProgrammingError, ScaledInteger, StatusType, \
|
||||||
StringType, StructOf, TextType, TupleOf, get_datatype
|
StringType, StructOf, TextType, TupleOf, get_datatype, \
|
||||||
|
DiscouragedConversion
|
||||||
|
from secop.lib import generalConfig
|
||||||
|
|
||||||
|
|
||||||
def copytest(dt):
|
def copytest(dt):
|
||||||
@ -36,6 +38,7 @@ def copytest(dt):
|
|||||||
assert dt.export_datatype() == dt.copy().export_datatype()
|
assert dt.export_datatype() == dt.copy().export_datatype()
|
||||||
assert dt != dt.copy()
|
assert dt != dt.copy()
|
||||||
|
|
||||||
|
|
||||||
def test_DataType():
|
def test_DataType():
|
||||||
dt = DataType()
|
dt = DataType()
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
@ -116,7 +119,6 @@ def test_IntRange():
|
|||||||
dt('1.3')
|
dt('1.3')
|
||||||
dt(1)
|
dt(1)
|
||||||
dt(0)
|
dt(0)
|
||||||
dt('1')
|
|
||||||
with pytest.raises(ProgrammingError):
|
with pytest.raises(ProgrammingError):
|
||||||
IntRange('xc', 'Yx')
|
IntRange('xc', 'Yx')
|
||||||
|
|
||||||
@ -132,6 +134,7 @@ def test_IntRange():
|
|||||||
with pytest.raises(ConfigError):
|
with pytest.raises(ConfigError):
|
||||||
dt.checkProperties()
|
dt.checkProperties()
|
||||||
|
|
||||||
|
|
||||||
def test_ScaledInteger():
|
def test_ScaledInteger():
|
||||||
dt = ScaledInteger(0.01, -3, 3)
|
dt = ScaledInteger(0.01, -3, 3)
|
||||||
copytest(dt)
|
copytest(dt)
|
||||||
@ -407,6 +410,7 @@ def test_ArrayOf():
|
|||||||
dt = ArrayOf(EnumType('myenum', single=0), 5)
|
dt = ArrayOf(EnumType('myenum', single=0), 5)
|
||||||
copytest(dt)
|
copytest(dt)
|
||||||
|
|
||||||
|
|
||||||
def test_TupleOf():
|
def test_TupleOf():
|
||||||
# test constructor catching illegal arguments
|
# test constructor catching illegal arguments
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
@ -641,6 +645,7 @@ def test_oneway_compatible(dt, contained_in):
|
|||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
contained_in.compatible(dt)
|
contained_in.compatible(dt)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('dt1, dt2', [
|
@pytest.mark.parametrize('dt1, dt2', [
|
||||||
(FloatRange(-5.5, 5.5), ScaledInteger(10, -5.5, 5.5)),
|
(FloatRange(-5.5, 5.5), ScaledInteger(10, -5.5, 5.5)),
|
||||||
(IntRange(0,1), BoolType()),
|
(IntRange(0,1), BoolType()),
|
||||||
@ -650,6 +655,7 @@ def test_twoway_compatible(dt1, dt2):
|
|||||||
dt1.compatible(dt1)
|
dt1.compatible(dt1)
|
||||||
dt2.compatible(dt2)
|
dt2.compatible(dt2)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('dt1, dt2', [
|
@pytest.mark.parametrize('dt1, dt2', [
|
||||||
(StringType(), FloatRange()),
|
(StringType(), FloatRange()),
|
||||||
(IntRange(-10, 10), StringType()),
|
(IntRange(-10, 10), StringType()),
|
||||||
@ -665,3 +671,12 @@ def test_incompatible(dt1, dt2):
|
|||||||
dt1.compatible(dt2)
|
dt1.compatible(dt2)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
dt2.compatible(dt1)
|
dt2.compatible(dt1)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('dt', [FloatRange(), IntRange(), ScaledInteger(1)])
|
||||||
|
def test_lazy_validation(dt):
|
||||||
|
generalConfig.defaults['lazy_number_validation'] = True
|
||||||
|
dt('0')
|
||||||
|
generalConfig.defaults['lazy_number_validation'] = False
|
||||||
|
with pytest.raises(DiscouragedConversion):
|
||||||
|
dt('0')
|
||||||
|
234
test/test_handler.py
Normal file
234
test/test_handler.py
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# *****************************************************************************
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Module authors:
|
||||||
|
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
|
||||||
|
|
||||||
|
from secop.rwhandler import ReadHandler, WriteHandler, \
|
||||||
|
CommonReadHandler, CommonWriteHandler, nopoll
|
||||||
|
from secop.core import Module, Parameter, FloatRange, Done
|
||||||
|
|
||||||
|
|
||||||
|
class DispatcherStub:
|
||||||
|
# the first update from the poller comes a very short time after the
|
||||||
|
# initial value from the timestamp. However, in the test below
|
||||||
|
# the second update happens after the updates dict is cleared
|
||||||
|
# -> we have to inhibit the 'omit unchanged update' feature
|
||||||
|
omit_unchanged_within = 0
|
||||||
|
|
||||||
|
def __init__(self, updates):
|
||||||
|
self.updates = updates
|
||||||
|
|
||||||
|
def announce_update(self, modulename, pname, pobj):
|
||||||
|
self.updates.setdefault(modulename, {})
|
||||||
|
if pobj.readerror:
|
||||||
|
self.updates[modulename]['error', pname] = str(pobj.readerror)
|
||||||
|
else:
|
||||||
|
self.updates[modulename][pname] = pobj.value
|
||||||
|
|
||||||
|
|
||||||
|
class LoggerStub:
|
||||||
|
def debug(self, fmt, *args):
|
||||||
|
print(fmt % args)
|
||||||
|
info = warning = exception = error = debug
|
||||||
|
handlers = []
|
||||||
|
|
||||||
|
|
||||||
|
logger = LoggerStub()
|
||||||
|
|
||||||
|
|
||||||
|
class ServerStub:
|
||||||
|
def __init__(self, updates):
|
||||||
|
self.dispatcher = DispatcherStub(updates)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleTest(Module):
|
||||||
|
def __init__(self, updates=None, **opts):
|
||||||
|
opts['description'] = ''
|
||||||
|
super().__init__('mod', logger, opts, ServerStub(updates or {}))
|
||||||
|
|
||||||
|
|
||||||
|
def test_handler():
|
||||||
|
data = []
|
||||||
|
|
||||||
|
class Mod(ModuleTest):
|
||||||
|
a = Parameter('', FloatRange(), readonly=False)
|
||||||
|
b = Parameter('', FloatRange(), readonly=False)
|
||||||
|
|
||||||
|
@ReadHandler(['a', 'b'])
|
||||||
|
def read_hdl(self, pname):
|
||||||
|
value = data.pop()
|
||||||
|
data.append(pname)
|
||||||
|
return value
|
||||||
|
|
||||||
|
@WriteHandler(['a', 'b'])
|
||||||
|
def write_hdl(self, pname, value):
|
||||||
|
data.append(pname)
|
||||||
|
return value
|
||||||
|
|
||||||
|
assert Mod.read_a.poll is True
|
||||||
|
assert Mod.read_b.poll is True
|
||||||
|
|
||||||
|
m = Mod()
|
||||||
|
|
||||||
|
data.append(1.2)
|
||||||
|
assert m.read_a() == 1.2
|
||||||
|
assert data.pop() == 'a'
|
||||||
|
|
||||||
|
data.append(1.3)
|
||||||
|
assert m.read_b() == 1.3
|
||||||
|
assert data.pop() == 'b'
|
||||||
|
|
||||||
|
assert m.write_a(1.5) == 1.5
|
||||||
|
assert m.a == 1.5
|
||||||
|
assert data.pop() == 'a'
|
||||||
|
|
||||||
|
assert m.write_b(7) == 7
|
||||||
|
assert m.b == 7
|
||||||
|
assert data.pop() == 'b'
|
||||||
|
|
||||||
|
data.append(Done)
|
||||||
|
assert m.read_b() == 7
|
||||||
|
assert data.pop() == 'b'
|
||||||
|
|
||||||
|
assert data == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_common_handler():
|
||||||
|
data = []
|
||||||
|
|
||||||
|
class Mod(ModuleTest):
|
||||||
|
a = Parameter('', FloatRange(), readonly=False)
|
||||||
|
b = Parameter('', FloatRange(), readonly=False)
|
||||||
|
|
||||||
|
@CommonReadHandler(['a', 'b'])
|
||||||
|
def read_hdl(self):
|
||||||
|
self.a, self.b = data.pop()
|
||||||
|
data.append('read_hdl')
|
||||||
|
|
||||||
|
@CommonWriteHandler(['a', 'b'])
|
||||||
|
def write_hdl(self, values):
|
||||||
|
self.a = values['a']
|
||||||
|
self.b = values['b']
|
||||||
|
data.append('write_hdl')
|
||||||
|
|
||||||
|
assert set([Mod.read_a.poll, Mod.read_b.poll]) == {True, False}
|
||||||
|
|
||||||
|
m = Mod(a=1, b=2)
|
||||||
|
assert m.writeDict == {'a': 1, 'b': 2}
|
||||||
|
m.write_a(3)
|
||||||
|
assert m.a == 3
|
||||||
|
assert m.b == 2
|
||||||
|
assert data.pop() == 'write_hdl'
|
||||||
|
assert m.writeDict == {}
|
||||||
|
|
||||||
|
m.write_b(4)
|
||||||
|
assert m.a == 3
|
||||||
|
assert m.b == 4
|
||||||
|
assert data.pop() == 'write_hdl'
|
||||||
|
|
||||||
|
data.append((3, 4))
|
||||||
|
assert m.read_a() == 3
|
||||||
|
assert m.a == 3
|
||||||
|
assert m.b == 4
|
||||||
|
assert data.pop() == 'read_hdl'
|
||||||
|
data.append((5, 6))
|
||||||
|
assert m.read_b() == 6
|
||||||
|
assert data.pop() == 'read_hdl'
|
||||||
|
|
||||||
|
data.append((1.1, 2.2))
|
||||||
|
assert m.read_b() == 2.2
|
||||||
|
assert m.a == 1.1
|
||||||
|
assert m.b == 2.2
|
||||||
|
assert data.pop() == 'read_hdl'
|
||||||
|
|
||||||
|
assert data == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_nopoll():
|
||||||
|
class Mod1(ModuleTest):
|
||||||
|
a = Parameter('', FloatRange(), readonly=False)
|
||||||
|
b = Parameter('', FloatRange(), readonly=False)
|
||||||
|
|
||||||
|
@ReadHandler(['a', 'b'])
|
||||||
|
def read_hdl(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert Mod1.read_a.poll is True
|
||||||
|
assert Mod1.read_b.poll is True
|
||||||
|
|
||||||
|
class Mod2(ModuleTest):
|
||||||
|
a = Parameter('', FloatRange(), readonly=False)
|
||||||
|
b = Parameter('', FloatRange(), readonly=False)
|
||||||
|
|
||||||
|
@CommonReadHandler(['a', 'b'])
|
||||||
|
def read_hdl(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert Mod2.read_a.poll is True
|
||||||
|
assert Mod2.read_b.poll is False
|
||||||
|
|
||||||
|
class Mod3(ModuleTest):
|
||||||
|
a = Parameter('', FloatRange(), readonly=False)
|
||||||
|
b = Parameter('', FloatRange(), readonly=False)
|
||||||
|
|
||||||
|
@ReadHandler(['a', 'b'])
|
||||||
|
@nopoll
|
||||||
|
def read_hdl(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert Mod3.read_a.poll is False
|
||||||
|
assert Mod3.read_b.poll is False
|
||||||
|
|
||||||
|
class Mod4(ModuleTest):
|
||||||
|
a = Parameter('', FloatRange(), readonly=False)
|
||||||
|
b = Parameter('', FloatRange(), readonly=False)
|
||||||
|
|
||||||
|
@nopoll
|
||||||
|
@ReadHandler(['a', 'b'])
|
||||||
|
def read_hdl(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert Mod4.read_a.poll is False
|
||||||
|
assert Mod4.read_b.poll is False
|
||||||
|
|
||||||
|
class Mod5(ModuleTest):
|
||||||
|
a = Parameter('', FloatRange(), readonly=False)
|
||||||
|
b = Parameter('', FloatRange(), readonly=False)
|
||||||
|
|
||||||
|
@CommonReadHandler(['a', 'b'])
|
||||||
|
@nopoll
|
||||||
|
def read_hdl(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert Mod5.read_a.poll is False
|
||||||
|
assert Mod5.read_b.poll is False
|
||||||
|
|
||||||
|
class Mod6(ModuleTest):
|
||||||
|
a = Parameter('', FloatRange(), readonly=False)
|
||||||
|
b = Parameter('', FloatRange(), readonly=False)
|
||||||
|
|
||||||
|
@nopoll
|
||||||
|
@CommonReadHandler(['a', 'b'])
|
||||||
|
def read_hdl(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert Mod6.read_a.poll is False
|
||||||
|
assert Mod6.read_b.poll is False
|
@ -120,7 +120,7 @@ def test_IOHandler():
|
|||||||
real = Parameter('a float value', FloatRange(), default=12.3, handler=group2, readonly=False)
|
real = Parameter('a float value', FloatRange(), default=12.3, handler=group2, readonly=False)
|
||||||
text = Parameter('a string value', StringType(), default='x', handler=group2, readonly=False)
|
text = Parameter('a string value', StringType(), default='x', handler=group2, readonly=False)
|
||||||
|
|
||||||
def sendRecv(self, command):
|
def communicate(self, command):
|
||||||
assert data.pop('command') == command
|
assert data.pop('command') == command
|
||||||
return data.pop('reply')
|
return data.pop('reply')
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ def test_IOHandler():
|
|||||||
print(updates)
|
print(updates)
|
||||||
updates.clear() # get rid of updates from initialisation
|
updates.clear() # get rid of updates from initialisation
|
||||||
|
|
||||||
# for sendRecv
|
# for communicate
|
||||||
data.push('command', 'SIMPLE?')
|
data.push('command', 'SIMPLE?')
|
||||||
data.push('reply', '4.51')
|
data.push('reply', '4.51')
|
||||||
# for analyze_group1
|
# for analyze_group1
|
||||||
@ -159,7 +159,7 @@ def test_IOHandler():
|
|||||||
assert updates.pop('simple') == 45.1
|
assert updates.pop('simple') == 45.1
|
||||||
assert not updates
|
assert not updates
|
||||||
|
|
||||||
# for sendRecv
|
# for communicate
|
||||||
data.push('command', 'CMD?3')
|
data.push('command', 'CMD?3')
|
||||||
data.push('reply', '1.23,text,5')
|
data.push('reply', '1.23,text,5')
|
||||||
# for analyze_group2
|
# for analyze_group2
|
||||||
@ -172,7 +172,7 @@ def test_IOHandler():
|
|||||||
assert data.empty()
|
assert data.empty()
|
||||||
assert not updates
|
assert not updates
|
||||||
|
|
||||||
# for sendRecv
|
# for communicate
|
||||||
data.push('command', 'CMD?3')
|
data.push('command', 'CMD?3')
|
||||||
data.push('reply', '1.23,text,5')
|
data.push('reply', '1.23,text,5')
|
||||||
# for analyze_group2
|
# for analyze_group2
|
||||||
@ -183,7 +183,7 @@ def test_IOHandler():
|
|||||||
data.push('self', 12.3, 'string')
|
data.push('self', 12.3, 'string')
|
||||||
data.push('new', 12.3, 'FOO')
|
data.push('new', 12.3, 'FOO')
|
||||||
data.push('changed', 1.23, 'foo', 9)
|
data.push('changed', 1.23, 'foo', 9)
|
||||||
# for sendRecv
|
# for communicate
|
||||||
data.push('command', 'CMD 3,1.23,foo,9|CMD?3')
|
data.push('command', 'CMD 3,1.23,foo,9|CMD?3')
|
||||||
data.push('reply', '1.23,foo,9')
|
data.push('reply', '1.23,foo,9')
|
||||||
# for analyze_group2
|
# for analyze_group2
|
||||||
|
274
test/test_logging.py
Normal file
274
test/test_logging.py
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# *****************************************************************************
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Module authors:
|
||||||
|
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import mlzlog
|
||||||
|
from secop.modules import Module
|
||||||
|
from secop.protocol.dispatcher import Dispatcher
|
||||||
|
from secop.protocol.interface import encode_msg_frame, decode_msg
|
||||||
|
import secop.logging
|
||||||
|
from secop.logging import logger, generalConfig, HasComlog
|
||||||
|
|
||||||
|
|
||||||
|
class ServerStub:
|
||||||
|
restart = None
|
||||||
|
shutdown = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.dispatcher = Dispatcher('', logger.log.getChild('dispatcher'), {}, self)
|
||||||
|
|
||||||
|
|
||||||
|
class Connection:
|
||||||
|
def __init__(self, name, dispatcher, result):
|
||||||
|
self.result = result
|
||||||
|
self.dispatcher = dispatcher
|
||||||
|
self.name = name
|
||||||
|
dispatcher.add_connection(self)
|
||||||
|
|
||||||
|
def send_reply(self, msg):
|
||||||
|
self.result.append(encode_msg_frame(*msg).strip().decode())
|
||||||
|
|
||||||
|
def send(self, msg):
|
||||||
|
request = decode_msg(msg.encode())
|
||||||
|
assert self.dispatcher.handle_request(self, request) == request
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name='init')
|
||||||
|
def init_(monkeypatch):
|
||||||
|
logger.__init__()
|
||||||
|
|
||||||
|
class Playground:
|
||||||
|
def __init__(self, console_level='debug', comlog=True, com_module=True):
|
||||||
|
self.result_dict = result_dict = dict(
|
||||||
|
console=[], comlog=[], conn1=[], conn2=[])
|
||||||
|
|
||||||
|
class ConsoleHandler(mlzlog.Handler):
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
super().__init__()
|
||||||
|
self.result = result_dict['console']
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
if record.name != 'frappy.dispatcher':
|
||||||
|
self.result.append('%s %s %s' % (record.name, record.levelname, record.getMessage()))
|
||||||
|
|
||||||
|
class ComLogHandler(mlzlog.Handler):
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
super().__init__()
|
||||||
|
self.result = result_dict['comlog']
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
self.result.append('%s %s' % (record.name.split('.')[1], record.getMessage()))
|
||||||
|
|
||||||
|
class LogfileHandler(mlzlog.Handler):
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def noop(self, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
close = flush = emit = noop
|
||||||
|
|
||||||
|
monkeypatch.setattr(mlzlog, 'ColoredConsoleHandler', ConsoleHandler)
|
||||||
|
monkeypatch.setattr(secop.logging, 'ComLogfileHandler', ComLogHandler)
|
||||||
|
monkeypatch.setattr(secop.logging, 'LogfileHandler', LogfileHandler)
|
||||||
|
|
||||||
|
class Mod(Module):
|
||||||
|
result = []
|
||||||
|
|
||||||
|
def __init__(self, name, srv, **kwds):
|
||||||
|
kwds['description'] = ''
|
||||||
|
super().__init__(name or 'mod', logger.log.getChild(name), kwds, srv)
|
||||||
|
srv.dispatcher.register_module(self, name, name)
|
||||||
|
self.result[:] = []
|
||||||
|
|
||||||
|
def earlyInit(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Com(HasComlog, Mod):
|
||||||
|
def __init__(self, name, srv, **kwds):
|
||||||
|
super().__init__(name, srv, **kwds)
|
||||||
|
self.earlyInit()
|
||||||
|
self.log.handlers[-1].result = result_dict['comlog']
|
||||||
|
|
||||||
|
def communicate(self, request):
|
||||||
|
self.comLog('> %s', request)
|
||||||
|
|
||||||
|
generalConfig.testinit(logger_root='frappy', comlog=comlog)
|
||||||
|
logger.init(console_level)
|
||||||
|
self.srv = ServerStub()
|
||||||
|
|
||||||
|
self.conn1 = Connection('conn1', self.srv.dispatcher, self.result_dict['conn1'])
|
||||||
|
self.conn2 = Connection('conn2', self.srv.dispatcher, self.result_dict['conn2'])
|
||||||
|
self.mod = Mod('mod', self.srv)
|
||||||
|
self.com = Com('com', self.srv, comlog=com_module)
|
||||||
|
for item in self.result_dict.values():
|
||||||
|
assert item == []
|
||||||
|
|
||||||
|
def check(self, both=None, **expected):
|
||||||
|
if both:
|
||||||
|
expected['conn1'] = expected['conn2'] = both
|
||||||
|
assert self.result_dict['console'] == expected.get('console', [])
|
||||||
|
assert self.result_dict['comlog'] == expected.get('comlog', [])
|
||||||
|
assert self.result_dict['conn1'] == expected.get('conn1', [])
|
||||||
|
assert self.result_dict['conn2'] == expected.get('conn2', [])
|
||||||
|
for item in self.result_dict.values():
|
||||||
|
item[:] = []
|
||||||
|
|
||||||
|
def comlog(self, flag):
|
||||||
|
logger.comlog = flag
|
||||||
|
|
||||||
|
yield Playground
|
||||||
|
# revert settings
|
||||||
|
generalConfig.testinit()
|
||||||
|
logger.__init__()
|
||||||
|
|
||||||
|
|
||||||
|
def test_mod_info(init):
|
||||||
|
p = init()
|
||||||
|
p.mod.log.info('i')
|
||||||
|
p.check(console=['frappy.mod INFO i'])
|
||||||
|
p.conn1.send('logging mod "debug"')
|
||||||
|
p.conn2.send('logging mod "info"')
|
||||||
|
p.mod.log.info('i')
|
||||||
|
p.check(console=['frappy.mod INFO i'], both=['log mod:info "i"'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_mod_debug(init):
|
||||||
|
p = init()
|
||||||
|
p.mod.log.debug('d')
|
||||||
|
p.check(console=['frappy.mod DEBUG d'])
|
||||||
|
p.conn1.send('logging mod "debug"')
|
||||||
|
p.conn2.send('logging mod "info"')
|
||||||
|
p.mod.log.debug('d')
|
||||||
|
p.check(console=['frappy.mod DEBUG d'], conn1=['log mod:debug "d"'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_com_info(init):
|
||||||
|
p = init()
|
||||||
|
p.com.log.info('i')
|
||||||
|
p.check(console=['frappy.com INFO i'])
|
||||||
|
p.conn1.send('logging com "info"')
|
||||||
|
p.conn2.send('logging com "debug"')
|
||||||
|
p.com.log.info('i')
|
||||||
|
p.check(console=['frappy.com INFO i'], both=['log com:info "i"'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_com_debug(init):
|
||||||
|
p = init()
|
||||||
|
p.com.log.debug('d')
|
||||||
|
p.check(console=['frappy.com DEBUG d'])
|
||||||
|
p.conn2.send('logging com "debug"')
|
||||||
|
p.com.log.debug('d')
|
||||||
|
p.check(console=['frappy.com DEBUG d'], conn2=['log com:debug "d"'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_com_com(init):
|
||||||
|
p = init()
|
||||||
|
p.com.communicate('x')
|
||||||
|
p.check(console=['frappy.com COMLOG > x'], comlog=['com > x'])
|
||||||
|
p.conn1.send('logging mod "debug"')
|
||||||
|
p.conn2.send('logging mod "info"')
|
||||||
|
p.conn2.send('logging com "debug"')
|
||||||
|
p.com.communicate('x')
|
||||||
|
p.check(console=['frappy.com COMLOG > x'], comlog=['com > x'], conn2=['log com:comlog "> x"'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_info(init):
|
||||||
|
p = init(console_level='info')
|
||||||
|
p.mod.log.debug('d')
|
||||||
|
p.com.communicate('x')
|
||||||
|
p.check(comlog=['com > x'])
|
||||||
|
p.conn1.send('logging mod "debug"')
|
||||||
|
p.conn2.send('logging mod "info"')
|
||||||
|
p.conn2.send('logging com "debug"')
|
||||||
|
p.com.communicate('x')
|
||||||
|
p.check(comlog=['com > x'], conn2=['log com:comlog "> x"'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_comlog_off(init):
|
||||||
|
p = init(console_level='info', comlog=False)
|
||||||
|
p.mod.log.debug('d')
|
||||||
|
p.com.communicate('x')
|
||||||
|
p.check()
|
||||||
|
|
||||||
|
|
||||||
|
def test_comlog_module_off(init):
|
||||||
|
p = init(console_level='info', com_module=False)
|
||||||
|
p.mod.log.debug('d')
|
||||||
|
p.com.communicate('x')
|
||||||
|
p.check()
|
||||||
|
|
||||||
|
|
||||||
|
def test_remote_all_off(init):
|
||||||
|
p = init()
|
||||||
|
p.conn1.send('logging mod "debug"')
|
||||||
|
p.conn2.send('logging mod "info"')
|
||||||
|
p.conn2.send('logging com "debug"')
|
||||||
|
p.mod.log.debug('d')
|
||||||
|
p.com.communicate('x')
|
||||||
|
p.mod.log.info('i')
|
||||||
|
checks = dict(
|
||||||
|
console=['frappy.mod DEBUG d', 'frappy.com COMLOG > x', 'frappy.mod INFO i'],
|
||||||
|
comlog=['com > x'],
|
||||||
|
conn1=['log mod:debug "d"', 'log mod:info "i"'],
|
||||||
|
conn2=['log com:comlog "> x"', 'log mod:info "i"'])
|
||||||
|
p.check(**checks)
|
||||||
|
p.conn1.send('logging "off"')
|
||||||
|
p.mod.log.debug('d')
|
||||||
|
p.com.communicate('x')
|
||||||
|
p.mod.log.info('i')
|
||||||
|
checks.pop('conn1')
|
||||||
|
p.check(**checks)
|
||||||
|
p.conn2.send('logging . "off"')
|
||||||
|
p.mod.log.debug('d')
|
||||||
|
p.com.communicate('x')
|
||||||
|
p.mod.log.info('i')
|
||||||
|
checks.pop('conn2')
|
||||||
|
p.check(**checks)
|
||||||
|
|
||||||
|
|
||||||
|
def test_remote_single_off(init):
|
||||||
|
p = init()
|
||||||
|
p.conn1.send('logging mod "debug"')
|
||||||
|
p.conn2.send('logging mod "info"')
|
||||||
|
p.conn2.send('logging com "debug"')
|
||||||
|
p.mod.log.debug('d')
|
||||||
|
p.com.communicate('x')
|
||||||
|
p.mod.log.info('i')
|
||||||
|
checks = dict(
|
||||||
|
console=['frappy.mod DEBUG d', 'frappy.com COMLOG > x', 'frappy.mod INFO i'],
|
||||||
|
comlog=['com > x'],
|
||||||
|
conn1=['log mod:debug "d"', 'log mod:info "i"'],
|
||||||
|
conn2=['log com:comlog "> x"', 'log mod:info "i"'])
|
||||||
|
p.check(**checks)
|
||||||
|
p.conn2.send('logging com "off"')
|
||||||
|
p.mod.log.debug('d')
|
||||||
|
p.com.communicate('x')
|
||||||
|
p.mod.log.info('i')
|
||||||
|
checks['conn2'] = ['log mod:info "i"']
|
||||||
|
p.check(**checks)
|
||||||
|
p.conn2.send('logging mod "off"')
|
||||||
|
p.mod.log.debug('d')
|
||||||
|
p.com.communicate('x')
|
||||||
|
p.mod.log.info('i')
|
||||||
|
checks['conn2'] = []
|
||||||
|
p.check(**checks)
|
@ -22,15 +22,16 @@
|
|||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
"""test data types."""
|
"""test data types."""
|
||||||
|
|
||||||
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from secop.datatypes import BoolType, FloatRange, StringType, IntRange
|
from secop.datatypes import BoolType, FloatRange, StringType, IntRange, ScaledInteger
|
||||||
from secop.errors import ProgrammingError, ConfigError
|
from secop.errors import ProgrammingError, ConfigError
|
||||||
from secop.modules import Communicator, Drivable, Readable, Module
|
from secop.modules import Communicator, Drivable, Readable, Module
|
||||||
from secop.params import Command, Parameter
|
from secop.params import Command, Parameter
|
||||||
from secop.poller import BasicPoller
|
from secop.rwhandler import ReadHandler, WriteHandler, nopoll
|
||||||
|
from secop.lib import generalConfig
|
||||||
|
|
||||||
|
|
||||||
class DispatcherStub:
|
class DispatcherStub:
|
||||||
@ -52,9 +53,10 @@ class DispatcherStub:
|
|||||||
|
|
||||||
|
|
||||||
class LoggerStub:
|
class LoggerStub:
|
||||||
def debug(self, *args):
|
def debug(self, fmt, *args):
|
||||||
print(*args)
|
print(fmt % args)
|
||||||
info = warning = exception = debug
|
info = warning = exception = error = debug
|
||||||
|
handlers = []
|
||||||
|
|
||||||
|
|
||||||
logger = LoggerStub()
|
logger = LoggerStub()
|
||||||
@ -65,13 +67,21 @@ class ServerStub:
|
|||||||
self.dispatcher = DispatcherStub(updates)
|
self.dispatcher = DispatcherStub(updates)
|
||||||
|
|
||||||
|
|
||||||
|
class DummyMultiEvent(threading.Event):
|
||||||
|
def get_trigger(self):
|
||||||
|
def trigger(event=self):
|
||||||
|
event.set()
|
||||||
|
sys.exit()
|
||||||
|
return trigger
|
||||||
|
|
||||||
|
|
||||||
def test_Communicator():
|
def test_Communicator():
|
||||||
o = Communicator('communicator', LoggerStub(), {'.description':''}, ServerStub({}))
|
o = Communicator('communicator', LoggerStub(), {'.description': ''}, ServerStub({}))
|
||||||
o.earlyInit()
|
o.earlyInit()
|
||||||
o.initModule()
|
o.initModule()
|
||||||
event = threading.Event()
|
event = DummyMultiEvent()
|
||||||
o.startModule(event.set)
|
o.startModule(event)
|
||||||
assert event.is_set() # event should be set immediately
|
assert event.is_set() # event should be set immediately
|
||||||
|
|
||||||
|
|
||||||
def test_ModuleMagic():
|
def test_ModuleMagic():
|
||||||
@ -87,14 +97,13 @@ def test_ModuleMagic():
|
|||||||
a1 = Parameter('a1', datatype=BoolType(), default=False)
|
a1 = Parameter('a1', datatype=BoolType(), default=False)
|
||||||
a2 = Parameter('a2', datatype=BoolType(), default=True)
|
a2 = Parameter('a2', datatype=BoolType(), default=True)
|
||||||
value = Parameter(datatype=StringType(), default='first')
|
value = Parameter(datatype=StringType(), default='first')
|
||||||
|
target = Parameter(datatype=StringType(), default='')
|
||||||
|
|
||||||
@Command(argument=BoolType(), result=BoolType())
|
@Command(argument=BoolType(), result=BoolType())
|
||||||
def cmd2(self, arg):
|
def cmd2(self, arg):
|
||||||
"""another stuff"""
|
"""another stuff"""
|
||||||
return not arg
|
return not arg
|
||||||
|
|
||||||
pollerClass = BasicPoller
|
|
||||||
|
|
||||||
def read_param1(self):
|
def read_param1(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -104,12 +113,16 @@ def test_ModuleMagic():
|
|||||||
def read_a1(self):
|
def read_a1(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@nopoll
|
||||||
def read_a2(self):
|
def read_a2(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def read_value(self):
|
def read_value(self):
|
||||||
return 'second'
|
return 'second'
|
||||||
|
|
||||||
|
def read_status(self):
|
||||||
|
return 'IDLE', 'ok'
|
||||||
|
|
||||||
with pytest.raises(ProgrammingError):
|
with pytest.raises(ProgrammingError):
|
||||||
class Mod1(Module): # pylint: disable=unused-variable
|
class Mod1(Module): # pylint: disable=unused-variable
|
||||||
def do_this(self): # old style command
|
def do_this(self): # old style command
|
||||||
@ -132,9 +145,12 @@ def test_ModuleMagic():
|
|||||||
return arg
|
return arg
|
||||||
|
|
||||||
value = Parameter(datatype=FloatRange(unit='deg'))
|
value = Parameter(datatype=FloatRange(unit='deg'))
|
||||||
|
target = Parameter(datatype=FloatRange(), default=0)
|
||||||
a1 = Parameter(datatype=FloatRange(unit='$/s'), readonly=False)
|
a1 = Parameter(datatype=FloatRange(unit='$/s'), readonly=False)
|
||||||
|
# remark: it might be a programming error to override the datatype
|
||||||
|
# and not overriding the read_* method. This is not checked!
|
||||||
b2 = Parameter('<b2>', datatype=BoolType(), default=True,
|
b2 = Parameter('<b2>', datatype=BoolType(), default=True,
|
||||||
poll=True, readonly=False, initwrite=True)
|
readonly=False, initwrite=True)
|
||||||
|
|
||||||
def write_a1(self, value):
|
def write_a1(self, value):
|
||||||
self._a1_written = value
|
self._a1_written = value
|
||||||
@ -170,30 +186,33 @@ def test_ModuleMagic():
|
|||||||
|
|
||||||
# check for inital updates working properly
|
# check for inital updates working properly
|
||||||
o1 = Newclass1('o1', logger, {'.description':''}, srv)
|
o1 = Newclass1('o1', logger, {'.description':''}, srv)
|
||||||
expectedBeforeStart = {'target': 0.0, 'status': (Drivable.Status.IDLE, ''),
|
expectedBeforeStart = {'target': '', 'status': (Drivable.Status.IDLE, ''),
|
||||||
'param1': False, 'param2': 1.0, 'a1': 0.0, 'a2': True, 'pollinterval': 5.0,
|
'param1': False, 'param2': 1.0, 'a1': 0.0, 'a2': True, 'pollinterval': 5.0,
|
||||||
'value': 'first'}
|
'value': 'first'}
|
||||||
assert updates.pop('o1') == expectedBeforeStart
|
assert updates.pop('o1') == expectedBeforeStart
|
||||||
o1.earlyInit()
|
o1.earlyInit()
|
||||||
event = threading.Event()
|
event = DummyMultiEvent()
|
||||||
o1.startModule(event.set)
|
o1.startModule(event)
|
||||||
event.wait()
|
event.wait()
|
||||||
# should contain polled values
|
# should contain polled values
|
||||||
expectedAfterStart = {'status': (Drivable.Status.IDLE, ''),
|
expectedAfterStart = {
|
||||||
'value': 'second'}
|
'status': (Drivable.Status.IDLE, 'ok'), 'value': 'second',
|
||||||
|
'param1': True, 'param2': 0.0, 'a1': True}
|
||||||
assert updates.pop('o1') == expectedAfterStart
|
assert updates.pop('o1') == expectedAfterStart
|
||||||
|
|
||||||
# check in addition if parameters are written
|
# check in addition if parameters are written
|
||||||
o2 = Newclass2('o2', logger, {'.description':'', 'a1': 2.7}, srv)
|
o2 = Newclass2('o2', logger, {'.description':'', 'a1': 2.7}, srv)
|
||||||
# no update for b2, as this has to be written
|
# no update for b2, as this has to be written
|
||||||
expectedBeforeStart['a1'] = 2.7
|
expectedBeforeStart['a1'] = 2.7
|
||||||
|
expectedBeforeStart['target'] = 0.0
|
||||||
assert updates.pop('o2') == expectedBeforeStart
|
assert updates.pop('o2') == expectedBeforeStart
|
||||||
o2.earlyInit()
|
o2.earlyInit()
|
||||||
event = threading.Event()
|
event = DummyMultiEvent()
|
||||||
o2.startModule(event.set)
|
o2.startModule(event)
|
||||||
event.wait()
|
event.wait()
|
||||||
# value has changed type, b2 and a1 are written
|
# value has changed type, b2 and a1 are written
|
||||||
expectedAfterStart.update(value=0, b2=True, a1=2.7)
|
expectedAfterStart.update(value=0, b2=True, a1=True)
|
||||||
|
# ramerk: a1=True: this behaviour is a Porgamming error
|
||||||
assert updates.pop('o2') == expectedAfterStart
|
assert updates.pop('o2') == expectedAfterStart
|
||||||
assert o2._a1_written == 2.7
|
assert o2._a1_written == 2.7
|
||||||
assert o2._b2_written is True
|
assert o2._b2_written is True
|
||||||
@ -210,13 +229,15 @@ def test_ModuleMagic():
|
|||||||
# check '$' in unit works properly
|
# check '$' in unit works properly
|
||||||
assert o2.parameters['a1'].datatype.unit == 'mm/s'
|
assert o2.parameters['a1'].datatype.unit == 'mm/s'
|
||||||
cfg = Newclass2.configurables
|
cfg = Newclass2.configurables
|
||||||
assert set(cfg.keys()) == {'export', 'group', 'description',
|
assert set(cfg.keys()) == {
|
||||||
|
'export', 'group', 'description', 'disable_value_range_check',
|
||||||
'meaning', 'visibility', 'implementation', 'interface_classes', 'target', 'stop',
|
'meaning', 'visibility', 'implementation', 'interface_classes', 'target', 'stop',
|
||||||
'status', 'param1', 'param2', 'cmd', 'a2', 'pollinterval', 'b2', 'cmd2', 'value',
|
'status', 'param1', 'param2', 'cmd', 'a2', 'pollinterval', 'slowinterval', 'b2',
|
||||||
'a1'}
|
'cmd2', 'value', 'a1'}
|
||||||
assert set(cfg['value'].keys()) == {'group', 'export', 'relative_resolution',
|
assert set(cfg['value'].keys()) == {
|
||||||
|
'group', 'export', 'relative_resolution',
|
||||||
'visibility', 'unit', 'default', 'datatype', 'fmtstr',
|
'visibility', 'unit', 'default', 'datatype', 'fmtstr',
|
||||||
'absolute_resolution', 'poll', 'max', 'min', 'readonly', 'constant',
|
'absolute_resolution', 'max', 'min', 'readonly', 'constant',
|
||||||
'description', 'needscfg'}
|
'description', 'needscfg'}
|
||||||
|
|
||||||
# check on the level of classes
|
# check on the level of classes
|
||||||
@ -459,3 +480,177 @@ def test_command_none():
|
|||||||
|
|
||||||
assert 'stop' in Mod('o', logger, {'description': ''}, srv).accessibles
|
assert 'stop' in Mod('o', logger, {'description': ''}, srv).accessibles
|
||||||
assert 'stop' not in Mod2('o', logger, {'description': ''}, srv).accessibles
|
assert 'stop' not in Mod2('o', logger, {'description': ''}, srv).accessibles
|
||||||
|
|
||||||
|
|
||||||
|
def test_bad_method():
|
||||||
|
class Mod0(Drivable): # pylint: disable=unused-variable
|
||||||
|
def write_target(self, value):
|
||||||
|
pass
|
||||||
|
|
||||||
|
with pytest.raises(ProgrammingError):
|
||||||
|
class Mod1(Drivable): # pylint: disable=unused-variable
|
||||||
|
def write_taget(self, value):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Mod2(Drivable): # pylint: disable=unused-variable
|
||||||
|
def read_value(self, value):
|
||||||
|
pass
|
||||||
|
|
||||||
|
with pytest.raises(ProgrammingError):
|
||||||
|
class Mod3(Drivable): # pylint: disable=unused-variable
|
||||||
|
def read_valu(self, value):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_generic_access():
|
||||||
|
class Mod(Module):
|
||||||
|
param = Parameter('handled param', StringType(), readonly=False)
|
||||||
|
unhandled = Parameter('unhandled param', StringType(), default='', readonly=False)
|
||||||
|
data = {'param': ''}
|
||||||
|
|
||||||
|
@ReadHandler(['param'])
|
||||||
|
def read_handler(self, pname):
|
||||||
|
value = self.data[pname]
|
||||||
|
setattr(self, pname, value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
@WriteHandler(['param'])
|
||||||
|
def write_handler(self, pname, value):
|
||||||
|
value = value.lower()
|
||||||
|
self.data[pname] = value
|
||||||
|
setattr(self, pname, value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
updates = {}
|
||||||
|
srv = ServerStub(updates)
|
||||||
|
|
||||||
|
obj = Mod('obj', logger, {'description': '', 'param': 'initial value'}, srv)
|
||||||
|
assert obj.param == 'initial value'
|
||||||
|
assert obj.write_param('Cheese') == 'cheese'
|
||||||
|
assert obj.write_unhandled('Cheese') == 'Cheese'
|
||||||
|
assert updates == {'obj': {'param': 'cheese', 'unhandled': 'Cheese'}}
|
||||||
|
updates.clear()
|
||||||
|
assert obj.write_param('Potato') == 'potato'
|
||||||
|
assert updates == {'obj': {'param': 'potato'}}
|
||||||
|
updates.clear()
|
||||||
|
assert obj.read_param() == 'potato'
|
||||||
|
assert obj.read_unhandled()
|
||||||
|
assert updates == {'obj': {'param': 'potato'}}
|
||||||
|
updates.clear()
|
||||||
|
assert updates == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_duplicate_handler_name():
|
||||||
|
with pytest.raises(ProgrammingError):
|
||||||
|
class Mod(Module): # pylint: disable=unused-variable
|
||||||
|
param = Parameter('handled param', StringType(), readonly=False)
|
||||||
|
|
||||||
|
@ReadHandler(['param'])
|
||||||
|
def handler(self, pname):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@WriteHandler(['param'])
|
||||||
|
def handler(self, pname, value): # pylint: disable=function-redefined
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_handler_overwrites_method():
|
||||||
|
with pytest.raises(RuntimeError):
|
||||||
|
class Mod1(Module): # pylint: disable=unused-variable
|
||||||
|
param = Parameter('handled param', StringType(), readonly=False)
|
||||||
|
|
||||||
|
@ReadHandler(['param'])
|
||||||
|
def read_handler(self, pname):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read_param(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
with pytest.raises(RuntimeError):
|
||||||
|
class Mod2(Module): # pylint: disable=unused-variable
|
||||||
|
param = Parameter('handled param', StringType(), readonly=False)
|
||||||
|
|
||||||
|
@WriteHandler(['param'])
|
||||||
|
def write_handler(self, pname, value):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def write_param(self, value):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_read_write():
|
||||||
|
class Mod(Module):
|
||||||
|
param = Parameter('test param', StringType(), readonly=False)
|
||||||
|
|
||||||
|
updates = {}
|
||||||
|
srv = ServerStub(updates)
|
||||||
|
|
||||||
|
obj = Mod('obj', logger, {'description': '', 'param': 'cheese'}, srv)
|
||||||
|
assert obj.param == 'cheese'
|
||||||
|
assert obj.read_param() == 'cheese'
|
||||||
|
assert updates == {'obj': {'param': 'cheese'}}
|
||||||
|
assert obj.write_param('egg') == 'egg'
|
||||||
|
assert obj.param == 'egg'
|
||||||
|
assert updates == {'obj': {'param': 'egg'}}
|
||||||
|
|
||||||
|
|
||||||
|
def test_incompatible_value_target():
|
||||||
|
class Mod1(Drivable):
|
||||||
|
value = Parameter('', FloatRange(0, 10), default=0)
|
||||||
|
target = Parameter('', FloatRange(0, 11), default=0)
|
||||||
|
|
||||||
|
class Mod2(Drivable):
|
||||||
|
value = Parameter('', FloatRange(), default=0)
|
||||||
|
target = Parameter('', StringType(), default='')
|
||||||
|
|
||||||
|
class Mod3(Drivable):
|
||||||
|
value = Parameter('', FloatRange(), default=0)
|
||||||
|
target = Parameter('', ScaledInteger(1, 0, 10), default=0)
|
||||||
|
|
||||||
|
srv = ServerStub({})
|
||||||
|
|
||||||
|
with pytest.raises(ConfigError):
|
||||||
|
obj = Mod1('obj', logger, {'description': ''}, srv) # pylint: disable=unused-variable
|
||||||
|
|
||||||
|
with pytest.raises(ProgrammingError):
|
||||||
|
obj = Mod2('obj', logger, {'description': ''}, srv)
|
||||||
|
|
||||||
|
obj = Mod3('obj', logger, {'description': ''}, srv)
|
||||||
|
|
||||||
|
|
||||||
|
def test_problematic_value_range():
|
||||||
|
class Mod(Drivable):
|
||||||
|
value = Parameter('', FloatRange(0, 10), default=0)
|
||||||
|
target = Parameter('', FloatRange(0, 10), default=0)
|
||||||
|
|
||||||
|
srv = ServerStub({})
|
||||||
|
|
||||||
|
obj = Mod('obj', logger, {'description': '', 'value.max': 10.1}, srv) # pylint: disable=unused-variable
|
||||||
|
|
||||||
|
with pytest.raises(ConfigError):
|
||||||
|
obj = Mod('obj', logger, {'description': ''}, srv)
|
||||||
|
|
||||||
|
class Mod2(Drivable):
|
||||||
|
value = Parameter('', FloatRange(), default=0)
|
||||||
|
target = Parameter('', FloatRange(), default=0)
|
||||||
|
|
||||||
|
obj = Mod2('obj', logger, {'description': ''}, srv)
|
||||||
|
obj = Mod2('obj', logger, {'description': '', 'target.min': 0, 'target.max': 10}, srv)
|
||||||
|
|
||||||
|
with pytest.raises(ConfigError):
|
||||||
|
obj = Mod('obj', logger, {
|
||||||
|
'value.min': 0, 'value.max': 10,
|
||||||
|
'target.min': 0, 'target.max': 10, 'description': ''}, srv)
|
||||||
|
|
||||||
|
obj = Mod('obj', logger, {'disable_value_range_check': True,
|
||||||
|
'value.min': 0, 'value.max': 10,
|
||||||
|
'target.min': 0, 'target.max': 10, 'description': ''}, srv)
|
||||||
|
|
||||||
|
generalConfig.defaults['disable_value_range_check'] = True
|
||||||
|
|
||||||
|
class Mod4(Drivable):
|
||||||
|
value = Parameter('', FloatRange(0, 10), default=0)
|
||||||
|
target = Parameter('', FloatRange(0, 10), default=0)
|
||||||
|
obj = Mod4('obj', logger, {
|
||||||
|
'value.min': 0, 'value.max': 10,
|
||||||
|
'target.min': 0, 'target.max': 10, 'description': ''}, srv)
|
||||||
|
60
test/test_multievent.py
Normal file
60
test/test_multievent.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# *****************************************************************************
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Module authors:
|
||||||
|
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
|
||||||
|
import time
|
||||||
|
from secop.lib.multievent import MultiEvent
|
||||||
|
|
||||||
|
|
||||||
|
def test_without_timeout():
|
||||||
|
m = MultiEvent()
|
||||||
|
s1 = m.get_trigger(name='s1')
|
||||||
|
s2 = m.get_trigger(name='s2')
|
||||||
|
assert not m.wait(0)
|
||||||
|
assert m.deadline() is None
|
||||||
|
assert m.waiting_for() == {'s1', 's2'}
|
||||||
|
s2()
|
||||||
|
assert m.waiting_for() == {'s1'}
|
||||||
|
assert not m.wait(0)
|
||||||
|
s1()
|
||||||
|
assert not m.waiting_for()
|
||||||
|
assert m.wait(0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_with_timeout(monkeypatch):
|
||||||
|
current_time = 1000
|
||||||
|
monkeypatch.setattr(time, 'monotonic', lambda: current_time)
|
||||||
|
m = MultiEvent()
|
||||||
|
assert m.deadline() == 0
|
||||||
|
m.name = 's1'
|
||||||
|
s1 = m.get_trigger(10)
|
||||||
|
assert m.deadline() == 1010
|
||||||
|
m.name = 's2'
|
||||||
|
s2 = m.get_trigger(20)
|
||||||
|
assert m.deadline() == 1020
|
||||||
|
current_time += 21
|
||||||
|
assert not m.wait(0)
|
||||||
|
assert m.waiting_for() == {'s1', 's2'}
|
||||||
|
s1()
|
||||||
|
assert m.waiting_for() == {'s2'}
|
||||||
|
s2()
|
||||||
|
assert not m.waiting_for()
|
||||||
|
assert m.wait(0)
|
@ -21,18 +21,20 @@
|
|||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
"""test poller."""
|
"""test poller."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
from collections import OrderedDict
|
import logging
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from secop.modules import Drivable
|
from secop.core import Module, Parameter, FloatRange, Readable, ReadHandler, nopoll
|
||||||
from secop.poller import DYNAMIC, REGULAR, SLOW, Poller
|
from secop.lib.multievent import MultiEvent
|
||||||
|
|
||||||
Status = Drivable.Status
|
|
||||||
|
|
||||||
class Time:
|
class Time:
|
||||||
STARTTIME = 1000 # artificial time zero
|
STARTTIME = 1000 # artificial time zero
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.reset()
|
self.reset()
|
||||||
self.finish = float('inf')
|
self.finish = float('inf')
|
||||||
@ -61,187 +63,103 @@ class Time:
|
|||||||
self.seconds += seconds
|
self.seconds += seconds
|
||||||
self.busytime += seconds
|
self.busytime += seconds
|
||||||
|
|
||||||
|
|
||||||
artime = Time() # artificial test time
|
artime = Time() # artificial test time
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def patch_time(monkeypatch):
|
class Event(threading.Event):
|
||||||
monkeypatch.setattr(time, 'time', artime.time)
|
def wait(self, timeout=None):
|
||||||
|
artime.sleep(max(0, timeout))
|
||||||
|
|
||||||
|
|
||||||
class Event:
|
class DispatcherStub:
|
||||||
def __init__(self):
|
maxcycles = 10
|
||||||
self.flag = False
|
|
||||||
|
|
||||||
def wait(self, timeout):
|
def announce_update(self, modulename, pname, pobj):
|
||||||
artime.sleep(max(0,timeout))
|
|
||||||
|
|
||||||
def set(self):
|
|
||||||
self.flag = True
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
self.flag = False
|
|
||||||
|
|
||||||
def is_set(self):
|
|
||||||
return self.flag
|
|
||||||
|
|
||||||
|
|
||||||
class Parameter:
|
|
||||||
def __init__(self, name, readonly, poll, polltype, interval):
|
|
||||||
self.poll = poll
|
|
||||||
self.polltype = polltype # used for check only
|
|
||||||
self.export = name
|
|
||||||
self.readonly = readonly
|
|
||||||
self.interval = interval
|
|
||||||
self.timestamp = 0
|
|
||||||
self.handler = None
|
|
||||||
self.reset()
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
self.cnt = 0
|
|
||||||
self.span = 0
|
|
||||||
self.maxspan = 0
|
|
||||||
|
|
||||||
def rfunc(self):
|
|
||||||
artime.busy(artime.commtime)
|
|
||||||
now = artime.time()
|
now = artime.time()
|
||||||
self.span = now - self.timestamp
|
if hasattr(pobj, 'stat'):
|
||||||
self.maxspan = max(self.maxspan, self.span)
|
pobj.stat.append(now)
|
||||||
self.timestamp = now
|
|
||||||
self.cnt += 1
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'Parameter(%s)' % ", ".join("%s=%r" % item for item in self.__dict__.items())
|
|
||||||
|
|
||||||
|
|
||||||
class Module:
|
|
||||||
properties = {}
|
|
||||||
pollerClass = Poller
|
|
||||||
iodev = 'common_iodev'
|
|
||||||
def __init__(self, name, pollinterval=5, fastfactor=0.25, slowfactor=4, busy=False,
|
|
||||||
counts=(), auto=None):
|
|
||||||
'''create a dummy module
|
|
||||||
|
|
||||||
nauto, ndynamic, nregular, nslow are the number of parameters of each polltype
|
|
||||||
'''
|
|
||||||
self.pollinterval = pollinterval
|
|
||||||
self.fast_pollfactor = fastfactor
|
|
||||||
self.slow_pollfactor = slowfactor
|
|
||||||
self.parameters = OrderedDict()
|
|
||||||
self.name = name
|
|
||||||
self.is_busy = busy
|
|
||||||
if auto is not None:
|
|
||||||
self.pvalue = self.addPar('value', True, auto or DYNAMIC, DYNAMIC)
|
|
||||||
# readonly = False should not matter:
|
|
||||||
self.pstatus = self.addPar('status', False, auto or DYNAMIC, DYNAMIC)
|
|
||||||
self.pregular = self.addPar('regular', True, auto or REGULAR, REGULAR)
|
|
||||||
self.pslow = self.addPar('slow', False, auto or SLOW, SLOW)
|
|
||||||
self.addPar('notpolled', True, False, 0)
|
|
||||||
self.counts = 'auto'
|
|
||||||
else:
|
else:
|
||||||
ndynamic, nregular, nslow = counts
|
pobj.stat = [now]
|
||||||
for i in range(ndynamic):
|
self.maxcycles -= 1
|
||||||
self.addPar('%s:d%d' % (name, i), True, DYNAMIC, DYNAMIC)
|
if self.maxcycles <= 0:
|
||||||
for i in range(nregular):
|
self.finish_event.set()
|
||||||
self.addPar('%s:r%d' % (name, i), True, REGULAR, REGULAR)
|
sys.exit() # stop thread
|
||||||
for i in range(nslow):
|
|
||||||
self.addPar('%s:s%d' % (name, i), False, SLOW, SLOW)
|
|
||||||
self.counts = counts
|
|
||||||
|
|
||||||
def addPar(self, name, readonly, poll, expected_polltype):
|
|
||||||
# self.count[polltype] += 1
|
|
||||||
expected_interval = self.pollinterval
|
|
||||||
if expected_polltype == SLOW:
|
|
||||||
expected_interval *= self.slow_pollfactor
|
|
||||||
elif expected_polltype == DYNAMIC and self.is_busy:
|
|
||||||
expected_interval *= self.fast_pollfactor
|
|
||||||
pobj = Parameter(name, readonly, poll, expected_polltype, expected_interval)
|
|
||||||
setattr(self, 'read_' + pobj.export, pobj.rfunc)
|
|
||||||
self.parameters[pobj.export] = pobj
|
|
||||||
return pobj
|
|
||||||
|
|
||||||
def isBusy(self):
|
class ServerStub:
|
||||||
return self.is_busy
|
def __init__(self):
|
||||||
|
self.dispatcher = DispatcherStub()
|
||||||
|
|
||||||
def pollOneParam(self, pname):
|
|
||||||
getattr(self, 'read_' + pname)()
|
|
||||||
|
|
||||||
def writeInitParams(self):
|
class Base(Module):
|
||||||
pass
|
def __init__(self):
|
||||||
|
srv = ServerStub()
|
||||||
|
super().__init__('mod', logging.getLogger('dummy'), dict(description=''), srv)
|
||||||
|
self.dispatcher = srv.dispatcher
|
||||||
|
self.nextPollEvent = Event()
|
||||||
|
|
||||||
def __repr__(self):
|
def run(self, maxcycles):
|
||||||
rdict = self.__dict__.copy()
|
self.dispatcher.maxcycles = maxcycles
|
||||||
rdict.pop('parameters')
|
self.dispatcher.finish_event = threading.Event()
|
||||||
return 'Module(%r, counts=%r, f=%r, pollinterval=%g, is_busy=%r)' % (self.name,
|
self.startModule(MultiEvent())
|
||||||
self.counts, (self.fast_pollfactor, self.slow_pollfactor, 1),
|
self.dispatcher.finish_event.wait(1)
|
||||||
self.pollinterval, self.is_busy)
|
|
||||||
|
|
||||||
module_list = [
|
|
||||||
[Module('x', 3.0, 0.125, 10, False, auto=True),
|
|
||||||
Module('y', 3.0, 0.125, 10, False, auto=False)],
|
|
||||||
[Module('a', 1.0, 0.25, 4, True, (5, 5, 10)),
|
|
||||||
Module('b', 2.0, 0.25, 4, True, (5, 5, 50))],
|
|
||||||
[Module('c', 1.0, 0.25, 4, False, (5, 0, 0))],
|
|
||||||
[Module('d', 1.0, 0.25, 4, True, (0, 9, 0))],
|
|
||||||
[Module('e', 1.0, 0.25, 4, True, (0, 0, 9))],
|
|
||||||
[Module('f', 1.0, 0.25, 4, True, (0, 0, 0))],
|
|
||||||
]
|
|
||||||
@pytest.mark.parametrize('modules', module_list)
|
|
||||||
def test_Poller(modules):
|
|
||||||
# check for proper timing
|
|
||||||
|
|
||||||
for overloaded in False, True:
|
class Mod1(Base, Readable):
|
||||||
artime.reset()
|
param1 = Parameter('', FloatRange())
|
||||||
count = {DYNAMIC: 0, REGULAR: 0, SLOW: 0}
|
param2 = Parameter('', FloatRange())
|
||||||
maxspan = {DYNAMIC: 0, REGULAR: 0, SLOW: 0}
|
param3 = Parameter('', FloatRange())
|
||||||
pollTable = dict()
|
param4 = Parameter('', FloatRange())
|
||||||
for module in modules:
|
|
||||||
Poller.add_to_table(pollTable, module)
|
|
||||||
for pobj in module.parameters.values():
|
|
||||||
if pobj.poll:
|
|
||||||
maxspan[pobj.polltype] = max(maxspan[pobj.polltype], pobj.interval)
|
|
||||||
count[pobj.polltype] += 1
|
|
||||||
pobj.reset()
|
|
||||||
assert len(pollTable) == 1
|
|
||||||
poller = pollTable[(Poller, 'common_iodev')]
|
|
||||||
artime.stop = poller.stop
|
|
||||||
poller._event = Event() # patch Event.wait
|
|
||||||
|
|
||||||
assert (sum(count.values()) > 0) == bool(poller)
|
@ReadHandler(('param1', 'param2', 'param3'))
|
||||||
|
def read_param(self, name):
|
||||||
|
artime.sleep(1.0)
|
||||||
|
return 0
|
||||||
|
|
||||||
def started_callback(modules=modules):
|
@nopoll
|
||||||
for module in modules:
|
def read_param4(self):
|
||||||
for pobj in module.parameters.values():
|
return 0
|
||||||
assert pobj.cnt == bool(pobj.poll) # all parameters have to be polled once
|
|
||||||
pobj.reset() # set maxspan and cnt to 0
|
|
||||||
|
|
||||||
if overloaded:
|
def read_status(self):
|
||||||
# overloaded scenario
|
artime.sleep(1.0)
|
||||||
artime.commtime = 1.0
|
return 0
|
||||||
ncycles = 10
|
|
||||||
if count[SLOW] > 0:
|
def read_value(self):
|
||||||
cycletime = (count[REGULAR] + 1) * count[SLOW] * 2
|
artime.sleep(1.0)
|
||||||
else:
|
return 0
|
||||||
cycletime = max(count[REGULAR], count[DYNAMIC]) * 2
|
|
||||||
artime.reset(cycletime * ncycles * 1.01) # poller will quit given time
|
|
||||||
poller.run(started_callback)
|
@pytest.mark.parametrize(
|
||||||
total = artime.time() - artime.STARTTIME
|
'ncycles, pollinterval, slowinterval, mspan, pspan',
|
||||||
for module in modules:
|
[ # normal case: 5+-1 15+-1
|
||||||
for pobj in module.parameters.values():
|
( 60, 5, 15, (4, 6), (14, 16)),
|
||||||
if pobj.poll:
|
# pollinterval faster then reading: mspan max 3 s (polls of value, status and ONE other parameter)
|
||||||
# average_span = total / (pobj.cnt + 1)
|
( 60, 1, 5, (1, 3), (5, 16)),
|
||||||
assert total / (pobj.cnt + 1) <= max(cycletime, pobj.interval * 1.1)
|
])
|
||||||
else:
|
def test_poll(ncycles, pollinterval, slowinterval, mspan, pspan, monkeypatch):
|
||||||
# normal scenario
|
monkeypatch.setattr(time, 'time', artime.time)
|
||||||
artime.commtime = 0.001
|
artime.reset()
|
||||||
artime.reset(max(maxspan.values()) * 5) # poller will quit given time
|
m = Mod1()
|
||||||
poller.run(started_callback)
|
m.pollinterval = pollinterval
|
||||||
total = artime.time() - artime.STARTTIME
|
m.slowInterval = slowinterval
|
||||||
for module in modules:
|
m.run(ncycles)
|
||||||
for pobj in module.parameters.values():
|
assert not hasattr(m.parameters['param4'], 'stat')
|
||||||
if pobj.poll:
|
for pname in ['value', 'status']:
|
||||||
assert pobj.cnt > 0
|
pobj = m.parameters[pname]
|
||||||
assert pobj.maxspan <= maxspan[pobj.polltype] * 1.1
|
lowcnt = 0
|
||||||
assert (pobj.cnt + 1) * pobj.interval >= total * 0.99
|
for t1, t2 in zip(pobj.stat[1:], pobj.stat[2:-1]):
|
||||||
assert abs(pobj.span - pobj.interval) < 0.01
|
if t2 - t1 < mspan[0]:
|
||||||
pobj.reset()
|
print(t2 - t1)
|
||||||
|
lowcnt += 1
|
||||||
|
assert t2 - t1 <= mspan[1]
|
||||||
|
assert lowcnt <= 1
|
||||||
|
for pname in ['param1', 'param2', 'param3']:
|
||||||
|
pobj = m.parameters[pname]
|
||||||
|
lowcnt = 0
|
||||||
|
for t1, t2 in zip(pobj.stat[1:], pobj.stat[2:-1]):
|
||||||
|
if t2 - t1 < pspan[0]:
|
||||||
|
print(pname, t2 - t1)
|
||||||
|
lowcnt += 1
|
||||||
|
assert t2 - t1 <= pspan[1]
|
||||||
|
assert lowcnt <= 1
|
||||||
|
@ -26,6 +26,7 @@ import pytest
|
|||||||
from secop.datatypes import FloatRange, IntRange, StringType, ValueType
|
from secop.datatypes import FloatRange, IntRange, StringType, ValueType
|
||||||
from secop.errors import BadValueError, ConfigError, ProgrammingError
|
from secop.errors import BadValueError, ConfigError, ProgrammingError
|
||||||
from secop.properties import HasProperties, Property
|
from secop.properties import HasProperties, Property
|
||||||
|
from secop.core import Parameter
|
||||||
|
|
||||||
|
|
||||||
def Prop(*args, name=None, **kwds):
|
def Prop(*args, name=None, **kwds):
|
||||||
@ -38,10 +39,10 @@ V_test_Property = [
|
|||||||
[Prop(StringType(), 'default', extname='extname', mandatory=False),
|
[Prop(StringType(), 'default', extname='extname', mandatory=False),
|
||||||
dict(default='default', extname='extname', export=True, mandatory=False)
|
dict(default='default', extname='extname', export=True, mandatory=False)
|
||||||
],
|
],
|
||||||
[Prop(IntRange(), '42', export=True, name='custom', mandatory=True),
|
[Prop(IntRange(), 42, export=True, name='custom', mandatory=True),
|
||||||
dict(default=42, extname='_custom', export=True, mandatory=True),
|
dict(default=42, extname='_custom', export=True, mandatory=True),
|
||||||
],
|
],
|
||||||
[Prop(IntRange(), '42', export=True, name='name'),
|
[Prop(IntRange(), 42, export=True, name='name'),
|
||||||
dict(default=42, extname='_name', export=True, mandatory=False)
|
dict(default=42, extname='_name', export=True, mandatory=False)
|
||||||
],
|
],
|
||||||
[Prop(IntRange(), 42, '_extname', mandatory=True),
|
[Prop(IntRange(), 42, '_extname', mandatory=True),
|
||||||
@ -85,12 +86,12 @@ def test_Property_basic():
|
|||||||
Property('')
|
Property('')
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Property('', 1)
|
Property('', 1)
|
||||||
Property('', IntRange(), '42', 'extname', False, False)
|
Property('', IntRange(), 42, 'extname', False, False)
|
||||||
|
|
||||||
|
|
||||||
def test_Properties():
|
def test_Properties():
|
||||||
class Cls(HasProperties):
|
class Cls(HasProperties):
|
||||||
aa = Property('', IntRange(0, 99), '42', export=True)
|
aa = Property('', IntRange(0, 99), 42, export=True)
|
||||||
bb = Property('', IntRange(), 0, export=False)
|
bb = Property('', IntRange(), 0, export=False)
|
||||||
|
|
||||||
assert Cls.aa.default == 42
|
assert Cls.aa.default == 42
|
||||||
@ -149,17 +150,25 @@ def test_Property_override():
|
|||||||
assert o2.a == 3
|
assert o2.a == 3
|
||||||
|
|
||||||
with pytest.raises(ProgrammingError) as e:
|
with pytest.raises(ProgrammingError) as e:
|
||||||
class cx(c): # pylint: disable=unused-variable
|
class cx(c): # pylint: disable=unused-variable
|
||||||
def a(self):
|
def a(self):
|
||||||
pass
|
pass
|
||||||
assert 'collides with' in str(e.value)
|
assert 'collides with' in str(e.value)
|
||||||
|
|
||||||
with pytest.raises(ProgrammingError) as e:
|
with pytest.raises(ProgrammingError) as e:
|
||||||
class cz(c): # pylint: disable=unused-variable
|
class cy(c): # pylint: disable=unused-variable
|
||||||
a = 's'
|
a = 's'
|
||||||
|
|
||||||
assert 'can not set' in str(e.value)
|
assert 'can not set' in str(e.value)
|
||||||
|
|
||||||
|
with pytest.raises(ProgrammingError) as e:
|
||||||
|
class cz(c): # pylint: disable=unused-variable
|
||||||
|
a = 's'
|
||||||
|
|
||||||
|
class cp(c): # pylint: disable=unused-variable
|
||||||
|
# overriding a Property with a Parameter is allowed
|
||||||
|
a = Parameter('x', IntRange())
|
||||||
|
|
||||||
|
|
||||||
def test_Properties_mro():
|
def test_Properties_mro():
|
||||||
class Base(HasProperties):
|
class Base(HasProperties):
|
||||||
|
148
test/test_statemachine.py
Normal file
148
test/test_statemachine.py
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# *****************************************************************************
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Module authors:
|
||||||
|
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
|
||||||
|
|
||||||
|
from secop.lib.statemachine import StateMachine, Stop, Retry
|
||||||
|
|
||||||
|
|
||||||
|
def rise(state):
|
||||||
|
state.step += 1
|
||||||
|
print('rise', state.step)
|
||||||
|
if state.init:
|
||||||
|
state.status = 'rise'
|
||||||
|
state.level += 1
|
||||||
|
if state.level > 3:
|
||||||
|
return turn
|
||||||
|
return Retry()
|
||||||
|
|
||||||
|
|
||||||
|
def turn(state):
|
||||||
|
state.step += 1
|
||||||
|
if state.init:
|
||||||
|
state.status = 'turn'
|
||||||
|
state.direction += 1
|
||||||
|
if state.direction > 3:
|
||||||
|
return fall
|
||||||
|
return Retry()
|
||||||
|
|
||||||
|
|
||||||
|
def fall(state):
|
||||||
|
state.step += 1
|
||||||
|
if state.init:
|
||||||
|
state.status = 'fall'
|
||||||
|
state.level -= 1
|
||||||
|
if state.level < 0:
|
||||||
|
raise ValueError('crash')
|
||||||
|
return Retry(0) # retry until crash!
|
||||||
|
|
||||||
|
|
||||||
|
def error_handler(state):
|
||||||
|
state.last_error_name = type(state.last_error).__name__
|
||||||
|
|
||||||
|
|
||||||
|
class LoggerStub:
|
||||||
|
def debug(self, fmt, *args):
|
||||||
|
print(fmt % args)
|
||||||
|
info = warning = exception = error = debug
|
||||||
|
handlers = []
|
||||||
|
|
||||||
|
|
||||||
|
class DummyThread:
|
||||||
|
def is_alive(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def test_fun():
|
||||||
|
s = StateMachine(step=0, status='', threaded=False, logger=LoggerStub())
|
||||||
|
assert s.step == 0
|
||||||
|
assert s.status == ''
|
||||||
|
s.cycle() # do nothing
|
||||||
|
assert s.step == 0
|
||||||
|
s.start(rise, level=0, direction=0)
|
||||||
|
s.cycle()
|
||||||
|
for i in range(1, 4):
|
||||||
|
assert s.status == 'rise'
|
||||||
|
assert s.step == i
|
||||||
|
assert s.level == i
|
||||||
|
assert s.direction == 0
|
||||||
|
s.cycle()
|
||||||
|
for i in range(5, 8):
|
||||||
|
assert s.status == 'turn'
|
||||||
|
assert s.step == i
|
||||||
|
assert s.level == 4
|
||||||
|
assert s.direction == i - 4
|
||||||
|
s.cycle()
|
||||||
|
s.cycle() # -> crash
|
||||||
|
assert isinstance(s.last_error, ValueError)
|
||||||
|
assert str(s.last_error) == 'crash'
|
||||||
|
assert s.state is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_max_chain():
|
||||||
|
s = StateMachine(step=0, status='', threaded=False, logger=LoggerStub())
|
||||||
|
s.start(fall, level=999+1, direction=0)
|
||||||
|
s.cycle()
|
||||||
|
assert isinstance(s.last_error, RuntimeError)
|
||||||
|
assert s.state is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_stop():
|
||||||
|
s = StateMachine(step=0, status='', threaded=False, logger=LoggerStub())
|
||||||
|
s.start(rise, level=0, direction=0)
|
||||||
|
for _ in range(1, 3):
|
||||||
|
s.cycle()
|
||||||
|
s.stop()
|
||||||
|
s.cycle()
|
||||||
|
assert isinstance(s.last_error, Stop)
|
||||||
|
assert s.state is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_std_error_handling():
|
||||||
|
s = StateMachine(step=0, status='', threaded=False, logger=LoggerStub())
|
||||||
|
s.start(rise, level=0, direction=0)
|
||||||
|
s.cycle()
|
||||||
|
s.level = None # -> TypeError on next step
|
||||||
|
s.cycle()
|
||||||
|
assert s.state is None # default error handler: stop machine
|
||||||
|
assert isinstance(s.last_error, TypeError)
|
||||||
|
assert not hasattr(s, 'last_error_name')
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_error_handling():
|
||||||
|
s = StateMachine(step=0, status='', cleanup=error_handler, threaded=False, logger=LoggerStub())
|
||||||
|
s.start(rise, level=0, direction=0)
|
||||||
|
s.cycle()
|
||||||
|
s.level = None
|
||||||
|
s.cycle()
|
||||||
|
assert s.state is None
|
||||||
|
assert s.last_error_name == 'TypeError'
|
||||||
|
assert isinstance(s.last_error, TypeError)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cleanup_on_restart():
|
||||||
|
s = StateMachine(step=0, status='', threaded=False, logger=LoggerStub())
|
||||||
|
s.start(rise, level=0, direction=0)
|
||||||
|
s.cycle()
|
||||||
|
s.start(turn)
|
||||||
|
s.cycle()
|
||||||
|
assert s.state is turn
|
||||||
|
assert s.last_error is None
|
Loading…
x
Reference in New Issue
Block a user