FreeCAD
147 строк · 6.1 Кб
1# SPDX-License-Identifier: LGPL-2.1-or-later
2# ***************************************************************************
3# * *
4# * Copyright (c) 2022-2023 FreeCAD Project Association *
5# * *
6# * This file is part of FreeCAD. *
7# * *
8# * FreeCAD is free software: you can redistribute it and/or modify it *
9# * under the terms of the GNU Lesser General Public License as *
10# * published by the Free Software Foundation, either version 2.1 of the *
11# * License, or (at your option) any later version. *
12# * *
13# * FreeCAD is distributed in the hope that it will be useful, but *
14# * WITHOUT ANY WARRANTY; without even the implied warranty of *
15# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
16# * Lesser General Public License for more details. *
17# * *
18# * You should have received a copy of the GNU Lesser General Public *
19# * License along with FreeCAD. If not, see *
20# * <https://www.gnu.org/licenses/>. *
21# * *
22# ***************************************************************************
23
24from PySide import QtCore, QtWidgets25
26from AddonManagerTest.app.mocks import SignalCatcher27
28
29class DialogInteractor(QtCore.QObject):30"""Takes the title of the dialog and a callable. The callable is passed the widget31we found and can do whatever it wants to it. Whatever it does should eventually
32close the dialog, however."""
33
34def __init__(self, dialog_to_watch_for, interaction):35super().__init__()36
37# Status variables for tests to check:38self.dialog_found = False39self.has_run = False40self.button_found = False41self.interaction = interaction42
43self.dialog_to_watch_for = dialog_to_watch_for44
45self.execution_counter = 046self.timer = QtCore.QTimer()47self.timer.timeout.connect(self.run)48self.timer.start(49150) # At 10 this occasionally left open dialogs; less than 1 produced failed tests51
52def run(self):53widget = QtWidgets.QApplication.activeModalWidget()54if widget and self._dialog_matches(widget):55# Found the dialog we are looking for: now try to run the interaction56if self.interaction is not None and callable(self.interaction):57self.interaction(widget)58self.dialog_found = True59self.timer.stop()60
61self.has_run = True62self.execution_counter += 163if self.execution_counter > 100:64print("Stopped timer after 100 iterations")65self.timer.stop()66
67def _dialog_matches(self, widget) -> bool:68# Is this the widget we are looking for? Only applies on Linux and Windows: macOS69# doesn't set the title of a modal dialog:70os = QtCore.QSysInfo.productType() # Qt5 gives "osx", Qt6 gives "macos"71if os in ["osx", "macos"] or (72hasattr(widget, "windowTitle")73and callable(widget.windowTitle)74and widget.windowTitle() == self.dialog_to_watch_for75):76return True77return False78
79
80class DialogWatcher(DialogInteractor):81"""Examine the running GUI and look for a modal dialog with a given title, containing a button82with a role. Click that button, which is expected to close the dialog. Generally run on
83a one-shot QTimer to allow the dialog time to open up. If the specified dialog is found, but
84it does not contain the expected button, button_found will be false, and the dialog will be
85closed with a reject() slot."""
86
87def __init__(self, dialog_to_watch_for, button=QtWidgets.QDialogButtonBox.NoButton):88super().__init__(dialog_to_watch_for, self.click_button)89if button != QtWidgets.QDialogButtonBox.NoButton:90self.button = button91else:92self.button = QtWidgets.QDialogButtonBox.Cancel93
94def click_button(self, widget):95button_boxes = widget.findChildren(QtWidgets.QDialogButtonBox)96if len(button_boxes) == 1: # There should be one, and only one97button_to_click = button_boxes[0].button(self.button)98if button_to_click:99self.button_found = True100button_to_click.click()101else:102widget.reject()103else:104widget.reject()105
106
107class FakeWorker:108def __init__(self):109self.called = False110self.should_continue = True111
112def work(self):113while self.should_continue:114QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 100)115
116def stop(self):117self.should_continue = False118
119
120class MockThread:121def wait(self):122pass123
124def isRunning(self):125return False126
127
128class AsynchronousMonitor:129"""Watch for a signal to be emitted for at most some given number of milliseconds"""130
131def __init__(self, signal):132self.signal = signal133self.signal_catcher = SignalCatcher()134self.signal.connect(self.signal_catcher.catch_signal)135self.kill_timer = QtCore.QTimer()136self.kill_timer.setSingleShot(True)137self.kill_timer.timeout.connect(self.signal_catcher.die)138
139def wait_for_at_most(self, max_wait_millis) -> None:140self.kill_timer.setInterval(max_wait_millis)141self.kill_timer.start()142while not self.signal_catcher.caught and not self.signal_catcher.killed:143QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 10)144self.kill_timer.stop()145
146def good(self) -> bool:147return self.signal_catcher.caught and not self.signal_catcher.killed148