diff --git a/ioc/tests/support/StreamDevice/simple/default.nix b/ioc/tests/support/StreamDevice/simple/default.nix index dc2ddcc3..45eeaf6e 100644 --- a/ioc/tests/support/StreamDevice/simple/default.nix +++ b/ioc/tests/support/StreamDevice/simple/default.nix @@ -2,10 +2,6 @@ inherit (pkgs) epnixLib; inherit (pkgs.stdenv.hostPlatform) system; - mock_server = pkgs.poetry2nix.mkPoetryApplication { - projectDir = ./mock-server; - }; - result = epnixLib.evalEpnixModules { nixpkgsConfig.system = system; epnixConfig.imports = [./top/epnix.nix]; @@ -19,70 +15,60 @@ in name = "support-StreamDevice-simple"; meta.maintainers = with epnixLib.maintainers; [minijackson]; - nodes.machine = let - listenAddr = "127.0.0.1:1234"; - in - {lib, ...}: { - environment.systemPackages = [pkgs.epnix.epics-base]; + nodes.machine = {lib, ...}: { + environment.systemPackages = [pkgs.epnix.epics-base]; - systemd.sockets.mock-server = { + systemd.services = { + "psu-simulator" = { wantedBy = ["multi-user.target"]; - listenStreams = [listenAddr]; - socketConfig.Accept = true; - }; - - systemd.services = { - "mock-server@".serviceConfig = { - ExecStart = "${mock_server}/bin/mock_server"; - StandardInput = "socket"; - StandardError = "journal"; + serviceConfig = { + ExecStart = lib.getExe pkgs.epnix.psu-simulator; }; - - ioc = lib.mkMerge [ - service - {environment.STREAM_PS1 = listenAddr;} - ]; }; + + ioc = lib.mkMerge [ + service + {environment.STREAM_PS1 = "localhost:9999";} + ]; }; + }; testScript = '' machine.wait_for_unit("default.target") machine.wait_for_unit("ioc.service") - with subtest("getting fixed values"): - machine.wait_until_succeeds("caget -t FLOAT:IN | grep -qxF '42.1234'") - machine.wait_until_succeeds("caget -t FLOAT_WITH_PREFIX:IN | grep -qxF '69.1337'") - machine.wait_until_succeeds("caget -t ENUM:IN | grep -qxF '1'") + def assert_caget(pv: str, expected: str) -> None: + machine.wait_until_succeeds(f"caget -t '{pv}' | grep -qxF '{expected}'", timeout=10) - with subtest("setting values"): - machine.wait_until_succeeds("caget -t VARFLOAT:IN | grep -qxF '0'") - - # Caput can simply not go through - def put_check_varfloat(_) -> bool: - machine.succeed("caput VARFLOAT:OUT 123.456") - status, _output = machine.execute("caget -t VARFLOAT:IN | grep -qxF '123.456'") + def assert_caput(pv: str, value: str) -> None: + def do_caput(_) -> bool: + machine.succeed(f"caput '{pv}' '{value}'") + status, _output = machine.execute(f"caget -t '{pv}' | grep -qxF '{value}'") return status == 0 - retry(put_check_varfloat) + retry(do_caput, timeout=10) - with subtest("calc integration"): - machine.wait_until_succeeds("caget -t SCALC:IN | grep -qxF '10A'") + with subtest("getting initial values"): + assert_caget("UCmd", "0") + assert_caget("URb", "0") + assert_caget("PowerCmd", "ON") + assert_caget("PowerRb", "ON") - def put_check_scalc(_) -> bool: - machine.succeed("caput SCALC:OUT.A 2") - status, _output = machine.execute("caget -t SCALC:IN | grep -qxF '14A'") - return status == 0 - - retry(put_check_scalc) + with subtest("setting values"): + assert_caput("UCmd", "10") + assert_caget("URb", "10") - machine.wait_until_succeeds("caget -t SCALC:OUT.SVAL | grep -qxF 'sent'") + with subtest("calc integration"): + assert_caput("2UCmd.A", "42") + assert_caget("2UCmd.SVAL", "184") + assert_caget("URb", "184") with subtest("regular expressions"): - machine.wait_until_succeeds("caget -t REGEX_TITLE:IN | grep -qxF 'Hello, World!'") - machine.wait_until_succeeds("caget -t REGEX_SUB:IN | grep -qxF 'abcXcXcabc'") + assert_caget("VersionNum", "0.1.0") + assert_caget("VersionCat", "010") ''; passthru = { - inherit mock_server ioc; + inherit ioc; }; } diff --git a/ioc/tests/support/StreamDevice/simple/mock-server/.gitignore b/ioc/tests/support/StreamDevice/simple/mock-server/.gitignore deleted file mode 100644 index 13189553..00000000 --- a/ioc/tests/support/StreamDevice/simple/mock-server/.gitignore +++ /dev/null @@ -1,158 +0,0 @@ -# Created by https://www.toptal.com/developers/gitignore/api/python -# Edit at https://www.toptal.com/developers/gitignore?templates=python - -### Python ### -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - -# End of https://www.toptal.com/developers/gitignore/api/python diff --git a/ioc/tests/support/StreamDevice/simple/mock-server/mock_server/__init__.py b/ioc/tests/support/StreamDevice/simple/mock-server/mock_server/__init__.py deleted file mode 100644 index 92fb10c6..00000000 --- a/ioc/tests/support/StreamDevice/simple/mock-server/mock_server/__init__.py +++ /dev/null @@ -1,66 +0,0 @@ -"""A simple server mocking an ASCII communication.""" - -import sys - -__version__ = "0.1.0" - - -def log(*args: str, **kwargs: str) -> None: - """Print a message to stderr.""" - print(*args, file=sys.stderr, **kwargs) - - -def send(*args: str, **kwargs: str) -> None: - """Send a message.""" - print(*args, end="\r\n", flush=True, **kwargs) - - -def main() -> None: - """Start the mock server.""" - log("received connection") - - varfloat = 0.0 - scalc = "" - - while True: - data = sys.stdin.readline().strip() - - if not data: - break - - log("received command:", data) - - # TODO(minijackson): change that with a command-line parsing tool? - - if data == "FLOAT": - send("42.1234") - elif data == "FLOAT_WITH_PREFIX": - send("VALUE: 69.1337") - elif data == "ENUM": - send("TWO") - elif data.startswith("SET_VARFLOAT "): - varfloat = float(data.split(" ", maxsplit=1)[1]) - elif data == "GET_VARFLOAT": - send(str(varfloat)) - elif data == "REGEX_TITLE": - send( - """ - - - Hello, World! - - -

Hello, World!

- - -""", - ) - elif data == "REGEX_SUB": - send("abcabcabcabc") - elif data.startswith("SET_SCALC "): - send("sent") - scalc = data.split(" ", maxsplit=1)[1] - elif data == "GET_SCALC": - send(scalc) - else: - log("unknown command") diff --git a/ioc/tests/support/StreamDevice/simple/mock-server/poetry.lock b/ioc/tests/support/StreamDevice/simple/mock-server/poetry.lock deleted file mode 100644 index d9af7385..00000000 --- a/ioc/tests/support/StreamDevice/simple/mock-server/poetry.lock +++ /dev/null @@ -1,8 +0,0 @@ -package = [] - -[metadata] -lock-version = "1.1" -python-versions = "*" -content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8" - -[metadata.files] diff --git a/ioc/tests/support/StreamDevice/simple/mock-server/pyproject.toml b/ioc/tests/support/StreamDevice/simple/mock-server/pyproject.toml deleted file mode 100644 index d248b538..00000000 --- a/ioc/tests/support/StreamDevice/simple/mock-server/pyproject.toml +++ /dev/null @@ -1,15 +0,0 @@ -[tool.poetry] -name = "mock-server" -version = "0.1.0" -description = "Mock Server for the simple StreamDevice support test" -authors = ["RĂ©mi NICOLE "] - -[tool.poetry.scripts] -mock_server = "mock_server:main" - -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" - -[tool.ruff] -select = ["ALL"] diff --git a/ioc/tests/support/StreamDevice/simple/top/iocBoot/iocsimple/st.cmd b/ioc/tests/support/StreamDevice/simple/top/iocBoot/iocsimple/st.cmd index e170880f..d3ca8c76 100755 --- a/ioc/tests/support/StreamDevice/simple/top/iocBoot/iocsimple/st.cmd +++ b/ioc/tests/support/StreamDevice/simple/top/iocBoot/iocsimple/st.cmd @@ -13,6 +13,6 @@ epicsEnvSet("STREAM_PROTOCOL_PATH", ".:${TOP}/db") drvAsynIPPortConfigure("PS1", "${STREAM_PS1}") ## Load record instances -dbLoadRecords("../../db/simple.db", "PORT=PS1") +dbLoadRecords("../../db/simple.db", "PORT=PS1,P=") iocInit() diff --git a/ioc/tests/support/StreamDevice/simple/top/simpleApp/Db/simple.db b/ioc/tests/support/StreamDevice/simple/top/simpleApp/Db/simple.db index e68e5ebc..5134b54e 100644 --- a/ioc/tests/support/StreamDevice/simple/top/simpleApp/Db/simple.db +++ b/ioc/tests/support/StreamDevice/simple/top/simpleApp/Db/simple.db @@ -1,81 +1,82 @@ -# Fixed values +# Normal flow -record(ai, "FLOAT:IN") { - field(DESC, "A fixed float") +record(ao, "${P}UCmd") { + field(DESC, "Voltage command") field(DTYP, "stream") - field(INP, "@simple.proto getFloat $(PORT)") - field(PINI, "YES") - field(SCAN, "5 second") + field(OUT, "@simple.proto setUCmd $(PORT)") + field(EGU, "A") + + field(FLNK, "${P}URb") } -record(ai, "FLOAT_WITH_PREFIX:IN") { - field(DESC, "A fixed float with a returned prefix") +record(ai, "${P}URb") { + field(DESC, "Voltage read-back") field(DTYP, "stream") - field(INP, "@simple.proto getFloatWithPrefix $(PORT)") + field(INP, "@simple.proto getURb $(PORT)") + field(EGU, "A") field(PINI, "YES") - field(SCAN, "5 second") } -record(longin, "ENUM:IN") { - field(DESC, "An fixed enum parsed as integer") +record(ai, "${P}UMes") { + field(DESC, "Measure of voltage intensity") field(DTYP, "stream") - field(INP, "@simple.proto getEnum $(PORT)") + field(INP, "@simple.proto getUMes $(PORT)") + field(EGU, "A") field(PINI, "YES") field(SCAN, "5 second") } -# Variable values - -record(ai, "VARFLOAT:IN") { - field(DESC, "A variable float") +record(bo, "${P}PowerCmd") { + field(DESC, "Command for enabling output") field(DTYP, "stream") - field(INP, "@simple.proto getVarFloat $(PORT)") - field(PINI, "YES") - field(SCAN, "5 second") + field(OUT, "@simple.proto setPowerCmd $(PORT)") + field(ZNAM, "OFF") + field(ONAM, "ON") + + field(FLNK, "${P}PowerRb") } -record(ao, "VARFLOAT:OUT") { - field(DESC, "A variable float") +record(bi, "${P}PowerRb") { + field(DESC, "Read back of power supply is enabled") field(DTYP, "stream") - field(OUT, "@simple.proto setVarFloat $(PORT)") - field(FLNK, "VARFLOAT:IN") -} + field(INP, "@simple.proto getPowerRb $(PORT)") + field(PINI, YES) + field(ZNAM, "OFF") + field(ONAM, "ON") -# Calc + # field(FLNK, "${P}Status") +} -record(stringin, "SCALC:IN") { - field(DESC, "Result of scalcout record") +record(stringin, "${P}Version") { + field(DESC, "A fixed float with a returned prefix") field(DTYP, "stream") - field(INP, "@simple.proto getSCalc $(PORT)") + field(INP, "@simple.proto getVersion $(PORT)") field(PINI, "YES") - field(SCAN, "5 second") } -record(scalcout, "SCALC:OUT") { - field(DESC, "An scalcout record") +# Calc + +record(scalcout, "${P}2UCmd") { + field(DESC, "Twice the voltage command") field(A, "0") - field(CALC, "printf('1%i', A*A) + 'A'") + field(CALC, "printf('1%i', A*2)") field(DTYP, "stream") - field(OUT, "@simple.proto setSCalc $(PORT)") - field(FLNK, "SCALC:IN") - field(PINI, "YES") - field(SCAN, "5 second") + field(OUT, "@simple.proto setUCmd $(PORT)") + field(FLNK, "${P}URb") } # Regular Expressions -record(stringin, "REGEX_TITLE:IN") { - field(DESC, "A regex test") +record(stringin, "${P}VersionNum") { + field(DESC, "Just the version number") field(DTYP, "stream") - field(INP, "@simple.proto getRegexTitle $(PORT)") + field(INP, "@simple.proto getVersionNum $(PORT)") field(PINI, "YES") - field(SCAN, "5 second") } -record(stringin, "REGEX_SUB:IN") { - field(DESC, "A regex substitution test") +record(stringin, "${P}VersionCat") { + field(DESC, "Just the version numbers, concatenated") field(DTYP, "stream") - field(INP, "@simple.proto getRegexSub $(PORT)") + field(INP, "@simple.proto getVersionCat $(PORT)") field(PINI, "YES") - field(SCAN, "5 second") } diff --git a/ioc/tests/support/StreamDevice/simple/top/simpleApp/Db/simple.proto b/ioc/tests/support/StreamDevice/simple/top/simpleApp/Db/simple.proto index f7c28e6b..86b07964 100644 --- a/ioc/tests/support/StreamDevice/simple/top/simpleApp/Db/simple.proto +++ b/ioc/tests/support/StreamDevice/simple/top/simpleApp/Db/simple.proto @@ -1,59 +1,52 @@ -Terminator = CR LF; +Terminator = LF; -# Fixed values +# Normal flow -getFloat { - out "FLOAT"; - in "%f"; +getURb { + out ":volt?"; + in "%f"; } -getFloatWithPrefix { - out "FLOAT_WITH_PREFIX"; - in "VALUE: %f"; -} +setUCmd { + out ":volt %f"; + in "OK"; -getEnum { - out "ENUM"; - in "%{ONE|TWO|THREE}"; + @init { getURb; } } -# Variable values - -setVarFloat { - out "SET_VARFLOAT %f"; +getUMes { + out "meas:volt?"; + in "%f"; } -getVarFloat { - out "GET_VARFLOAT"; - in "%f"; +# Checks parsing enums +getPowerRb { + out "outp:pon?"; + in "%{OFF|ON}"; } -# Calc +# Checks writing enums +setPowerCmd { + out "outp:pon %{OFF|ON}"; + in "OK"; -getSCalc { - out "GET_SCALC"; - in "%s"; + @init { getPowerRb; } } -setSCalc { - out "SET_SCALC %s"; - in "%s"; +# Checks parsing with prefix +getVersion { + out ":idn?"; + in "psu-simulator %s"; } -# Regular Expressions +# Checks regular expressions -getRegexTitle { - out "REGEX_TITLE"; - # Added `[\s\S]+$` at the end to silence warning of extra input - in "%.1/(.*)<\/title>[\s\S]+$/" +getVersionNum { + out ":idn?"; + in "%.1/^psu-simulator (\d+\.\d+\.\d+)$/" } -getRegexSub { - out "REGEX_SUB"; - # TODO: weirdness in StreamDevice, the `+.2` here means "replace a maximum of - # 2", but it also needs 2 regex sub-expression, hence the "(())", as if it - # were a normal regex converter ("%/regex/"). - # - # Also %s needed to be after instead of before. - in "%#+-10.2/((ab))/X/%s"; +getVersionCat { + out ":idn?"; + in "%#/^psu-simulator (\d+)\.(\d+)\.(\d+)$/\1\2\3/%s"; }