Skip to content

The hooks other scenarios never reach

pytest 6.0.0 - 6.0.2 - Assertion comparison, deselection, string conditions and the debugger.

These 3 releases produced an identical flow, so they share one page. Identical as far as this scenario can measure - a scenario exercising more hooks may tell them apart.

A plain test run reaches about three quarters of pytest's hooks. The rest need provoking: an assertion that compares rather than merely fails, a deselected test, a skipif written as a string, the assertion-pass hook switched on, and a debugger session.

This scenario provokes all of them in one run, which is why it observes more hooks than any other. pytest.ini enables enable_assertion_pass_hook, and autopdb.py supplies a Pdb that continues the moment it is entered - so the debugger hooks fire without the run hanging.

Three hooks remain out of reach even here. pytest_cmdline_parse runs before any plugin exists to watch it. pytest_report_to_serializable and pytest_report_from_serializable are never called by pytest itself - they belong to plugins that ship reports between processes, such as xdist.

How this was produced

  • Source: scenarios/edge-cases/ - the project this diagram was traced from
  • Invocation: pytest -p hook_atlas_pytest . -k not deselect_me --pdb --pdbcls=autopdb:AutoPdb
  • Traced against: pytest 6.0.2, pluggy 0.13.1, Python 3.9.25
  • Covers: 3 releases - pytest 6.0.0 - 6.0.2, which produced an identical flow
  • Observed: 242 hook calls, 42 distinct hooks

The project is autopdb.py, conftest.py, pytest.ini, test_edges.py. Browse it in the repository rather than inline here - scenarios grow directory trees, and GitHub renders them better than a collapsed block can.

The whole run

Phases left to right, steps top to bottom. Colour says which phase a hook belongs to; how dark a step is says how often it was called. Click a column heading to jump to that phase in full detail, or any hook to open its pytest documentation. Why it looks like this

%3 cluster_phase_startup STARTUP AND CONFIGURATION cluster_startup_n1 pytest_load_initial_conftests cluster_startup_n3 pytest_configure historic · 2x in run cluster_startup_n6 pytest_sessionstart cluster_phase_collection COLLECTION cluster_collection_n1 pytest_collection firstresult cluster_phase_runtest THE RUN-TEST PROTOCOL cluster_runtest_n1 pytest_runtest_protocol firstresult · 4x in run cluster_phase_finish SESSION FINISH cluster_finish_n1 pytest_sessionfinish startup_n2 pytest_plugin_registered historic · x2 startup_n4 pytest_configure historic · 2x in run startup_n2->startup_n4 startup_n5 pytest_plugin_registered historic · x39 startup_n4->startup_n5 startup_n7 pytest_plugin_registered historic · x38 startup_n5->startup_n7 startup_n8 pytest_report_header startup_n7->startup_n8 collection_n2 pytest_collectstart 2x in run collection_n3 pytest_make_collect_report firstresult · 2x in run collection_n2->collection_n3 collection_n4 pytest_collectreport 2x in run collection_n3->collection_n4 collection_n5 pytest_collectstart 2x in run collection_n4->collection_n5 collection_n6 pytest_make_collect_report firstresult · 2x in run collection_n5->collection_n6 collection_n7 pytest_itemcollected x5 collection_n6->collection_n7 collection_n8 pytest_collectreport 2x in run collection_n7->collection_n8 collection_n9 pytest_collection_modifyitems collection_n8->collection_n9 collection_n10 pytest_collection_finish collection_n9->collection_n10 collection_n11 pytest_warning_captured historic collection_n10->collection_n11 collection_n12 pytest_warning_recorded historic collection_n11->collection_n12 runtest_n2 pytest_runtest_logstart 4x in run runtest_n3 pytest_runtest_setup 4x in run runtest_n2->runtest_n3 runtest_n4 pytest_runtest_makereport firstresult · 12x in run runtest_n3->runtest_n4 runtest_n5 pytest_runtest_logreport 12x in run runtest_n4->runtest_n5 runtest_n6 pytest_runtest_call 4x in run runtest_n5->runtest_n6 runtest_n7 pytest_runtest_makereport firstresult · 12x in run runtest_n6->runtest_n7 runtest_n8 pytest_runtest_logreport 12x in run runtest_n7->runtest_n8 runtest_n9 pytest_runtest_teardown 4x in run runtest_n8->runtest_n9 runtest_n10 pytest_runtest_makereport firstresult · 12x in run runtest_n9->runtest_n10 runtest_n11 pytest_runtest_logreport 12x in run runtest_n10->runtest_n11 runtest_n12 pytest_runtest_logfinish 4x in run runtest_n11->runtest_n12 finish_n2 pytest_terminal_summary finish_n3 pytest_unconfigure finish_n2->finish_n3

