Files
orisys-app/apps/autocal/tests/test_units_page.py
T
ykcheungab 22658dbfc1 autocal: 新增单位与换算页(默认锁定)及轴页报警与事件日志
- 设备设置新增“单位与换算”页:显示各轴 UNITS、读回值、脉冲/mm 换算与核对状态;
  默认锁定,点击“修改”后才可编辑,应用前弹窗确认,旋转轴偏离标定值时额外警告
- 轴调试页:脉冲当量改为只读并提供入口;新增报警与事件日志(报警、使能、
  回零、读取失败、单位不符等)及单位核对摘要
- 新增 motion/unit_report.py、motion/events.py 及对应测试
2026-10-19 10:14:55 +08:00

214 lines
7.3 KiB
Python

"""The units page (locked by default) and the axis page's alarm and event log.
Interface tests: they drive the widgets an operator uses. The controller is never
reached; snapshots are built by hand, and ``QMessageBox.question`` is answered by the
test, so nothing here opens a dialog.
"""
from __future__ import annotations
import pytest
from orisys_app_shared.i18n import t
from orisys_autocal.motion import AxisState, Snapshot
from orisys_autocal.motion.models import AxisStatusBits
from orisys_autocal.motion.service import DEFAULT_AXIS_ROLES
from orisys_autocal.motion.unit_report import build_unit_rows
from PySide6.QtWidgets import QMessageBox
ROLES = dict(DEFAULT_AXIS_ROLES)
def _snapshot(*, connected: bool = True, alarm_axis: int | None = None, units: float = 10.0):
axes = tuple(
AxisState(
axis=axis,
enabled=True,
units=units,
status=AxisStatusBits(512 if axis == alarm_axis else 0),
read_ok=True,
)
for axis in range(5)
)
return Snapshot(connected=connected, axes=axes)
def _rows(connected: bool = True, expected: float = 10.0):
return build_unit_rows(ROLES, lambda axis: expected, _snapshot(connected=connected))
def _answer(monkeypatch, button) -> list[str]:
"""Answer the confirmation with ``button`` and record what it asked."""
asked: list[str] = []
def question(parent, title, body, *args, **kwargs):
asked.append(body)
return button
monkeypatch.setattr(QMessageBox, "question", question)
return asked
def test_the_page_is_locked_until_the_operator_presses_modify(make_window):
window = make_window()
page = window.device_setup.units_page
page.set_rows(_rows(connected=False), False)
assert page.locked
assert all(edit.isReadOnly() for edit in page._edits.values()) # noqa: SLF001
# Nothing to change on a machine that is not connected.
assert not page.modify_button.isEnabled()
page.set_rows(_rows(), True)
assert page.modify_button.isEnabled()
page.modify_button.click()
assert not page.locked
assert not any(edit.isReadOnly() for edit in page._edits.values()) # noqa: SLF001
assert page.apply_button.isEnabled()
window.close()
def test_a_confirmed_change_is_emitted_and_the_page_locks_again(make_window, monkeypatch):
window = make_window()
page = window.device_setup.units_page
page.set_rows(_rows(), True)
emitted: list[dict] = []
page.unitsApplied.connect(emitted.append)
asked = _answer(monkeypatch, QMessageBox.StandardButton.Yes)
page.modify_button.click()
page._edits["x"].setText("5") # noqa: SLF001
page.apply_button.click()
assert emitted == [{0: 5.0}]
assert page.locked
assert "5" in asked[0] and "10" in asked[0]
window.close()
def test_declining_the_confirmation_changes_nothing(make_window, monkeypatch):
window = make_window()
page = window.device_setup.units_page
page.set_rows(_rows(), True)
emitted: list[dict] = []
page.unitsApplied.connect(emitted.append)
_answer(monkeypatch, QMessageBox.StandardButton.No)
page.modify_button.click()
page._edits["y"].setText("20") # noqa: SLF001
page.apply_button.click()
assert emitted == []
assert not page.locked # still the operator's to finish or cancel
page.cancel_button.click()
assert page.locked
assert page._edits["y"].text() == "10" # noqa: SLF001
window.close()
def test_a_rotary_value_off_the_calibration_is_warned_about(make_window, monkeypatch):
window = make_window()
page = window.device_setup.units_page
page.set_rows(_rows(), True)
asked = _answer(monkeypatch, QMessageBox.StandardButton.No)
page.modify_button.click()
page._edits["yaw"].setText("5") # noqa: SLF001
page.apply_button.click()
assert t("units.confirm_rotary", roles=t("setup.role_yaw"), cal="10") in asked[0]
window.close()
@pytest.mark.parametrize("bad", ["", "abc", "0", "-3", "nan", "inf"])
def test_an_unusable_value_is_refused_before_any_question(make_window, monkeypatch, bad):
window = make_window()
page = window.device_setup.units_page
page.set_rows(_rows(), True)
emitted: list[dict] = []
page.unitsApplied.connect(emitted.append)
asked = _answer(monkeypatch, QMessageBox.StandardButton.Yes)
page.modify_button.click()
page._edits["z"].setText(bad) # noqa: SLF001
page.apply_button.click()
assert emitted == [] and asked == []
assert t("units.invalid", role=t("setup.role_z")) == page.message_label.text()
window.close()
def test_applying_without_a_change_just_locks(make_window, monkeypatch):
window = make_window()
page = window.device_setup.units_page
page.set_rows(_rows(), True)
emitted: list[dict] = []
page.unitsApplied.connect(emitted.append)
asked = _answer(monkeypatch, QMessageBox.StandardButton.Yes)
page.modify_button.click()
page.apply_button.click()
assert emitted == [] and asked == []
assert page.locked
window.close()
def test_a_changed_axis_mapping_drops_an_open_edit(make_window):
window = make_window()
page = window.device_setup.units_page
page.set_rows(_rows(), True)
page.modify_button.click()
page._edits["x"].setText("5") # noqa: SLF001
swapped = dict(ROLES, x=1, y=0)
page.set_rows(build_unit_rows(swapped, lambda axis: 10.0, _snapshot()), True)
assert page.locked
assert page._edits["x"].text() == "10" # noqa: SLF001
window.close()
def test_the_units_tab_and_the_axis_page_are_wired(make_window, monkeypatch):
window = make_window()
monkeypatch.setattr(window.poller, "submit", lambda *args, **kwargs: 0)
panel = window.axis_panel
# The always-live form no longer takes a pulse equivalent.
assert panel.units_edit.isReadOnly()
# The button on the axis page opens the units tab.
panel.unitsRequested.emit()
assert window.device_setup.current_tab() == "units"
# A confirmed change goes out as an ordinary axis-parameter apply.
applied: list[tuple] = []
panel.paramsApplied.connect(lambda axis, params: applied.append((axis, params.units)))
window.device_setup.units_page.unitsApplied.emit({1: 5.0})
assert applied == [(1, 5.0)]
assert panel.expected_units(1) == 5.0
window.close()
def test_the_axis_page_logs_alarms_and_can_clear_the_log(make_window):
window = make_window()
panel = window.axis_panel
panel.update_snapshot(_snapshot())
assert panel.event_log.count() == 0
assert not panel.events_empty.isHidden()
panel.update_snapshot(_snapshot(alarm_axis=2))
assert panel.event_log.count() == 1
assert t("axes.ev_alarm_on", axis=2) in panel.event_log.item(0).text()
assert panel.events_empty.isHidden()
panel.clear_events_button.click()
assert panel.event_log.count() == 0
window.close()
def test_a_unit_mismatch_reaches_the_axis_page_summary(make_window):
window = make_window()
window.axis_panel.update_snapshot(_snapshot(units=5.0))
window._refresh_unit_report() # noqa: SLF001 - the window does this on every poll
assert t("units.summary_bad", bad=5, total=5) == window.axis_panel.units_chip.text()
window.axis_panel.update_snapshot(_snapshot(units=10.0))
window._refresh_unit_report() # noqa: SLF001
assert t("units.summary_ok", total=5) == window.axis_panel.units_chip.text()
window.close()