From 9cab5b82d99c2930615e9b834c0c00142f35374a Mon Sep 17 00:00:00 2001 From: Stefan Mathis Date: Mon, 3 Nov 2025 16:31:18 +0100 Subject: [PATCH] Added tests for new EL734 driver --- .gitignore | 1 + config.yaml | 12 +- ioc/motors/el734_1.cmd | 12 ++ ioc/motors/el734_1.substitutions | 6 + ioc/motors/masterMacs1.cmd | 3 +- ioc/motors/turboPmac1.cmd | 1 + ioc/st.cmd | 2 + ioc/startioc | 11 ++ runtests | 16 +-- setup/classes.py | 4 + setup/home.py | 26 ++-- setup/sinqMotor/masterMacs/reset.py | 13 +- tests/conftest.py | 2 +- .../{masterMacs1/_ax1 => el734_1}/__init__.py | 0 tests/el734_1/ax1/__init__.py | 0 tests/el734_1/ax1/conftest.py | 19 +++ tests/el734_1/ax1/test_common.py | 123 ++++++++++++++++++ tests/masterMacs1/_ax1/conftest.py | 9 -- tests/masterMacs1/_ax1/test_common.py | 50 ------- tests/masterMacs1/ax1/__init__.py | 0 tests/masterMacs1/ax1/conftest.py | 21 +++ tests/masterMacs1/ax1/test_common.py | 50 +++++++ 22 files changed, 293 insertions(+), 88 deletions(-) create mode 100644 ioc/motors/el734_1.cmd create mode 100644 ioc/motors/el734_1.substitutions rename tests/{masterMacs1/_ax1 => el734_1}/__init__.py (100%) mode change 100755 => 100644 create mode 100644 tests/el734_1/ax1/__init__.py create mode 100644 tests/el734_1/ax1/conftest.py create mode 100644 tests/el734_1/ax1/test_common.py delete mode 100755 tests/masterMacs1/_ax1/conftest.py delete mode 100755 tests/masterMacs1/_ax1/test_common.py create mode 100755 tests/masterMacs1/ax1/__init__.py create mode 100755 tests/masterMacs1/ax1/conftest.py create mode 100755 tests/masterMacs1/ax1/test_common.py diff --git a/.gitignore b/.gitignore index 5ecbaa0..420725b 100755 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Ignore all .pyc files *.pyc +__pycache__ # Ignore the EPICS environment variables (this file is recreated by ioc/startioc.py via config.yaml) config.cmd diff --git a/config.yaml b/config.yaml index b776343..fb54446 100755 --- a/config.yaml +++ b/config.yaml @@ -1,7 +1,8 @@ pvprefix: DRVTESTS versions: - turboPmac: "1.3.1" - masterMacs: "1.1" + turboPmac: "1.5" + masterMacs: "1.4" + el734: "mathis_s" controllers: turboPmac1: ip: "172.28.101.24" @@ -11,5 +12,10 @@ controllers: masterMacs1: ip: "172.28.101.66" port: 1912 - busypoll: 0.05 + busypoll: 0.10 idlepoll: 1 + el734_1: + ip: "sinqtest-ts" + port: 3002 + busypoll: 0.5 + idlepoll: 0.5 diff --git a/ioc/motors/el734_1.cmd b/ioc/motors/el734_1.cmd new file mode 100644 index 0000000..5339bc3 --- /dev/null +++ b/ioc/motors/el734_1.cmd @@ -0,0 +1,12 @@ +epicsEnvSet("NAME","el734_1") +epicsEnvSet("ASYN_PORT","p$(NAME)") + +drvAsynIPPortConfigure("$(ASYN_PORT)", "sinqtest-ts:3002") +el734Controller("$(NAME)","$(ASYN_PORT)",8,0.5,0.5,10); +el734Axis("$(NAME)",1); + +epicsEnvSet("SINQDBPATH","$(el734_DB)/sinqMotor.db") +dbLoadTemplate("$(IOCDIR)/motors/$(NAME).substitutions", "INSTR=$(PVPREFIX):$(NAME):,CONTROLLER=$(NAME)") +epicsEnvSet("SINQDBPATH","$(el734_DB)/el734.db") +dbLoadTemplate("$(IOCDIR)/motors/$(NAME).substitutions", "INSTR=$(PVPREFIX):$(NAME):,CONTROLLER=$(NAME)") +# IOC shell does not execute the last line, hence an empty line at the end is needed \ No newline at end of file diff --git a/ioc/motors/el734_1.substitutions b/ioc/motors/el734_1.substitutions new file mode 100644 index 0000000..d42c571 --- /dev/null +++ b/ioc/motors/el734_1.substitutions @@ -0,0 +1,6 @@ +file $(SINQDBPATH) +{ +pattern +{ AXIS, M, EGU, DIR, MRES, ENABLEMOVWATCHDOG, LIMITSOFFSET, CANSETSPEED } +{ 1, "ax1", mm, Pos, 0.001, 1, 1.0, 1 } +} \ No newline at end of file diff --git a/ioc/motors/masterMacs1.cmd b/ioc/motors/masterMacs1.cmd index fcb6689..2607fa0 100755 --- a/ioc/motors/masterMacs1.cmd +++ b/ioc/motors/masterMacs1.cmd @@ -1,5 +1,3 @@ -# Configuration for the "modern" masterMacs-Axes - epicsEnvSet("NAME","masterMacs1") epicsEnvSet("ASYN_PORT","p$(NAME)") @@ -12,3 +10,4 @@ masterMacsAxis("$(NAME)",2); epicsEnvSet("SINQDBPATH","$(masterMacs_DB)/sinqMotor.db") dbLoadTemplate("$(IOCDIR)/motors/$(NAME).substitutions", "INSTR=$(PVPREFIX):$(NAME):,CONTROLLER=$(NAME)") +# IOC shell does not execute the last line, hence an empty line at the end is needed \ No newline at end of file diff --git a/ioc/motors/turboPmac1.cmd b/ioc/motors/turboPmac1.cmd index 94eee5f..32bb993 100755 --- a/ioc/motors/turboPmac1.cmd +++ b/ioc/motors/turboPmac1.cmd @@ -17,3 +17,4 @@ epicsEnvSet("SINQDBPATH","$(turboPmac_DB)/sinqMotor.db") dbLoadTemplate("$(IOCDIR)/motors/$(NAME).substitutions", "INSTR=$(PVPREFIX):$(NAME):,CONTROLLER=$(NAME)") epicsEnvSet("SINQDBPATH","$(turboPmac_DB)/turboPmac.db") dbLoadTemplate("$(IOCDIR)/motors/$(NAME).substitutions", "INSTR=$(PVPREFIX):$(NAME):,CONTROLLER=$(NAME)") +# IOC shell does not execute the last line, hence an empty line at the end is needed \ No newline at end of file diff --git a/ioc/st.cmd b/ioc/st.cmd index fa919fe..b411899 100755 --- a/ioc/st.cmd +++ b/ioc/st.cmd @@ -5,6 +5,7 @@ require turboPmac, $(TURBOPMAC_VERSION) require masterMacs, $(MASTERMACS_VERSION) +require el734, $(EL734_VERSION) ################################################################################ # Motors @@ -12,6 +13,7 @@ require masterMacs, $(MASTERMACS_VERSION) # Initialize the motors itself < motors/turboPmac1.cmd < motors/masterMacs1.cmd +< motors/el734_1.cmd # Create the test record which is used to detect if the IOC is running dbLoadRecords("$(IOCDIR)/db/ready.db", "PVPREFIX=$(PVPREFIX)") diff --git a/ioc/startioc b/ioc/startioc index d37d03c..b9f2ad9 100755 --- a/ioc/startioc +++ b/ioc/startioc @@ -30,6 +30,8 @@ def startioc(): f'epicsEnvSet("TURBOPMAC_VERSION", "{config["versions"]["turboPmac"]}")\n') out.write( f'epicsEnvSet("MASTERMACS_VERSION", "{config["versions"]["masterMacs"]}")\n') + out.write( + f'epicsEnvSet("EL734_VERSION", "{config["versions"]["el734"]}")\n') out.write( f'epicsEnvSet("TURBOPMAC1_IP", "{config["controllers"]["turboPmac1"]["ip"]}")\n') @@ -49,6 +51,15 @@ def startioc(): out.write( f'epicsEnvSet("MASTERMACS1_IDLEPOLL", "{config["controllers"]["masterMacs1"]["idlepoll"]}")\n') + out.write( + f'epicsEnvSet("EL734_1_IP", "{config["controllers"]["el734_1"]["ip"]}")\n') + out.write( + f'epicsEnvSet("EL734_1_PORT", "{config["controllers"]["el734_1"]["port"]}")\n') + out.write( + f'epicsEnvSet("EL734_1_BUSYPOLL", "{config["controllers"]["el734_1"]["busypoll"]}")\n') + out.write( + f'epicsEnvSet("EL734_1_IDLEPOLL", "{config["controllers"]["el734_1"]["idlepoll"]}")\n') + # Set environment variables os.environ["EPICS_BASE"] = "/usr/local/epics/base-7.0.7" os.environ["EPICS_HOST_ARCH"] = "RHEL8-x86_64" diff --git a/runtests b/runtests index 4467a47..d538295 100755 --- a/runtests +++ b/runtests @@ -36,7 +36,7 @@ for arg in sys.argv[1:]: extra_args.append(arg) # Find all axes paths (pa) within all controller paths (pc) -folders =[] +folders = [] for pc in Path("tests").glob("*"): if pc.is_dir() and "__pycache__" not in pc.parts: for pa in Path(pc).glob("*"): @@ -51,9 +51,12 @@ if not path_args: enabled_folders = folders else: for folder in folders: - if any(Path(arg).resolve().as_posix().startswith(Path(folder).resolve().as_posix()) for arg in path_args): + if any(Path(folder).resolve().as_posix().startswith(Path(arg).resolve().as_posix()) for arg in path_args): enabled_folders.append(folder) +if not enabled_folders: + print(f"No tests were found within the specified path {path_args}") + # Check if the IOC is running and try to start it, if it isn't try: check_ioc_running() @@ -77,14 +80,7 @@ except IocNotRunning: # Run each enabled folder's relevant tests in parallel 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.as_posix()] - ) - - command = ["pytest"] + folder_path_args + extra_args - print(f"Running: {' '.join(command)}") - + command = ["pytest"] + [str(folder)] + extra_args proc = subprocess.Popen(command) processes.append(proc) diff --git a/setup/classes.py b/setup/classes.py index f554200..1c96193 100755 --- a/setup/classes.py +++ b/setup/classes.py @@ -285,3 +285,7 @@ class TurboPMAC(SinqMotor): class MasterMACS(SinqMotor): pass + + +class EL734(SinqMotor): + pass diff --git a/setup/home.py b/setup/home.py index 252255c..01aadde 100755 --- a/setup/home.py +++ b/setup/home.py @@ -6,9 +6,11 @@ def home(motor, forward): encoder = motor.get_pv('encoder_type', as_string=True) if encoder == 'absolute': - is_absolute = True + can_home = False elif encoder == 'incremental': - is_absolute = False + can_home = True + elif encoder == 'none': + can_home = False else: raise ValueError(f'Unknown encoder type {encoder}') @@ -21,16 +23,7 @@ def home(motor, forward): motor.wait_for_start() - if is_absolute: - # EPICS initially assumes the motor is moving and sets it into moving state. - # However, after the first poll it should realize that the motor is in fact not moving. - time.sleep(2 * motor.busypoll) - - # Motor should not move at all - assert motor.get_pv('moving') == 0 - assert motor.get_pv('donemoving') == 1 - assert not motor.has_error() - else: + if can_home: # The controller might take a bit of time to actually start the movement, # therefore we wait a bit. time.sleep(2 * motor.busypoll) @@ -48,6 +41,15 @@ def home(motor, forward): assert motor.get_pv('donemoving') == 1 assert not motor.has_error() assert motor.is_homed() + else: + # EPICS initially assumes the motor is moving and sets it into moving state. + # However, after the first poll it should realize that the motor is in fact not moving. + time.sleep(3 * motor.busypoll) + + # Motor should not move at all + assert motor.get_pv('moving') == 0 + assert motor.get_pv('donemoving') == 1 + assert not motor.has_error() def start_home_while_moving(motor, target, forward): """ diff --git a/setup/sinqMotor/masterMacs/reset.py b/setup/sinqMotor/masterMacs/reset.py index d50d52f..36ca594 100755 --- a/setup/sinqMotor/masterMacs/reset.py +++ b/setup/sinqMotor/masterMacs/reset.py @@ -1,3 +1,5 @@ +import time + def reset(motor, target): """ Reset the motor for the next test. This means the following things: @@ -7,9 +9,18 @@ def reset(motor, target): 4) Moving to zero """ + # Wait a bit before starting each test so the IOC has some time + # to recover from any previous failed tests. + time.sleep(0.2) + + if target is None: + target = motor.default_position + motor.put_pv('stop', 1) + motor.wait_for_done() motor.put_pv('reseterrorpv', 1) - motor.put_pv('enable', 1) + motor.wait_disabled() + motor.enable_and_wait() motor.move_and_wait(target) assert motor.at_target(target) assert not motor.has_error() diff --git a/tests/conftest.py b/tests/conftest.py index 412f456..9798511 100755 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,7 +21,7 @@ def check_ioc_running(): return except TimeoutError: # IOC startup failed in the given time -> Raise an error - path = Path.cwd() / 'startioc' + path = Path.cwd() / 'ioc' / 'startioc' raise IocNotRunning(f'Start the test IOC first by running {path}') @pytest.fixture(autouse=True) diff --git a/tests/masterMacs1/_ax1/__init__.py b/tests/el734_1/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from tests/masterMacs1/_ax1/__init__.py rename to tests/el734_1/__init__.py diff --git a/tests/el734_1/ax1/__init__.py b/tests/el734_1/ax1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/el734_1/ax1/conftest.py b/tests/el734_1/ax1/conftest.py new file mode 100644 index 0000000..0a70c1d --- /dev/null +++ b/tests/el734_1/ax1/conftest.py @@ -0,0 +1,19 @@ +# This module defines fixtures which are shared for all tests of motor "lin1". +import time + +import pytest +from setup.classes import EL734 + +# Prepare the motor at the start of the test by resetting and homing it. +@pytest.fixture(scope="session", autouse=True) +def reset_and_home(): + mot = EL734('el734_1', 'ax1') + mot.put_pv('stop', 1) + mot.put_pv('reseterrorpv', 1) + mot.put_pv('homeforward', 1) + time.sleep(1) + mot.wait_for_done() + +@pytest.fixture(autouse=True) +def motor(): + return EL734('el734_1', 'ax1') diff --git a/tests/el734_1/ax1/test_common.py b/tests/el734_1/ax1/test_common.py new file mode 100644 index 0000000..a4de0a7 --- /dev/null +++ b/tests/el734_1/ax1/test_common.py @@ -0,0 +1,123 @@ +# Run a selection of common tests + +import pytest + +from setup.move import * +from setup.home import * +from setup.sinqMotor.limits import * +from setup.sinqMotor.speed import * + + +def test_move_to_low_limit_switch(motor): + motor.put_pv('stop', 1) + motor.wait_for_done() + move_to_low_limit_switch(motor) + + +def test_move_to_high_limit_switch(motor): + motor.put_pv('stop', 1) + motor.wait_for_done() + move_to_high_limit_switch(motor) + + +def test_move_while_move_same_direction(motor): + motor.put_pv('stop', 1) + motor.wait_for_done() + move_while_move(motor, -30, -10, 2) + + +def test_move_while_move_opposite_direction(motor): + motor.put_pv('stop', 1) + motor.wait_for_done() + move_while_move(motor, 10, -10, 2) + + +def test_stop(motor): + stop(motor, -60) + + +def test_stop_then_move(motor): + motor.put_pv('stop', 1) + motor.wait_for_done() + stop_then_move(motor, 10) + +@pytest.mark.stresstest +def test_stop_then_move_stress(motor): + motor.put_pv('stop', 1) + motor.wait_for_done() + for _ in range(50): + stop_then_move(motor, 5) + stop_then_move(motor, 0) + + +def test_speed_fields_valid(motor): + motor.put_pv('stop', 1) + motor.wait_for_done() + speed_fields_valid(motor) + + +def test_set_minspeed_maxspeed_meanspeed(motor): + motor.put_pv('stop', 1) + motor.wait_for_done() + set_minspeed_maxspeed_meanspeed(motor) + + +def test_set_invalid_speed_above_min_below_max(motor): + motor.put_pv('stop', 1) + motor.wait_for_done() + set_invalid_speed_above_min_below_max(motor) + + +def test_set_speed_and_move(motor): + motor.put_pv('stop', 1) + motor.wait_for_done() + set_speed_and_move(motor, motor.get_pv('maxspeed'), 5) + set_speed_and_move(motor, 0.5*motor.get_pv('maxspeed'), 0) + + +@pytest.mark.stresstest +def test_set_speed_and_move_stress(motor): + motor.put_pv('stop', 1) + motor.wait_for_done() + for _ in range(50): + set_speed_and_move(motor, motor.get_pv('maxspeed'), 5) + set_speed_and_move(motor, 0.5*motor.get_pv('maxspeed'), 0) + + +def test_targets_outside_limits(motor): + motor.put_pv('stop', 1) + motor.wait_for_done() + targets_outside_limits(motor) + + +def test_home(motor): + motor.put_pv('stop', 1) + motor.wait_for_done() + home(motor, True) + + +def test_start_home_while_moving(motor): + motor.put_pv('stop', 1) + motor.wait_for_done() + start_home_while_moving(motor, 15, True) + + +def test_start_home_while_moving(motor): + motor.put_pv('stop', 1) + motor.wait_for_done() + start_home_while_moving(motor, 15, True) + + +def test_move_then_home(motor): + motor.put_pv('stop', 1) + motor.wait_for_done() + move_then_home(motor, 15, True) + + +def test_home_then_move(motor): + motor.put_pv('stop', 1) + motor.wait_for_done() + home_then_move(motor, 15, True) + +# def test_reread_limits_from_hw(motor): +# reread_limits_from_hw(motor) diff --git a/tests/masterMacs1/_ax1/conftest.py b/tests/masterMacs1/_ax1/conftest.py deleted file mode 100755 index 3cfe2c8..0000000 --- a/tests/masterMacs1/_ax1/conftest.py +++ /dev/null @@ -1,9 +0,0 @@ -# This module defines fixtures which are shared for all tests of motor "lin1". - -import pytest -from setup.classes import MasterMACS - - -@pytest.fixture(autouse=True) -def motor(): - return MasterMACS('masterMacs1', 'ax1') diff --git a/tests/masterMacs1/_ax1/test_common.py b/tests/masterMacs1/_ax1/test_common.py deleted file mode 100755 index 26c3168..0000000 --- a/tests/masterMacs1/_ax1/test_common.py +++ /dev/null @@ -1,50 +0,0 @@ -# Run a selection of common tests - -from setup.move import * -from setup.sinqMotor.limits import * -from setup.home import * -from setup.sinqMotor.masterMacs.reset import reset - - -def test_reset(motor): - reset(motor, 4) - reset(motor, 2) - - -def test_move_to_low_limit_switch(motor): - reset(motor, 1) - move_to_low_limit_switch(motor) - - -def test_move_to_high_limit_switch(motor): - reset(motor, 1) - move_to_high_limit_switch(motor) - - -def test_move_while_move(motor): - reset(motor, 1) - move_while_move(motor, -60, -20) - - -def test_stop(motor): - reset(motor, 1) - stop(motor, 18) - - -def test_home(motor): - reset(motor, 1) - home(motor, True) - - -def test_start_home_while_moving(motor): - reset(motor, 1) - start_home_while_moving(motor, 10, True) - - -def test_start_home_while_moving(motor): - reset(motor, 1) - start_home_while_moving(motor, 10, True) - - -# def test_reread_limits_from_hw(motor): -# reread_limits_from_hw(motor) diff --git a/tests/masterMacs1/ax1/__init__.py b/tests/masterMacs1/ax1/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/tests/masterMacs1/ax1/conftest.py b/tests/masterMacs1/ax1/conftest.py new file mode 100755 index 0000000..9c1e1b6 --- /dev/null +++ b/tests/masterMacs1/ax1/conftest.py @@ -0,0 +1,21 @@ +# This module defines fixtures which are shared for all tests of motor "ax1". +import time + +import pytest +from setup.classes import MasterMACS + +# Prepare the motor at the start of the test by resetting and homing it. +@pytest.fixture(scope="session", autouse=True) +def reset_and_home(): + mot = MasterMACS('masterMacs1', 'ax1') + mot.put_pv('stop', 1) + mot.put_pv('reseterrorpv', 1) + mot.wait_disabled() + mot.enable_and_wait() + mot.put_pv('homeforward', 1) + time.sleep(1) + mot.wait_for_done() + +@pytest.fixture(autouse=True) +def motor(): + return MasterMACS('masterMacs1', 'ax1') diff --git a/tests/masterMacs1/ax1/test_common.py b/tests/masterMacs1/ax1/test_common.py new file mode 100755 index 0000000..deb359a --- /dev/null +++ b/tests/masterMacs1/ax1/test_common.py @@ -0,0 +1,50 @@ +# Run a selection of common tests + +from setup.move import * +from setup.sinqMotor.limits import * +from setup.home import * +from setup.sinqMotor.masterMacs.reset import reset + + +def test_reset(motor): + reset(motor, 4) + reset(motor, 2) + + +# def test_move_to_low_limit_switch(motor): +# reset(motor, 1) +# move_to_low_limit_switch(motor) + + +# def test_move_to_high_limit_switch(motor): +# reset(motor, 1) +# move_to_high_limit_switch(motor) + + +# def test_move_while_move(motor): +# reset(motor, 1) +# move_while_move(motor, -60, -20) + + +# def test_stop(motor): +# reset(motor, 1) +# stop(motor, 18) + + +# def test_home(motor): +# reset(motor, 1) +# home(motor, True) + + +# def test_start_home_while_moving(motor): +# reset(motor, 1) +# start_home_while_moving(motor, 10, True) + + +# def test_start_home_while_moving(motor): +# reset(motor, 1) +# start_home_while_moving(motor, 10, True) + + +# def test_reread_limits_from_hw(motor): +# reread_limits_from_hw(motor)