waitSignal: Waiting for threads, processes, etc.

New in version 1.2.

If your program has long running computations running in other threads or processes, you can use qtbot.waitSignal to block a test until a signal is emitted (such as QThread.finished) or a timeout is reached. This makes it easy to write tests that wait until a computation running in another thread or process is completed before ensuring the results are correct:

def test_long_computation(qtbot):
    app = Application()

    # Watch for the app.worker.finished signal, then start the worker.
    with qtbot.waitSignal(app.worker.finished, timeout=10000) as blocker:
        blocker.connect(app.worker.failed)  # Can add other signals to blocker
        app.worker.start()
        # Test will block at this point until either the "finished" or the
        # "failed" signal is emitted. If 10 seconds passed without a signal,
        # TimeoutError will be raised.

    assert_application_results(app)

raising parameter

New in version 1.4.

Changed in version 2.0.

You can pass raising=False to avoid raising a qtbot.TimeoutError if the timeout is reached before the signal is triggered:

def test_long_computation(qtbot):
    ...
    with qtbot.waitSignal(app.worker.finished, raising=False) as blocker:
        app.worker.start()

    assert_application_results(app)

    # qtbot.TimeoutError is not raised, but you can still manually
    # check whether the signal was triggered:
    assert blocker.signal_triggered, "process timed-out"

qt_default_raising ini option

New in version 1.11.

Changed in version 2.0.

Changed in version 3.1.

The qt_default_raising ini option can be used to override the default value of the raising parameter of the qtbot.waitSignal and qtbot.waitSignals functions when omitted:

[pytest]
qt_default_raising = false

Calls which explicitly pass the raising parameter are not affected.

This option was called qt_wait_signal_raising before 3.1.0.

check_params_cb parameter

New in version 2.0.

If the signal has parameters you want to compare with expected values, you can pass check_params_cb=some_callable that compares the provided signal parameters to some expected parameters. It has to match the signature of signal (just like a slot function would) and return True if parameters match, False otherwise.

def test_status_100(status):
    """Return true if status has reached 100%."""
    return status == 100


def test_status_complete(qtbot):
    app = Application()

    # the following raises if the worker's status signal (which has an int parameter) wasn't raised
    # with value=100 within the default timeout
    with qtbot.waitSignal(
        app.worker.status, raising=True, check_params_cb=test_status_100
    ) as blocker:
        app.worker.start()

timeout parameter

The timeout parameter specifies how long waitSignal should wait for a signal to arrive. If the timeout is None, there won’t be any timeout, i.e. it’ll wait indefinitely.

If the timeout is set to 0, it’s expected that the signal arrives directly in the code inside the with qtbot.waitSignal(...): block.

Getting arguments of the emitted signal

New in version 1.10.

The arguments emitted with the signal are available as the args attribute of the blocker:

def test_signal(qtbot):
    ...
    with qtbot.waitSignal(app.got_cmd) as blocker:
        app.listen()
    assert blocker.args == ["test"]

Signals without arguments will set args to an empty list. If the time out is reached instead, args will be None.

Getting all arguments of non-matching arguments

New in version 2.1.

When using the check_params_cb parameter, it may happen that the provided signal is received multiple times with different parameter values, which may or may not match the requirements of the callback. all_args then contains the list of signal parameters (as tuple) in the order they were received.

waitSignals

New in version 1.4.

If you have to wait until all signals in a list are triggered, use qtbot.waitSignals, which receives a list of signals instead of a single signal. As with qtbot.waitSignal, it also supports the raising parameter:

def test_workers(qtbot):
    workers = spawn_workers()
    with qtbot.waitSignals([w.finished for w in workers]):
        for w in workers:
            w.start()

    # this will be reached after all workers emit their "finished"
    # signal or a qtbot.TimeoutError will be raised
    assert_application_results(app)

check_params_cbs parameter

New in version 2.0.

Corresponding to the check_params_cb parameter of waitSignal you can use the check_params_cbs parameter to check whether one or more of the provided signals are emitted with expected parameters. Provide a list of callables, each matching the signature of the corresponding signal in signals (just like a slot function would). Like for waitSignal, each callable has to return True if parameters match, False otherwise. Instead of a specific callable, None can be provided, to disable parameter checking for the corresponding signal. If the number of callbacks doesn’t match the number of signals ValueError will be raised.

The following example shows that the app.worker.status signal has to be emitted with values 50 and 100, and the app.worker.finished signal has to be emitted too (for which no signal parameter evaluation takes place).

def test_status_100(status):
    """Return true if status has reached 100%."""
    return status == 100


def test_status_50(status):
    """Return true if status has reached 50%."""
    return status == 50


def test_status_complete(qtbot):
    app = Application()

    signals = [app.worker.status, app.worker.status, app.worker.finished]
    callbacks = [test_status_50, test_status_100, None]
    with qtbot.waitSignals(
        signals, raising=True, check_params_cbs=callbacks
    ) as blocker:
        app.worker.start()

order parameter

New in version 2.0.

By default a test using qtbot.waitSignals completes successfully if all signals in signals are emitted, irrespective of their exact order. The order parameter can be set to "strict" to enforce strict signal order. Exemplary, this means that blocker.signal_triggered will be False if waitSignals expects the signals [a, b] but the sender emitted signals [a, a, b].

Note

The tested component can still emit signals unknown to the blocker. E.g. blocker.waitSignals([a, b], raising=True, order="strict") won’t raise if the signal-sender emits signals [a, c, b], as c is not part of the observed signals.

A third option is to set order="simple" which is like “strict”, but signals may be emitted in-between the provided ones, e.g. if the expected signals are [a, b, c] and the sender actually emits [a, a, b, a, c], the test completes successfully (it would fail with order="strict").

Getting emitted signals and arguments

New in version 2.1.

To determine which of the expected signals were emitted during a wait() you can use blocker.all_signals_and_args which contains a list of wait_signal.SignalAndArgs objects, indicating the signals (and their arguments) in the order they were received.

Making sure a given signal is not emitted

New in version 1.11.

If you want to ensure a signal is not emitted in a given block of code, use the qtbot.assertNotEmitted context manager:

def test_no_error(qtbot):
    ...
    with qtbot.assertNotEmitted(app.worker.error):
        app.worker.start()

By default, this only catches signals emitted directly inside the block. You can pass wait=... to wait for a given duration (in milliseconds) for asynchronous signals to (not) arrive:

def test_no_error(qtbot):
    ...
    with qtbot.assertNotEmitted(page.loadFinished, wait=100):
        page.runJavaScript("document.getElementById('not-a-link').click()")