diff --git a/notes.org b/notes.org index 6a0424a5..486f8031 100644 --- a/notes.org +++ b/notes.org @@ -49,4 +49,5 @@ - [X] iocs_window_action - [ ] App needs to pass actions to the window - [ ] Apps needs to connect queue_status_changed to main_window.update_queue_controls - +** TODO [/] Queue button + - [ ] App needs to pass queue_status_updated signal down to update_queue_style slot diff --git a/src/firefly/main_window.py b/src/firefly/main_window.py index 2275bb34..74599a01 100644 --- a/src/firefly/main_window.py +++ b/src/firefly/main_window.py @@ -395,6 +395,7 @@ def update_queue_controls(self, new_status): super().update_queue_controls(new_status) self.ui.navbar.setVisible(bool(new_status['in_use'])) + # ----------------------------------------------------------------------------- # :author: Mark Wolfman # :email: wolfman@anl.gov diff --git a/src/firefly/plans/xafs_scan.py b/src/firefly/plans/xafs_scan.py index 452f28a1..e5919349 100644 --- a/src/firefly/plans/xafs_scan.py +++ b/src/firefly/plans/xafs_scan.py @@ -27,7 +27,8 @@ float_accuracy = 4 -# the energy resolution will not be better than 0.01 eV at S25, e.g., 0.01 eV /4000 eV -> 10-6 level, Si(311) is 10-5 level +# The energy resolution will not be better than 0.01 eV at S25 +# e.g. 0.01 eV / 4000 eV -> 10^-6 level, Si(311) is 10-5 level def wavenumber_to_energy_round(wavenumber): return round(wavenumber_to_energy(wavenumber), 2) @@ -503,11 +504,10 @@ def queue_plan(self, *args, **kwargs): md=md, ) # Submit the item to the queueserver - app = FireflyApplication.instance() log.info(f"Adding XAFS scan to queue.") # repeat scans for i in range(repeat_scan_num): - app.add_queue_item(item) + self.queue_item_submitted.emit(item) def ui_filename(self): return "plans/xafs_scan.ui" diff --git a/src/firefly/queue_button.py b/src/firefly/queue_button.py index e1bf19e8..3a741d52 100644 --- a/src/firefly/queue_button.py +++ b/src/firefly/queue_button.py @@ -6,9 +6,6 @@ from qtpy import QtGui, QtWidgets from strenum import StrEnum -from firefly import FireflyApplication - - log = logging.getLogger(__name__) @@ -22,15 +19,8 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Initially disable the button until the status of the queue can be determined self.setDisabled(True) - # Listen for changes to the run engine - app = FireflyApplication.instance() - try: - app.queue_status_changed.connect(self.handle_queue_status_change) - except AttributeError: - log.warning("Application has no slot `handle_queue_status_change`. " - "Queue button will not respond to queue state changes.") - def handle_queue_status_change(self, status: dict): + def update_queue_style(self, status: dict): if status["worker_environment_exists"]: self.setEnabled(True) else: diff --git a/src/firefly/tests/test_application.py b/src/firefly/tests/test_application.py index eeaa7ee2..fb180fa0 100644 --- a/src/firefly/tests/test_application.py +++ b/src/firefly/tests/test_application.py @@ -162,6 +162,92 @@ def test_open_area_detector_viewer_actions(ffapp, qtbot, sim_camera): list(ffapp.area_detector_actions.values())[0].trigger() assert "FireflyMainWindow_area_detector_s255id-gige-A" in ffapp.windows.keys() + +############ +# From old src/firefly/tests/test_motor_menu.py + + +@pytest.fixture +def fake_motors(sim_registry): + motor_names = ["motorA", "motorB", "motorC"] + motors = [] + for name in motor_names: + this_motor = make_fake_device(motor.HavenMotor)( + name=name, labels={"extra_motors"} + ) + motors.append(this_motor) + return motors + + +def test_open_motor_window(fake_motors, qapp, qtbot): + # Simulate clicking on the menu action (they're in alpha order) + action = ffapp.motor_actions["motorC"] + action.trigger() + # See if the window was created + motor_3_name = "FireflyMainWindow_motor_motorC" + assert motor_3_name in ffapp.windows.keys() + macros = ffapp.windows[motor_3_name].display_widget().macros() + assert macros["MOTOR"] == "motorC" + + +def test_motor_menu(fake_motors, qapp, qtbot): + # Create the window + window = FireflyMainWindow() + qtbot.addWidget(window) + # Check that the menu items have been created + assert hasattr(window.ui, "positioners_menu") + assert len(ffapp.motor_actions) == 3 + window.destroy() + + +########################################## +# From src/firefly/tests/test_queue_client.py + + +def test_queue_stopped(client): + """Does the action respond to changes in the queue stopped pending?""" + assert not ffapp.queue_stop_action.isChecked() + client.queue_stop_changed.emit(True) + assert ffapp.queue_stop_action.isChecked() + client.queue_stop_changed.emit(False) + assert not ffapp.queue_stop_action.isChecked() + + +def test_autostart_changed(client, qtbot): + """Does the action respond to changes in the queue autostart + status? + + """ + ffapp.queue_autostart_action.setChecked(True) + assert ffapp.queue_autostart_action.isChecked() + with qtbot.waitSignal(client.autostart_changed, timeout=3): + client.autostart_changed.emit(False) + assert not ffapp.queue_autostart_action.isChecked() + with qtbot.waitSignal(client.autostart_changed, timeout=3): + client.autostart_changed.emit(True) + assert ffapp.queue_autostart_action.isChecked() + + + +############################################################### +# From: src/firefly/tests/test_xrf_detector_display.py + +def test_open_xrf_detector_viewer_actions(ffapp, qtbot, det_fixture, request): + sim_det = request.getfixturevalue(det_fixture) + # Get the area detector parts ready + ffapp._prepare_device_windows( + device_label="xrf_detectors", + attr_name="xrf_detector", + ui_file="xrf_detector.py", + device_key="DEV", + ) + assert hasattr(ffapp, "xrf_detector_actions") + assert len(ffapp.xrf_detector_actions) == 1 + # Launch an action and see that a window opens + list(ffapp.xrf_detector_actions.values())[0].trigger() + assert "FireflyMainWindow_xrf_detector_vortex_me4" in ffapp.windows.keys() + + # ----------------------------------------------------------------------------- # :author: Mark Wolfman # :email: wolfman@anl.gov diff --git a/src/firefly/tests/test_cameras_display.py b/src/firefly/tests/test_cameras_display.py index 76af7808..c9efdcb4 100644 --- a/src/firefly/tests/test_cameras_display.py +++ b/src/firefly/tests/test_cameras_display.py @@ -19,7 +19,7 @@ def cameras_display(qtbot, sim_camera): @pytest.fixture() def camera_display(qtbot, sim_camera): - display = CamerasDisplay() + display = CameraDisplay() qtbot.addWidget(display) return display @@ -36,57 +36,6 @@ def test_embedded_displays(cameras_display, sim_camera): assert json.loads(display._camera_displays[0].macros) == expected_macros -def test_camera_channel_status(camera_display): - """Test that the camera status indicator responds to camera connection - status PV. - - """ - display = camera_display - # Check that the pydm connections have been made to EPICS - assert isinstance(display.detector_state, PyDMChannel) - assert display.detector_state.address == "camera_ioc:cam1:DetectorState_RBV" - - -def test_set_status_byte(camera_display): - display = camera_display - display.show() - # All devices are disconnected - byte = display.camera_status_indicator - bit = byte._indicators[0] - label = display.camera_status_label - # Set the color to something else, then check that it gets set back to white - bit.setColor(QtGui.QColor(255, 0, 0)) - # Simulated the IOC being disconnected - display.update_camera_connection(False) - assert bit._brush.color().getRgb() == (255, 255, 255, 255) - assert not label.isVisible(), "State label should be hidden by default" - # Make the signals connected and see that it's green - display.update_camera_connection(True) - display.update_camera_state(DetectorStates.IDLE) - assert bit._brush.color().getRgb() == (0, 255, 0, 255) - assert not label.isVisible(), "State label should be hidden by default" - # Make the camera be disconnected and see if it's red - display.update_camera_state(DetectorStates.DISCONNECTED) - assert bit._brush.color().getRgb() == (255, 0, 0, 255) - assert label.isVisible(), "State label should be visible when disconnected" - # Make the camera be acquiring and see if it's yellow - display.update_camera_state(DetectorStates.ACQUIRE) - assert bit._brush.color().getRgb() == (255, 255, 0, 255) - assert not label.isVisible(), "State label should be hidden by default" - - -@pytest.mark.xfail -def test_camera_viewer_button(qtbot, ffapp, ioc_area_detector, mocker): - action = QtWidgets.QAction(ffapp) - ffapp.camera_actions.append(action) - display = CameraDisplay(macros=macros) - display.show() - # Click the button - btn = display.ui.camera_viewer_button - with qtbot.waitSignal(action.triggered): - qtbot.mouseClick(btn, QtCore.Qt.LeftButton) - - # ----------------------------------------------------------------------------- # :author: Mark Wolfman # :email: wolfman@anl.gov diff --git a/src/firefly/tests/test_motor_menu.py b/src/firefly/tests/test_motor_menu.py deleted file mode 100644 index 41340fae..00000000 --- a/src/firefly/tests/test_motor_menu.py +++ /dev/null @@ -1,68 +0,0 @@ -import pytest -from ophyd.sim import make_fake_device - -from firefly.main_window import FireflyMainWindow -from haven.instrument import motor - - -@pytest.fixture -def fake_motors(sim_registry): - motor_names = ["motorA", "motorB", "motorC"] - motors = [] - for name in motor_names: - this_motor = make_fake_device(motor.HavenMotor)( - name=name, labels={"extra_motors"} - ) - motors.append(this_motor) - return motors - - -def test_motor_menu(fake_motors, qtbot, ffapp): - ffapp.setup_window_actions() - ffapp.setup_runengine_actions() - # Create the window - window = FireflyMainWindow() - # Check that the menu items have been created - assert hasattr(window.ui, "positioners_menu") - assert len(ffapp.motor_actions) == 3 - window.destroy() - - -def test_open_motor_window(fake_motors, monkeypatch, ffapp): - # Set up the application - ffapp.setup_window_actions() - ffapp.setup_runengine_actions() - # Simulate clicking on the menu action (they're in alpha order) - action = ffapp.motor_actions["motorC"] - action.trigger() - # See if the window was created - motor_3_name = "FireflyMainWindow_motor_motorC" - assert motor_3_name in ffapp.windows.keys() - macros = ffapp.windows[motor_3_name].display_widget().macros() - assert macros["MOTOR"] == "motorC" - - -# ----------------------------------------------------------------------------- -# :author: Mark Wolfman -# :email: wolfman@anl.gov -# :copyright: Copyright © 2023, UChicago Argonne, LLC -# -# Distributed under the terms of the 3-Clause BSD License -# -# The full license is in the file LICENSE, distributed with this software. -# -# DISCLAIMER -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# ----------------------------------------------------------------------------- diff --git a/src/firefly/tests/test_queue_button.py b/src/firefly/tests/test_queue_button.py index 9f15fd60..885044dc 100644 --- a/src/firefly/tests/test_queue_button.py +++ b/src/firefly/tests/test_queue_button.py @@ -15,7 +15,7 @@ def test_queue_button_style(qtbot): "re_state": "idle", "queue_autostart_enabled": False, } - ffapp.queue_status_changed.emit(queue_state) + btn.update_queue_style(queue_state) assert btn.isEnabled() assert Colors.ADD_TO_QUEUE in btn.styleSheet() assert btn.text() == "Add to Queue" @@ -26,7 +26,7 @@ def test_queue_button_style(qtbot): "re_state": "idle", "queue_autostart_enabled": True, } - btn.handle_queue_status_change(queue_state) + btn.update_queue_style(queue_state) assert btn.isEnabled() assert Colors.RUN_QUEUE in btn.styleSheet() assert btn.text() == "Run" @@ -37,7 +37,7 @@ def test_queue_button_style(qtbot): "re_state": "running", "queue_autostart_enabled": True, } - btn.handle_queue_status_change(queue_state) + btn.update_queue_style(queue_state) assert btn.isEnabled() assert Colors.ADD_TO_QUEUE in btn.styleSheet() assert btn.text() == "Add to Queue" diff --git a/src/firefly/tests/test_queue_client.py b/src/firefly/tests/test_queue_client.py index ddd20179..7f1abf20 100644 --- a/src/firefly/tests/test_queue_client.py +++ b/src/firefly/tests/test_queue_client.py @@ -246,12 +246,6 @@ def client(): yield client -@pytest.fixture() -def ffapp(ffapp, client): - ffapp.prepare_queue_client(api=client.api, client=client) - return ffapp - - def test_client_timer(client): assert isinstance(client.timer, QTimer) @@ -320,21 +314,6 @@ async def test_toggle_autostart(client, qtbot): api.queue_autostart.assert_called_once_with(True) -def test_autostart_changed(client, ffapp, qtbot): - """Does the action respond to changes in the queue autostart - status? - - """ - ffapp.queue_autostart_action.setChecked(True) - assert ffapp.queue_autostart_action.isChecked() - with qtbot.waitSignal(client.autostart_changed, timeout=3): - client.autostart_changed.emit(False) - assert not ffapp.queue_autostart_action.isChecked() - with qtbot.waitSignal(client.autostart_changed, timeout=3): - client.autostart_changed.emit(True) - assert ffapp.queue_autostart_action.isChecked() - - # def test_start_queue(ffapp, client, qtbot): # ffapp.start_queue_action.trigger() # qtbot.wait(1000) @@ -356,15 +335,6 @@ async def test_stop_queue(client, qtbot): api.queue_stop_cancel.assert_called_once() -def test_queue_stopped(client, ffapp): - """Does the action respond to changes in the queue stopped pending?""" - assert not ffapp.queue_stop_action.isChecked() - client.queue_stop_changed.emit(True) - assert ffapp.queue_stop_action.isChecked() - client.queue_stop_changed.emit(False) - assert not ffapp.queue_stop_action.isChecked() - - @pytest.mark.asyncio async def test_check_queue_status(client, qtbot): # Check that the queue length is changed diff --git a/src/firefly/tests/test_xafs_scan.py b/src/firefly/tests/test_xafs_scan.py index 56d35214..91096059 100644 --- a/src/firefly/tests/test_xafs_scan.py +++ b/src/firefly/tests/test_xafs_scan.py @@ -1,6 +1,7 @@ from pprint import pprint from unittest import mock +import pytest import numpy as np from bluesky_queueserver_api import BPlan from qtpy import QtCore @@ -14,76 +15,77 @@ default_values = [pre_edge, XANES_region, EXAFS_region] -def test_region_number(qtbot): +@pytest.fixture() +def display(qtbot): + display = XafsScanDisplay() + qtbot.addWidget(display) + return display + + +def test_region_number(display): """Does changing the region number affect the UI?""" - disp = XafsScanDisplay() - qtbot.addWidget(disp) # Check that the display has the right number of rows to start with - assert disp.ui.regions_spin_box.value() == 3 - assert hasattr(disp, "regions") - assert len(disp.regions) == 3 + assert display.ui.regions_spin_box.value() == 3 + assert hasattr(display, "regions") + assert len(display.regions) == 3 # Check that regions can be inserted - disp.ui.regions_spin_box.setValue(5) - assert len(disp.regions) == 5 + display.ui.regions_spin_box.setValue(5) + assert len(display.regions) == 5 # Check that regions can be removed - disp.ui.regions_spin_box.setValue(1) - assert len(disp.regions) == 1 + display.ui.regions_spin_box.setValue(1) + assert len(display.regions) == 1 -def test_E0_checkbox(qtbot): +def test_E0_checkbox(display): """Does selecting the E0 checkbox adjust the UI properly?""" - disp = XafsScanDisplay() - qtbot.addWidget(disp) # check whether extracted edge value is correct - disp.edge_combo_box.setCurrentText("Pt L3 (11500.8 eV)") - disp.ui.use_edge_checkbox.setChecked(True) + display.edge_combo_box.setCurrentText("Pt L3 (11500.8 eV)") + display.ui.use_edge_checkbox.setChecked(True) # check whether the math is done correctly when switching off E0 - disp.ui.use_edge_checkbox.setChecked(False) + display.ui.use_edge_checkbox.setChecked(False) # check whether edge value is extracted correctly - np.testing.assert_equal(disp.edge_value, 11500.8) + np.testing.assert_equal(display.edge_value, 11500.8) # K-space checkboxes should be disabled when E0 is unchecked - assert not disp.regions[0].k_space_checkbox.isEnabled() + assert not display.regions[0].k_space_checkbox.isEnabled() # check whether energy values is added correctly for i in range(len(default_values)): np.testing.assert_almost_equal( - float(disp.regions[i].start_line_edit.text()), - default_values[i][0] + disp.edge_value, + float(display.regions[i].start_line_edit.text()), + default_values[i][0] + display.edge_value, decimal=3, ) np.testing.assert_almost_equal( - float(disp.regions[i].stop_line_edit.text()), - default_values[i][1] + disp.edge_value, + float(display.regions[i].stop_line_edit.text()), + default_values[i][1] + display.edge_value, decimal=3, ) np.testing.assert_almost_equal( - float(disp.regions[i].step_line_edit.text()), + float(display.regions[i].step_line_edit.text()), default_values[i][2], decimal=3, ) # check whether k range is calculated right - disp.ui.use_edge_checkbox.setChecked(True) + display.ui.use_edge_checkbox.setChecked(True) # K-space checkbox should become re-enabled after E0 is checked - assert disp.regions[-1].k_space_checkbox.isEnabled() - disp.regions[-1].k_space_checkbox.setChecked(True) + assert display.regions[-1].k_space_checkbox.isEnabled() + display.regions[-1].k_space_checkbox.setChecked(True) np.testing.assert_almost_equal( - float(disp.regions[i].start_line_edit.text()), 3.6226, decimal=4 + float(display.regions[i].start_line_edit.text()), 3.6226, decimal=4 ) np.testing.assert_almost_equal( - float(disp.regions[i].stop_line_edit.text()), 11.4557, decimal=4 + float(display.regions[i].stop_line_edit.text()), 11.4557, decimal=4 ) np.testing.assert_almost_equal( - float(disp.regions[i].step_line_edit.text()), 3.64069 - 3.6226, decimal=4 + float(display.regions[i].step_line_edit.text()), 3.64069 - 3.6226, decimal=4 ) -def test_xafs_scan_plan_queued_energies(ffapp, qtbot): - display = XafsScanDisplay() - +def test_xafs_scan_plan_queued_energies(display, qtbot): display.edge_combo_box.setCurrentText("Pt L3 (11500.8 eV)") display.regions[-1].region_checkbox.setChecked(False) # Set up detector list @@ -157,13 +159,12 @@ def check_item(item): # Click the run button and see if the plan is queued display.ui.run_button.setEnabled(True) with qtbot.waitSignal( - ffapp.queue_item_added, timeout=1000, check_params_cb=check_item + display.queue_item_submitted, timeout=1000, check_params_cb=check_item ): qtbot.mouseClick(display.ui.run_button, QtCore.Qt.LeftButton) -def test_xafs_scan_plan_queued_energies_k_mixed(ffapp, qtbot): - display = XafsScanDisplay() +def test_xafs_scan_plan_queued_energies_k_mixed(display, qtbot): display.ui.regions_spin_box.setValue(2) display.edge_combo_box.setCurrentText("Pt L3 (11500.8 eV)") @@ -265,7 +266,7 @@ def check_item(item): # Click the run button and see if the plan is queued display.ui.run_button.setEnabled(True) with qtbot.waitSignal( - ffapp.queue_item_added, timeout=1000, check_params_cb=check_item + display.queue_item_submitted, timeout=1000, check_params_cb=check_item ): qtbot.mouseClick(display.ui.run_button, QtCore.Qt.LeftButton) diff --git a/src/firefly/tests/test_xrf_detector_display.py b/src/firefly/tests/test_xrf_detector_display.py index 7e36894c..eb090711 100644 --- a/src/firefly/tests/test_xrf_detector_display.py +++ b/src/firefly/tests/test_xrf_detector_display.py @@ -10,7 +10,7 @@ @pytest.fixture() -def xrf_display(ffapp, request): +def xrf_display(request, qtbot): """Parameterized fixture for creating a display based on a specific detector class. @@ -19,6 +19,7 @@ def xrf_display(ffapp, request): det = request.getfixturevalue(request.param) # Create the display display = XRFDetectorDisplay(macros={"DEV": det.name}) + qtbot.addWidget(display) # Set sensible starting values spectra = np.random.default_rng(seed=0).integers( 0, 65536, dtype=np.int_, size=(4, 1024) @@ -31,23 +32,6 @@ def xrf_display(ffapp, request): yield display -@pytest.mark.parametrize("det_fixture", detectors) -def test_open_xrf_detector_viewer_actions(ffapp, qtbot, det_fixture, request): - sim_det = request.getfixturevalue(det_fixture) - # Get the area detector parts ready - ffapp._prepare_device_windows( - device_label="xrf_detectors", - attr_name="xrf_detector", - ui_file="xrf_detector.py", - device_key="DEV", - ) - assert hasattr(ffapp, "xrf_detector_actions") - assert len(ffapp.xrf_detector_actions) == 1 - # Launch an action and see that a window opens - list(ffapp.xrf_detector_actions.values())[0].trigger() - assert "FireflyMainWindow_xrf_detector_vortex_me4" in ffapp.windows.keys() - - @pytest.mark.parametrize("xrf_display", detectors, indirect=True) def test_roi_widgets(xrf_display): xrf_display.draw_roi_widgets(2) @@ -57,7 +41,7 @@ def test_roi_widgets(xrf_display): @pytest.mark.parametrize("xrf_display", detectors, indirect=True) -def test_roi_element_comboboxes(ffapp, qtbot, xrf_display): +def test_roi_element_comboboxes(xrf_display): # Check that the comboboxes have the right number of entries element_cb = xrf_display.ui.mca_combobox assert element_cb.count() == xrf_display.device.num_elements @@ -66,7 +50,7 @@ def test_roi_element_comboboxes(ffapp, qtbot, xrf_display): @pytest.mark.parametrize("det_fixture", detectors) -def test_roi_selection(ffapp, qtbot, det_fixture, request): +def test_roi_selection(qtbot, det_fixture, request): det = request.getfixturevalue(det_fixture) display = XRFROIDisplay(macros={"DEV": det.name, "NUM": 2, "MCA": 2, "ROI": 2}) # Unchecked box should be bland