-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
158 additions
and
1 deletion.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Added :confval:`pythonpath` setting that adds listed paths to :data:`sys.path` for the duration of the test session. If you currently use the pytest-pythonpath or pytest-srcpaths plugins, you should be able to replace them with built-in `pythonpath` setting. |
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import sys | ||
|
||
import pytest | ||
from pytest import Config | ||
from pytest import Parser | ||
|
||
|
||
def pytest_addoption(parser: Parser) -> None: | ||
parser.addini("pythonpath", type="paths", help="Add paths to sys.path", default=[]) | ||
|
||
|
||
@pytest.hookimpl(tryfirst=True) | ||
def pytest_load_initial_conftests(early_config: Config) -> None: | ||
# `pythonpath = a b` will set `sys.path` to `[a, b, x, y, z, ...]` | ||
for path in reversed(early_config.getini("pythonpath")): | ||
sys.path.insert(0, str(path)) | ||
|
||
|
||
@pytest.hookimpl(trylast=True) | ||
def pytest_unconfigure(config: Config) -> None: | ||
for path in config.getini("pythonpath"): | ||
path_str = str(path) | ||
if path_str in sys.path: | ||
sys.path.remove(path_str) |
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import sys | ||
from textwrap import dedent | ||
from typing import Generator | ||
from typing import List | ||
from typing import Optional | ||
|
||
import pytest | ||
from _pytest.pytester import Pytester | ||
|
||
|
||
@pytest.fixture() | ||
def file_structure(pytester: Pytester) -> None: | ||
pytester.makepyfile( | ||
test_foo=""" | ||
from foo import foo | ||
def test_foo(): | ||
assert foo() == 1 | ||
""" | ||
) | ||
|
||
pytester.makepyfile( | ||
test_bar=""" | ||
from bar import bar | ||
def test_bar(): | ||
assert bar() == 2 | ||
""" | ||
) | ||
|
||
foo_py = pytester.mkdir("sub") / "foo.py" | ||
content = dedent( | ||
""" | ||
def foo(): | ||
return 1 | ||
""" | ||
) | ||
foo_py.write_text(content, encoding="utf-8") | ||
|
||
bar_py = pytester.mkdir("sub2") / "bar.py" | ||
content = dedent( | ||
""" | ||
def bar(): | ||
return 2 | ||
""" | ||
) | ||
bar_py.write_text(content, encoding="utf-8") | ||
|
||
|
||
def test_one_dir(pytester: Pytester, file_structure) -> None: | ||
pytester.makefile(".ini", pytest="[pytest]\npythonpath=sub\n") | ||
result = pytester.runpytest("test_foo.py") | ||
assert result.ret == 0 | ||
result.assert_outcomes(passed=1) | ||
|
||
|
||
def test_two_dirs(pytester: Pytester, file_structure) -> None: | ||
pytester.makefile(".ini", pytest="[pytest]\npythonpath=sub sub2\n") | ||
result = pytester.runpytest("test_foo.py", "test_bar.py") | ||
assert result.ret == 0 | ||
result.assert_outcomes(passed=2) | ||
|
||
|
||
def test_module_not_found(pytester: Pytester, file_structure) -> None: | ||
"""Without the pythonpath setting, the module should not be found.""" | ||
pytester.makefile(".ini", pytest="[pytest]\n") | ||
result = pytester.runpytest("test_foo.py") | ||
assert result.ret == pytest.ExitCode.INTERRUPTED | ||
result.assert_outcomes(errors=1) | ||
expected_error = "E ModuleNotFoundError: No module named 'foo'" | ||
result.stdout.fnmatch_lines([expected_error]) | ||
|
||
|
||
def test_no_ini(pytester: Pytester, file_structure) -> None: | ||
"""If no ini file, test should error.""" | ||
result = pytester.runpytest("test_foo.py") | ||
assert result.ret == pytest.ExitCode.INTERRUPTED | ||
result.assert_outcomes(errors=1) | ||
expected_error = "E ModuleNotFoundError: No module named 'foo'" | ||
result.stdout.fnmatch_lines([expected_error]) | ||
|
||
|
||
def test_clean_up(pytester: Pytester) -> None: | ||
"""Test that the pythonpath plugin cleans up after itself.""" | ||
# This is tough to test behaviorly because the cleanup really runs last. | ||
# So the test make several implementation assumptions: | ||
# - Cleanup is done in pytest_unconfigure(). | ||
# - Not a hookwrapper. | ||
# So we can add a hookwrapper ourselves to test what it does. | ||
pytester.makefile(".ini", pytest="[pytest]\npythonpath=I_SHALL_BE_REMOVED\n") | ||
pytester.makepyfile(test_foo="""def test_foo(): pass""") | ||
|
||
before: Optional[List[str]] = None | ||
after: Optional[List[str]] = None | ||
|
||
class Plugin: | ||
@pytest.hookimpl(hookwrapper=True, tryfirst=True) | ||
def pytest_unconfigure(self) -> Generator[None, None, None]: | ||
nonlocal before, after | ||
before = sys.path.copy() | ||
yield | ||
after = sys.path.copy() | ||
|
||
result = pytester.runpytest_inprocess(plugins=[Plugin()]) | ||
assert result.ret == 0 | ||
|
||
assert before is not None | ||
assert after is not None | ||
assert any("I_SHALL_BE_REMOVED" in entry for entry in before) | ||
assert not any("I_SHALL_BE_REMOVED" in entry for entry in after) |