From 76c5786585b6a88b1bcae83ab31485542461cc5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Mon, 20 Dec 2021 16:25:48 +0100 Subject: [PATCH] =?UTF-8?q?=E2=98=94=F0=9F=AA=B2=20Change=20properly=20cat?= =?UTF-8?q?ch=20error=20in=20tests.=20Remove=20code=20that=20was=20wrongfu?= =?UTF-8?q?lly=20added=20by=20git=20during=20a=20merge.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opencodeblocks/blocks/codeblock.py | 82 --------------------------- tests/integration/blocks/test_flow.py | 26 +++++---- tests/integration/test_window.py | 10 ++-- tests/integration/utils.py | 40 ++++++++++++- 4 files changed, 56 insertions(+), 102 deletions(-) diff --git a/opencodeblocks/blocks/codeblock.py b/opencodeblocks/blocks/codeblock.py index 07ae52ca..db6ce55a 100644 --- a/opencodeblocks/blocks/codeblock.py +++ b/opencodeblocks/blocks/codeblock.py @@ -126,88 +126,6 @@ def execution_finished(self): self.run_button.setText(">") self.run_all_button.setText(">>") - def has_input(self) -> bool: - """Checks whether a block has connected input blocks""" - for input_socket in self.sockets_in: - if len(input_socket.edges) != 0: - return True - return False - - def has_output(self) -> bool: - """Checks whether a block has connected output blocks""" - for output_socket in self.sockets_out: - if len(output_socket.edges) != 0: - return True - return False - - def _interrupt_execution(self): - """Interrupt an execution, reset the blocks in the queue""" - for block, _ in self.source_editor.kernel.execution_queue: - # Reset the blocks that have not been run - block.reset_buttons() - block.has_been_run = False - # Clear the queue - self.source_editor.kernel.execution_queue = [] - # Interrupt the kernel - self.source_editor.kernel.kernel_manager.interrupt_kernel() - - def run_left(self, in_right_button=False): - """ - Run all of the block's dependencies and then run the block - """ - # If the user presses left run when running, cancel the execution - if self.run_button.text() == "..." and not in_right_button: - self._interrupt_execution() - return - - # If no dependencies - if not self.has_input(): - return self.run_code() - - # Create the graph from the scene - graph = self.scene().create_graph() - # BFS through the input graph - edges = bfs_edges(graph, self, reverse=True) - # Run the blocks found except self - blocks_to_run: List["OCBCodeBlock"] = [v for _, v in edges] - for block in blocks_to_run[::-1]: - if not block.has_been_run: - block.run_code() - - if in_right_button: - # If run_left was called inside of run_right - # self is not necessarily the block that was clicked - # which means that self does not need to be run - if not self.has_been_run: - self.run_code() - else: - # On the contrary if run_left was called outside of run_right - # self is the block that was clicked - # so self needs to be run - self.run_code() - - def run_right(self): - """Run all of the output blocks and all their dependencies""" - # If the user presses right run when running, cancel the execution - if self.run_all_button.text() == "...": - self._interrupt_execution() - return - - # If no output, run left - if not self.has_output(): - return self.run_left(in_right_button=True) - - # Same as run_left but instead of running the blocks, we'll use run_left - graph = self.scene().create_graph() - edges = bfs_edges(graph, self) - blocks_to_run: List["OCBCodeBlock"] = [self] + [v for _, v in edges] - for block in blocks_to_run[::-1]: - block.run_left(in_right_button=True) - - def reset_has_been_run(self): - """Reset has_been_run, is called when the output is an error""" - self.has_been_run = False - def update_title(self): """Change the geometry of the title widget""" self.title_widget.setGeometry( diff --git a/tests/integration/blocks/test_flow.py b/tests/integration/blocks/test_flow.py index 8d408d53..3121567c 100644 --- a/tests/integration/blocks/test_flow.py +++ b/tests/integration/blocks/test_flow.py @@ -9,10 +9,8 @@ import time from opencodeblocks.blocks.codeblock import OCBCodeBlock -from opencodeblocks.graphics.window import OCBWindow -from opencodeblocks.graphics.widget import OCBWidget -from tests.integration.utils import apply_function_inapp, CheckingQueue +from tests.integration.utils import apply_function_inapp, CheckingQueue, start_app class TestCodeBlocks: @@ -20,20 +18,17 @@ class TestCodeBlocks: @pytest.fixture(autouse=True) def setup(self): """ Setup reused variables. """ - self.window = OCBWindow() - self.ocb_widget = OCBWidget() - self.subwindow = self.window.mdiArea.addSubWindow(self.ocb_widget) - self.subwindow.show() + start_app(self) self.ocb_widget.scene.load("tests/assets/flow_test.ipyg") - titles = ["Test flow 5", "Test flow 4", "Test no connection 1", + self.titles = ["Test flow 5", "Test flow 4", "Test no connection 1", "Test input only 2", "Test output only 1"] self.blocks_to_run = [None]*5 for item in self.ocb_widget.scene.items(): if isinstance(item, OCBCodeBlock): - if item.title in titles: - self.blocks_to_run[titles.index(item.title)] = item + if item.title in self.titles: + self.blocks_to_run[self.titles.index(item.title)] = item def test_duplicated_run(self): """ Don't run a block twice when the execution flows """ @@ -95,8 +90,13 @@ def testing_run(msgQueue: CheckingQueue): def run_block(): block_to_run.run_left() + print("About to run !") + msgQueue.run_lambda(run_block) - time.sleep(0.5) + time.sleep(0.1) + while block_to_run.is_running: + print("wait ...") + time.sleep(0.1) msgQueue.check_equal(block_to_run.stdout.strip(), "1") msgQueue.stop() @@ -116,7 +116,9 @@ def run_block(): block_to_run.run_right() msgQueue.run_lambda(run_block) - time.sleep(0.5) + time.sleep(0.1) + while block_to_run.is_running: + time.sleep(0.1) # Just check that it doesn't crash msgQueue.stop() diff --git a/tests/integration/test_window.py b/tests/integration/test_window.py index 2544b81f..780f041c 100644 --- a/tests/integration/test_window.py +++ b/tests/integration/test_window.py @@ -18,14 +18,14 @@ def setup(self, mocker: MockerFixture): """Setup reused variables.""" self.window = OCBWindow() - def test_window_close(self, qtbot): - """closes""" - self.window.close() - - def test_open_file(self): + def test_open_file(self, qtbot): """loads files""" wnd = OCBWindow() file_example_path = "./tests/assets/example_graph1.ipyg" subwnd = wnd.createNewMdiChild(os.path.abspath(file_example_path)) subwnd.show() wnd.close() + + def test_window_close(self, qtbot): + """closes""" + self.window.close() \ No newline at end of file diff --git a/tests/integration/utils.py b/tests/integration/utils.py index 9c5af1ec..89f41067 100644 --- a/tests/integration/utils.py +++ b/tests/integration/utils.py @@ -5,14 +5,17 @@ Utilities functions for integration testing. """ -import os -import asyncio from typing import Callable +import os +import asyncio import threading +import time from queue import Queue + from qtpy.QtWidgets import QApplication import pytest_check as check +import warnings from opencodeblocks.graphics.widget import OCBWidget from opencodeblocks.graphics.window import OCBWindow @@ -32,6 +35,27 @@ def run_lambda(self, func: Callable, *args, **kwargs): def stop(self): self.put([STOP_MSG]) +class ExceptionForwardingThread(threading.Thread): + """ A Thread class that forwards the exceptions to the calling thread """ + def __init__(self, *args, **kwargs): + """ Create an exception forwarding thread """ + super().__init__(*args, **kwargs) + self.e = None + + def run(self): + """ Code ran in another thread """ + try: + super().run() + except Exception as e: + self.e = e + + def join(self): + """ Used to sync the thread with the caller """ + super().join() + print("except: ",self.e) + if self.e != None: + raise self.e + def start_app(obj): """ Create a new app for testing """ @@ -47,11 +71,14 @@ def apply_function_inapp(window: OCBWindow, run_func: Callable): QApplication.processEvents() msgQueue = CheckingQueue() - t = threading.Thread(target=run_func, args=(msgQueue,)) + t = ExceptionForwardingThread(target=run_func, args=(msgQueue,)) t.start() stop = False + deadCounter = 0 + while not stop: + time.sleep(1 / 30) # 30 fps QApplication.processEvents() if not msgQueue.empty(): msg = msgQueue.get() @@ -61,4 +88,11 @@ def apply_function_inapp(window: OCBWindow, run_func: Callable): stop = True elif msg[0] == RUN_MSG: msg[1](*msg[2], **msg[3]) + + if not t.is_alive() and not stop: + deadCounter += 1 + if deadCounter >= 3: + # Test failed, close was not called + warnings.warn("Warning: you need to call CheckingQueue.stop() at the end of your test !") + break t.join()