From 7ab4486748a32a41508ef5072742c0f01f80865a Mon Sep 17 00:00:00 2001 From: Stefan Mathis Date: Mon, 11 Aug 2025 12:01:37 +0200 Subject: [PATCH] Restructured the test setup General test configurations are now separated from specific axis test definitions. --- README.md | 37 +++++++++++++++---- config.yaml | 2 +- ioc/motors/turboPmac1.cmd | 25 ------------- runtests | 19 +++++++--- setup/classes.py | 8 ++-- {tests => setup}/home.py | 0 {tests => setup}/move.py | 0 {tests => setup}/sinqMotor/__init__.py | 0 {tests => setup}/sinqMotor/limits.py | 0 .../sinqMotor/masterMacs/__init__.py | 0 .../sinqMotor/masterMacs/reset.py | 0 {tests => setup}/sinqMotor/speed.py | 0 .../sinqMotor/turboPmac}/__init__.py | 0 {tests => setup}/sinqMotor/turboPmac/reset.py | 0 .../turboPmac => masterMacs1}/__init__.py | 0 .../ax1 => masterMacs1/_ax1}/__init__.py | 0 .../ax1 => masterMacs1/_ax1}/conftest.py | 2 +- .../ax1 => masterMacs1/_ax1}/test_common.py | 8 ++-- .../turboPmac/ax5 => turboPmac1}/__init__.py | 0 tests/turboPmac1/ax1/__init__.py | 0 .../turboPmac => turboPmac1}/ax1/conftest.py | 0 .../ax1/test_common.py | 10 ++--- tests/turboPmac1/ax5/__init__.py | 0 .../turboPmac => turboPmac1}/ax5/conftest.py | 0 .../ax5/test_common.py | 10 ++--- 25 files changed, 62 insertions(+), 59 deletions(-) rename {tests => setup}/home.py (100%) rename {tests => setup}/move.py (100%) rename {tests => setup}/sinqMotor/__init__.py (100%) rename {tests => setup}/sinqMotor/limits.py (100%) rename {tests => setup}/sinqMotor/masterMacs/__init__.py (100%) rename {tests => setup}/sinqMotor/masterMacs/reset.py (100%) rename {tests => setup}/sinqMotor/speed.py (100%) rename {tests/sinqMotor/masterMacs/ax1 => setup/sinqMotor/turboPmac}/__init__.py (100%) rename {tests => setup}/sinqMotor/turboPmac/reset.py (100%) rename tests/{sinqMotor/turboPmac => masterMacs1}/__init__.py (100%) rename tests/{sinqMotor/turboPmac/ax1 => masterMacs1/_ax1}/__init__.py (100%) rename tests/{sinqMotor/masterMacs/ax1 => masterMacs1/_ax1}/conftest.py (79%) rename tests/{sinqMotor/masterMacs/ax1 => masterMacs1/_ax1}/test_common.py (85%) rename tests/{sinqMotor/turboPmac/ax5 => turboPmac1}/__init__.py (100%) create mode 100755 tests/turboPmac1/ax1/__init__.py rename tests/{sinqMotor/turboPmac => turboPmac1}/ax1/conftest.py (100%) rename tests/{sinqMotor/turboPmac => turboPmac1}/ax1/test_common.py (93%) create mode 100755 tests/turboPmac1/ax5/__init__.py rename tests/{sinqMotor/turboPmac => turboPmac1}/ax5/conftest.py (100%) rename tests/{sinqMotor/turboPmac => turboPmac1}/ax5/test_common.py (91%) diff --git a/README.md b/README.md index 7e40c01..2a67a43 100755 --- a/README.md +++ b/README.md @@ -7,23 +7,28 @@ the tests. The general architecture of the framework is as follows: - `ioc` contains all files used to create the IOC used in the tests. -- `tests` contains the tests itself in a hierarchical order: - - `tests/.` contains tests and configuration applicable for any motor record-based +- `setup` contains the motor classes, the EPICS communication interface and generalized tests (which are not tied to one specific axis). These tests are arranged in a hierarchical order: + - `tests/.` contains tests and utility functions applicable for any motor record-based driver. - - `tests/sinqMotor/.` contains tests and configuration applicable for drivers + - `tests/sinqMotor/.` contains tests and utility functions applicable for drivers based on https://gitea.psi.ch/lin-epics-modules/sinqMotor - - `tests/sinqMotor/turboPmac/.` contains tests and configuration applicable for the + - `tests/sinqMotor/turboPmac/.` contains tests and utility functions applicable for the sinqMotor-based Turbo PMAC driver https://gitea.psi.ch/lin-epics-modules/turboPmac - - `tests/sinqMotor/masterMacs/.` contains tests and configuration applicable for the + - `tests/sinqMotor/masterMacs/.` contains tests and utility functions applicable for the sinqMotor-based MasterMACS driver https://gitea.psi.ch/lin-epics-modules/mastermacs -Individual motors at the test instrument are then set up as subfolders of their -respective driver type. -- `setup` contains classes representing the motors and the EPICS communication interface. +- `tests` contains the tests for the individual axes within subfolders of their respective controller folders. For example, the tests for axis "ax1" of the controller "turboPmac1" configured in `ioc/motors/turboPmac1` should be in `tests/turboPmac1/ax1`, the tests for axis "ax2" of the same controller should be in `tests/turboPmac1/ax2` and so on. If an axis folder has a prepended underscore, it is ignored by `runtests` (e.g. the tests in `tests/turboPmac1/_ax3` would be ignored). This can be used to disable tests when e.g. the axis is not available temporarily. The classes are used to initialize the individual motors within the `tests` directory. - `config.yaml` is the test configuration (see [Configuration](#configuration)) - `maketestenv` can be used to create a Python virtual environment for running the tests. - `runtests` is a parallelizing `pytest` wrapper. See section for more information [Parallelizing tests over motors](#parallelizing-tests-over-motors). +## Installation + +The following prerequisites need to be fulfilled: +1) EPICS 7 and the IOC shell must be installed. +2) The PSI EPICS drive `/sq_epics/ioc` must be mounted as `/ioc`. +The repository can then be simply cloned anywhere. Depending on the available hardware, the [configuration](#configuration) needs to be adjusted. + ## Configuration The test setup is defined in `config.yaml`. This file contains information which @@ -33,6 +38,22 @@ identifier of the records created by the motor drivers. - `versions`: Driver versions used in the IOC - `controllers`: Configuration of the different motor controllers (IP + port and poll periods) +### Adding / removing a controller to the IOC + +The controller name and its configuration needs to added under `controllers` in the following format: + +```yaml +turboPmac1: + ip: "172.28.101.24" # Controller IP adress + port: 1025 # Controller port + busypoll: 0.05 # Time in seconds + idlepoll: 1 # Time in seconds +``` + +Additionally, two files need to be added under `ioc/motors`: `ioc/motors/turboPmac1.cmd` and `ioc/motors/turboPmac1.substitutions`. +These files need to be configured according to the corresponding motor driver documentation (e.g. according to https://gitea.psi.ch/lin-epics-modules/turboPmac for a TurboPMAC driver). +Lastly, `ioc/st.cmd` needs to run the `.cmd` file under motors and to import the driver. See the documentation of [sinqMotor](https://gitea.psi.ch/lin-epics-modules/sinqMotor) for details. + ## Starting the IOC It is recommended to start the IOC via `ioc/startioc`: diff --git a/config.yaml b/config.yaml index ef3ef43..b776343 100755 --- a/config.yaml +++ b/config.yaml @@ -1,6 +1,6 @@ pvprefix: DRVTESTS versions: - turboPmac: "mathis_s" + turboPmac: "1.3.1" masterMacs: "1.1" controllers: turboPmac1: diff --git a/ioc/motors/turboPmac1.cmd b/ioc/motors/turboPmac1.cmd index 6a2f271..94eee5f 100755 --- a/ioc/motors/turboPmac1.cmd +++ b/ioc/motors/turboPmac1.cmd @@ -1,28 +1,3 @@ -# Configuration for the Turbo PMAC motor controller -# -# Important functions: -# -# turboPmacController: -# Creates the controller object and specifies busy poll period, idle poll period and communication timeout. -# A typical call looks like this: -# turboPmacController("$(NAME)","$(ASYN_PORT)",8,0.05,1,0.05); -# with -# 8 = Total number of axes -# 0.05 = Busy poll period in seconds -# 1 = Idle poll period in seconds -# 0.05 = Communication timeout in seconds -# -# setMaxSubsequentTimeouts: -# Set the number of subsequent timeouts which may occur before the user is informed in NICOS. -# -# setThresholdComTimeout: -# Set the maximum number of timeouts which may happen in a given timespan before the user is informed in NICOS. -# A typical call looks like this: -# setThresholdComTimeout("$(NAME)", 3600, 60); -# with -# 3600 = Timespan in seconds -# 60 = Maximum number of timeout events which may occur before the user is informed - epicsEnvSet("NAME","turboPmac1") epicsEnvSet("ASYN_PORT","p$(NAME)") diff --git a/runtests b/runtests index 19b6c1e..4467a47 100755 --- a/runtests +++ b/runtests @@ -22,9 +22,6 @@ import time from tests.conftest import check_ioc_running from setup.classes import IocNotRunning -# Define the test folders you want to run in parallel -FOLDERS = ["tests/sinqMotor/turboPmac/ax1", "tests/sinqMotor/turboPmac/ax5"] - # Time we excpect the IOC needs to start up TIMEOUT_IOC_STARTUP = 10 @@ -38,12 +35,22 @@ for arg in sys.argv[1:]: else: extra_args.append(arg) +# Find all axes paths (pa) within all controller paths (pc) +folders =[] +for pc in Path("tests").glob("*"): + if pc.is_dir() and "__pycache__" not in pc.parts: + for pa in Path(pc).glob("*"): + if pa.is_dir() and "__pycache__" not in pa.parts: + # Ignore all axis paths which start with an underscore + if not pa.parts[-1].startswith("_"): + folders.append(pa) + # Filter folders to run based on path_args enabled_folders = [] if not path_args: - enabled_folders = FOLDERS + enabled_folders = folders else: - for folder in FOLDERS: + for folder in folders: if any(Path(arg).resolve().as_posix().startswith(Path(folder).resolve().as_posix()) for arg in path_args): enabled_folders.append(folder) @@ -72,7 +79,7 @@ processes = [] for folder in enabled_folders: folder_path_args = ( [arg for arg in path_args if Path(arg).resolve().as_posix().startswith(Path(folder).resolve().as_posix())] - if path_args else [folder] + if path_args else [folder.as_posix()] ) command = ["pytest"] + folder_path_args + extra_args diff --git a/setup/classes.py b/setup/classes.py index da0c8b8..f554200 100755 --- a/setup/classes.py +++ b/setup/classes.py @@ -240,11 +240,11 @@ class SinqMotor(Motor): def error_message(self): return self.get_pv('errormsgpv', as_string=True) - def enable_and_wait(self, timeout=10): + def enable_and_wait(self, timeout=20): self.put_pv('enable', 1) self.wait_enabled(timeout) - def disable_and_wait(self, timeout=10): + def disable_and_wait(self, timeout=20): self.put_pv('enable', 0) self.wait_disabled(timeout) @@ -254,7 +254,7 @@ class SinqMotor(Motor): """ return self._wait_enabled_disabled(True, timeout) - def wait_disabled(self, timeout=10): + def wait_disabled(self, timeout=20): """ Wait until the motor is enabled or a timeout has been reached. """ @@ -276,7 +276,7 @@ class SinqMotor(Motor): else: pytest.fail( f'Motor {self.pv} could not be disabled in {timeout} seconds') - time.sleep(0.1) + time.sleep(0.5) class TurboPMAC(SinqMotor): diff --git a/tests/home.py b/setup/home.py similarity index 100% rename from tests/home.py rename to setup/home.py diff --git a/tests/move.py b/setup/move.py similarity index 100% rename from tests/move.py rename to setup/move.py diff --git a/tests/sinqMotor/__init__.py b/setup/sinqMotor/__init__.py similarity index 100% rename from tests/sinqMotor/__init__.py rename to setup/sinqMotor/__init__.py diff --git a/tests/sinqMotor/limits.py b/setup/sinqMotor/limits.py similarity index 100% rename from tests/sinqMotor/limits.py rename to setup/sinqMotor/limits.py diff --git a/tests/sinqMotor/masterMacs/__init__.py b/setup/sinqMotor/masterMacs/__init__.py similarity index 100% rename from tests/sinqMotor/masterMacs/__init__.py rename to setup/sinqMotor/masterMacs/__init__.py diff --git a/tests/sinqMotor/masterMacs/reset.py b/setup/sinqMotor/masterMacs/reset.py similarity index 100% rename from tests/sinqMotor/masterMacs/reset.py rename to setup/sinqMotor/masterMacs/reset.py diff --git a/tests/sinqMotor/speed.py b/setup/sinqMotor/speed.py similarity index 100% rename from tests/sinqMotor/speed.py rename to setup/sinqMotor/speed.py diff --git a/tests/sinqMotor/masterMacs/ax1/__init__.py b/setup/sinqMotor/turboPmac/__init__.py similarity index 100% rename from tests/sinqMotor/masterMacs/ax1/__init__.py rename to setup/sinqMotor/turboPmac/__init__.py diff --git a/tests/sinqMotor/turboPmac/reset.py b/setup/sinqMotor/turboPmac/reset.py similarity index 100% rename from tests/sinqMotor/turboPmac/reset.py rename to setup/sinqMotor/turboPmac/reset.py diff --git a/tests/sinqMotor/turboPmac/__init__.py b/tests/masterMacs1/__init__.py similarity index 100% rename from tests/sinqMotor/turboPmac/__init__.py rename to tests/masterMacs1/__init__.py diff --git a/tests/sinqMotor/turboPmac/ax1/__init__.py b/tests/masterMacs1/_ax1/__init__.py similarity index 100% rename from tests/sinqMotor/turboPmac/ax1/__init__.py rename to tests/masterMacs1/_ax1/__init__.py diff --git a/tests/sinqMotor/masterMacs/ax1/conftest.py b/tests/masterMacs1/_ax1/conftest.py similarity index 79% rename from tests/sinqMotor/masterMacs/ax1/conftest.py rename to tests/masterMacs1/_ax1/conftest.py index 3516536..3cfe2c8 100755 --- a/tests/sinqMotor/masterMacs/ax1/conftest.py +++ b/tests/masterMacs1/_ax1/conftest.py @@ -6,4 +6,4 @@ from setup.classes import MasterMACS @pytest.fixture(autouse=True) def motor(): - return MasterMACS('masterMacs1', 'lin1') + return MasterMACS('masterMacs1', 'ax1') diff --git a/tests/sinqMotor/masterMacs/ax1/test_common.py b/tests/masterMacs1/_ax1/test_common.py similarity index 85% rename from tests/sinqMotor/masterMacs/ax1/test_common.py rename to tests/masterMacs1/_ax1/test_common.py index 05fd885..26c3168 100755 --- a/tests/sinqMotor/masterMacs/ax1/test_common.py +++ b/tests/masterMacs1/_ax1/test_common.py @@ -1,9 +1,9 @@ # Run a selection of common tests -from tests.move import * -from tests.sinqMotor.limits import * -from tests.home import * -from tests.sinqMotor.masterMacs.reset import reset +from setup.move import * +from setup.sinqMotor.limits import * +from setup.home import * +from setup.sinqMotor.masterMacs.reset import reset def test_reset(motor): diff --git a/tests/sinqMotor/turboPmac/ax5/__init__.py b/tests/turboPmac1/__init__.py similarity index 100% rename from tests/sinqMotor/turboPmac/ax5/__init__.py rename to tests/turboPmac1/__init__.py diff --git a/tests/turboPmac1/ax1/__init__.py b/tests/turboPmac1/ax1/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/tests/sinqMotor/turboPmac/ax1/conftest.py b/tests/turboPmac1/ax1/conftest.py similarity index 100% rename from tests/sinqMotor/turboPmac/ax1/conftest.py rename to tests/turboPmac1/ax1/conftest.py diff --git a/tests/sinqMotor/turboPmac/ax1/test_common.py b/tests/turboPmac1/ax1/test_common.py similarity index 93% rename from tests/sinqMotor/turboPmac/ax1/test_common.py rename to tests/turboPmac1/ax1/test_common.py index 609c104..59fc8aa 100755 --- a/tests/sinqMotor/turboPmac/ax1/test_common.py +++ b/tests/turboPmac1/ax1/test_common.py @@ -2,11 +2,11 @@ import pytest -from tests.move import * -from tests.home import * -from tests.sinqMotor.limits import * -from tests.sinqMotor.speed import * -from tests.sinqMotor.turboPmac.reset import reset +from setup.move import * +from setup.home import * +from setup.sinqMotor.limits import * +from setup.sinqMotor.speed import * +from setup.sinqMotor.turboPmac.reset import reset def test_reset(motor): diff --git a/tests/turboPmac1/ax5/__init__.py b/tests/turboPmac1/ax5/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/tests/sinqMotor/turboPmac/ax5/conftest.py b/tests/turboPmac1/ax5/conftest.py similarity index 100% rename from tests/sinqMotor/turboPmac/ax5/conftest.py rename to tests/turboPmac1/ax5/conftest.py diff --git a/tests/sinqMotor/turboPmac/ax5/test_common.py b/tests/turboPmac1/ax5/test_common.py similarity index 91% rename from tests/sinqMotor/turboPmac/ax5/test_common.py rename to tests/turboPmac1/ax5/test_common.py index bfe2ba4..c0d01e8 100755 --- a/tests/sinqMotor/turboPmac/ax5/test_common.py +++ b/tests/turboPmac1/ax5/test_common.py @@ -1,11 +1,11 @@ # Run a selection of common tests import pytest -from tests.move import * -from tests.sinqMotor.limits import * -from tests.sinqMotor.speed import * -from tests.home import * -from tests.sinqMotor.turboPmac.reset import reset +from setup.move import * +from setup.sinqMotor.limits import * +from setup.sinqMotor.speed import * +from setup.home import * +from setup.sinqMotor.turboPmac.reset import reset def test_reset(motor):