Startup and configuration

Everything before collection begins: initial conftests are loaded, plugins register, and the session is configured.

Collection

Finding tests. Note that pytest_make_collect_report recurses - directories contain directories contain files.

%3 cluster_n1 pytest_collection firstresult cluster_n3 pytest_make_collect_report firstresult · 2x in run cluster_n17 pytest_collect_file 7x in run cluster_n21 pytest_make_collect_report firstresult · 2x in run cluster_n23 pytest_pycollect_makeitem firstresult · x3 cluster_n26 pytest_pycollect_makeitem firstresult · x2 cluster_n31 pytest_collection_modifyitems cluster_n33 pytest_collection_finish n2 pytest_collectstart 2x in run n4 pytest_ignore_collect firstresult · 7x in run n2->n4 n5 pytest_collect_file 7x in run n4->n5 n6 pytest_ignore_collect firstresult · 7x in run n5->n6 n7 pytest_collect_file 7x in run n6->n7 n8 pytest_ignore_collect firstresult · 7x in run n7->n8 n9 pytest_collect_file 7x in run n8->n9 n10 pytest_ignore_collect firstresult · 7x in run n9->n10 n11 pytest_collect_file 7x in run n10->n11 n12 pytest_ignore_collect firstresult · 7x in run n11->n12 n13 pytest_collect_file 7x in run n12->n13 n14 pytest_ignore_collect firstresult · 7x in run n13->n14 n15 pytest_collect_file 7x in run n14->n15 n16 pytest_ignore_collect firstresult · 7x in run n15->n16 n18 pytest_pycollect_makemodule firstresult n16->n18 n19 pytest_collectreport 2x in run n18->n19 n20 pytest_collectstart 2x in run n19->n20 n22 pytest_pycollect_makeitem firstresult · x11 n20->n22 n24 pytest_generate_tests 5x in run n22->n24 n25 pytest_pycollect_makeitem firstresult · 40x in run n24->n25 n27 pytest_generate_tests 5x in run n25->n27 n28 pytest_pycollect_makeitem firstresult · x23 n27->n28 n29 pytest_itemcollected x5 n28->n29 n30 pytest_collectreport 2x in run n29->n30 n32 pytest_deselected n30->n32 n34 pytest_report_collectionfinish n32->n34 n35 pytest_warning_captured historic n34->n35 n36 pytest_warning_recorded historic n35->n36

The run-test protocol

pytest_runtestloop runs this once per collected test. Note the rhythm: setup, call and teardown are each followed by their own makereport and logreport pair - three reports per test, not one.

The path 1 of 4 took.

The others differed:

  • 1 test also called pytest_enter_pdb, pytest_exception_interact, pytest_leave_pdb and skipped pytest_assertion_pass
  • 1 test skipped pytest_assertion_pass, pytest_assertrepr_compare
  • 1 test skipped pytest_assertrepr_compare

Session finish

Summary reporting and teardown of the session.

Every hook observed

