test_python_path.py
Overview
`test_python_path.py` is a test utility file designed to verify the behavior of the `pythonpath` configuration in pytest. It uses the `Pytester` testing helper from [_pytest.pytester](/projects/286/67470) to dynamically create test files, modules, and directories on the fly, and then runs pytest against those files to assert correct import behaviors.
The core purpose of this file is to ensure that when `pythonpath` is specified in pytest's configuration (`pytest.ini`), Python modules located in those paths can be correctly imported in tests. It also verifies that plugins are loaded correctly when using `pythonpath`, and that cleanup after test runs correctly resets the system path.
Detailed Explanation
Imports and Fixtures
Imports:
sys: Used to inspect and manipulate the Python interpreter's system path.dedent from
textwrap: Used to clean up multi-line strings.Pytesterfrom _pytest.pytester: A pytest testing utility to create and run test files programmatically.pytest: Main testing framework used here.
Fixture:
file_structure(pytester: Pytester) -> NoneThis fixture sets up a directory and file structure before tests are run:
Creates two test files
test_foo.pyandtest_bar.pythat each import and test functions from modulesfooandbar, respectively.Creates two subdirectories
subandsub2each containing a module filefoo.pyandbar.pyrespectively, which define the foo() and bar() functions returning 1 and2.
This structure simulates a project with multiple source directories and tests, useful for verifying `pythonpath` behavior.
Test Functions
1. test_one_dir(pytester: Pytester, file_structure) -> None
Purpose: Verify that adding a single directory (
sub) topythonpathallows importing modules from it.Behavior:
Creates a pytest
.inifile withpythonpath=sub.Runs pytest on
test_foo.py.Asserts the tests pass and the return code is zero.
Usage Example:
pytester.makefile(".ini", pytest="[pytest]\npythonpath=sub\n") result = pytester.runpytest("test_foo.py") assert result.ret == 0
2. test_two_dirs(pytester: Pytester, file_structure) -> None
Purpose: Verify multiple directories can be added to
pythonpathsimultaneously (subandsub2).Behavior:
Creates
.iniwithpythonpath=sub sub2.Runs pytest on both
test_foo.pyandtest_bar.py.Asserts both tests pass.
Usage Example:
pytester.makefile(".ini", pytest="[pytest]\npythonpath=sub sub2\n") result = pytester.runpytest("test_foo.py", "test_bar.py") assert result.ret == 0
3. test_local_plugin(pytester: Pytester, file_structure) -> None
Purpose: Ensure that
pythonpathis respected early enough in pytest startup to load local plugins dynamically.Behavior:
Creates a local plugin file
localplugin.pyinsubwith hookspytest_load_initial_conftests()andpytest_unconfigure()that print messages.Sets
pythonpath=subin.ini.Runs pytest with
-plocalpluginand-s(to show print output).Asserts that plugin load/unconfigure messages appear and tests pass.
Usage Example:
pytester.runpytest("-plocalplugin", "-s", "test_foo.py")
4. test_module_not_found(pytester: Pytester, file_structure) -> None
Purpose: Verify that if
pythonpathis not set, modules insubcannot be imported and an error is raised.Behavior:
Creates
.iniwithoutpythonpath.Runs pytest on
test_foo.py.Checks that pytest errors with a
ModuleNotFoundErrorforfoo.
Usage Example:
pytester.makefile(".ini", pytest="[pytest]\n") result = pytester.runpytest("test_foo.py") assert "ModuleNotFoundError" in result.stdout.str()
5. test_no_ini(pytester: Pytester, file_structure) -> None
Purpose: Verify that no
.inifile leads to the same import failure as above.Behavior:
Runs pytest on
test_foo.pywithout any.ini.Checks for the same error as above.
Usage Example:
result = pytester.runpytest("test_foo.py")
6. test_clean_up(pytester: Pytester) -> None
Purpose: Test that the
pythonpathentries added during the test run are properly removed (cleaned up) at the end.Behavior:
Creates
.iniwith a dummy pathI_SHALL_BE_REMOVEDinpythonpath.Creates a trivial
test_foo.py.Defines a local plugin class that hooks into
pytest_unconfigureto capturesys.pathat that point.Runs pytest in-process with this plugin.
Asserts
sys.pathbefore unconfigure contains the dummy path, but after it does not.
Implementation detail: Relies on
pytest_unconfigurehook to detect cleanup timing.Usage Example:
class Plugin: @pytest.hookimpl(tryfirst=True) def pytest_unconfigure(self): # capture sys.path before cleanup pytester.runpytest_inprocess(plugins=[Plugin()])
Implementation Details and Algorithms
Dynamic File/Directory Creation: Uses
Pytesterto create source files, test files, and configuration files dynamically in isolated test directories.Pythonpath Injection: Tests rely on
pythonpathsettings in pytest.inifiles to altersys.pathduring test runs, simulating how users might extend the module search path.Plugin Loading: Demonstrates that
pythonpathinfluences plugin discovery by placing a plugin module inside a directory added topythonpath.Cleanup Verification: Confirms that the pytest plugin responsible for injecting
pythonpathentries cleans them up properly post-test viapytest_unconfigure.
Interaction with Other System Components
Pytester Plugin: This file depends heavily on the
Pytesterhelper class from pytest's internal testing utilities to create test environments and run pytest programmatically.pytest Configuration: It manipulates pytest's
.iniconfiguration files to test how pytest reacts to differentpythonpathsettings.pytest Plugin System: It tests loading of local plugins placed inside directories added to
pythonpath, demonstrating integration with pytest's plugin discovery mechanism.Python sys.path: Directly checks and manipulates the Python import path (
sys.path) to verify the effects of thepythonpathsetting.
Usage Summary
This file is primarily used during pytest's own test suite development or when testing pytest plugins and configurations related to `pythonpath`. It ensures that:
Modules in additional paths are discoverable.
Local plugins in those paths load correctly.
Failures occur as expected without proper
pythonpath.Cleanup of paths after test runs is reliable.
Visual Diagram
flowchart TD
A[Fixture: file_structure] --> B[test_one_dir]
A --> C[test_two_dirs]
A --> D[test_local_plugin]
A --> E[test_module_not_found]
A --> F[test_no_ini]
G[test_clean_up] --> H[Plugin: pytest_unconfigure hook]
B --> I[Creates .ini with pythonpath=sub]
C --> J[Creates .ini with pythonpath=sub sub2]
D --> K[Creates localplugin.py in sub]
E --> L[Creates .ini without pythonpath]
F --> M[No .ini file created]
G --> N[Creates .ini with dummy pythonpath]
subgraph "Test Runs"
I --> O[Run pytest on test_foo.py]
J --> P[Run pytest on test_foo.py and test_bar.py]
K --> Q[Run pytest with local plugin]
L --> R[Run pytest on test_foo.py expecting error]
M --> S[Run pytest on test_foo.py expecting error]
N --> T[Run pytest in-process with Plugin]
end
Summary
`test_python_path.py` is a pytest integration test file that validates the behavior of the `pythonpath` setting in pytest configurations. By dynamically creating test modules and configurations, it verifies import success and failure scenarios, plugin loading behavior, and cleanup mechanisms related to modifying the Python module search path. It is tightly coupled with pytest's internal testing facilities and serves as a crucial check on pytest's support for extended Python paths and plugin discovery.