fix(gui): reach the file-exists guard from every tab, test exact names
The tabs are handed parent=DataCollectionSettings but QStackedWidget.addWidget() reparents them to the stack, so the guard's parent() lookup never found file_path_panel and every Run (rotation, screening, raster, X-ray centering, Simple) skipped the check. Lookup now walks up the widget tree; regression test drives all five runs through the real DataCollectionSettings. The "taken" test is exact again: <run>_master.h5 plus the DAQ's derived <run>_raster2d/_raster1d_master.h5. The directory test and the _*_master.h5 glob are gone: neither is a file a run writes, and they painted run numbers red that nothing would ever produce. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import glob
|
||||
import os
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
@@ -19,6 +18,22 @@ from aare.gui.widgets.title_label import TitleLabel
|
||||
## 5. If sample is registered in the database as manual, it is by default placed in <date>/manual/<sample name>
|
||||
|
||||
|
||||
def find_file_path_panel(widget) -> "FilePathPanel | None":
|
||||
"""The FilePathPanel that owns ``widget``'s tab, or None outside the GUI.
|
||||
|
||||
Walks up the widget tree: the scan panels are handed parent=DataCollectionSettings
|
||||
but QStackedWidget.addWidget() reparents them to the stack, so a plain
|
||||
parent() lookup finds no file_path_panel and the Run guard is skipped.
|
||||
"""
|
||||
w = widget.parent()
|
||||
while w is not None:
|
||||
panel = getattr(w, "file_path_panel", None)
|
||||
if panel is not None:
|
||||
return panel
|
||||
w = w.parent()
|
||||
return None
|
||||
|
||||
|
||||
class FilePathPanel(QWidget):
|
||||
path_updated = Signal(str)
|
||||
|
||||
@@ -117,16 +132,16 @@ class FilePathPanel(QWidget):
|
||||
pass
|
||||
return str(root / p)
|
||||
|
||||
# Master files a run can produce: plain scans write "<run>_master.h5",
|
||||
# X-ray Centering writes "<run>_raster2d_master.h5" / "<run>_raster1d_master.h5"
|
||||
# (see daq/operations/raster/service.py). Exact names only: a glob or a
|
||||
# directory test marks runs red that nothing will ever write.
|
||||
_MASTER_SUFFIXES = ("_master.h5", "_raster2d_master.h5", "_raster1d_master.h5")
|
||||
|
||||
def _exists_for_run(self, expanded_base_with_run: str) -> bool:
|
||||
# expanded_base_with_run is the base without scan-kind transforms yet
|
||||
effective = self._effective_dataset_base(expanded_base_with_run)
|
||||
# Taken = master file, directory, or any derived dataset of this run:
|
||||
# X-ray centering writes "<run>_raster2d_master.h5", not "<run>_master.h5".
|
||||
return (
|
||||
os.path.exists(f"{effective}_master.h5")
|
||||
or os.path.exists(effective)
|
||||
or bool(glob.glob(f"{effective}_*_master.h5"))
|
||||
)
|
||||
return any(os.path.exists(effective + suffix) for suffix in self._MASTER_SUFFIXES)
|
||||
|
||||
def update_filename(self):
|
||||
dir_name = self.directory_edit.text()
|
||||
|
||||
@@ -42,6 +42,7 @@ from PySide6.QtWidgets import (
|
||||
)
|
||||
|
||||
from aare.gui.constants import LOGGER_NAME
|
||||
from aare.gui.panels.file_path_panel import find_file_path_panel
|
||||
from aare.gui.widgets.message_box import precondition_check
|
||||
from aare.gui.widgets.number_line_edit import NumberLineEdit
|
||||
|
||||
@@ -388,9 +389,9 @@ class ScanSettingsPanel(QWidget):
|
||||
):
|
||||
logger.warning("Beamline not ready; user chose not to continue scan")
|
||||
return False
|
||||
p = self.parent()
|
||||
if hasattr(p, "file_path_panel"):
|
||||
reply = p.file_path_panel.file_path_error_box(scan_kind=scan_kind)
|
||||
file_path_panel = find_file_path_panel(self)
|
||||
if file_path_panel is not None:
|
||||
reply = file_path_panel.file_path_error_box(scan_kind=scan_kind)
|
||||
logger.debug(f"reply from file path panel: {reply}")
|
||||
if not reply:
|
||||
logger.warning("Error with file path.")
|
||||
|
||||
@@ -9,6 +9,7 @@ from PySide6.QtCore import Qt, Signal, Slot
|
||||
from PySide6.QtWidgets import QGridLayout, QLabel, QPushButton, QSizePolicy, QSpacerItem, QWidget
|
||||
|
||||
from aare.gui.constants import LOGGER_NAME
|
||||
from aare.gui.panels.file_path_panel import find_file_path_panel
|
||||
from aare.gui.panels.rotation_data_collection import (
|
||||
MAX_OMEGA_SPEED_DEG_S,
|
||||
MIN_EXP_TIME_S,
|
||||
@@ -517,7 +518,7 @@ class SimpleRotationSettingsPanel(QWidget):
|
||||
def run_measurement(self):
|
||||
# Same file-exists guard as the Rotation tab; this panel is not a
|
||||
# ScanSettingsPanel, so it has no check_before_run() to inherit.
|
||||
file_path_panel = getattr(self.parent(), "file_path_panel", None)
|
||||
file_path_panel = find_file_path_panel(self)
|
||||
if file_path_panel is not None and not file_path_panel.file_path_error_box(
|
||||
scan_kind="rotation"
|
||||
):
|
||||
|
||||
@@ -547,18 +547,52 @@ def test_a_derived_dataset_counts_as_taken(file_panel, monkeypatch):
|
||||
# X-ray centering writes <run>_raster2d_master.h5, not <run>_master.h5.
|
||||
from aare.gui.panels import file_path_panel
|
||||
|
||||
_taken(monkeypatch)
|
||||
monkeypatch.setattr(
|
||||
file_path_panel.glob,
|
||||
"glob",
|
||||
lambda pat: ["hit"] if pat.endswith("raster/d/x_001_*_master.h5") else [],
|
||||
)
|
||||
_taken(monkeypatch, "raster/d/x_001_raster2d_master.h5")
|
||||
monkeypatch.setattr(file_path_panel.QMessageBox, "warning", lambda *a, **k: None)
|
||||
|
||||
assert file_panel.file_path_error_box(scan_kind="raster") is False
|
||||
assert file_panel.run_number_edit.value() == 2
|
||||
|
||||
|
||||
def test_a_directory_or_a_sibling_run_is_not_taken(file_panel, monkeypatch):
|
||||
# Only the exact master files count; anything else would paint runs red
|
||||
# that nothing will write.
|
||||
_taken(monkeypatch, "raster/d/x_001", "raster/d/x_0011_master.h5", "raster/d/x_001_data.h5")
|
||||
file_panel.set_scan_kind("raster")
|
||||
assert file_panel.file_name_label.styleSheet() == ""
|
||||
assert file_panel.file_path_error_box(scan_kind="raster") is True
|
||||
|
||||
|
||||
def test_every_tab_reaches_the_file_guard(settings_panel, monkeypatch):
|
||||
# The tabs live in a QStackedWidget, which reparents them: a parent()
|
||||
# lookup of file_path_panel found nothing and every Run skipped the guard.
|
||||
from aare.gui.panels import scan_settings_panel
|
||||
|
||||
asked = []
|
||||
monkeypatch.setattr(
|
||||
settings_panel.file_path_panel,
|
||||
"file_path_error_box",
|
||||
lambda scan_kind: asked.append(scan_kind) or False,
|
||||
)
|
||||
monkeypatch.setattr(scan_settings_panel, "precondition_check", lambda *a, **k: True)
|
||||
scans = []
|
||||
for panel in (settings_panel.screening, settings_panel.raster):
|
||||
panel._beamline_state = BeamlineStateEnum.SampleAlignment
|
||||
settings_panel.screening.rotation_scan.connect(scans.append)
|
||||
settings_panel.simple.rotation_scan.connect(scans.append)
|
||||
settings_panel.raster.evaluate_grid.connect(lambda: scans.append("grid"))
|
||||
settings_panel.raster.evaluate_grid_auto.connect(lambda: scans.append("auto"))
|
||||
|
||||
settings_panel.screening.run_screening()
|
||||
settings_panel.screening.run_measurement()
|
||||
settings_panel.simple.run_measurement()
|
||||
settings_panel.raster._on_evaluate_clicked()
|
||||
settings_panel.raster._on_evaluate_auto_clicked()
|
||||
|
||||
assert asked == ["screening", "rotation", "rotation", "raster", "raster"]
|
||||
assert scans == []
|
||||
|
||||
|
||||
def test_simple_tab_run_is_blocked_by_the_file_guard(qapp):
|
||||
from PySide6.QtWidgets import QWidget
|
||||
|
||||
|
||||
Reference in New Issue
Block a user