Experimental _pytest.scenario API: nested configs + in-memory collection (PoC) - #14809
Draft
RonnyPfannschmidt wants to merge 1 commit into
Draft
Experimental _pytest.scenario API: nested configs + in-memory collection (PoC)#14809RonnyPfannschmidt wants to merge 1 commit into
RonnyPfannschmidt wants to merge 1 commit into
Conversation
…lection pytest's own testsuite is heavily integration-based: most tests write files via pytester and run a full session, then glob-match terminal output. This adds an experimental internal API to test pytest with pytest hermetically instead: * ConfigSpec/configured() build a parsed+configured nested Config from declarative data through the real parse phases -- no rootdir discovery, config files, conftests, plugin autoload, or env vars. Config.parse() is split into behavior-preserving phase methods so the programmatic path shares code with command line parsing, and ArgsSource.SPEC marks verbatim args. * fake_module()/collect_tests()/run_tests() collect test items from in-memory objects (functions, classes, module namespaces) through the standard pytest_collection flow via a ScenarioModule whose _getobj serves the preset object; nodeids derive from synthetic rootdir-relative paths that are never touched on disk. * RunRecord/ItemRunRecord provide typed structured results derived from real report objects via pytest_report_teststatus, with a pytester-compatible assert_outcomes(). The default scenario plugin set deliberately excludes capture, terminal, assertion, cacheprovider and the process-global-state plugins; capture nesting is the documented follow-up. Supporting core fixes: * Class.from_parent() no longer silently discards a passed obj. * get_config() accepts an explicit invocation dir=. * debugging/unittest guard cross-plugin option reads (trace/usepdb) so they work when the debugging plugin is not registered. Validated by 33 self-tests (0.3s, hermeticity asserted) and by converting seven existing tests in testing/python/ to the new API. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RonnyPfannschmidt
force-pushed
the
scenario-api-poc
branch
from
July 31, 2026 11:43
714974e to
8845122
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
pytest's own testsuite is heavily integration-based: of ~3200 real test functions in
testing/, ~61% usepytester, ~91% of those write files to disk, and roughly 1400 assertion sites glob-match rendered terminal output (vs ~270 structured ones). Tests need a full session because stdout is the only observable most of them have — there is no API to collect an item from an in-memory object, or to build aConfigwithout a real rootdir/cwd.testing/conftest.pyeven reorders tests by decompiling__code__.co_namesbecause "uses pytester" is the only available proxy for "slow".This PR is a proof of concept for fixing that at the API level. Draft — the API shape and naming are up for discussion.
What's included
New experimental package
_pytest/scenario/(internal, not exported frompytest):ConfigSpec/configured()— a parsed+configured nestedConfigfrom declarative data, built through the real parse phases but without rootdir discovery, config files, conftests, plugin autoload, or env consultation. Hermetic by default; teardown is paired via_ensure_unconfigure().fake_module(name, *members)— group loose functions/classes into an in-memory module with an explicit name;collect_tests()/run_tests()/ stepwisescenario()collect through the standardpytest_collectionflow (so-k/-m, parametrize, fixture scoping andpytest_collection_modifyitemsall work) via aScenarioModulewhose_getobjserves the preset object. Nodeids derive from synthetic rootdir-relative paths that are never touched on disk.RunRecord/ItemRunRecord— typed results derived from real report objects viapytest_report_teststatus, withassert_outcomes()signature-compatible with pytester and per-test lookup (record["test_x"].setup.longreprtext).Core changes in support:
Config.parse()split into behavior-preserving phase methods (_preparse_addopts,_apply_rootdir,_register_core_ini_options,_load_plugins_phase,_load_initial_conftests_phase,_finalize_parse(decide_args=)), plusArgsSource.SPEC. The programmatic path shares real code with command line parsing instead of bypassing it.Class.from_parent()no longer silently discards a passedobj(it was accepted and dropped).get_config()gained an explicit invocationdir=(was hardcodedPath.cwd()).debugging/unittestguard their cross-plugin option reads (trace/usepdb) so they work when the debugging plugin is not registered.Validation: 33 self-tests in
testing/test_scenario.pyrun in ~0.3s with hermeticity asserted (no sys.path/sys.modules/cwd/environ mutation, no files created); seven existing tests intesting/python/{collect,fixtures,metafunc}.pyconverted, including the hand-rolledmake_functionharness.Deliberate scope cuts (documented follow-ups)
capture,terminal,assertion,cacheproviderand the process-global-state plugins.capsys/capfdinside scenarios are unavailable untilCaptureManagerbecomes stack-aware (suspend/resume is currently absolute, restoring values memoised at__init__— nested managers work only by LIFO luck today). That is the next pillar.load_conftests=True) raisesNotImplementedError; plugin objects viaConfigSpec.extra_pluginsare the replacement.filterwarnings = error) are inherited by scenarios; a scenario'sinicfg={"filterwarnings": [...]}takes precedence.inline_runonto this API, migrate stdout outcome-assertions toRunRecord, and considerpytest.scenariograduation.The changelog fragment is
14809.feature.rst.Per our AI contribution policy: this was researched, designed and implemented with Claude Code under my direction and review.
🤖 Generated with Claude Code