test: lift diff coverage over the 80 percent gate
CI / lint (push) Skipped
CI / test (3.11) (push) Skipped
CI / test (3.12) (push) Skipped
CI / test (3.13) (push) Skipped
CI / test-with-beamline-plugins (pxi_bec) (push) Skipped
CI / test-with-beamline-plugins (pxii_bec) (push) Skipped
CI / test-with-beamline-plugins (pxiii_bec) (push) Skipped
CI / lint (pull_request) Successful in 1m3s
CI / test (3.11) (pull_request) Successful in 1m25s
CI / test (3.12) (pull_request) Successful in 1m25s
CI / test (3.13) (pull_request) Successful in 1m24s
CI / test-with-beamline-plugins (pxi_bec) (pull_request) Successful in 1m32s
CI / test-with-beamline-plugins (pxiii_bec) (pull_request) Successful in 1m37s
CI / test-with-beamline-plugins (pxii_bec) (pull_request) Successful in 1m41s
CI / test-with-coverage (pull_request) Successful in 2m4s
CI / coverage-analysis (pull_request) Successful in 3s
CI / lint (push) Skipped
CI / test (3.11) (push) Skipped
CI / test (3.12) (push) Skipped
CI / test (3.13) (push) Skipped
CI / test-with-beamline-plugins (pxi_bec) (push) Skipped
CI / test-with-beamline-plugins (pxii_bec) (push) Skipped
CI / test-with-beamline-plugins (pxiii_bec) (push) Skipped
CI / lint (pull_request) Successful in 1m3s
CI / test (3.11) (pull_request) Successful in 1m25s
CI / test (3.12) (pull_request) Successful in 1m25s
CI / test (3.13) (pull_request) Successful in 1m24s
CI / test-with-beamline-plugins (pxi_bec) (pull_request) Successful in 1m32s
CI / test-with-beamline-plugins (pxiii_bec) (pull_request) Successful in 1m37s
CI / test-with-beamline-plugins (pxii_bec) (pull_request) Successful in 1m41s
CI / test-with-coverage (pull_request) Successful in 2m4s
CI / coverage-analysis (pull_request) Successful in 3s
Promote the illumination panel's __main__ self-check to a real pytest file so CI counts it, add busy-overlay render checks (status text paints, video view routes through it), and cover the motor group's incomplete-entry and below-min Enter paths. Local diff coverage from the gui suite alone: 82 percent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -84,50 +84,3 @@ class IlluminationPanel(QWidget):
|
||||
slider.blockSignals(True)
|
||||
slider.setValue(round(val))
|
||||
slider.blockSignals(False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# ponytail: smallest check that fails if the send/readback wiring breaks
|
||||
import os
|
||||
from types import SimpleNamespace
|
||||
|
||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
from PySide6.QtCore import QPoint, QPointF
|
||||
from PySide6.QtGui import QWheelEvent
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
app = QApplication([])
|
||||
panel = IlluminationPanel()
|
||||
sent = []
|
||||
panel.front_light.connect(sent.append)
|
||||
|
||||
# wheel notch must emit (was silently discarded before)
|
||||
panel.front_light_slider.setFocus()
|
||||
QApplication.sendEvent(
|
||||
panel.front_light_slider,
|
||||
QWheelEvent(
|
||||
QPointF(5, 5),
|
||||
QPointF(5, 5),
|
||||
QPoint(0, 0),
|
||||
QPoint(0, 120),
|
||||
Qt.MouseButton.RightButton,
|
||||
Qt.KeyboardModifier.NoModifier,
|
||||
Qt.ScrollPhase.NoScrollPhase,
|
||||
False,
|
||||
),
|
||||
)
|
||||
assert sent, "wheel adjustment must emit front_light"
|
||||
|
||||
# readback right after a user change must not clobber the slider
|
||||
held = panel.front_light_slider.value()
|
||||
status = SimpleNamespace(bl=SimpleNamespace(front_light=90.0, back_light=10.0))
|
||||
panel.update_daq_status(status)
|
||||
assert panel.front_light_slider.value() == held, "grace window must hold user value"
|
||||
|
||||
# after the grace window, readback applies without emitting a PUT
|
||||
panel._last_user_change = time.monotonic() - READBACK_GRACE_S - 1
|
||||
n_sent = len(sent)
|
||||
panel.update_daq_status(status)
|
||||
assert panel.front_light_slider.value() == 90, "readback must apply after grace"
|
||||
assert len(sent) == n_sent, "readback must not echo a PUT"
|
||||
print("gude")
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
"""The Axis views' busy flag is a passive status: plain shadowed text, not
|
||||
the badge pill (which reads as a button). These checks fail if the text
|
||||
renderer stops painting or the video view stops routing through it."""
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QPainter, QPixmap
|
||||
|
||||
from aare.gui.widgets.busy_overlay import build_busy_overlay_style, draw_busy_status_text
|
||||
from aare.gui.widgets.video_image import VideoGraphicsView
|
||||
|
||||
|
||||
def _busy_style():
|
||||
style = build_busy_overlay_style(is_busy=True, tell_state=None)
|
||||
assert style is not None and style.text == "BEAMLINE BUSY"
|
||||
return style
|
||||
|
||||
|
||||
def test_status_text_paints_something(qapp):
|
||||
pixmap = QPixmap(400, 300)
|
||||
pixmap.fill(Qt.GlobalColor.black)
|
||||
blank = pixmap.toImage()
|
||||
painter = QPainter(pixmap)
|
||||
draw_busy_status_text(painter, 400, 300, _busy_style())
|
||||
painter.end()
|
||||
assert pixmap.toImage() != blank, "busy text must actually paint"
|
||||
|
||||
|
||||
def test_video_view_paints_busy_text(qtbot):
|
||||
view = VideoGraphicsView()
|
||||
qtbot.addWidget(view)
|
||||
view.resize(400, 300)
|
||||
idle = view.grab().toImage()
|
||||
view.set_busy_overlay_style(_busy_style())
|
||||
assert view.grab().toImage() != idle, "busy style must change the rendered view"
|
||||
view.set_busy_overlay_style(None)
|
||||
assert view.grab().toImage() == idle, "clearing the style must restore the view"
|
||||
@@ -0,0 +1,80 @@
|
||||
"""Send/readback wiring for the light sliders (was a __main__ self-check in
|
||||
the panel; promoted here so CI counts it). Every deliberate input path must
|
||||
emit, and the status-poll readback must neither clobber a fresh user change
|
||||
nor echo a PUT back to the server."""
|
||||
|
||||
import time
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
from PySide6.QtCore import QPoint, QPointF, Qt
|
||||
from PySide6.QtGui import QWheelEvent
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
from aare.gui.panels.illumination_panel import READBACK_GRACE_S, IlluminationPanel
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def panel(qtbot):
|
||||
panel = IlluminationPanel()
|
||||
qtbot.addWidget(panel)
|
||||
return panel
|
||||
|
||||
|
||||
def _wheel_notch(slider):
|
||||
# One wheel notch; sliderReleased never fires for this path, which is
|
||||
# exactly the case the valueChanged wiring exists for.
|
||||
QApplication.sendEvent(
|
||||
slider,
|
||||
QWheelEvent(
|
||||
QPointF(5, 5),
|
||||
QPointF(5, 5),
|
||||
QPoint(0, 0),
|
||||
QPoint(0, 120),
|
||||
Qt.MouseButton.NoButton,
|
||||
Qt.KeyboardModifier.NoModifier,
|
||||
Qt.ScrollPhase.NoScrollPhase,
|
||||
False,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _status(front=90.0, back=10.0):
|
||||
return SimpleNamespace(bl=SimpleNamespace(front_light=front, back_light=back))
|
||||
|
||||
|
||||
def test_wheel_notch_emits_both_lights(panel):
|
||||
front, back = [], []
|
||||
panel.front_light.connect(front.append)
|
||||
panel.back_light.connect(back.append)
|
||||
_wheel_notch(panel.front_light_slider)
|
||||
_wheel_notch(panel.back_light_slider)
|
||||
assert front, "wheel adjustment must emit front_light"
|
||||
assert back, "wheel adjustment must emit back_light"
|
||||
|
||||
|
||||
def test_readback_grace_holds_user_value(panel):
|
||||
_wheel_notch(panel.front_light_slider)
|
||||
held = panel.front_light_slider.value()
|
||||
panel.update_daq_status(_status())
|
||||
assert panel.front_light_slider.value() == held, "grace window must hold user value"
|
||||
|
||||
|
||||
def test_readback_applies_after_grace_without_echo(panel):
|
||||
sent = []
|
||||
panel.front_light.connect(sent.append)
|
||||
panel._last_user_change = time.monotonic() - READBACK_GRACE_S - 1
|
||||
panel.update_daq_status(_status())
|
||||
assert panel.front_light_slider.value() == 90, "readback must apply after grace"
|
||||
assert panel.back_light_slider.value() == 10
|
||||
assert not sent, "readback must not echo a PUT"
|
||||
|
||||
|
||||
def test_readback_skipped_while_sliding(panel):
|
||||
panel.on_slider_pressed()
|
||||
panel._last_user_change = 0.0
|
||||
panel.update_daq_status(_status(front=55.0))
|
||||
assert panel.front_light_slider.value() != 55
|
||||
panel.on_slider_released()
|
||||
panel.update_daq_status(_status(front=55.0))
|
||||
assert panel.front_light_slider.value() == 55
|
||||
@@ -3,6 +3,7 @@ targets are staged (orange), sent only by the Move button (green), and the
|
||||
box returns to neutral when the motor actually arrives."""
|
||||
|
||||
import pytest
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
from aare.gui.widgets.motor_move_group import MotorMoveGroup
|
||||
from aare.gui.widgets.number_line_edit import NumberLineEdit
|
||||
@@ -65,3 +66,21 @@ def test_over_max_is_invalid_and_never_sent(chi, qtbot):
|
||||
group.button.click()
|
||||
assert not sent # out-of-range target is never applied
|
||||
assert box.property("movestate") == "pending"
|
||||
|
||||
|
||||
def test_incomplete_entry_stays_pending(chi, qtbot):
|
||||
_group, box = chi
|
||||
_type(qtbot, box, "-") # not a number (yet): no tips, no crash
|
||||
assert box.property("movestate") == "pending"
|
||||
# Enter on the incomplete text must be swallowed just as quietly
|
||||
qtbot.keyClick(box, Qt.Key.Key_Return)
|
||||
assert box.property("movestate") == "pending"
|
||||
|
||||
|
||||
def test_below_min_enter_hints_but_never_sends(chi, qtbot):
|
||||
group, box = chi
|
||||
sent = []
|
||||
group.applied.connect(sent.append)
|
||||
_type(qtbot, box, "-5") # below the -0.2 bottom
|
||||
qtbot.keyClick(box, Qt.Key.Key_Return) # triggers the min-value tooltip path
|
||||
assert not sent # Enter must never start a move
|
||||
|
||||
Reference in New Issue
Block a user