Skip to content

A run cut short by Ctrl-C

pytest 6.2.0 - 6.2.5 - What pytest does when a test raises KeyboardInterrupt.

These 6 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.

The second test raises KeyboardInterrupt, which is what pressing Ctrl-C during a run amounts to. pytest stops immediately: the third test is never collected for running, and the session unwinds through pytest_keyboard_interrupt before reaching pytest_sessionfinish.

The flow is shorter than a normal run, and deliberately so - this page is here to show the shape of an abandoned session, not a healthy one.

How this was produced

  • Source: scenarios/interrupted/ - the project this diagram was traced from
  • Invocation: pytest -p hook_atlas_pytest . -q
  • Traced against: pytest 6.2.5, pluggy 1.6.0, Python 3.9.25
  • Covers: 6 releases - pytest 6.2.0 - 6.2.5, which produced an identical flow
  • Observed: 142 hook calls, 34 distinct hooks

The project is test_interrupted.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 · 2x in run cluster_phase_finish SESSION FINISH cluster_finish_n1 pytest_sessionfinish startup_n2 pytest_plugin_registered historic · 77x in run startup_n4 pytest_configure historic · 2x in run startup_n2->startup_n4 startup_n5 pytest_plugin_registered historic · x37 startup_n4->startup_n5 startup_n7 pytest_plugin_registered historic · x37 startup_n5->startup_n7 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 x3 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 runtest_n2 pytest_runtest_logstart 2x in run runtest_n3 pytest_runtest_setup 2x in run runtest_n2->runtest_n3 runtest_n4 pytest_runtest_makereport firstresult · 4x in run runtest_n3->runtest_n4 runtest_n5 pytest_runtest_logreport 4x in run runtest_n4->runtest_n5 runtest_n6 pytest_runtest_call 2x in run runtest_n5->runtest_n6 runtest_n7 pytest_runtest_makereport firstresult · 4x in run runtest_n6->runtest_n7 runtest_n8 pytest_runtest_logreport 4x in run runtest_n7->runtest_n8 runtest_n9 pytest_runtest_teardown runtest_n8->runtest_n9 runtest_n10 pytest_runtest_makereport firstresult · 4x in run runtest_n9->runtest_n10 runtest_n11 pytest_runtest_logreport 4x in run runtest_n10->runtest_n11 runtest_n12 pytest_runtest_logfinish 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.

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 2 took.

The others differed:

  • 1 test skipped pytest_runtest_logfinish, pytest_runtest_teardown

Session finish

Summary reporting and teardown of the session.

Every hook observed

Hook Calls Semantics Implemented by, in call order
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 4 _pytest.doctest
_pytest.python
pytest_collection 1 firstresult _pytest.logging.LoggingPlugin (logging-plugin) [hookwrapper, tryfirst]
_pytest.warnings [hookwrapper, tryfirst]
_pytest.config.Config (pytestconfig) [hookwrapper]
_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.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_generate_tests 3 _pytest.fixtures.FixtureManager (funcmanage)
_pytest.python
pytest_ignore_collect 4 firstresult _pytest.main
pytest_itemcollected 3 -
pytest_keyboard_interrupt 1 _pytest.capture.CaptureManager [tryfirst]
_pytest.terminal.TerminalReporter
pytest_load_initial_conftests 1 _pytest.warnings [hookwrapper]
_pytest.capture [hookwrapper]
_pytest.config.Config (pytestconfig) [trylast]
pytest_make_collect_report 2 firstresult _pytest.capture.CaptureManager [hookwrapper]
_pytest.runner
pytest_plugin_registered 77 historic _pytest.terminal.TerminalReporter
_pytest.fixtures.FixtureManager (funcmanage)
pytest_pycollect_makeitem 5 firstresult _pytest.unittest
_pytest.python [trylast]
pytest_pycollect_makemodule 1 firstresult _pytest.python
pytest_pyfunc_call 2 firstresult _pytest.python [trylast]
pytest_report_collectionfinish 1 -
pytest_report_teststatus 4 firstresult _pytest.skipping
_pytest.runner
_pytest.terminal [trylast]
pytest_runtest_call 2 _pytest.threadexception [hookwrapper, tryfirst]
_pytest.unraisableexception [hookwrapper, tryfirst]
_pytest.logging.LoggingPlugin (logging-plugin) [hookwrapper]
_pytest.capture.CaptureManager [hookwrapper]
_pytest.skipping [hookwrapper]
_pytest.runner
pytest_runtest_logfinish 1 _pytest.logging.LoggingPlugin (logging-plugin)
_pytest.terminal.TerminalReporter
pytest_runtest_logreport 4 _pytest.main.Session [tryfirst]
_pytest.logging.LoggingPlugin (logging-plugin)
_pytest.terminal.TerminalReporter
pytest_runtest_logstart 2 _pytest.logging.LoggingPlugin (logging-plugin)
_pytest.terminal.TerminalReporter
pytest_runtest_makereport 4 firstresult _pytest.skipping [hookwrapper]
_pytest.unittest [tryfirst]
_pytest.runner
pytest_runtest_protocol 2 firstresult _pytest.warnings [hookwrapper, tryfirst]
_pytest.assertion [hookwrapper, tryfirst]
_pytest.unittest [hookwrapper]
_pytest.faulthandler.FaultHandlerHooks (faulthandler-hooks) [hookwrapper, trylast]
_pytest.runner
pytest_runtest_setup 2 _pytest.unraisableexception [hookwrapper, tryfirst]
_pytest.logging.LoggingPlugin (logging-plugin) [hookwrapper]
_pytest.capture.CaptureManager [hookwrapper]
_pytest.threadexception [hookwrapper, trylast]
_pytest.skipping [tryfirst]
_pytest.runner
_pytest.nose [trylast]
pytest_runtest_teardown 1 _pytest.threadexception [hookwrapper, tryfirst]
_pytest.unraisableexception [hookwrapper, tryfirst]
_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.junitxml
_pytest.pastebin
_pytest.mark