@@ -66,8 +66,8 @@ activate a new fresh environment and execute::
66
66
67
67
.. _virtualenv : http://virtualenv.readthedocs.org/
68
68
69
- Quick Tutorial
70
- ==============
69
+ Tutorial
70
+ ========
71
71
72
72
``pytest-qt `` registers a new fixture _ named ``qtbot ``, which acts as *bot * in the sense
73
73
that it can send keyboard and mouse events to any widgets being tested. This way, the programmer
@@ -261,13 +261,225 @@ You can disable the automatic exception hook on individual tests by using a
261
261
262
262
Or even disable it for your entire project in your ``pytest.ini `` file::
263
263
264
+ .. code-block :: ini
265
+
264
266
[pytest]
265
267
qt_no_exception_capture = 1
266
268
267
269
This might be desirable if you plan to install a custom exception hook.
268
270
271
+ Qt Logging Capture
272
+ ==================
273
+
274
+ .. versionadded :: 1.4
275
+
276
+ Qt features its own logging mechanism through ``qInstallMsgHandler ``
277
+ (``qInstallMessageHandler `` on Qt5) and ``qDebug ``, ``qWarning ``, ``qCritical ``
278
+ functions. These are used by Qt to print warning messages when internal errors
279
+ occur.
280
+
281
+ ``pytest-qt `` automatically captures these messages and displays them when a
282
+ test fails, similar to what ``pytest `` does for ``stderr `` and ``stdout `` and
283
+ the `pytest-catchlog <https://github.com/eisensheng/pytest-catchlog >`_ plugin.
284
+ For example:
285
+
286
+ .. code-block :: python
287
+
288
+ from pytestqt.qt_compat import qWarning
289
+
290
+ def do_something ():
291
+ qWarning(' this is a WARNING message' )
292
+
293
+ def test_foo (qtlog ):
294
+ do_something()
295
+ assert 0
296
+
297
+
298
+ .. code-block :: bash
299
+
300
+ $ py.test test.py -q
301
+ F
302
+ ================================== FAILURES ===================================
303
+ _________________________________ test_types __________________________________
304
+
305
+ def test_foo ():
306
+ do_something ()
307
+ > assert 0
308
+ E assert 0
309
+
310
+ test.py:8: AssertionError
311
+ ---------------------------- Captured Qt messages -----------------------------
312
+ QtWarningMsg: this is a WARNING message
313
+ 1 failed in 0.01 seconds
314
+
315
+
316
+ Qt logging capture can be disabled altogether by passing the ``--no-qt-log ``
317
+ to the command line, which will fallback to the default Qt bahavior of printing
318
+ emitted messages directly to ``stderr ``:
319
+
320
+ .. code-block :: bash
321
+
322
+ py.test test.py -q --no-qt-log
323
+ F
324
+ ================================== FAILURES ===================================
325
+ _________________________________ test_types __________________________________
326
+
327
+ def test_foo ():
328
+ do_something ()
329
+ > assert 0
330
+ E assert 0
331
+
332
+ test.py:8: AssertionError
333
+ ---------------------------- Captured stderr call -----------------------------
334
+ this is a WARNING message
335
+
336
+
337
+ ``pytest-qt `` also provides a ``qtlog `` fixture, which tests can use
338
+ to check if certain messages were emitted during a test::
339
+
340
+ def do_something():
341
+ qWarning('this is a WARNING message')
342
+
343
+ def test_foo(qtlog):
344
+ do_something()
345
+ emitted = [(m.type, m.message.strip()) for m in qtlog.records]
346
+ assert emitted == [(QtWarningMsg, 'this is a WARNING message')]
347
+
348
+ Keep in mind that when ``--no-qt-log `` is passed in the command line,
349
+ ``qtlog.records `` will always be an empty list. See
350
+ :class: `Record <pytestqt.plugin.Record> ` for reference documentation on
351
+ ``Record `` objects.
352
+
353
+ The output format of the messages can also be controlled by using the
354
+ ``--qt-log-format `` command line option, which accepts a string with standard
355
+ ``{} `` formatting which can make use of attribute interpolation of the record
356
+ objects:
357
+
358
+ .. code-block :: bash
359
+
360
+ $ py.test test.py --qt-log-format=" {rec.when} {rec.type_name}: {rec.message}"
361
+
362
+ Keep in mind that you can make any of the options above the default
363
+ for your project by using pytest's standard ``addopts `` option in you
364
+ ``pytest.ini `` file:
365
+
366
+
367
+ .. code-block :: ini
368
+
369
+ [pytest]
370
+ qt_log_format = {rec.when} {rec.type_name}: {rec.message}
371
+
372
+
373
+ Automatically failing tests when logging messages are emitted
374
+ -------------------------------------------------------------
375
+
376
+ Printing messages to ``stderr `` is not the best solution to notice that
377
+ something might not be working as expected, specially when running in a
378
+ continuous integration server where errors in logs are rarely noticed.
379
+
380
+ You can configure ``pytest-qt `` to automatically fail a test if it emits
381
+ a message of a certain level or above using the ``qt_log_level_fail `` ini
382
+ option:
383
+
384
+
385
+ .. code-block :: ini
386
+
387
+ [pytest]
388
+ qt_log_level_fail = CRITICAL
389
+
390
+ With this configuration, any test which emits a CRITICAL message or above
391
+ will fail, even if no actual asserts fail within the test:
392
+
393
+ .. code-block :: python
394
+
395
+ from pytestqt.qt_compat import qCritical
396
+
397
+ def do_something ():
398
+ qCritical(' WM_PAINT failed' )
399
+
400
+ def test_foo (qtlog ):
401
+ do_something()
402
+
403
+
404
+ .. code-block :: bash
405
+
406
+ > py.test test.py --color=no -q
407
+ F
408
+ ================================== FAILURES ===================================
409
+ __________________________________ test_foo ___________________________________
410
+ test.py:5: Failure: Qt messages with level CRITICAL or above emitted
411
+ ---------------------------- Captured Qt messages -----------------------------
412
+ QtCriticalMsg: WM_PAINT failed
413
+
414
+ The possible values for ``qt_log_level_fail `` are:
415
+
416
+ * ``NO ``: disables test failure by log messages.
417
+ * ``DEBUG ``: messages emitted by ``qDebug `` function or above.
418
+ * ``WARNING ``: messages emitted by ``qWarning `` function or above.
419
+ * ``CRITICAL ``: messages emitted by ``qCritical `` function only.
420
+
421
+ If some failures are known to happen and considered harmless, they can
422
+ be ignored by using the ``qt_log_ignore `` ini option, which
423
+ is a list of regular expressions matched using ``re.search ``:
424
+
425
+ .. code-block :: ini
426
+
427
+ [pytest]
428
+ qt_log_level_fail = CRITICAL
429
+ qt_log_ignore =
430
+ WM_DESTROY.*sent
431
+ WM_PAINT failed
432
+
433
+ .. code-block :: bash
434
+
435
+ py.test test.py --color=no -q
436
+ .
437
+ 1 passed in 0.01 seconds
438
+
439
+
440
+ Messages which do not match any of the regular expressions
441
+ defined by ``qt_log_ignore `` make tests fail as usual:
442
+
443
+ .. code-block :: python
444
+
445
+ def do_something ():
446
+ qCritical(' WM_PAINT not handled' )
447
+ qCritical(' QObject: widget destroyed in another thread' )
448
+
449
+ def test_foo (qtlog ):
450
+ do_something()
451
+
452
+ .. code-block :: bash
453
+
454
+ py.test test.py --color=no -q
455
+ F
456
+ ================================== FAILURES ===================================
457
+ __________________________________ test_foo ___________________________________
458
+ test.py:6: Failure: Qt messages with level CRITICAL or above emitted
459
+ ---------------------------- Captured Qt messages -----------------------------
460
+ QtCriticalMsg: WM_PAINT not handled (IGNORED)
461
+ QtCriticalMsg: QObject: widget destroyed in another thread
462
+
463
+
464
+ You can also override ``qt_log_level_fail `` and ``qt_log_ignore `` settins
465
+ from ``pytest.ini `` in some tests by using a mark with the same name:
466
+
467
+ .. code-block :: python
468
+
469
+ def do_something ():
470
+ qCritical(' WM_PAINT not handled' )
471
+ qCritical(' QObject: widget destroyed in another thread' )
472
+
473
+ @pytest.mark.qt_log_level_fail (' CRITICAL' )
474
+ @pytest.mark.qt_log_ignore (' WM_DESTROY.*sent' , ' WM_PAINT failed' )
475
+ def test_foo (qtlog ):
476
+ do_something()
477
+
478
+ Reference
479
+ =========
480
+
269
481
QtBot
270
- =====
482
+ -----
271
483
272
484
.. module :: pytestqt.plugin
273
485
.. autoclass :: QtBot
@@ -280,6 +492,11 @@ Signals
280
492
281
493
.. autoclass :: SignalTimeoutError
282
494
495
+ Log Capture
496
+ -----------
497
+
498
+ .. autoclass :: Record
499
+
283
500
Versioning
284
501
==========
285
502
0 commit comments