Skip to content

Commit c8b1790

Browse files
committed
python: change pytest pkg/__init__.py to only collect the __init__.py Module
Previously it would collect the entire package, but this is not what users expect. Refs pytest-dev#3749 Fixes pytest-dev#8976 Fixes pytest-dev#9263 Fixes pytest-dev#9313
1 parent 2870157 commit c8b1790

File tree

9 files changed

+56
-17
lines changed

9 files changed

+56
-17
lines changed

changelog/8976.breaking.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Running `pytest pkg/__init__.py` now collects the `pkg/__init__.py` file (module) only.
2+
Previously, it collected the entire `pkg` package, including other test files in the directory, but excluding tests in the `__init__.py` file itself
3+
(unless :confval:`python_files` was changed to allow `__init__.py` file).
4+
5+
To collect the entire package, specify just the directory: `pytest pkg`.

doc/en/deprecations.rst

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -467,12 +467,26 @@ The ``yield_fixture`` function/decorator
467467
It has been so for a very long time, so can be search/replaced safely.
468468

469469

470-
Removed Features
471-
----------------
470+
Removed Features and Breaking Changes
471+
-------------------------------------
472472

473473
As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after
474474
an appropriate period of deprecation has passed.
475475

476+
Some breaking changes which could not be deprecated are also listed.
477+
478+
479+
Collecting ``__init__.py`` files no longer collects package
480+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
481+
482+
.. versionremoved:: 8.0
483+
484+
Running `pytest pkg/__init__.py` now collects the `pkg/__init__.py` file (module) only.
485+
Previously, it collected the entire `pkg` package, including other test files in the directory, but excluding tests in the `__init__.py` file itself
486+
(unless :confval:`python_files` was changed to allow `__init__.py` file).
487+
488+
To collect the entire package, specify just the directory: `pytest pkg`.
489+
476490

477491
The ``pytest.collect`` module
478492
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

src/_pytest/python.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,9 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
736736
this_path = self.path.parent
737737

738738
# Always collect the __init__ first.
739-
if path_matches_patterns(self.path, self.config.getini("python_files")):
739+
if self.session.isinitpath(self.path) or path_matches_patterns(
740+
self.path, self.config.getini("python_files")
741+
):
740742
yield Module.from_parent(self, path=self.path)
741743

742744
pkg_prefixes: Set[Path] = set()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def test_init():
2+
pass
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
def test():
1+
def test_foo():
22
pass

testing/python/collect.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,10 +1420,15 @@ def test_package_collection_infinite_recursion(pytester: Pytester) -> None:
14201420

14211421

14221422
def test_package_collection_init_given_as_argument(pytester: Pytester) -> None:
1423-
"""Regression test for #3749"""
1423+
"""Regression test for #3749, #8976, #9263, #9313.
1424+
1425+
Specifying an __init__.py file directly should collect only the __init__.py
1426+
Module, not the entire package.
1427+
"""
14241428
p = pytester.copy_example("collect/package_init_given_as_arg")
1425-
result = pytester.runpytest(p / "pkg" / "__init__.py")
1426-
result.stdout.fnmatch_lines(["*1 passed*"])
1429+
items, hookrecorder = pytester.inline_genitems(p / "pkg" / "__init__.py")
1430+
assert len(items) == 1
1431+
assert items[0].name == "test_init"
14271432

14281433

14291434
def test_package_with_modules(pytester: Pytester) -> None:

testing/test_collection.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1392,19 +1392,27 @@ def test_collect_pkg_init_and_file_in_args(pytester: Pytester) -> None:
13921392
p = subdir.joinpath("test_file.py")
13931393
p.write_text("def test_file(): pass", encoding="utf-8")
13941394

1395-
# NOTE: without "-o python_files=*.py" this collects test_file.py twice.
1396-
# This changed/broke with "Add package scoped fixtures #2283" (2b1410895)
1397-
# initially (causing a RecursionError).
1398-
result = pytester.runpytest("-v", str(init), str(p))
1395+
# Just the package directory, the __init__.py module is filtered out.
1396+
result = pytester.runpytest("-v", subdir)
13991397
result.stdout.fnmatch_lines(
14001398
[
14011399
"sub/test_file.py::test_file PASSED*",
1400+
"*1 passed in*",
1401+
]
1402+
)
1403+
1404+
# But it's included if specified directly.
1405+
result = pytester.runpytest("-v", init, p)
1406+
result.stdout.fnmatch_lines(
1407+
[
1408+
"sub/__init__.py::test_init PASSED*",
14021409
"sub/test_file.py::test_file PASSED*",
14031410
"*2 passed in*",
14041411
]
14051412
)
14061413

1407-
result = pytester.runpytest("-v", "-o", "python_files=*.py", str(init), str(p))
1414+
# Or if the pattern allows it.
1415+
result = pytester.runpytest("-v", "-o", "python_files=*.py", subdir)
14081416
result.stdout.fnmatch_lines(
14091417
[
14101418
"sub/__init__.py::test_init PASSED*",
@@ -1419,10 +1427,13 @@ def test_collect_pkg_init_only(pytester: Pytester) -> None:
14191427
init = subdir.joinpath("__init__.py")
14201428
init.write_text("def test_init(): pass", encoding="utf-8")
14211429

1422-
result = pytester.runpytest(str(init))
1430+
result = pytester.runpytest(subdir)
14231431
result.stdout.fnmatch_lines(["*no tests ran in*"])
14241432

1425-
result = pytester.runpytest("-v", "-o", "python_files=*.py", str(init))
1433+
result = pytester.runpytest("-v", init)
1434+
result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"])
1435+
1436+
result = pytester.runpytest("-v", "-o", "python_files=*.py", subdir)
14261437
result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"])
14271438

14281439

testing/test_doctest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def test_simple_doctestfile(self, pytester: Pytester):
114114
reprec.assertoutcome(failed=1)
115115

116116
def test_importmode(self, pytester: Pytester):
117-
p = pytester.makepyfile(
117+
pytester.makepyfile(
118118
**{
119119
"namespacepkg/innerpkg/__init__.py": "",
120120
"namespacepkg/innerpkg/a.py": """
@@ -132,7 +132,7 @@ def my_func():
132132
""",
133133
}
134134
)
135-
reprec = pytester.inline_run(p, "--doctest-modules", "--import-mode=importlib")
135+
reprec = pytester.inline_run("--doctest-modules", "--import-mode=importlib")
136136
reprec.assertoutcome(passed=1)
137137

138138
def test_new_pattern(self, pytester: Pytester):

testing/test_nose.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ def test_it():
504504
pass
505505
""",
506506
)
507-
result = pytester.runpytest(p, "-p", "nose")
507+
result = pytester.runpytest(p.parent, "-p", "nose")
508508
assert result.ret == 0
509509

510510

0 commit comments

Comments
 (0)