CI for csaxs_bec / test (push) Successful in 3m10s
Queue-control "command job" changes: - Remove the "optimize_idgap" stub action (not commissioned, always a no-op) from both Flomni's and LamNI's action registry, and delete the now-dead TomoQueueMixin.optimize_idgap() method. - Fix the "move" action's device allow-list: "mokev" doesn't exist in the current device config at all -- the real energy device is "ccm_energy" (LamNI's copy already had this right). Also drop "ftray" (sample transfer tray), which was only ever there as a sim-testable move target. - Add a new "move_foptz_with_shift" action: moves foptz (the FZP optics-Z stage) and sets the compensating manual_shift_x/manual_shift_y in one atomic step. foptz is deliberately NOT in the plain move allow-list -- moving it shifts the aligned scan center (interferometer mirrors aren't perfectly parallel to the beam), so this is the only way to queue a foptz move, specifically so the compensating shift can never be forgotten. - The foptz target is validated against the device's real, live limits (not a hand-duplicated static number) both in the GUI spinbox's range and in the CLI's add-time validation, so a queued job can't raise when it executes unattended later; a defense-in-depth check also exists at execution time. Retargeted the e2e command-job tests from ftray to foptz (now the only sim-testable move-adjacent path) and updated the AI_docs testing/GUI-testing notes to match the new action set. Also fixes a pre-existing, unrelated broken import (plugins.omny.omny_general_tools -> plugins.OMNY_shared.omny_general_tools, already broken on main) in _bootstrap.py/test_tomo_queue.py/ test_tomo_queue_command_jobs.py that was blocking omny_e2e_tests collection entirely, needed to actually verify this change's own test rewrites. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NPZ9gzN2rt3wgiMnTKyrug
114 lines
4.5 KiB
Python
114 lines
4.5 KiB
Python
"""Shared bootstrap for constructing a live Flomni against the running sim.
|
|
|
|
Used both by conftest.py (in-process, section A) and by
|
|
_run_queue_subprocess.py (spawned as a separate OS process, section B/C) --
|
|
pulled out here so both paths for "build a real Flomni pointed at the sim"
|
|
share one definition of what real-instrument setup and side-effect
|
|
neutralization is needed. See TOMO_QUEUE_TESTING.md section 3.
|
|
|
|
Deliberately does NOT call bec.config.load_demo_config()/
|
|
update_session_with_file(): assumes the running sim session already has
|
|
device_configs/simulated_omny/simulated_flomni.yaml loaded.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import builtins
|
|
import importlib
|
|
|
|
from bec_ipython_client import BECIPythonClient
|
|
from bec_lib.messaging_services import SciLogMessagingService
|
|
from bec_lib.redis_connector import RedisConnector
|
|
from bec_lib.service_config import ServiceConfig
|
|
|
|
from csaxs_bec.bec_ipython_client.plugins.flomni import flomni as flomni_module
|
|
from csaxs_bec.bec_ipython_client.plugins.flomni.flomni_webpage_generator import (
|
|
FlomniWebpageGenerator,
|
|
)
|
|
from csaxs_bec.bec_ipython_client.plugins.OMNY_shared.omny_general_tools import OMNYTools
|
|
|
|
|
|
class _NoOpSciLogMessage:
|
|
"""Chainable no-op standing in for a real SciLog message object."""
|
|
|
|
def add_text(self, *a, **k):
|
|
return self
|
|
|
|
def add_tags(self, *a, **k):
|
|
return self
|
|
|
|
def add_attachment(self, *a, **k):
|
|
return self
|
|
|
|
def send(self, *a, **k):
|
|
return self
|
|
|
|
|
|
def neutralize_side_effects() -> None:
|
|
"""Patch away operator-facing/networked side effects of Flomni().
|
|
|
|
Plain class-attribute assignment, not pytest's monkeypatch: every
|
|
caller (in-process fixture, subprocess script) wants the same patches
|
|
for the life of the process, so there's nothing to revert.
|
|
|
|
- OMNYTools.yesno -> always answers yes, so tomo_queue_execute()'s
|
|
confirmation prompt doesn't hang a headless run.
|
|
- FlomniWebpageGenerator.start -> no-op. Flomni.__init__ normally starts
|
|
this unconditionally, which opens a local HTTP server and spawns
|
|
background threads that POST to a real external URL.
|
|
- SciLogMessagingService.new -> no-op. tomo_scan() unconditionally sends
|
|
a SciLog summary at the end of every scan; SciLog isn't
|
|
configured/reachable in this dev environment.
|
|
"""
|
|
OMNYTools.yesno = lambda self, *a, **k: True
|
|
FlomniWebpageGenerator.start = lambda self: None
|
|
SciLogMessagingService.new = lambda self, *a, **k: _NoOpSciLogMessage()
|
|
|
|
|
|
def build_flomni(services_config_path):
|
|
"""Connect to the already-running sim and return (bec, flomni)."""
|
|
config = ServiceConfig(services_config_path)
|
|
bec = BECIPythonClient(config, RedisConnector, forced=True, wait_for_server=True)
|
|
bec.start()
|
|
|
|
# flomni.py's module-level `umv` resolves `scans` from the module's own
|
|
# globals, populated only by the `if builtins.__dict__.get("bec") is not
|
|
# None:` block at import time. Set builtins first, then reload so it
|
|
# re-runs (needed even on a fresh process: importing flomni_module above
|
|
# ran that block once already, before bec existed).
|
|
builtins.__dict__["bec"] = bec
|
|
builtins.__dict__["dev"] = bec.device_manager.devices
|
|
builtins.__dict__["scans"] = bec.scans
|
|
importlib.reload(flomni_module)
|
|
builtins.__dict__["umv"] = flomni_module.umv
|
|
|
|
neutralize_side_effects()
|
|
|
|
flomni = flomni_module.Flomni(bec)
|
|
|
|
# RT interferometer feedback + fsamx-in-position setup, mirroring
|
|
# Flomni._align_setup(). Only intervene when actually needed: fsamx is
|
|
# normally left read_only (locked) by a *previous*
|
|
# feedback_enable_with_reset() call (its own last step, so the RT
|
|
# controller's PID correction owns fine positioning going forward), and
|
|
# a move request against a read_only device raises DisabledDeviceError.
|
|
# feedback_enable_with_reset() itself is expensive (re-zeros the
|
|
# interferometers) -- skip it entirely if feedback is already running,
|
|
# which it normally is after the first caller in a test session sets it
|
|
# up (device-server state persists across client (re)connections).
|
|
rtx = bec.device_manager.devices.rtx
|
|
if not rtx.controller.feedback_is_running():
|
|
fsamx = bec.device_manager.devices.fsamx
|
|
fsamx_in = fsamx.user_parameter.get("in")
|
|
if abs(fsamx.readback.get() - fsamx_in) > 0.3:
|
|
fsamx.read_only = False
|
|
flomni_module.umv(fsamx, fsamx_in)
|
|
flomni.feedback_enable_with_reset()
|
|
|
|
return bec, flomni
|
|
|
|
|
|
def shutdown(bec) -> None:
|
|
bec.shutdown()
|
|
bec._client._reset_singleton()
|