main.py
Overview
`main.py` contains the core implementation of the testing process for the pytest framework. It handles the initialization of the testing session, test collection, the main test execution loop, and command-line option parsing. This file is central to pytest’s operation as it defines how tests are gathered from the filesystem or Python packages, how the test session lifecycle is managed, and how test execution is controlled.
Main responsibilities include:
Registering command-line options and ini-file configurations related to test running and collection.
Managing the creation and lifecycle of a
Sessionobject that represents the entire test run.Implementing the collection of tests from files and directories.
Running the test loop and handling interruptions, failures, and exit codes.
Providing utility functions for path resolution and module searching.
Classes and Functions
pytest_addoption(parser: Parser) -> None
Registers command-line options and ini-file configurations used for controlling test runs, warnings, collection, and debugging.
Parameters:
parser(Parser): The argument parser used to add options.
Usage:
parser = Parser()
pytest_addoption(parser)
Adds options such as
-x/--exitfirst,--maxfail,--collectonly,--ignore,--rootdir, and many others used to control pytest's behavior.
validate_basetemp(path: str) -> str
Validates the `--basetemp` directory path passed as a command-line argument. Ensures the path is not empty, the current working directory, or any of its ancestor directories.
Parameters:
path(str): The path string to validate.
Returns:
The validated path string if valid.
Raises:
argparse.ArgumentTypeErrorif the path is invalid.
Details:
Checks the path and its resolved symlink path against the current working directory and its parents.
wrap_session(config: Config, doit: Callable[[Config, Session], int | ExitCode | None]) -> int | ExitCode
The main wrapper for running a test session. It initializes the `Session` object, manages configuration, runs the test session lifecycle hooks, and handles exceptions and exit codes.
Parameters:
config(Config): The pytest configuration object.doit(Callable): The function that performs the actual testing process, typically_main.
Returns:
An integer or
ExitCoderepresenting the exit status of the test run.
Usage:
exit_status = wrap_session(config, _main)
Details:
Manages exceptions such as
UsageError,Failed(test failures), KeyboardInterrupt, and other errors.Calls hooks:
pytest_sessionstart,pytest_sessionfinish, and handles cleanup.
pytest_cmdline_main(config: Config) -> int | ExitCode
Entrypoint for the pytest command line main function. Delegates to `wrap_session` with `_main` as the test execution function.
Parameters:
config(Config): The pytest configuration.
Returns:
Exit code of the test session.
_main(config: Config, session: Session) -> int | ExitCode | None
Core command-line protocol performing test collection and running the test loop.
Parameters:
config(Config): Pytest configuration.session(Session): The active test session.
Returns:
Returns an exit code indicating success, failure, or no tests collected.
Workflow:
Calls
pytest_collectionhook to collect tests.Calls
pytest_runtestloophook to run tests.Returns appropriate exit code based on test results.
pytest_collection(session: Session) -> None
Default hook implementation to perform test collection by calling `session.perform_collect()`.
Parameters:
session(Session): The current test session.
pytest_runtestloop(session: Session) -> bool
Runs the main test execution loop over collected test items.
Parameters:
session(Session): The current test session.
Returns:
Trueon successful completion.
Raises:
session.Interruptedif collection errors occurred andcontinue_on_collection_errorsis False.session.Failedif a failure condition is met.
Details:
Iterates over
session.items, running each test viapytest_runtest_protocol.Handles session flags
shouldfailandshouldstop.
_in_venv(path: Path) -> bool
Helper to detect whether a directory is a Python virtual environment root.
Parameters:
path(Path): Directory to check.
Returns:
Trueif the path appears to be a virtual environment root (checks forpyvenv.cfgorconda-meta/history).
pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None
Determines if a given path should be ignored during test collection.
Parameters:
collection_path(Path): Path to check.config(Config): Pytest config containing ignore patterns.
Returns:
Trueif the path should be ignored, otherwiseNone.
Logic:
Ignores
__pycache__, paths listed inignoreandignore_globoptions, paths inside virtualenvs (unless allowed), and directories matchingnorecursedirs.
pytest_collect_directory(path: Path, parent: nodes.Collector) -> nodes.Collector | None
Collects a directory as a `Dir` collector node.
Parameters:
path(Path): Directory path.parent(nodes.Collector): Parent collector node.
Returns:
A
Dircollector instance or None.
pytest_collection_modifyitems(items: list[nodes.Item], config: Config) -> None
Modifies the list of collected test items by deselecting any items whose node ID starts with prefixes supplied by `--deselect`.
Parameters:
items(list ofnodes.Item): Collected test items.config(Config): pytest configuration.
class FSHookProxy
A proxy for pytest hook calls that excludes certain plugins (modules). Used internally to isolate hook calls for specific filesystem paths.
Attributes:
pm(PytestPluginManager): Plugin manager.remove_mods(AbstractSet[object]): Plugins to exclude.
Methods:
__getattr__(name: str) -> pluggy.HookCaller: Returns a hook caller excluding specified plugins.
class Interrupted(KeyboardInterrupt)
Exception indicating that the test run was interrupted by the user or system.
class Failed(Exception)
Exception indicating the test run failed and should stop.
class _bestrelpath_cache(dict[Path, str])
A cache to optimize repeated calls to `bestrelpath()` for paths relative to a base path.
Attributes:
path(Path): Base path for relative calculations.
Methods:
__missing__(path: Path) -> str: Computes and caches the relative path.
class Dir(nodes.Directory)
Collector representing a directory in the filesystem. Collects both subdirectories and files as test nodes.
Class Methods:
from_parent(parent: nodes.Collector, *, path: Path) -> SelfCreates a
Dircollector instance with the given parent and directory path.
Instance Methods:
collect() -> Iterable[nodes.Item | nodes.Collector]Collects child directories and files, yielding further collectors or test items.
Uses hooks to determine if paths should be ignored or collected.
class Session(nodes.Collector)
Represents the root of the test collection tree and manages the entire lifecycle of the test run.
Attributes:
Interrupted: Alias for theInterruptedexception.Failed: Alias for theFailedexception._setupstate(SetupState): Internal setup state._fixturemanager(FixtureManager): Fixture manager.exitstatus(int | ExitCode): Exit status of the session.testsfailed(int): Number of failed tests.testscollected(int): Number of collected tests._shouldstop(bool | str): Flag to indicate test run should stop._shouldfail(bool | str): Flag to indicate test run should fail.trace: Trace logger._initialpaths(frozenset[Path]): Initial paths given to pytest._initialpaths_with_parents(frozenset[Path]): Initial paths plus their parents._notfound(list): List of not found collection arguments._initial_parts(list): Parsed collection arguments._collection_cache(dict): Cache for collected nodes.items(list): List of collected test items._bestrelpathcache(dict): Cache for relative paths.
Class Methods:
from_config(config: Config) -> SessionCreates a new Session from a pytest config.
Instance Methods:
__init__(config: Config)__repr__() -> strshouldstop(property): Getter and setter for stop flag.shouldfail(property): Getter and setter for failure flag.startpath(property): Returns the invocation directory path._node_location_to_relpath(node_path: Path) -> str: Returns relative path using cache.pytest_collectstart(): Hook called at the start of collection; raises if shouldfail/shouldstop.pytest_runtest_logreport(report): Hook to track test failures and enforce maxfail.pytest_collectreport = pytest_runtest_logreport(alias)isinitpath(path, *, with_parents=False) -> bool: Checks if a path is one of the initial test paths.gethookproxy(fspath) -> pluggy.HookRelay: Returns a hook proxy with filtered plugins based on path._collect_path(path: Path, path_cache: dict) -> Sequence[nodes.Collector]: Collects nodes for a given path with caching.perform_collect(args=None, genitems=True) -> Sequence[nodes.Item | nodes.Collector]: Performs test collection from given arguments._collect_one_node(node, handle_dupes=True) -> tuple[CollectReport, bool]: Collects a single node, with cache checking.collect() -> Iterator[nodes.Item | nodes.Collector]: Iterates over all initial arguments and yields collected nodes.genitems(node) -> Iterator[nodes.Item]: Recursively yields test items from a collector or item.
Usage Example:
session = Session.from_config(config)
collected_items = session.perform_collect()
for item in session.genitems(session):
# run or analyze test item
search_pypath(module_name: str, *, consider_namespace_packages: bool = False) -> str | None
Searches `sys.path` for a given dotted Python module name and returns its file system path if found.
Parameters:
module_name(str): Dotted module name (e.g.,"my_package.module").consider_namespace_packages(bool): Whether to consider namespace packages.
Returns:
Filesystem path (
str) of the module/package root orNoneif not found.
@dataclasses.dataclass(frozen=True)
class CollectionArgument
Represents a resolved collection argument from the command line.
Attributes:
path(Path): Filesystem path of the test file/directory or module.parts(Sequence[str]): Additional selection parts (e.g., test names).module_name(str | None): Module name if resolved as a Python package.
resolve_collection_argument(invocation_path: Path, arg: str, *, as_pypath: bool = False, consider_namespace_packages: bool = False) -> CollectionArgument
Parses command-line test path arguments (possibly including test selection parts like `::TestClass::test_method`) and resolves them to absolute filesystem paths or module paths.
Parameters:
invocation_path(Path): Base path from where pytest was invoked.arg(str): Argument string to resolve.as_pypath(bool): Treat argument as a Python module path instead of a filesystem path.consider_namespace_packages(bool): Whether to consider namespace packages when resolving.
Returns:
A
CollectionArgumentinstance with resolved path, parts, and module name.
Raises:
UsageErrorif the path or module is not found or arguments are invalid.
Important Implementation Details
Session as Root Collector: The
Sessionclass is the root of the collection tree, responsible for managing initial collection arguments and recursively collecting tests.Collection Caching: Collection results are cached to avoid redundant work, especially for nodes collected multiple times.
Hook Proxies: To optimize plugin hook calls per path,
FSHookProxyandPathAwareHookProxyare used to exclude plugins irrelevant to certain filesystem locations.Exception Handling in wrap_session: Carefully handles different exceptions to set the correct exit code and perform cleanup, including special handling for
UsageError,Failed, andKeyboardInterrupt.Test Selection Syntax: Supports selecting specific tests via
::notation in command-line arguments.Virtual Environment Detection:
_in_venv()detects virtual environments to optionally skip collecting tests inside them.Configurable Ignoring: Paths can be ignored via command-line options (
--ignore,--ignore-glob) or configuration (collect_ignore,norecursedirs).Strictness Options: Command-line options control strictness of config parsing and marker registration.
Interaction with Other System Components
Nodes Module: Defines the tree structure of test collections (
Session,Dir,Item, etc.).Config Module: Supplies test configuration, command-line options, and ini-file settings.
Plugin Manager: Manages pytest plugins and hook calls, allowing extensibility.
Runner and Fixtures: Session interacts with runner and fixture components, which manage test setup, execution, and teardown.
Reports: Collection and test run reports (
CollectReport,TestReport) are generated and used to track progress and results.Path Utilities: Several utility functions (
absolutepath,bestrelpath,safe_exists,scandir) assist in filesystem operations.
Visual Diagram
classDiagram
class Session {
+testsfailed: int
+testscollected: int
+shouldstop: bool | str
+shouldfail: bool | str
+perform_collect(args: Sequence[str] | None, genitems: bool) Sequence[nodes.Item | nodes.Collector]
+collect() Iterator[nodes.Item | nodes.Collector]
+genitems(node: nodes.Item | nodes.Collector) Iterator[nodes.Item]
+gethookproxy(fspath: os.PathLike) pluggy.HookRelay
}
class Dir {
+from_parent(parent: nodes.Collector, path: Path) Dir
+collect() Iterable[nodes.Item | nodes.Collector]
}
class FSHookProxy {
-pm: PytestPluginManager
-remove_mods: AbstractSet[object]
+__getattr__(name: str) pluggy.HookCaller
}
class CollectionArgument {
+path: Path
+parts: Sequence[str]
+module_name: str | None
}
Session --> Dir : collects directories
Session --> CollectionArgument : resolves cli args
Session ..> FSHookProxy : uses for hook calls
Dir --> nodes.Item : collects test items
Summary
`main.py` is the heart of pytest's test run process, orchestrating the command-line interface, test discovery, and execution lifecycle. It defines key classes such as `Session` and `Dir` for managing test collection and running, handles configuration options, and ensures correct handling of test outcomes and user interruptions. This file interacts closely with pytest’s plugin system and node hierarchy, making it critical to the framework’s extensibility and core functioning.