-
-
Notifications
You must be signed in to change notification settings - Fork 70
Add qtbot.waitCallback, take 2 #236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
7f0e51a
Add qtbot.waitCallback
The-Compiler 97d2937
Get rid of CallbackTimeoutError
The-Compiler 1911059
Rename qt_wait_signal_raising to qt_default_raising
The-Compiler c9d2efe
Update CallbackBlocker based on AbstractBlocker
The-Compiler 9c2bc60
callback blocker: Show error when callback was called twice
The-Compiler a1cd403
callback blocker: Add tests for timeout
The-Compiler 03bc7a4
Add docs for waitCallback
The-Compiler 5eb82ed
Don't refer to deprecated SignalTimeoutError in docs
The-Compiler 34f0f1d
Run black
The-Compiler 488c2aa
Remove accidentally readded timer fixture
The-Compiler e9ea2f8
Update changelog
The-Compiler 2a5277b
Require pytest>=3.8
nicoddemus 1108eea
Revert "Require pytest>=3.8"
The-Compiler 95fee8b
Force registering @pytest.filterwarnings
The-Compiler 1b88231
Merge branch 'master' into wait-callback-2
nicoddemus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ pytest-qt | |
| logging | ||
| signals | ||
| wait_until | ||
| wait_callback | ||
| virtual_methods | ||
| modeltester | ||
| app_exit | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| waitCallback: Waiting for methods taking a callback | ||
| =================================================== | ||
|
|
||
| .. versionadded:: 3.1 | ||
|
|
||
| Some methods in Qt (especially ``QtWebEngine``) take a callback as argument, | ||
| which gets called by Qt once a given operation is done. | ||
|
|
||
| To test such code, you can use :meth:`qtbot.waitCallback <pytestqt.plugin.QtBot.waitCallback>` | ||
| which waits until the callback has been called or a timeout is reached. | ||
|
|
||
| The ``qtbot.waitCallback()`` method returns a callback which is callable | ||
| directly. | ||
|
|
||
| For example: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| def test_js(qtbot): | ||
| page = QWebEnginePage() | ||
| with qtbot.waitCallback() as cb: | ||
| page.runJavaScript("1 + 1", cb) | ||
| # After callback | ||
|
|
||
| Anything following the ``with`` block will be run only after the callback has been called. | ||
|
|
||
| If the callback doesn't get called during the given timeout, | ||
| :class:`qtbot.TimeoutError <TimeoutError>` is raised. If it is called more than once, | ||
| :class:`qtbot.CallbackCalledTwiceError <CallbackCalledTwiceError>` is raised. | ||
|
|
||
| raising parameter | ||
| ----------------- | ||
|
|
||
| Similarly to ``qtbot.waitSignal``, you can pass a ``raising=False`` parameter | ||
| (or set the ``qt_default_raising`` ini option) to avoid raising an exception on | ||
| timeouts. See :doc:`signals` for details. | ||
|
|
||
| Getting arguments the callback was called with | ||
| ---------------------------------------------- | ||
|
|
||
| After the callback is called, the arguments and keyword arguments passed to it | ||
| are available via ``.args`` (as a list) and ``.kwargs`` (as a dict), | ||
| respectively. | ||
|
|
||
| In the example above, we could check the result via: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| assert cb.args == [2] | ||
| assert cb.kwargs == {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,8 @@ | |
| MultiSignalBlocker, | ||
| SignalEmittedSpy, | ||
| SignalEmittedError, | ||
| CallbackBlocker, | ||
| CallbackCalledTwiceError, | ||
| ) | ||
|
|
||
|
|
||
|
|
@@ -137,6 +139,19 @@ class QtBot(object): | |
| def __init__(self, request): | ||
| self._request = request | ||
|
|
||
| def _should_raise(self, raising_arg): | ||
| ini_val = self._request.config.getini("qt_default_raising") | ||
| legacy_ini_val = self._request.config.getini("qt_wait_signal_raising") | ||
|
|
||
| if raising_arg is not None: | ||
| return raising_arg | ||
| elif legacy_ini_val: | ||
| return _parse_ini_boolean(legacy_ini_val) | ||
| elif ini_val: | ||
| return _parse_ini_boolean(ini_val) | ||
| else: | ||
| return True | ||
|
|
||
| def addWidget(self, widget): | ||
| """ | ||
| Adds a widget to be tracked by this bot. This is not required, but will ensure that the | ||
|
|
@@ -298,7 +313,7 @@ def waitSignal(self, signal=None, timeout=1000, raising=None, check_params_cb=No | |
| :param bool raising: | ||
| If :class:`QtBot.TimeoutError <pytestqt.plugin.TimeoutError>` | ||
| should be raised if a timeout occurred. | ||
| This defaults to ``True`` unless ``qt_wait_signal_raising = false`` | ||
| This defaults to ``True`` unless ``qt_default_raising = false`` | ||
| is set in the config. | ||
| :param Callable check_params_cb: | ||
| Optional ``callable`` that compares the provided signal parameters to some expected parameters. | ||
|
|
@@ -314,12 +329,7 @@ def waitSignal(self, signal=None, timeout=1000, raising=None, check_params_cb=No | |
| .. note:: | ||
| This method is also available as ``wait_signal`` (pep-8 alias) | ||
| """ | ||
| if raising is None: | ||
| raising_val = self._request.config.getini("qt_wait_signal_raising") | ||
| if not raising_val: | ||
| raising = True | ||
| else: | ||
| raising = _parse_ini_boolean(raising_val) | ||
| raising = self._should_raise(raising) | ||
| blocker = SignalBlocker( | ||
| timeout=timeout, raising=raising, check_params_cb=check_params_cb | ||
| ) | ||
|
|
@@ -367,7 +377,7 @@ def waitSignals( | |
| :param bool raising: | ||
| If :class:`QtBot.TimeoutError <pytestqt.plugin.TimeoutError>` | ||
| should be raised if a timeout occurred. | ||
| This defaults to ``True`` unless ``qt_wait_signal_raising = false`` | ||
| This defaults to ``True`` unless ``qt_default_raising = false`` | ||
| is set in the config. | ||
| :param list check_params_cbs: | ||
| optional list of callables that compare the provided signal parameters to some expected parameters. | ||
|
|
@@ -399,8 +409,7 @@ def waitSignals( | |
| if order not in ["none", "simple", "strict"]: | ||
| raise ValueError("order has to be set to 'none', 'simple' or 'strict'") | ||
|
|
||
| if raising is None: | ||
| raising = self._request.config.getini("qt_wait_signal_raising") | ||
| raising = self._should_raise(raising) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just noticed that I actually fixed another bug here 😆 |
||
|
|
||
| if check_params_cbs: | ||
| if len(check_params_cbs) != len(signals): | ||
|
|
@@ -529,6 +538,49 @@ def timed_out(): | |
|
|
||
| wait_until = waitUntil # pep-8 alias | ||
|
|
||
| def waitCallback(self, timeout=1000, raising=None): | ||
| """ | ||
| .. versionadded:: 3.1 | ||
|
|
||
| Stops current test until a callback is called. | ||
|
|
||
| Used to stop the control flow of a test until the returned callback is | ||
| called, or a number of milliseconds, specified by ``timeout``, has | ||
| elapsed. | ||
|
|
||
| Best used as a context manager:: | ||
|
|
||
| with qtbot.waitCallback() as callback: | ||
| function_taking_a_callback(callback) | ||
| assert callback.args == [True] | ||
|
|
||
| Also, you can use the :class:`CallbackBlocker` directly if the | ||
| context manager form is not convenient:: | ||
|
|
||
| blocker = qtbot.waitCallback(timeout=1000) | ||
| function_calling_a_callback(blocker) | ||
| blocker.wait() | ||
|
|
||
|
|
||
| :param int timeout: | ||
| How many milliseconds to wait before resuming control flow. | ||
| :param bool raising: | ||
| If :class:`QtBot.TimeoutError <pytestqt.plugin.TimeoutError>` | ||
| should be raised if a timeout occurred. | ||
| This defaults to ``True`` unless ``qt_default_raising = false`` | ||
| is set in the config. | ||
| :returns: | ||
| A ``CallbackBlocker`` object which can be used directly as a | ||
| callback as it implements ``__call__``. | ||
|
|
||
| .. note:: This method is also available as ``wait_callback`` (pep-8 alias) | ||
| """ | ||
| raising = self._should_raise(raising) | ||
| blocker = CallbackBlocker(timeout=timeout, raising=raising) | ||
| return blocker | ||
|
|
||
| wait_callback = waitCallback # pep-8 alias | ||
|
|
||
| @contextlib.contextmanager | ||
| def captureExceptions(self): | ||
| """ | ||
|
|
@@ -599,6 +651,7 @@ def result(*args, **kwargs): | |
| QtBot.SignalTimeoutError = SignalTimeoutError | ||
| QtBot.SignalEmittedError = SignalEmittedError | ||
| QtBot.TimeoutError = TimeoutError | ||
| QtBot.CallbackCalledTwiceError = CallbackCalledTwiceError | ||
|
|
||
|
|
||
| def _add_widget(item, widget): | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we check if
config.getini("qt_wait_signal_raising")returnsTrueorFalseto raise the warning? Because if users configure it asFalse, the if block won't execute.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I think it's a string (because there's
_parse_ini_booleaninqtbot.py), but I wonder why we don't passtype="bool"in theparser.addinicalls inpytest_addoptionto let pytest do the ini parsing?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No idea... 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if you see the warning if
qt_wait_signal_raisingis set to False or True, I'm happy!