|
4 | 4 |
|
5 | 5 | import pytest
|
6 | 6 |
|
7 |
| -from pytestqt.qt_compat import QtGui |
8 |
| -from pytestqt.qt_compat import QtTest |
| 7 | +from pytestqt.qt_compat import QtCore, QtGui, QtTest |
9 | 8 |
|
10 | 9 |
|
11 | 10 | def _inject_qtest_methods(cls):
|
@@ -61,6 +60,7 @@ class QtBot(object):
|
61 | 60 | .. automethod:: addWidget
|
62 | 61 | .. automethod:: waitForWindowShown
|
63 | 62 | .. automethod:: stopForInteraction
|
| 63 | + .. automethod:: waitSignal |
64 | 64 |
|
65 | 65 | **Raw QTest API**
|
66 | 66 |
|
@@ -212,6 +212,105 @@ def stopForInteraction(self):
|
212 | 212 |
|
213 | 213 | stop = stopForInteraction
|
214 | 214 |
|
| 215 | + def waitSignal(self, signal=None, timeout=1000): |
| 216 | + """ |
| 217 | + Stops current test until a signal is triggered. |
| 218 | +
|
| 219 | + Used to stop the control flow of a test until a signal is emitted, or |
| 220 | + a number of milliseconds, specified by ``timeout``, has elapsed. |
| 221 | +
|
| 222 | + Best used as a context manager:: |
| 223 | +
|
| 224 | + with qtbot.waitSignal(signal, timeout=1000): |
| 225 | + long_function_that_calls_signal() |
| 226 | +
|
| 227 | + Also, you can use the :class:`SignalBlocker` directly if the context |
| 228 | + manager form is not convenient:: |
| 229 | +
|
| 230 | + blocker = qtbot.waitSignal(signal, timeout=1000) |
| 231 | + blocker.connect(other_signal) |
| 232 | + long_function_that_calls_signal() |
| 233 | + blocker.wait() |
| 234 | +
|
| 235 | + :param Signal signal: |
| 236 | + A signal to wait for. Set to ``None`` to just use timeout. |
| 237 | + :param int timeout: |
| 238 | + How many milliseconds to wait before resuming control flow. |
| 239 | + :returns: |
| 240 | + ``SignalBlocker`` object. Call ``SignalBlocker.wait()`` to wait. |
| 241 | +
|
| 242 | + .. note:: |
| 243 | + Cannot have both ``signals`` and ``timeout`` equal ``None``, or |
| 244 | + else you will block indefinitely. We throw an error if this occurs. |
| 245 | +
|
| 246 | + """ |
| 247 | + blocker = SignalBlocker(timeout=timeout) |
| 248 | + if signal is not None: |
| 249 | + blocker.connect(signal) |
| 250 | + return blocker |
| 251 | + |
| 252 | + |
| 253 | +class SignalBlocker(object): |
| 254 | + """ |
| 255 | + Returned by :meth:`QtBot.waitSignal` method. |
| 256 | +
|
| 257 | + .. automethod:: wait |
| 258 | + .. automethod:: connect |
| 259 | +
|
| 260 | + :ivar int timeout: maximum time to wait for a signal to be triggered. Can |
| 261 | + be changed before :meth:`wait` is called. |
| 262 | +
|
| 263 | + :ivar bool signal_triggered: set to ``True`` if a signal was triggered, or |
| 264 | + ``False`` if timeout was reached instead. Until :meth:`wait` is called, |
| 265 | + this is set to ``None``. |
| 266 | + """ |
| 267 | + |
| 268 | + def __init__(self, timeout=1000): |
| 269 | + self._loop = QtCore.QEventLoop() |
| 270 | + self._signals = [] |
| 271 | + self.timeout = timeout |
| 272 | + self.signal_triggered = None |
| 273 | + |
| 274 | + def wait(self): |
| 275 | + """ |
| 276 | + Waits until either condition signal is triggered or |
| 277 | + timeout is reached. |
| 278 | +
|
| 279 | + :raise ValueError: if no signals are connected and timeout is None; in |
| 280 | + this case it would wait forever. |
| 281 | + """ |
| 282 | + if self.timeout is None and len(self._signals) == 0: |
| 283 | + raise ValueError("No signals or timeout specified.") |
| 284 | + if self.timeout is not None: |
| 285 | + QtCore.QTimer.singleShot(self.timeout, self._loop.quit) |
| 286 | + self.signal_triggered = False |
| 287 | + self._loop.exec_() |
| 288 | + |
| 289 | + def connect(self, signal): |
| 290 | + """ |
| 291 | + Connects to the given signal, making :meth:`wait()` return once this signal |
| 292 | + is emitted. |
| 293 | +
|
| 294 | + :param signal: QtCore.Signal |
| 295 | + """ |
| 296 | + signal.connect(self._quit_loop_by_signal) |
| 297 | + self._signals.append(signal) |
| 298 | + |
| 299 | + |
| 300 | + def _quit_loop_by_signal(self): |
| 301 | + """ |
| 302 | + quits the event loop and marks that we finished because of a signal. |
| 303 | + """ |
| 304 | + self.signal_triggered = True |
| 305 | + self._loop.quit() |
| 306 | + |
| 307 | + def __enter__(self): |
| 308 | + # Return self for testing purposes. Generally not needed. |
| 309 | + return self |
| 310 | + |
| 311 | + def __exit__(self, type, value, traceback): |
| 312 | + self.wait() |
| 313 | + |
215 | 314 |
|
216 | 315 | def pytest_configure(config):
|
217 | 316 | """
|
|
0 commit comments