Hook Calls Semantics Implemented by, in call order
pytest_assertion_pass 2 conftest.py
pytest_assertrepr_compare 2 _pytest.assertion
pytest_cmdline_main 1 firstresult _pytest.setupplan [tryfirst]
_pytest.setuponly [tryfirst]
_pytest.mark [tryfirst]
_pytest.python
_pytest.helpconfig
_pytest.main
pytest_cmdline_preparse 1 -
pytest_collect_file 7 _pytest.doctest
_pytest.python
pytest_collection 1 firstresult _pytest.logging.LoggingPlugin (logging-plugin) [hookwrapper, tryfirst]
_pytest.warnings [hookwrapper, tryfirst]
_pytest.terminal.TerminalReporter
_pytest.assertion
_pytest.main
pytest_collection_finish 1 _pytest.terminal.TerminalReporter
pytest_collection_modifyitems 1 _pytest.fixtures.FixtureManager (funcmanage)
_pytest.main
_pytest.mark
pytest_collectreport 2 _pytest.main.Session.pytest_runtest_logreport (session) [tryfirst]
_pytest.terminal.TerminalReporter
pytest_collectstart 2 _pytest.main.Session [tryfirst]
pytest_configure 2 historic _pytest.faulthandler
_pytest.warnings
_pytest.resultlog
_pytest.junitxml
_pytest.tmpdir
_pytest.skipping
_pytest.debugging
_pytest.terminal
_pytest.python
_pytest.mark
_pytest.config.PytestPluginManager (<anonymous>)
_pytest.pastebin [trylast]
_pytest.logging [trylast]
_pytest.faulthandler.FaultHandlerHooks (faulthandler-hooks)
pytest_deselected 1 _pytest.terminal.TerminalReporter
pytest_enter_pdb 1 _pytest.faulthandler.FaultHandlerHooks (faulthandler-hooks) [tryfirst]
pytest_exception_interact 1 _pytest.faulthandler.FaultHandlerHooks (faulthandler-hooks) [tryfirst]
_pytest.debugging.PdbInvoke
pytest_generate_tests 5 _pytest.fixtures.FixtureManager (funcmanage)
_pytest.python
pytest_ignore_collect 7 firstresult _pytest.main
pytest_itemcollected 5 -
pytest_leave_pdb 1 -
pytest_load_initial_conftests 1 _pytest.capture [hookwrapper]
_pytest.config.Config (pytestconfig) [trylast]
pytest_make_collect_report 2 firstresult _pytest.capture.CaptureManager [hookwrapper]
_pytest.runner
pytest_plugin_registered 81 historic _pytest.terminal.TerminalReporter
_pytest.fixtures.FixtureManager (funcmanage)
pytest_pycollect_makeitem 40 firstresult _pytest.unittest
_pytest.python [trylast]
pytest_pycollect_makemodule 1 firstresult _pytest.python
pytest_pyfunc_call 4 firstresult _pytest.python [trylast]
pytest_report_collectionfinish 1 -
pytest_report_header 1 _pytest.terminal.TerminalReporter
_pytest.helpconfig
pytest_report_teststatus 13 firstresult _pytest.skipping
_pytest.runner
_pytest.terminal [trylast]
pytest_runtest_call 4 _pytest.logging.LoggingPlugin (logging-plugin) [hookwrapper]
_pytest.capture.CaptureManager [hookwrapper]
_pytest.skipping [hookwrapper]
_pytest.runner
pytest_runtest_logfinish 4 _pytest.logging.LoggingPlugin (logging-plugin)
_pytest.terminal.TerminalReporter
pytest_runtest_logreport 12 _pytest.main.Session [tryfirst]
_pytest.logging.LoggingPlugin (logging-plugin)
_pytest.terminal.TerminalReporter
pytest_runtest_logstart 4 _pytest.logging.LoggingPlugin (logging-plugin)
_pytest.terminal.TerminalReporter
pytest_runtest_makereport 12 firstresult _pytest.skipping [hookwrapper]
_pytest.unittest [tryfirst]
_pytest.runner
pytest_runtest_protocol 4 firstresult _pytest.warnings [hookwrapper, tryfirst]
_pytest.assertion [hookwrapper, tryfirst]
_pytest.unittest [hookwrapper]
_pytest.faulthandler.FaultHandlerHooks (faulthandler-hooks) [hookwrapper, trylast]
_pytest.runner
pytest_runtest_setup 4 _pytest.logging.LoggingPlugin (logging-plugin) [hookwrapper]
_pytest.capture.CaptureManager [hookwrapper]
_pytest.skipping [tryfirst]
_pytest.runner
_pytest.nose [trylast]
pytest_runtest_teardown 4 _pytest.logging.LoggingPlugin (logging-plugin) [hookwrapper]
_pytest.capture.CaptureManager [hookwrapper]
_pytest.runner
pytest_runtestloop 1 firstresult _pytest.logging.LoggingPlugin (logging-plugin) [hookwrapper]
_pytest.main
pytest_sessionfinish 1 _pytest.logging.LoggingPlugin (logging-plugin) [hookwrapper, tryfirst]
_pytest.terminal.TerminalReporter [hookwrapper]
_pytest.warnings [hookwrapper]
_pytest.assertion
_pytest.runner
pytest_sessionstart 1 _pytest.logging.LoggingPlugin (logging-plugin) [hookwrapper, tryfirst]
_pytest.fixtures
_pytest.runner
_pytest.terminal.TerminalReporter [trylast]
pytest_terminal_summary 1 _pytest.terminal.TerminalReporter [hookwrapper]
_pytest.warnings [hookwrapper]
_pytest.pastebin
_pytest.runner
pytest_unconfigure 1 _pytest.logging.LoggingPlugin (logging-plugin)
_pytest.terminal.TerminalReporter
_pytest.faulthandler.FaultHandlerHooks (faulthandler-hooks)
_pytest.doctest
_pytest.resultlog
_pytest.junitxml
_pytest.pastebin
_pytest.mark
pytest_warning_captured 1 historic -
pytest_warning_recorded 1 historic _pytest.terminal.TerminalReporter