From a39a5576e491de69600698b6bd8ca62df66f6a53 Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Fri, 12 Jun 2026 16:24:20 +0100 Subject: [PATCH 1/6] use a initialised process list in RATRunner (with prints) timer --- rascal2/core/runner.py | 44 ++++++++++++++++++++++++++-------------- rascal2/ui/presenter.py | 45 +++++++++++++++++++++++++---------------- 2 files changed, 57 insertions(+), 32 deletions(-) diff --git a/rascal2/core/runner.py b/rascal2/core/runner.py index 7e18f2cf..220cee6e 100644 --- a/rascal2/core/runner.py +++ b/rascal2/core/runner.py @@ -7,7 +7,6 @@ import ratapi as rat from PyQt6 import QtCore -from ratapi.utils.enums import Procedures from rascal2.config import MatlabHelper, get_matlab_engine @@ -19,33 +18,33 @@ class RATRunner(QtCore.QObject): finished = QtCore.pyqtSignal() stopped = QtCore.pyqtSignal() - def __init__(self, rat_inputs, procedure: Procedures, display_on: bool): + def __init__(self): super().__init__() self.timer = QtCore.QTimer() self.timer.setInterval(1) self.timer.timeout.connect(self.check_queue) + self.matlab_helper = MatlabHelper() # this queue handles both event data and results self.queue = Queue() - matlab_helper = MatlabHelper() - self.process = Process( - target=run, - args=( - self.queue, - rat_inputs, - procedure, - display_on, - matlab_helper.ready_event, - matlab_helper.engine_output, - ), - ) + self.arg_queue = Queue() + self.rat_inputs = None + self.procedure = None + self.display_on = None + self.refresh_process_list() self.updated_problem = None self.results = None self.error = None self.events = [] + def set_runner_args(self, rat_inputs, procedure, display_on: bool): + self.arg_queue.put((rat_inputs, procedure, display_on)) + def start(self): """Start the calculation.""" + if not self.processes_list: + self.refresh_process_list() + self.process = self.processes_list.pop(0) self.process.start() self.timer.start() @@ -71,8 +70,21 @@ def check_queue(self): self.events.append(item) self.event_received.emit() + def refresh_process_list(self): + self.processes_list = [Process(target=run, args=( + self.queue, + self.arg_queue, + self.matlab_helper.ready_event, + self.matlab_helper.engine_output, + )) for _ in range(5)] + + def clear_queues(self): + self.queue.empty() + self.arg_queue.empty() + self.events.clear() -def run(queue, rat_inputs: tuple, procedure: str, display: bool, engine_ready, engine_output): + +def run(queue, arg_queue, engine_ready, engine_output): """Run RAT and put the result into the queue. Parameters @@ -87,6 +99,8 @@ def run(queue, rat_inputs: tuple, procedure: str, display: bool, engine_ready, e Whether to display events. """ + rat_inputs, procedure, display = arg_queue.get() + problem_definition, cpp_controls = rat_inputs if display: diff --git a/rascal2/ui/presenter.py b/rascal2/ui/presenter.py index 0f9e80e6..4bbf5403 100644 --- a/rascal2/ui/presenter.py +++ b/rascal2/ui/presenter.py @@ -1,5 +1,6 @@ import os import re +import time import warnings from typing import Any @@ -29,6 +30,11 @@ def __init__(self, view): self.view = view self.model = MainWindowModel() self.worker = None + self.runner = RATRunner() + self.runner.finished.connect(self.handle_results) + self.runner.stopped.connect(self.handle_interrupt) + self.runner.event_received.connect(self.handle_event) + def create_project(self, name: str, save_path: str): """Create a new RAT project and controls object then initialise UI. @@ -219,6 +225,7 @@ def quick_run(self, project=None): def run(self): """Run rat using multiprocessing.""" # reset terminal + self.t1 = time.perf_counter() self.view.terminal_widget.progress_bar.setVisible(False) if SETTINGS.clear_terminal: self.view.terminal_widget.clear() @@ -230,11 +237,10 @@ def run(self): rat_inputs = rat.inputs.make_input(self.model.project, self.model.controls) display_on = self.model.controls.display != rat.utils.enums.Display.Off - self.runner = RATRunner(rat_inputs, self.model.controls.procedure, display_on) - self.runner.finished.connect(self.handle_results) - self.runner.stopped.connect(self.handle_interrupt) - self.runner.event_received.connect(self.handle_event) + self.runner.set_runner_args(rat_inputs, self.model.controls.procedure, display_on) + self.view.terminal_widget.write("Initializing RAT Process...") + self.t2 = time.perf_counter() self.runner.start() def handle_results(self): @@ -249,6 +255,10 @@ def handle_results(self): ) self.view.handle_results(self.runner.results) self.model.controls.delete_IPC() + self.runner.clear_queues() + self.t3 = time.perf_counter() + print(f"{(self.t2 - self.t1)=}") + print(f"{(self.t3 - self.t1)=}") def handle_interrupt(self): """Handle a RAT run being interrupted.""" @@ -261,19 +271,20 @@ def handle_interrupt(self): def handle_event(self): """Handle event data produced by the RAT run.""" - event = self.runner.events.pop(0) - match event: - case str(): - self.view.terminal_widget.write(event) - chi_squared = get_live_chi_squared(event, str(self.model.controls.procedure)) - if chi_squared is not None: - self.view.controls_widget.update_chi_squared(chi_squared) - case rat.events.ProgressEventData(): - self.view.terminal_widget.update_progress(event) - case rat.events.PlotEventData(): - self.view.plot_widget.plot_with_blit(event) - case LogData(): - LOGGER.log(event.level, event.msg) + if self.runner.events: + event = self.runner.events.pop(0) + match event: + case str(): + self.view.terminal_widget.write(event) + chi_squared = get_live_chi_squared(event, str(self.model.controls.procedure)) + if chi_squared is not None: + self.view.controls_widget.chi_squared.setText(chi_squared) + case rat.events.ProgressEventData(): + self.view.terminal_widget.update_progress(event) + case rat.events.PlotEventData(): + self.view.plot_widget.plot_with_blit(event) + case LogData(): + LOGGER.log(event.level, event.msg) def edit_project(self, updated_project: dict, preview: bool = True) -> None: """Edit the Project with a dictionary of attributes. From 85431b2d209237040f38f591de67c4a5321af26e Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Mon, 22 Jun 2026 15:58:42 +0100 Subject: [PATCH 2/6] Added additional LogData messages to inform the use of the current RATRunner progress (with timings) Cleanup test fixes more test fixes ruff fixes --- rascal2/core/runner.py | 41 ++++++++++++++++++++++++--------------- rascal2/ui/presenter.py | 40 +++++++++++++++----------------------- tests/core/test_runner.py | 25 +++++++++++++++++------- 3 files changed, 59 insertions(+), 47 deletions(-) diff --git a/rascal2/core/runner.py b/rascal2/core/runner.py index 220cee6e..7d64591d 100644 --- a/rascal2/core/runner.py +++ b/rascal2/core/runner.py @@ -18,8 +18,9 @@ class RATRunner(QtCore.QObject): finished = QtCore.pyqtSignal() stopped = QtCore.pyqtSignal() - def __init__(self): + def __init__(self, parent=None): super().__init__() + self.parent = parent self.timer = QtCore.QTimer() self.timer.setInterval(1) self.timer.timeout.connect(self.check_queue) @@ -31,7 +32,9 @@ def __init__(self): self.rat_inputs = None self.procedure = None self.display_on = None + self.processes_list = [] self.refresh_process_list() + self.process = self.processes_list.pop(0) self.updated_problem = None self.results = None self.error = None @@ -46,6 +49,8 @@ def start(self): self.refresh_process_list() self.process = self.processes_list.pop(0) self.process.start() + if self.parent: + self.parent.view.terminal_widget.write("Starting RAT Runner process...") self.timer.start() def interrupt(self): @@ -71,12 +76,18 @@ def check_queue(self): self.event_received.emit() def refresh_process_list(self): - self.processes_list = [Process(target=run, args=( - self.queue, - self.arg_queue, - self.matlab_helper.ready_event, - self.matlab_helper.engine_output, - )) for _ in range(5)] + self.processes_list = [ + Process( + target=run, + args=( + self.queue, + self.arg_queue, + self.matlab_helper.ready_event, + self.matlab_helper.engine_output, + ), + ) + for _ in range(5) + ] def clear_queues(self): self.queue.empty() @@ -91,16 +102,11 @@ def run(queue, arg_queue, engine_ready, engine_output): ---------- queue : Queue The interprocess queue for the RATRunner. - rat_inputs : tuple - The C++ inputs for rat. - procedure : str - The optimisation procedure. - display : bool - Whether to display events. + arg_queue : + A queue of arguments used to initialize the RAT process, passed from the Main Presenter """ rat_inputs, procedure, display = arg_queue.get() - problem_definition, cpp_controls = rat_inputs if display: @@ -112,17 +118,20 @@ def run(queue, arg_queue, engine_ready, engine_output): try: engine_future = None if any([file["language"] == "matlab" for file in problem_definition.customFiles.files]): - if not engine_output: + if not engine_output and display: queue.put(LogData(INFO, "Attempting to start Matlab...")) result = get_matlab_engine(engine_ready, engine_output) + if display: + queue.put(LogData(INFO, "Got Matlab engine")) if isinstance(result, Exception): raise result else: engine_future = result engine_future.result().cd(os.getcwd()) - problem_definition, output_results, bayes_results = rat.rat_core.RATMain(problem_definition, cpp_controls) + if display: + queue.put(LogData(INFO, "Creating RAT Results...")) results = rat.outputs.make_results(procedure, output_results, bayes_results) if engine_future is not None: engine_future.result().exit() diff --git a/rascal2/ui/presenter.py b/rascal2/ui/presenter.py index 4bbf5403..34a8ec8c 100644 --- a/rascal2/ui/presenter.py +++ b/rascal2/ui/presenter.py @@ -1,11 +1,11 @@ import os import re -import time import warnings from typing import Any import ratapi as rat import ratapi.wrappers +from PyQt6.QtCore import QCoreApplication from rascal2.config import LOGGER, SETTINGS, MatlabHelper from rascal2.core import commands @@ -30,12 +30,11 @@ def __init__(self, view): self.view = view self.model = MainWindowModel() self.worker = None - self.runner = RATRunner() + self.runner = RATRunner(self) self.runner.finished.connect(self.handle_results) self.runner.stopped.connect(self.handle_interrupt) self.runner.event_received.connect(self.handle_event) - def create_project(self, name: str, save_path: str): """Create a new RAT project and controls object then initialise UI. @@ -225,7 +224,6 @@ def quick_run(self, project=None): def run(self): """Run rat using multiprocessing.""" # reset terminal - self.t1 = time.perf_counter() self.view.terminal_widget.progress_bar.setVisible(False) if SETTINGS.clear_terminal: self.view.terminal_widget.clear() @@ -236,11 +234,8 @@ def run(self): self.model.controls.initialise_IPC() rat_inputs = rat.inputs.make_input(self.model.project, self.model.controls) display_on = self.model.controls.display != rat.utils.enums.Display.Off - self.runner.set_runner_args(rat_inputs, self.model.controls.procedure, display_on) - self.view.terminal_widget.write("Initializing RAT Process...") - self.t2 = time.perf_counter() self.runner.start() def handle_results(self): @@ -256,9 +251,6 @@ def handle_results(self): self.view.handle_results(self.runner.results) self.model.controls.delete_IPC() self.runner.clear_queues() - self.t3 = time.perf_counter() - print(f"{(self.t2 - self.t1)=}") - print(f"{(self.t3 - self.t1)=}") def handle_interrupt(self): """Handle a RAT run being interrupted.""" @@ -271,20 +263,20 @@ def handle_interrupt(self): def handle_event(self): """Handle event data produced by the RAT run.""" - if self.runner.events: - event = self.runner.events.pop(0) - match event: - case str(): - self.view.terminal_widget.write(event) - chi_squared = get_live_chi_squared(event, str(self.model.controls.procedure)) - if chi_squared is not None: - self.view.controls_widget.chi_squared.setText(chi_squared) - case rat.events.ProgressEventData(): - self.view.terminal_widget.update_progress(event) - case rat.events.PlotEventData(): - self.view.plot_widget.plot_with_blit(event) - case LogData(): - LOGGER.log(event.level, event.msg) + event = self.runner.events.pop(0) + match event: + case str(): + self.view.terminal_widget.write(event) + chi_squared = get_live_chi_squared(event, str(self.model.controls.procedure)) + if chi_squared is not None: + self.view.controls_widget.update_chi_squared(chi_squared) + case rat.events.ProgressEventData(): + self.view.terminal_widget.update_progress(event) + case rat.events.PlotEventData(): + self.view.plot_widget.plot_with_blit(event) + case LogData(): + LOGGER.log(event.level, event.msg) + QCoreApplication.processEvents() def edit_project(self, updated_project: dict, preview: bool = True) -> None: """Edit the Project with a dictionary of attributes. diff --git a/tests/core/test_runner.py b/tests/core/test_runner.py index 339f43d3..6da3a83b 100644 --- a/tests/core/test_runner.py +++ b/tests/core/test_runner.py @@ -38,7 +38,8 @@ def mock_rat_main(*args, **kwargs): def test_start(mock_process, mock_matlab): """Test that `start` creates and starts a process and timer.""" mock_matlab.return_value = MagicMock() - runner = RATRunner(make_rat_input(), "", True) + runner = RATRunner() + runner.set_runner_args(make_rat_input(), "", True) runner.start() runner.process.start.assert_called_once() @@ -50,7 +51,8 @@ def test_start(mock_process, mock_matlab): def test_interrupt(mock_process, mock_matlab): """Test that `interrupt` kills the process and stops the timer.""" mock_matlab.return_value = MagicMock() - runner = RATRunner([], "", True) + runner = RATRunner() + runner.set_runner_args([], "", True) runner.interrupt() runner.process.kill.assert_called_once() @@ -73,7 +75,8 @@ def test_interrupt(mock_process, mock_matlab): def test_check_queue(mock_process, mock_matlab, queue_items): """Test that queue data is appropriately assigned.""" mock_matlab.return_value = MagicMock() - runner = RATRunner([], "", True) + runner = RATRunner() + runner.set_runner_args([], "", True) runner.queue = Queue() for item in queue_items: @@ -101,7 +104,8 @@ def test_check_queue(mock_process, mock_matlab, queue_items): def test_empty_queue(mock_process, mock_matlab): """Test that nothing happens if the queue is empty.""" mock_matlab.return_value = MagicMock() - runner = RATRunner(make_rat_input(), "", True) + runner = RATRunner() + runner.set_runner_args(make_rat_input(), "", True) runner.check_queue() assert len(runner.events) == 0 @@ -114,7 +118,9 @@ def test_empty_queue(mock_process, mock_matlab): def test_run(display): """Test that a run puts the correct items in the queue.""" queue = Queue() - run(queue, make_rat_input(), "", display, None, None) + arg_queue = Queue() + arg_queue.put((make_rat_input(), "", display)) + run(queue, arg_queue, None, None) expected_display = [ LogData(20, "Starting RAT"), 0.2, @@ -122,6 +128,7 @@ def test_run(display): "test message", "test message 2", 0.7, + LogData(20, "Creating RAT Results..."), LogData(20, "Finished RAT"), ] @@ -147,7 +154,9 @@ def erroring_ratmain(*args): queue = Queue() with patch("ratapi.rat_core.RATMain", new=erroring_ratmain): - run(queue, make_rat_input(), "", True, None, None) + args_queue = Queue() + args_queue.put((make_rat_input(), "", True)) + run(queue, args_queue, None, None) queue.put(None) queue_contents = list(iter(queue.get, None)) @@ -172,7 +181,9 @@ def test_run_examples(example): rat_inputs = rat.inputs.make_input(project, rat.Controls()) queue = Queue() - run(queue, rat_inputs, "calculate", False, None, None) + args_queue = Queue() + args_queue.put((rat_inputs, "calculate", False)) + run(queue, args_queue, None, None) output = queue.get() From 9f638568c5b481d9700d58f66b590c51d66b668f Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Wed, 24 Jun 2026 11:10:27 +0100 Subject: [PATCH 3/6] start all RATRunner processes on startup and signal them when to start calculating --- rascal2/core/runner.py | 67 +++++++++++++++++++++++++++++++++-------- rascal2/ui/presenter.py | 4 +++ rascal2/ui/view.py | 2 ++ 3 files changed, 61 insertions(+), 12 deletions(-) diff --git a/rascal2/core/runner.py b/rascal2/core/runner.py index 7d64591d..38a66c55 100644 --- a/rascal2/core/runner.py +++ b/rascal2/core/runner.py @@ -1,9 +1,10 @@ """QObject for running rat.""" import os +import time from dataclasses import dataclass from logging import INFO -from multiprocessing import Process, Queue +from multiprocessing import Process, Queue, cpu_count, Event import ratapi as rat from PyQt6 import QtCore @@ -25,10 +26,14 @@ def __init__(self, parent=None): self.timer.setInterval(1) self.timer.timeout.connect(self.check_queue) self.matlab_helper = MatlabHelper() + self.num_cores = cpu_count() # this queue handles both event data and results self.queue = Queue() self.arg_queue = Queue() + self.go_event = Event() + self.exit_event = Event() + self.processes_list_go_exit_events = [(Event(), Event()) for _ in range(self.num_cores)] self.rat_inputs = None self.procedure = None self.display_on = None @@ -45,19 +50,23 @@ def set_runner_args(self, rat_inputs, procedure, display_on: bool): def start(self): """Start the calculation.""" - if not self.processes_list: - self.refresh_process_list() - self.process = self.processes_list.pop(0) - self.process.start() - if self.parent: - self.parent.view.terminal_widget.write("Starting RAT Runner process...") - self.timer.start() + if not self.process.is_alive(): + if not self.processes_list: + self.refresh_process_list() + self.process = self.processes_list.pop(0) + # self.process.start() + # if self.parent: + # self.parent.view.terminal_widget.write("Starting RAT Runner process...") + self.go_event, self.exit_event = self.processes_list_go_exit_events.pop(0) + self.go_event.set() + self.timer.start() def interrupt(self): """Interrupt the running process.""" self.timer.stop() self.process.kill() self.stopped.emit() + self.go_event.clear() def check_queue(self): """Check for new data in the queue.""" @@ -67,15 +76,19 @@ def check_queue(self): for item in iter(self.queue.get, None): if isinstance(item, tuple): self.updated_problem, self.results = item + self.go_event.clear() self.finished.emit() elif isinstance(item, Exception): self.error = item + self.go_event.clear() self.stopped.emit() else: # else, assume item is an event + print(f"{item=}") self.events.append(item) self.event_received.emit() def refresh_process_list(self): + self.processes_list = [ Process( target=run, @@ -84,18 +97,44 @@ def refresh_process_list(self): self.arg_queue, self.matlab_helper.ready_event, self.matlab_helper.engine_output, + self.processes_list_go_exit_events[ind][0], + self.processes_list_go_exit_events[ind][1] ), ) - for _ in range(5) + for ind in range(self.num_cores) ] def clear_queues(self): self.queue.empty() self.arg_queue.empty() self.events.clear() - - -def run(queue, arg_queue, engine_ready, engine_output): + self.go_event.clear() + self.exit_event.clear() + + def start_processes(self): + for process in self.processes_list: + process.start() + + def stop_processes(self): + print("stopping processes") + self.exit_event.set() + self.go_event.set() + for process in self.processes_list: + print(f"{process.is_alive()}") + for go_event, exit_event in self.processes_list_go_exit_events: + exit_event.set() + go_event.set() + for _ in range(self.queue.qsize()): + print(self.queue.get()) + self.clear_queues() + for process in self.processes_list: + print(process.name) + process.join() + print(f"{process.is_alive()}") + process.close() + + +def run(queue: Queue, arg_queue: Queue, engine_ready, engine_output, go_event, exit_event): """Run RAT and put the result into the queue. Parameters @@ -106,6 +145,10 @@ def run(queue, arg_queue, engine_ready, engine_output): A queue of arguments used to initialize the RAT process, passed from the Main Presenter """ + go_event.wait() + if exit_event.is_set(): + queue.put(LogData(INFO, "exit_event triggers")) + return rat_inputs, procedure, display = arg_queue.get() problem_definition, cpp_controls = rat_inputs diff --git a/rascal2/ui/presenter.py b/rascal2/ui/presenter.py index 34a8ec8c..91e9946d 100644 --- a/rascal2/ui/presenter.py +++ b/rascal2/ui/presenter.py @@ -1,5 +1,6 @@ import os import re +import time import warnings from typing import Any @@ -34,6 +35,7 @@ def __init__(self, view): self.runner.finished.connect(self.handle_results) self.runner.stopped.connect(self.handle_interrupt) self.runner.event_received.connect(self.handle_event) + self.runner.start_processes() def create_project(self, name: str, save_path: str): """Create a new RAT project and controls object then initialise UI. @@ -224,6 +226,7 @@ def quick_run(self, project=None): def run(self): """Run rat using multiprocessing.""" # reset terminal + self.t1 = time.perf_counter() self.view.terminal_widget.progress_bar.setVisible(False) if SETTINGS.clear_terminal: self.view.terminal_widget.clear() @@ -251,6 +254,7 @@ def handle_results(self): self.view.handle_results(self.runner.results) self.model.controls.delete_IPC() self.runner.clear_queues() + print(f"{time.perf_counter() - self.t1} seconds elapsed.") def handle_interrupt(self): """Handle a RAT run being interrupted.""" diff --git a/rascal2/ui/view.py b/rascal2/ui/view.py index 6325910b..7af051dd 100644 --- a/rascal2/ui/view.py +++ b/rascal2/ui/view.py @@ -81,6 +81,8 @@ def closeEvent(self, event): event.accept() else: event.ignore() + self.presenter.runner.stop_processes() + event.accept() def show_project_dialog(self, dialog: StartupDialog): """Show a startup dialog of a given type. From 917f3b099003c440b163ec4e12962ef49f6c336b Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Thu, 25 Jun 2026 21:43:57 +0100 Subject: [PATCH 4/6] various test fixes to take running processes early into account --- rascal2/core/runner.py | 54 ++++++++++++++++++++++++--------------- rascal2/ui/presenter.py | 2 -- tests/core/test_runner.py | 42 +++++++++++++++++++++++------- tests/test_ui.py | 5 ++++ 4 files changed, 71 insertions(+), 32 deletions(-) diff --git a/rascal2/core/runner.py b/rascal2/core/runner.py index 38a66c55..f6ef67ba 100644 --- a/rascal2/core/runner.py +++ b/rascal2/core/runner.py @@ -18,8 +18,10 @@ class RATRunner(QtCore.QObject): event_received = QtCore.pyqtSignal() finished = QtCore.pyqtSignal() stopped = QtCore.pyqtSignal() + go_event = Event() + processes_list_go_exit_events = [] - def __init__(self, parent=None): + def __init__(self, parent=None, start_runners_early: bool = True): super().__init__() self.parent = parent self.timer = QtCore.QTimer() @@ -27,19 +29,20 @@ def __init__(self, parent=None): self.timer.timeout.connect(self.check_queue) self.matlab_helper = MatlabHelper() self.num_cores = cpu_count() + self.start_runners_early = start_runners_early # this queue handles both event data and results self.queue = Queue() self.arg_queue = Queue() self.go_event = Event() self.exit_event = Event() - self.processes_list_go_exit_events = [(Event(), Event()) for _ in range(self.num_cores)] + # self.processes_list_go_exit_events = [(Event(), Event()) for _ in range(self.num_cores)] self.rat_inputs = None self.procedure = None self.display_on = None self.processes_list = [] self.refresh_process_list() - self.process = self.processes_list.pop(0) + self.process = None self.updated_problem = None self.results = None self.error = None @@ -49,17 +52,19 @@ def set_runner_args(self, rat_inputs, procedure, display_on: bool): self.arg_queue.put((rat_inputs, procedure, display_on)) def start(self): + print("========= START =================") """Start the calculation.""" - if not self.process.is_alive(): - if not self.processes_list: - self.refresh_process_list() - self.process = self.processes_list.pop(0) - # self.process.start() - # if self.parent: - # self.parent.view.terminal_widget.write("Starting RAT Runner process...") - self.go_event, self.exit_event = self.processes_list_go_exit_events.pop(0) - self.go_event.set() - self.timer.start() + self.process, (self.go_event, self.exit_event) = self.get_new_process() + print(self.process) + print(self.go_event) + print(self.exit_event) + self.go_event.set() + self.timer.start() + + def get_new_process(self): + if not self.processes_list: + self.refresh_process_list() + return self.processes_list.pop(0), self.processes_list_go_exit_events.pop(0) def interrupt(self): """Interrupt the running process.""" @@ -88,7 +93,7 @@ def check_queue(self): self.event_received.emit() def refresh_process_list(self): - + self.processes_list_go_exit_events = [(Event(), Event()) for _ in range(self.num_cores)] self.processes_list = [ Process( target=run, @@ -112,26 +117,33 @@ def clear_queues(self): self.exit_event.clear() def start_processes(self): - for process in self.processes_list: - process.start() + if self.start_runners_early: + for process in self.processes_list: + process.start() def stop_processes(self): print("stopping processes") + self.clear_queues() self.exit_event.set() self.go_event.set() for process in self.processes_list: print(f"{process.is_alive()}") + if process.is_alive(): + process.kill() + self.processes_list.clear() for go_event, exit_event in self.processes_list_go_exit_events: exit_event.set() go_event.set() + self.processes_list_go_exit_events = [] for _ in range(self.queue.qsize()): print(self.queue.get()) self.clear_queues() - for process in self.processes_list: - print(process.name) - process.join() - print(f"{process.is_alive()}") - process.close() + # for process in self.processes_list: + # print(process.name) + # if process.is_alive(): + # process.join() + # print(f"{process.is_alive()}") + # process.close() def run(queue: Queue, arg_queue: Queue, engine_ready, engine_output, go_event, exit_event): diff --git a/rascal2/ui/presenter.py b/rascal2/ui/presenter.py index 91e9946d..eecd7c3e 100644 --- a/rascal2/ui/presenter.py +++ b/rascal2/ui/presenter.py @@ -226,7 +226,6 @@ def quick_run(self, project=None): def run(self): """Run rat using multiprocessing.""" # reset terminal - self.t1 = time.perf_counter() self.view.terminal_widget.progress_bar.setVisible(False) if SETTINGS.clear_terminal: self.view.terminal_widget.clear() @@ -254,7 +253,6 @@ def handle_results(self): self.view.handle_results(self.runner.results) self.model.controls.delete_IPC() self.runner.clear_queues() - print(f"{time.perf_counter() - self.t1} seconds elapsed.") def handle_interrupt(self): """Handle a RAT run being interrupted.""" diff --git a/tests/core/test_runner.py b/tests/core/test_runner.py index 6da3a83b..67d3476c 100644 --- a/tests/core/test_runner.py +++ b/tests/core/test_runner.py @@ -2,6 +2,7 @@ import contextlib import os +from multiprocessing import Event from queue import Queue # we need a non-multiprocessing queue because mocks cannot be serialised from unittest.mock import MagicMock, patch @@ -35,29 +36,39 @@ def mock_rat_main(*args, **kwargs): @patch("rascal2.core.runner.MatlabHelper", autospec=True) @patch("rascal2.core.runner.Process") -def test_start(mock_process, mock_matlab): +@patch("rascal2.core.runner.RATRunner.get_new_process") +def test_start(mock_process_go_exit, mock_process, mock_matlab): """Test that `start` creates and starts a process and timer.""" mock_matlab.return_value = MagicMock() - runner = RATRunner() + mock_go = MagicMock() + mock_process_go_exit.return_value = MagicMock(), (mock_go, MagicMock()) + runner = RATRunner(start_runners_early=False) + runner.process = MagicMock() + runner.num_cores = MagicMock(return_value=1) runner.set_runner_args(make_rat_input(), "", True) runner.start() - runner.process.start.assert_called_once() + mock_go.set.assert_called_once() assert runner.timer.isActive() + runner.stop_processes() + @patch("rascal2.core.runner.MatlabHelper", autospec=True) @patch("rascal2.core.runner.Process") def test_interrupt(mock_process, mock_matlab): """Test that `interrupt` kills the process and stops the timer.""" mock_matlab.return_value = MagicMock() - runner = RATRunner() + runner = RATRunner(start_runners_early=False) + runner.process = MagicMock() runner.set_runner_args([], "", True) runner.interrupt() runner.process.kill.assert_called_once() assert not runner.timer.isActive() + runner.stop_processes() + @pytest.mark.parametrize( "queue_items", @@ -75,7 +86,8 @@ def test_interrupt(mock_process, mock_matlab): def test_check_queue(mock_process, mock_matlab, queue_items): """Test that queue data is appropriately assigned.""" mock_matlab.return_value = MagicMock() - runner = RATRunner() + runner = RATRunner(start_runners_early=False) + runner.process = MagicMock() runner.set_runner_args([], "", True) runner.queue = Queue() @@ -98,19 +110,24 @@ def test_check_queue(mock_process, mock_matlab, queue_items): assert isinstance(runner.error, ValueError) assert str(runner.error) == "Runner error!" + runner.stop_processes() + @patch("rascal2.core.runner.MatlabHelper", autospec=True) @patch("rascal2.core.runner.Process") def test_empty_queue(mock_process, mock_matlab): """Test that nothing happens if the queue is empty.""" mock_matlab.return_value = MagicMock() - runner = RATRunner() + runner = RATRunner(start_runners_early=False) + runner.process = MagicMock() runner.set_runner_args(make_rat_input(), "", True) runner.check_queue() assert len(runner.events) == 0 assert runner.results is None + runner.stop_processes() + @pytest.mark.parametrize("display", [True, False]) @patch("ratapi.rat_core.RATMain", new=mock_rat_main) @@ -120,7 +137,10 @@ def test_run(display): queue = Queue() arg_queue = Queue() arg_queue.put((make_rat_input(), "", display)) - run(queue, arg_queue, None, None) + go_event, exit_event = (Event(), Event()) + go_event.set() + run(queue, arg_queue, None, None, go_event, exit_event) + expected_display = [ LogData(20, "Starting RAT"), 0.2, @@ -156,7 +176,9 @@ def erroring_ratmain(*args): with patch("ratapi.rat_core.RATMain", new=erroring_ratmain): args_queue = Queue() args_queue.put((make_rat_input(), "", True)) - run(queue, args_queue, None, None) + go_event, exit_event = (Event(), Event()) + go_event.set() + run(queue, args_queue, None, None, go_event, exit_event) queue.put(None) queue_contents = list(iter(queue.get, None)) @@ -183,7 +205,9 @@ def test_run_examples(example): queue = Queue() args_queue = Queue() args_queue.put((rat_inputs, "calculate", False)) - run(queue, args_queue, None, None) + go_event, exit_event = (Event(), Event()) + go_event.set() + run(queue, args_queue, None, None, go_event, exit_event) output = queue.get() diff --git a/tests/test_ui.py b/tests/test_ui.py index ae90d9ea..88b57760 100644 --- a/tests/test_ui.py +++ b/tests/test_ui.py @@ -35,12 +35,17 @@ def test_integration(qt_application, make_main_window): _ = qt_application window = make_main_window() window.show() + print("test_integration 1") window.presenter.create_project("project", ".") + print("test_integration 2") names = [win.windowTitle() for win in window.mdi.subWindowList()] + print("test_integration 3") # QMDIArea is first in last out hence the reversed list assert names == ["Fitting Controls", "Terminal", "Project", "Plots"] + print("test_integration 4") # Work through the different sections of the UI window.close() + print("test_integration 5") From 44b71aae4e27374eb316e250d2575e19bc3af9d7 Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Fri, 26 Jun 2026 15:49:40 +0100 Subject: [PATCH 5/6] If matlab engine is only checked when it is needed ruff fixes runner test fixes runner test fixes --- rascal2/core/runner.py | 106 +++++++++++++++++++++----------------- tests/core/test_runner.py | 33 +++++++----- 2 files changed, 81 insertions(+), 58 deletions(-) diff --git a/rascal2/core/runner.py b/rascal2/core/runner.py index f6ef67ba..db3093ed 100644 --- a/rascal2/core/runner.py +++ b/rascal2/core/runner.py @@ -1,10 +1,9 @@ """QObject for running rat.""" import os -import time from dataclasses import dataclass from logging import INFO -from multiprocessing import Process, Queue, cpu_count, Event +from multiprocessing import Event, Process, Queue, cpu_count import ratapi as rat from PyQt6 import QtCore @@ -21,14 +20,14 @@ class RATRunner(QtCore.QObject): go_event = Event() processes_list_go_exit_events = [] - def __init__(self, parent=None, start_runners_early: bool = True): + def __init__(self, parent=None, start_runners_early: bool = True, num_cores: int = cpu_count()): super().__init__() self.parent = parent self.timer = QtCore.QTimer() self.timer.setInterval(1) self.timer.timeout.connect(self.check_queue) self.matlab_helper = MatlabHelper() - self.num_cores = cpu_count() + self.num_cores = num_cores self.start_runners_early = start_runners_early # this queue handles both event data and results @@ -36,7 +35,6 @@ def __init__(self, parent=None, start_runners_early: bool = True): self.arg_queue = Queue() self.go_event = Event() self.exit_event = Event() - # self.processes_list_go_exit_events = [(Event(), Event()) for _ in range(self.num_cores)] self.rat_inputs = None self.procedure = None self.display_on = None @@ -47,17 +45,18 @@ def __init__(self, parent=None, start_runners_early: bool = True): self.results = None self.error = None self.events = [] + self.engine_future = None def set_runner_args(self, rat_inputs, procedure, display_on: bool): self.arg_queue.put((rat_inputs, procedure, display_on)) + self.rat_inputs = rat_inputs + self.display_on = display_on def start(self): - print("========= START =================") """Start the calculation.""" self.process, (self.go_event, self.exit_event) = self.get_new_process() - print(self.process) - print(self.go_event) - print(self.exit_event) + if self.engine_future is None: + self.get_runner_matlab_engine() self.go_event.set() self.timer.start() @@ -66,6 +65,19 @@ def get_new_process(self): self.refresh_process_list() return self.processes_list.pop(0), self.processes_list_go_exit_events.pop(0) + def get_runner_matlab_engine(self): + problem_definition, cpp_controls = self.rat_inputs + if any([file["language"] == "matlab" for file in problem_definition.customFiles.files]): + engine_ready = (self.matlab_helper.ready_event,) + engine_output = self.matlab_helper.engine_output + matlab_queue = Queue() + get_runner_matlab_engine_process = Process( + target=run_matlab_init_engine, + args=(matlab_queue, engine_output, engine_ready, self.display_on), + ) + get_runner_matlab_engine_process.start() + self.engine_future = self.filter_queue(matlab_queue) + def interrupt(self): """Interrupt the running process.""" self.timer.stop() @@ -77,8 +89,11 @@ def check_queue(self): """Check for new data in the queue.""" if not self.process.is_alive(): self.timer.stop() - self.queue.put(None) - for item in iter(self.queue.get, None): + self.filter_queue(self.queue) + + def filter_queue(self, queue: Queue): + queue.put(None) + for item in iter(queue.get, None): if isinstance(item, tuple): self.updated_problem, self.results = item self.go_event.clear() @@ -87,8 +102,9 @@ def check_queue(self): self.error = item self.go_event.clear() self.stopped.emit() + elif isinstance(item, list): + return item[0] else: # else, assume item is an event - print(f"{item=}") self.events.append(item) self.event_received.emit() @@ -100,10 +116,8 @@ def refresh_process_list(self): args=( self.queue, self.arg_queue, - self.matlab_helper.ready_event, - self.matlab_helper.engine_output, self.processes_list_go_exit_events[ind][0], - self.processes_list_go_exit_events[ind][1] + self.processes_list_go_exit_events[ind][1], ), ) for ind in range(self.num_cores) @@ -122,31 +136,25 @@ def start_processes(self): process.start() def stop_processes(self): - print("stopping processes") - self.clear_queues() self.exit_event.set() self.go_event.set() + for go_event, exit_event in self.processes_list_go_exit_events: + exit_event.set() + go_event.set() for process in self.processes_list: - print(f"{process.is_alive()}") if process.is_alive(): process.kill() self.processes_list.clear() - for go_event, exit_event in self.processes_list_go_exit_events: - exit_event.set() - go_event.set() - self.processes_list_go_exit_events = [] - for _ in range(self.queue.qsize()): - print(self.queue.get()) self.clear_queues() - # for process in self.processes_list: - # print(process.name) - # if process.is_alive(): - # process.join() - # print(f"{process.is_alive()}") - # process.close() + self.processes_list_go_exit_events.clear() + print(f"{self.queue=}") + self.queue.close() + self.arg_queue.close() + if self.engine_future is not None: + self.engine_future.result().exit() -def run(queue: Queue, arg_queue: Queue, engine_ready, engine_output, go_event, exit_event): +def run(queue: Queue, arg_queue: Queue, go_event, exit_event): """Run RAT and put the result into the queue. Parameters @@ -171,25 +179,10 @@ def run(queue: Queue, arg_queue: Queue, engine_ready, engine_output, go_event, e queue.put(LogData(INFO, "Starting RAT")) try: - engine_future = None - if any([file["language"] == "matlab" for file in problem_definition.customFiles.files]): - if not engine_output and display: - queue.put(LogData(INFO, "Attempting to start Matlab...")) - - result = get_matlab_engine(engine_ready, engine_output) - if display: - queue.put(LogData(INFO, "Got Matlab engine")) - if isinstance(result, Exception): - raise result - else: - engine_future = result - engine_future.result().cd(os.getcwd()) problem_definition, output_results, bayes_results = rat.rat_core.RATMain(problem_definition, cpp_controls) if display: queue.put(LogData(INFO, "Creating RAT Results...")) results = rat.outputs.make_results(procedure, output_results, bayes_results) - if engine_future is not None: - engine_future.result().exit() except Exception as err: queue.put(err) return @@ -201,6 +194,27 @@ def run(queue: Queue, arg_queue: Queue, engine_ready, engine_output, go_event, e queue.put((problem_definition, results)) +def run_matlab_init_engine(queue, engine_output, engine_ready, display_on): + """Get the engine future from the matlab engine and put in queue if successfully.""" + try: + if not engine_output and display_on: + queue.put(LogData(INFO, "Attempting to start Matlab...")) + + result = get_matlab_engine(engine_ready, engine_output) + if display_on: + queue.put(LogData(INFO, "Got Matlab engine")) + if isinstance(result, Exception): + raise result + else: + engine_future = result + engine_future.result().cd(os.getcwd()) + queue.put([engine_future]) + + except Exception as err: + queue.put(err) + return + + @dataclass class LogData: """Dataclass for logging data.""" diff --git a/tests/core/test_runner.py b/tests/core/test_runner.py index 67d3476c..258d52c5 100644 --- a/tests/core/test_runner.py +++ b/tests/core/test_runner.py @@ -34,6 +34,13 @@ def mock_rat_main(*args, **kwargs): return 1, 2, 3 +def close_processes(runner): + # Non serialised queue does not have a close attribute so have to mock it out + runner.queue.close = MagicMock() + runner.arg_queue.close = MagicMock() + runner.stop_processes() + + @patch("rascal2.core.runner.MatlabHelper", autospec=True) @patch("rascal2.core.runner.Process") @patch("rascal2.core.runner.RATRunner.get_new_process") @@ -42,16 +49,16 @@ def test_start(mock_process_go_exit, mock_process, mock_matlab): mock_matlab.return_value = MagicMock() mock_go = MagicMock() mock_process_go_exit.return_value = MagicMock(), (mock_go, MagicMock()) - runner = RATRunner(start_runners_early=False) + runner = RATRunner(start_runners_early=False, num_cores=1) runner.process = MagicMock() - runner.num_cores = MagicMock(return_value=1) + runner.get_runner_matlab_engine = MagicMock() runner.set_runner_args(make_rat_input(), "", True) runner.start() mock_go.set.assert_called_once() assert runner.timer.isActive() - runner.stop_processes() + close_processes(runner) @patch("rascal2.core.runner.MatlabHelper", autospec=True) @@ -59,7 +66,7 @@ def test_start(mock_process_go_exit, mock_process, mock_matlab): def test_interrupt(mock_process, mock_matlab): """Test that `interrupt` kills the process and stops the timer.""" mock_matlab.return_value = MagicMock() - runner = RATRunner(start_runners_early=False) + runner = RATRunner(start_runners_early=False, num_cores=1) runner.process = MagicMock() runner.set_runner_args([], "", True) runner.interrupt() @@ -67,7 +74,7 @@ def test_interrupt(mock_process, mock_matlab): runner.process.kill.assert_called_once() assert not runner.timer.isActive() - runner.stop_processes() + close_processes(runner) @pytest.mark.parametrize( @@ -86,8 +93,9 @@ def test_interrupt(mock_process, mock_matlab): def test_check_queue(mock_process, mock_matlab, queue_items): """Test that queue data is appropriately assigned.""" mock_matlab.return_value = MagicMock() - runner = RATRunner(start_runners_early=False) + runner = RATRunner(start_runners_early=False, num_cores=1) runner.process = MagicMock() + runner.get_runner_matlab_engine = MagicMock() runner.set_runner_args([], "", True) runner.queue = Queue() @@ -110,7 +118,7 @@ def test_check_queue(mock_process, mock_matlab, queue_items): assert isinstance(runner.error, ValueError) assert str(runner.error) == "Runner error!" - runner.stop_processes() + close_processes(runner) @patch("rascal2.core.runner.MatlabHelper", autospec=True) @@ -118,15 +126,16 @@ def test_check_queue(mock_process, mock_matlab, queue_items): def test_empty_queue(mock_process, mock_matlab): """Test that nothing happens if the queue is empty.""" mock_matlab.return_value = MagicMock() - runner = RATRunner(start_runners_early=False) + runner = RATRunner(start_runners_early=False, num_cores=1) runner.process = MagicMock() runner.set_runner_args(make_rat_input(), "", True) + runner.check_queue() assert len(runner.events) == 0 assert runner.results is None - runner.stop_processes() + close_processes(runner) @pytest.mark.parametrize("display", [True, False]) @@ -139,7 +148,7 @@ def test_run(display): arg_queue.put((make_rat_input(), "", display)) go_event, exit_event = (Event(), Event()) go_event.set() - run(queue, arg_queue, None, None, go_event, exit_event) + run(queue, arg_queue, go_event, exit_event) expected_display = [ LogData(20, "Starting RAT"), @@ -178,7 +187,7 @@ def erroring_ratmain(*args): args_queue.put((make_rat_input(), "", True)) go_event, exit_event = (Event(), Event()) go_event.set() - run(queue, args_queue, None, None, go_event, exit_event) + run(queue, args_queue, go_event, exit_event) queue.put(None) queue_contents = list(iter(queue.get, None)) @@ -207,7 +216,7 @@ def test_run_examples(example): args_queue.put((rat_inputs, "calculate", False)) go_event, exit_event = (Event(), Event()) go_event.set() - run(queue, args_queue, None, None, go_event, exit_event) + run(queue, args_queue, go_event, exit_event) output = queue.get() From f34f070cf0846e0c9a5d880abdff9052432ab8a5 Mon Sep 17 00:00:00 2001 From: Mike Sullivan Date: Tue, 30 Jun 2026 16:06:04 +0100 Subject: [PATCH 6/6] fix tests to take multiple processes starting early into account --- rascal2/core/runner.py | 5 ++++- rascal2/ui/presenter.py | 5 +++-- tests/conftest.py | 12 ++++++++++++ tests/ui/test_presenter.py | 2 +- tests/ui/test_view.py | 7 +++++-- tests/widgets/project/test_slider_view.py | 9 ++++++--- 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/rascal2/core/runner.py b/rascal2/core/runner.py index db3093ed..4aedd3c5 100644 --- a/rascal2/core/runner.py +++ b/rascal2/core/runner.py @@ -58,6 +58,8 @@ def start(self): if self.engine_future is None: self.get_runner_matlab_engine() self.go_event.set() + if not self.process.is_alive(): + self.process.start() self.timer.start() def get_new_process(self): @@ -76,6 +78,7 @@ def get_runner_matlab_engine(self): args=(matlab_queue, engine_output, engine_ready, self.display_on), ) get_runner_matlab_engine_process.start() + get_runner_matlab_engine_process.join() self.engine_future = self.filter_queue(matlab_queue) def interrupt(self): @@ -147,11 +150,11 @@ def stop_processes(self): self.processes_list.clear() self.clear_queues() self.processes_list_go_exit_events.clear() - print(f"{self.queue=}") self.queue.close() self.arg_queue.close() if self.engine_future is not None: self.engine_future.result().exit() + self.matlab_helper.close_event.set() def run(queue: Queue, arg_queue: Queue, go_event, exit_event): diff --git a/rascal2/ui/presenter.py b/rascal2/ui/presenter.py index eecd7c3e..5a8270c3 100644 --- a/rascal2/ui/presenter.py +++ b/rascal2/ui/presenter.py @@ -1,6 +1,5 @@ import os import re -import time import warnings from typing import Any @@ -17,6 +16,8 @@ from .model import MainWindowModel +START_PROCESSES = bool(os.getenv("START_PROCESSES", "True")) + class MainWindowPresenter: """Facilitates interaction between View and Model. @@ -31,7 +32,7 @@ def __init__(self, view): self.view = view self.model = MainWindowModel() self.worker = None - self.runner = RATRunner(self) + self.runner = RATRunner(self, start_runners_early=START_PROCESSES) self.runner.finished.connect(self.handle_results) self.runner.stopped.connect(self.handle_interrupt) self.runner.event_received.connect(self.handle_event) diff --git a/tests/conftest.py b/tests/conftest.py index ff31b498..a1f2f688 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,6 +19,18 @@ def global_setting(): return GLOBAL_SETTING +@pytest.fixture(autouse=True) +@patch("rascal2.core.runner.cpu_count") +def fix_cpu_count(cpu_count): + cpu_count.return_value = 1 + yield + + +@pytest.fixture(scope="function", autouse=True) +def mock_start_processes_setting(monkeypatch): + monkeypatch.setenv("START_PROCESSES", "False") + + @pytest.fixture(scope="session", autouse=True) def mock_setting(request): global GLOBAL_SETTING diff --git a/tests/ui/test_presenter.py b/tests/ui/test_presenter.py index 0d85e053..151f83e0 100644 --- a/tests/ui/test_presenter.py +++ b/tests/ui/test_presenter.py @@ -53,9 +53,9 @@ def presenter(): with ( patch("rascal2.ui.presenter.LOGGER", autospec=True) as mock_log, patch("rascal2.ui.model.os.chdir", autospec=True), + patch("rascal2.ui.presenter.RATRunner", autospec=True, return_value=MagicMock()), ): pr = MainWindowPresenter(MockWindowView()) - pr.runner = MagicMock() pr.model.controls = Controls() pr.model.project = MagicMock() pr.model.project.name = "test_name" diff --git a/tests/ui/test_view.py b/tests/ui/test_view.py index e9754f8a..400e561c 100644 --- a/tests/ui/test_view.py +++ b/tests/ui/test_view.py @@ -33,6 +33,7 @@ def test_view(): with ( patch("rascal2.widgets.plot.FigureCanvasQTAgg", return_value=MockFigureCanvas()), patch("rascal2.widgets.plot.NavigationToolbar2QT", return_value=MockNavigationToolbar()), + patch("rascal2.ui.presenter.RATRunner", autospec=True, return_value=MagicMock()), ): yield MainWindowView() @@ -116,7 +117,8 @@ def test_set_enabled(test_view): @patch("PyQt6.QtWidgets.QFileDialog.getExistingDirectory") @patch("rascal2.ui.view.get_global_settings") -def test_get_project_folder(mock_get_global, mock_get_dir: MagicMock): +@patch("rascal2.ui.presenter.RATRunner", autospec=True, return_value=MagicMock()) +def test_get_project_folder(mock_runner, mock_get_global, mock_get_dir: MagicMock): """Test that getting a specified folder works as expected.""" with tempfile.TemporaryDirectory() as tmp_dir: ini_file = Path(tmp_dir) / "settings.ini" @@ -206,7 +208,8 @@ def test_help_menu_actions_present(test_view, submenu_name, action_names_and_lay assert action.text() == name -def test_toggle_slider(): +@patch("rascal2.ui.presenter.RATRunner", autospec=True, return_value=MagicMock()) +def test_toggle_slider(mock_runner): mw = MainWindowView() with patch.object(mw, "project_widget") as project_mock: show_text = mw.toggle_slider_action.property("show_text") diff --git a/tests/widgets/project/test_slider_view.py b/tests/widgets/project/test_slider_view.py index cb7270c3..4bc11c59 100644 --- a/tests/widgets/project/test_slider_view.py +++ b/tests/widgets/project/test_slider_view.py @@ -52,7 +52,8 @@ def draft_project(): return draft -def test_no_sliders_creation(): +@patch("rascal2.ui.presenter.RATRunner", autospec=True, return_value=MagicMock()) +def test_no_sliders_creation(mock_runner): """Slider view should show warning when there is no fitted parameter.""" mw = MainWindowView() draft = create_draft_project(ratapi.Project()) @@ -64,7 +65,8 @@ def test_no_sliders_creation(): assert label.text().startswith("There are no fitted parameters") -def test_sliders_creation(draft_project): +@patch("rascal2.ui.presenter.RATRunner", autospec=True, return_value=MagicMock()) +def test_sliders_creation(mock_runner, draft_project): """Sliders should be created for fitted parameter only.""" mw = MainWindowView() slider_view = SliderViewWidget(draft_project, mw) @@ -81,7 +83,8 @@ def test_sliders_creation(draft_project): assert draft_project["parameters"][0].name not in slider_view._sliders -def test_accept_and_cancel_slider_buttons(): +@patch("rascal2.ui.presenter.RATRunner", autospec=True, return_value=MagicMock()) +def test_accept_and_cancel_slider_buttons(mock_runner): mw = MainWindowView() draft = create_draft_project(ratapi.Project()) mw.toggle_sliders = MagicMock()