diff --git a/.github/workflows/pre_commit.yml b/.github/workflows/pre_commit.yml new file mode 100644 index 0000000..8584055 --- /dev/null +++ b/.github/workflows/pre_commit.yml @@ -0,0 +1,14 @@ +name: Tests + +on: + pull_request: + push: + branches: [main] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..c19913a --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,26 @@ +name: Tests +on: [push, pull_request] +jobs: + Linux: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os : [ubuntu-latest] + python: ["3.12"] + env: + DISPLAY: ':99.0' + steps: + - name: get repo + uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + - uses: tlambert03/setup-qt-libs@v1 + - name: build "display" + run: | + /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX + - name: pytest + run: | + pip install -e .[test] + pytest -sv diff --git a/.github/workflows/workflows.yml b/.github/workflows/workflows.yml deleted file mode 100644 index d158cd0..0000000 --- a/.github/workflows/workflows.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: tests - -on: - pull_request: - push: - branches: [main] - -jobs: - pre-commit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v3 - - uses: pre-commit/action@v3.0.1 - - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v3 - - name: Tests - run: | - pip install -e .[test] - pytest -sv diff --git a/README.md b/README.md index a6fbd0f..dc39fba 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Motor stage UI [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) +[![Tests](https://github.com/SiLab-Bonn/motor_stage_ui/actions/workflows/tests.yml/badge.svg)](https://github.com/SiLab-Bonn/motor_stage_ui/actions/workflows/tests.yml) Offers both a terminal and graphical user interface for the [C-863 Mercury controller](https://www.le.infn.it/~chiodini/allow_listing/pi/Manuals/C-863_UserManual_MS205E200.pdf) (Check also the [commands](https://twiki.cern.ch/twiki/bin/viewfile/ILCBDSColl/Phase2Preparations?rev=1;filename=MercuryNativeCommands_MS176E101.pdf)). Motor stages can be arranged in daisy chains. @@ -58,3 +59,21 @@ The step size of a specific stage is given in um for translation stages and deg | `stop` | `Stop` | Immediately stops all movement of the stage | motor_name (str): name of the motorstage | - | | `sethome` | `Set Zero` | Sets the current position of the stage as new origin | motor_name (str): name of the motorstage | - | | `gohome` | `MV. Zero` | Goes to origin of the stage | motor_name (str): name of the motorstage | - | +| `status` | -` | Returns the status of the motor controller | motor_name (str): name of the motorstage | - | + +## Tests + +General UI tests, utilizing a motor controller mock, are performed when setting the environmental variable `TEST` e.g.: + +```bash +TEST=True motor init x_axis +``` + +```bash +TEST=True motorgui +``` +Utilizing [pytest](https://docs.pytest.org/en/stable/) the mock tests the software. + +```bash +pytest -sv +``` diff --git a/motor_stage_ui/motor_stage_gui.py b/motor_stage_ui/motor_stage_gui.py index c19cbd3..095a3f7 100644 --- a/motor_stage_ui/motor_stage_gui.py +++ b/motor_stage_ui/motor_stage_gui.py @@ -1,6 +1,7 @@ from motor_stage_ui.pi_stages_interface import PIStagesInterface +from motor_stage_ui.pi_stages_interface import SerialInterface +from motor_stage_ui.test.utils import SerialInterfaceMock from motor_stage_ui import logger -import motor_stage_ui import yaml import logging @@ -8,6 +9,7 @@ from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLineEdit, QLabel import sys import os +from pathlib import Path """ @@ -17,7 +19,7 @@ class MainWindow(QMainWindow): - def __init__(self, config_path): + def __init__(self, config_path, interface=SerialInterface): super().__init__() self.log = logger.setup_main_logger(__class__.__name__, logging.DEBUG) @@ -35,6 +37,7 @@ def __init__(self, config_path): PIStagesInterface( port=self.conf[motor]["port"], baud_rate=self.conf[motor]["baud_rate"], + interface=interface, ) ) stage = self.conf[motor]["stage_type"] @@ -355,10 +358,19 @@ def get_position_clicked( def main(): - app = QApplication(sys.argv) + try: + if os.environ["TEST"]: + path = Path(__file__).parent / "test" + config_path = path / "test_configuration.yaml" + interface = SerialInterfaceMock + + except KeyError: + path = Path(__file__).parent + config_path = path / "configuration.yaml" + interface = SerialInterface - path = os.path.dirname(motor_stage_ui.__file__) - window = MainWindow(path + "/configuration.yaml") + app = QApplication(sys.argv) + window = MainWindow(config_path, interface=interface) window.show() app.exec() diff --git a/motor_stage_ui/motor_stage_terminal.py b/motor_stage_ui/motor_stage_terminal.py index 6b7d574..5e05e58 100644 --- a/motor_stage_ui/motor_stage_terminal.py +++ b/motor_stage_ui/motor_stage_terminal.py @@ -1,6 +1,9 @@ from motor_stage_ui.pi_stages_interface import PIStagesInterface as MC import motor_stage_ui +from motor_stage_ui.pi_stages_interface import SerialInterface +from motor_stage_ui.test.utils import SerialInterfaceMock +from pathlib import Path import os import click import yaml @@ -23,12 +26,24 @@ def motor(conf): Args: conf (str): Needs path to configuration yaml. This path is converted into a click object and passed to the individual functions. """ + try: + if os.environ["TEST"]: + path = Path(__file__).parent / "test" + config_path = path / "test_configuration.yaml" + test = True + + except KeyError: + path = Path(__file__).parent + config_path = path / "configuration.yaml" + test = False conf.ensure_object(dict) - path = os.path.dirname(motor_stage_ui.__file__) - config_path = path + "/configuration.yaml" with open(config_path, "r") as file: conf.obj["CONF"] = yaml.full_load(file) + if test: + conf.obj["MOCK"] = True + else: + conf.obj["MOCK"] = False @click.command() @@ -40,9 +55,14 @@ def init(conf, motor_name: str): Args: motor_name (str): name of the motorstage """ + if conf.obj["MOCK"]: + interface = SerialInterfaceMock + else: + interface = SerialInterface mc = MC( port=conf.obj["CONF"][motor_name]["port"], baud_rate=conf.obj["CONF"][motor_name]["baud_rate"], + interface=interface, ) mc.init_motor(conf.obj["CONF"][motor_name]["address"]) @@ -59,9 +79,14 @@ def move(conf, motor_name: str, a: str): motor_name (str): name of the motorstage a (str): Move amount """ + if conf.obj["MOCK"]: + interface = SerialInterfaceMock + else: + interface = SerialInterface mc = MC( port=conf.obj["CONF"][motor_name]["port"], baud_rate=conf.obj["CONF"][motor_name]["baud_rate"], + interface=interface, ) mc.move_relative( conf.obj["CONF"][motor_name]["address"], @@ -84,9 +109,14 @@ def moveto(conf, motor_name: str, a: str): motor_name (str): name of the motorstage a (str): Move to position """ + if conf.obj["MOCK"]: + interface = SerialInterfaceMock + else: + interface = SerialInterface mc = MC( port=conf.obj["CONF"][motor_name]["port"], baud_rate=conf.obj["CONF"][motor_name]["baud_rate"], + interface=interface, ) mc.move_to_position( conf.obj["CONF"][motor_name]["address"], @@ -106,9 +136,14 @@ def pos(conf, motor_name: str): Args: motor_name (str): name of the motorstage """ + if conf.obj["MOCK"]: + interface = SerialInterfaceMock + else: + interface = SerialInterface mc = MC( port=conf.obj["CONF"][motor_name]["port"], baud_rate=conf.obj["CONF"][motor_name]["baud_rate"], + interface=interface, ) click.echo( "Position of: " @@ -119,7 +154,7 @@ def pos(conf, motor_name: str): conf.obj["CONF"][motor_name]["address"], conf.obj["CONF"][motor_name]["unit"], conf.obj["CONF"][motor_name]["stage_type"], - conf.obj["CONF"][motor_name]["step_size"], + float(conf.obj["CONF"][motor_name]["step_size"]), ) ) + " " @@ -136,9 +171,14 @@ def stop(conf, motor_name: str): Args: motor_name (str): name of the motorstage """ + if conf.obj["MOCK"]: + interface = SerialInterfaceMock + else: + interface = SerialInterface mc = MC( port=conf.obj["CONF"][motor_name]["port"], baud_rate=conf.obj["CONF"][motor_name]["baud_rate"], + interface=interface, ) mc.abort(conf.obj["CONF"][motor_name]["address"]) @@ -152,9 +192,14 @@ def sethome(conf, motor_name: str): Args: motor_name (str): name of the motorstage """ + if conf.obj["MOCK"]: + interface = SerialInterfaceMock + else: + interface = SerialInterface mc = MC( port=conf.obj["CONF"][motor_name]["port"], baud_rate=conf.obj["CONF"][motor_name]["baud_rate"], + interface=interface, ) mc.set_home(conf.obj["CONF"][motor_name]["address"]) @@ -168,9 +213,14 @@ def gohome(conf, motor_name: str): Args: motor_name (str): name of the motorstage """ + if conf.obj["MOCK"]: + interface = SerialInterfaceMock + else: + interface = SerialInterface mc = MC( port=conf.obj["CONF"][motor_name]["port"], baud_rate=conf.obj["CONF"][motor_name]["baud_rate"], + interface=interface, ) mc.go_home(conf.obj["CONF"][motor_name]["address"]) @@ -184,11 +234,21 @@ def status(conf, motor_name: str): Args: motor_name (str): name of the motorstage """ + if conf.obj["MOCK"]: + interface = SerialInterfaceMock + else: + interface = SerialInterface mc = MC( port=conf.obj["CONF"][motor_name]["port"], baud_rate=conf.obj["CONF"][motor_name]["baud_rate"], + interface=interface, + ) + click.echo( + "Status of: " + + motor_name + + " " + + str(mc.get_stat(conf.obj["CONF"][motor_name]["address"])) ) - mc.get_stat(conf.obj["CONF"][motor_name]["address"]) motor.add_command(init) diff --git a/motor_stage_ui/pi_stages_interface.py b/motor_stage_ui/pi_stages_interface.py index b43a42d..095794c 100644 --- a/motor_stage_ui/pi_stages_interface.py +++ b/motor_stage_ui/pi_stages_interface.py @@ -199,9 +199,8 @@ def get_stat(self, address: int) -> None: address (int): Address of the motorstage """ err_msg = self._write_read("TS", address) - self.log.warning( - "Status of motor stage with address %i: %s" % (address, err_msg) - ) + self.log.debug("Status of motor stage with address %i: %s" % (address, err_msg)) + return err_msg def abort(self, address: int) -> None: """Stops all movement of the motorstage. @@ -269,7 +268,7 @@ def get_position( address (int): Address of the motorstage unit (str): output unit stage (int): stage type either 'rotation' or translation - step_size (int): step size of the motorstage given in deg or um + step_size (float): step size of the motorstage given in deg or um Returns: str: current position of motorstage in unit 3 digits precision respectively @@ -329,7 +328,7 @@ def _get_position(self, address: int) -> None: return msg def _calculate_value( - self, amount: str, unit: str, stage: str, step_size: float + self, amount: str, unit: str, stage: str, step_size: float | str ) -> int: """Calculates number of motor steps from a given input amount and the given units @@ -344,12 +343,12 @@ def _calculate_value( """ if stage == "translation": try: - pos = self.ureg(amount).to("um").magnitude / step_size + pos = self.ureg(amount).to("um").magnitude / float(step_size) except: - pos = self.ureg(amount + unit).to("um").magnitude / step_size + pos = self.ureg(amount + unit).to("um").magnitude / float(step_size) if stage == "rotation": try: - pos = self.ureg(amount).to("deg").magnitude / step_size + pos = self.ureg(amount).to("deg").magnitude / float(step_size) except: - pos = self.ureg(amount + unit).to("deg").magnitude / step_size + pos = self.ureg(amount + unit).to("deg").magnitude / float(step_size) return pos diff --git a/motor_stage_ui/test/test_gui.py b/motor_stage_ui/test/test_gui.py new file mode 100644 index 0000000..2518c0d --- /dev/null +++ b/motor_stage_ui/test/test_gui.py @@ -0,0 +1,192 @@ +from pathlib import Path +import yaml +import pytest +from motor_stage_ui.motor_stage_gui import MainWindow +from motor_stage_ui.test.utils import SerialInterfaceMock +from PyQt5 import QtCore +import logging + +FILEPATH = Path(__file__).parent +CONFIG_FILE = FILEPATH / "test_configuration.yaml" + +INTERFACE = SerialInterfaceMock +with open(CONFIG_FILE) as yaml_file: + TESTCONFIG = yaml.safe_load(yaml_file) + + +@pytest.fixture +def app(qtbot): + motor_gui = MainWindow(CONFIG_FILE, interface=INTERFACE) + return motor_gui + + +def test_init_clicked(app): + ADDRESS = TESTCONFIG["x_axis"]["address"] + app.init_clicked(address=ADDRESS, index=0) + assert app.motor[0].serial_interface._serial_commands[-3:] == [ + b"\x010MN\r", + b"\x010RT\r", + b"\x010SV200000\r", + ] + + ADDRESS = TESTCONFIG["rot"]["address"] + app.init_clicked(address=ADDRESS, index=1) + assert app.motor[1].serial_interface._serial_commands[-3:] == [ + b"\x012MN\r", + b"\x012RT\r", + b"\x012SV200000\r", + ] + + +def test_move_back_clicked(app): + ADDRESS = TESTCONFIG["x_axis"]["address"] + UNIT = TESTCONFIG["x_axis"]["unit"] + STEPSIZE = TESTCONFIG["x_axis"]["step_size"] + STAGE = TESTCONFIG["x_axis"]["stage_type"] + app.move_back_clicked( + address=ADDRESS, unit=UNIT, stage=STAGE, step_size=STEPSIZE, index=0 + ) + assert app.motor[0].serial_interface._serial_commands[-1] == b"\x010MR-55555\r" + + ADDRESS = TESTCONFIG["rot"]["address"] + UNIT = TESTCONFIG["rot"]["unit"] + STEPSIZE = float(TESTCONFIG["rot"]["step_size"]) + STAGE = TESTCONFIG["rot"]["stage_type"] + app.move_back_clicked( + address=ADDRESS, unit=UNIT, stage=STAGE, step_size=STEPSIZE, index=1 + ) + assert app.motor[1].serial_interface._serial_commands[-1] == b"\x012MR-29411\r" + + +def test_move_ahead_clicked(app): + ADDRESS = TESTCONFIG["x_axis"]["address"] + UNIT = TESTCONFIG["x_axis"]["unit"] + STEPSIZE = TESTCONFIG["x_axis"]["step_size"] + STAGE = TESTCONFIG["x_axis"]["stage_type"] + app.move_ahead_clicked( + address=ADDRESS, unit=UNIT, stage=STAGE, step_size=STEPSIZE, index=0 + ) + assert app.motor[0].serial_interface._serial_commands[-1] == b"\x010MR55555\r" + + ADDRESS = TESTCONFIG["rot"]["address"] + UNIT = TESTCONFIG["rot"]["unit"] + STEPSIZE = float(TESTCONFIG["rot"]["step_size"]) + STAGE = TESTCONFIG["rot"]["stage_type"] + app.move_ahead_clicked( + address=ADDRESS, unit=UNIT, stage=STAGE, step_size=STEPSIZE, index=1 + ) + assert app.motor[1].serial_interface._serial_commands[-1] == b"\x012MR29411\r" + + +def test_abort_clicked(app): + ADDRESS = TESTCONFIG["x_axis"]["address"] + app.abort_clicked(address=ADDRESS, index=0) + assert app.motor[0].serial_interface._serial_commands[-1] == b"\x010AB\r" + + ADDRESS = TESTCONFIG["rot"]["address"] + app.abort_clicked(address=ADDRESS, index=1) + assert app.motor[1].serial_interface._serial_commands[-1] == b"\x012AB\r" + + +def test_set_home_clicked(app): + ADDRESS = TESTCONFIG["x_axis"]["address"] + app.set_home_clicked(address=ADDRESS, index=0) + assert app.motor[0].serial_interface._serial_commands[-1] == b"\x010DH\r" + + ADDRESS = TESTCONFIG["rot"]["address"] + app.set_home_clicked(address=ADDRESS, index=1) + assert app.motor[1].serial_interface._serial_commands[-1] == b"\x012DH\r" + + +def test_go_home_clicked(app): + ADDRESS = TESTCONFIG["x_axis"]["address"] + app.go_home_clicked(address=ADDRESS, index=0) + assert app.motor[0].serial_interface._serial_commands[-1] == b"\x010GH\r" + + ADDRESS = TESTCONFIG["rot"]["address"] + app.go_home_clicked(address=ADDRESS, index=1) + assert app.motor[1].serial_interface._serial_commands[-1] == b"\x012GH\r" + + +def test_set_position_abs_clicked(app): + ADDRESS = TESTCONFIG["x_axis"]["address"] + UNIT = TESTCONFIG["x_axis"]["unit"] + STEPSIZE = TESTCONFIG["x_axis"]["step_size"] + STAGE = TESTCONFIG["x_axis"]["stage_type"] + app.set_position_abs_clicked( + address=ADDRESS, + textbox="2cm", + unit=UNIT, + stage=STAGE, + step_size=STEPSIZE, + index=0, + ) + assert app.motor[0].serial_interface._serial_commands[-1] == b"\x010MA1111111\r" + + ADDRESS = TESTCONFIG["rot"]["address"] + UNIT = TESTCONFIG["rot"]["unit"] + STEPSIZE = float(TESTCONFIG["rot"]["step_size"]) + STAGE = TESTCONFIG["rot"]["stage_type"] + app.set_position_abs_clicked( + address=ADDRESS, + textbox="1deg", + unit=UNIT, + stage=STAGE, + step_size=STEPSIZE, + index=1, + ) + assert app.motor[1].serial_interface._serial_commands[-1] == b"\x012MA29411\r" + + +def test_set_position_rel_clicked(app): + ADDRESS = TESTCONFIG["x_axis"]["address"] + UNIT = TESTCONFIG["x_axis"]["unit"] + STEPSIZE = TESTCONFIG["x_axis"]["step_size"] + STAGE = TESTCONFIG["x_axis"]["stage_type"] + app.set_position_rel_clicked( + address=ADDRESS, + textbox="2cm", + unit=UNIT, + stage=STAGE, + step_size=STEPSIZE, + index=0, + ) + assert app.motor[0].serial_interface._serial_commands[-1] == b"\x010MR1111111\r" + + ADDRESS = TESTCONFIG["rot"]["address"] + UNIT = TESTCONFIG["rot"]["unit"] + STEPSIZE = float(TESTCONFIG["rot"]["step_size"]) + STAGE = TESTCONFIG["rot"]["stage_type"] + app.set_position_rel_clicked( + address=ADDRESS, + textbox="1.5deg", + unit=UNIT, + stage=STAGE, + step_size=STEPSIZE, + index=1, + ) + assert app.motor[1].serial_interface._serial_commands[-1] == b"\x012MR44117\r" + + +def test_get_position_clicked(app): + ADDRESS = TESTCONFIG["x_axis"]["address"] + UNIT = TESTCONFIG["x_axis"]["unit"] + STEPSIZE = TESTCONFIG["x_axis"]["step_size"] + STAGE = TESTCONFIG["x_axis"]["stage_type"] + app.get_position_clicked( + address=ADDRESS, unit=UNIT, stage=STAGE, step_size=STEPSIZE, index=0 + ) + assert app.motor[0].serial_interface._serial_commands[-1] == b"\x010TP\r" + + ADDRESS = TESTCONFIG["rot"]["address"] + UNIT = TESTCONFIG["rot"]["unit"] + STEPSIZE = float(TESTCONFIG["rot"]["step_size"]) + STAGE = TESTCONFIG["rot"]["stage_type"] + app.get_position_clicked( + address=ADDRESS, unit=UNIT, stage=STAGE, step_size=STEPSIZE, index=1 + ) + assert app.motor[1].serial_interface._serial_commands[-1] == b"\x012TP\r" + + +if __name__ == "__main__": + pytest.main() diff --git a/motor_stage_ui/test/test_pi_stage_interface.py b/motor_stage_ui/test/test_pi_stage_interface.py index 6d351bc..cf071c0 100644 --- a/motor_stage_ui/test/test_pi_stage_interface.py +++ b/motor_stage_ui/test/test_pi_stage_interface.py @@ -1,5 +1,4 @@ from pathlib import Path -import os import pytest import yaml from motor_stage_ui.pi_stages_interface import PIStagesInterface @@ -40,34 +39,32 @@ def test_init_motor(): ] +def test_motor_off(): + ADDRESS = TESTCONFIG["x_axis"]["address"] + PISTAGES.motor_off(address=ADDRESS) + assert PISTAGES.serial_interface._serial_commands[-1] == b"\x010MF\r" + + ADDRESS = TESTCONFIG["rot"]["address"] + PISTAGES.motor_off(address=ADDRESS) + assert PISTAGES.serial_interface._serial_commands[-1] == b"\x012MF\r" + + def test_find_edge(): ADDRESS = TESTCONFIG["x_axis"]["address"] UNIT = TESTCONFIG["x_axis"]["unit"] STEPSIZE = TESTCONFIG["x_axis"]["step_size"] STAGE = TESTCONFIG["x_axis"]["stage_type"] - def __get_position(self, address): - # Blank get Position function - return 0 - - func_type = type(PISTAGES._get_position) - PISTAGES._get_position = func_type(__get_position, PISTAGES) PISTAGES.find_edge(address=ADDRESS, unit=UNIT, stage=STAGE, step_size=STEPSIZE) - assert PISTAGES.serial_interface._serial_commands[-1] == b"\x010FE0\r" + assert PISTAGES.serial_interface._serial_commands[-1] == b"\x010TP\r" ADDRESS = TESTCONFIG["rot"]["address"] UNIT = TESTCONFIG["rot"]["unit"] STEPSIZE = float(TESTCONFIG["rot"]["step_size"]) STAGE = TESTCONFIG["rot"]["stage_type"] - def __get_position(self, address): - # Blank get Position function - return 0 - - func_type = type(PISTAGES._get_position) - PISTAGES._get_position = func_type(__get_position, PISTAGES) PISTAGES.find_edge(address=ADDRESS, unit=UNIT, stage=STAGE, step_size=STEPSIZE) - assert PISTAGES.serial_interface._serial_commands[-1] == b"\x012FE0\r" + assert PISTAGES.serial_interface._serial_commands[-1] == b"\x012TP\r" def test_set_home() -> None: @@ -146,12 +143,6 @@ def test_get_position(): STEPSIZE = TESTCONFIG["x_axis"]["step_size"] STAGE = TESTCONFIG["x_axis"]["stage_type"] - def __get_position(self, address): - # Blank get Position function - return 0 - - func_type = type(PISTAGES._get_position) - PISTAGES._get_position = func_type(__get_position, PISTAGES) assert ( PISTAGES.get_position( address=ADDRESS, unit=UNIT, stage=STAGE, step_size=STEPSIZE @@ -164,12 +155,6 @@ def __get_position(self, address): STEPSIZE = float(TESTCONFIG["rot"]["step_size"]) STAGE = TESTCONFIG["rot"]["stage_type"] - def __get_position(self, address): - # Blank get Position function - return 0 - - func_type = type(PISTAGES._get_position) - PISTAGES._get_position = func_type(__get_position, PISTAGES) assert ( PISTAGES.get_position( address=ADDRESS, unit=UNIT, stage=STAGE, step_size=STEPSIZE diff --git a/motor_stage_ui/test/test_terminal_ui.py b/motor_stage_ui/test/test_terminal_ui.py new file mode 100644 index 0000000..35a1bc0 --- /dev/null +++ b/motor_stage_ui/test/test_terminal_ui.py @@ -0,0 +1,80 @@ +from click.testing import CliRunner +import os +import motor_stage_ui.motor_stage_terminal as terminal_ui + + +os.environ["TEST"] = "True" + + +def test_motor(): + runner = CliRunner() + result = runner.invoke(terminal_ui.motor) + assert result.exit_code == 0 + + +def test_init(): + runner = CliRunner() + result = runner.invoke(terminal_ui.motor, ["init", "x_axis"]) + assert result.exit_code == 0 + result = runner.invoke(terminal_ui.motor, ["init", "rot"]) + assert result.exit_code == 0 + + +def test_move(): + runner = CliRunner() + result = runner.invoke(terminal_ui.motor, ["move", "-a", "2cm", "x_axis"]) + assert result.exit_code == 0 + result = runner.invoke(terminal_ui.motor, ["move", "-a", "2deg", "rot"]) + assert result.exit_code == 0 + + +def test_moveto(): + runner = CliRunner() + result = runner.invoke(terminal_ui.motor, ["moveto", "-a", "-1cm", "x_axis"]) + assert result.exit_code == 0 + result = runner.invoke(terminal_ui.motor, ["moveto", "-a", "-1deg", "rot"]) + assert result.exit_code == 0 + + +def test_pos(): + runner = CliRunner() + result = runner.invoke(terminal_ui.motor, ["pos", "x_axis"]) + assert result.exit_code == 0 + assert result.output == "Position of: x_axis 0.000 mm\n" + result = runner.invoke(terminal_ui.motor, ["pos", "rot"]) + assert result.exit_code == 0 + assert result.output == "Position of: rot 0.000 deg\n" + + +def test_stop(): + runner = CliRunner() + result = runner.invoke(terminal_ui.motor, ["stop", "x_axis"]) + assert result.exit_code == 0 + result = runner.invoke(terminal_ui.motor, ["stop", "rot"]) + assert result.exit_code == 0 + + +def test_sethome(): + runner = CliRunner() + result = runner.invoke(terminal_ui.motor, ["sethome", "x_axis"]) + assert result.exit_code == 0 + result = runner.invoke(terminal_ui.motor, ["sethome", "rot"]) + assert result.exit_code == 0 + + +def test_gohome(): + runner = CliRunner() + result = runner.invoke(terminal_ui.motor, ["gohome", "x_axis"]) + assert result.exit_code == 0 + result = runner.invoke(terminal_ui.motor, ["gohome", "rot"]) + assert result.exit_code == 0 + + +def test_status(): + runner = CliRunner() + result = runner.invoke(terminal_ui.motor, ["status", "x_axis"]) + assert result.exit_code == 0 + assert result.output == "Status of: x_axis \x010TS\n" + result = runner.invoke(terminal_ui.motor, ["status", "rot"]) + assert result.exit_code == 0 + assert result.output == "Status of: rot \x012TS\n" diff --git a/motor_stage_ui/test/utils.py b/motor_stage_ui/test/utils.py index f7fea3b..00ef375 100644 --- a/motor_stage_ui/test/utils.py +++ b/motor_stage_ui/test/utils.py @@ -37,6 +37,8 @@ def _read(self): str: message """ msg = self._serial_commands[-1].decode().strip().replace(self._terminator, "") + if msg[-2:] == "TP": + msg = "0.000" if msg == "": self.log.error("No responds from serial interface.") raise ValueError diff --git a/pyproject.toml b/pyproject.toml index d1d38f2..d3a026d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "motor_stage_ui" -version = "1.1" +version = "1.2.0" license = { "text" = "AGPL-3.0" } description = "User interface for the Mercury motor controller" readme = {file = "README.md", content-type = "text/markdown"} @@ -22,7 +22,7 @@ dependencies = [ ] [project.optional-dependencies] -test = ["pytest", "coverage"] +test = ["pytest", "coverage", "pytest-qt"] [project.urls] "Repository" = "https://github.com/SiLab-Bonn/motor_stage_ui"