Skip to content

Commit

Permalink
python: change pytest pkg/__init__.py to only collect the `__init__…
Browse files Browse the repository at this point in the history
….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
  • Loading branch information
bluetech committed Jun 23, 2023
1 parent 2870157 commit c8b1790
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 17 deletions.
5 changes: 5 additions & 0 deletions changelog/8976.breaking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Running `pytest pkg/__init__.py` now collects the `pkg/__init__.py` file (module) only.
Previously, it collected the entire `pkg` package, including other test files in the directory, but excluding tests in the `__init__.py` file itself
(unless :confval:`python_files` was changed to allow `__init__.py` file).

To collect the entire package, specify just the directory: `pytest pkg`.
18 changes: 16 additions & 2 deletions doc/en/deprecations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -467,12 +467,26 @@ The ``yield_fixture`` function/decorator
It has been so for a very long time, so can be search/replaced safely.


Removed Features
----------------
Removed Features and Breaking Changes
-------------------------------------

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

Some breaking changes which could not be deprecated are also listed.


Collecting ``__init__.py`` files no longer collects package
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. versionremoved:: 8.0

Running `pytest pkg/__init__.py` now collects the `pkg/__init__.py` file (module) only.
Previously, it collected the entire `pkg` package, including other test files in the directory, but excluding tests in the `__init__.py` file itself
(unless :confval:`python_files` was changed to allow `__init__.py` file).

To collect the entire package, specify just the directory: `pytest pkg`.


The ``pytest.collect`` module
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
4 changes: 3 additions & 1 deletion src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,9 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
this_path = self.path.parent

# Always collect the __init__ first.
if path_matches_patterns(self.path, self.config.getini("python_files")):
if self.session.isinitpath(self.path) or path_matches_patterns(
self.path, self.config.getini("python_files")
):
yield Module.from_parent(self, path=self.path)

pkg_prefixes: Set[Path] = set()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def test_init():
pass
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
def test():
def test_foo():
pass
11 changes: 8 additions & 3 deletions testing/python/collect.py
Original file line number Diff line number Diff line change
Expand Up @@ -1420,10 +1420,15 @@ def test_package_collection_infinite_recursion(pytester: Pytester) -> None:


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


def test_package_with_modules(pytester: Pytester) -> None:
Expand Down
25 changes: 18 additions & 7 deletions testing/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1392,19 +1392,27 @@ def test_collect_pkg_init_and_file_in_args(pytester: Pytester) -> None:
p = subdir.joinpath("test_file.py")
p.write_text("def test_file(): pass", encoding="utf-8")

# NOTE: without "-o python_files=*.py" this collects test_file.py twice.
# This changed/broke with "Add package scoped fixtures #2283" (2b1410895)
# initially (causing a RecursionError).
result = pytester.runpytest("-v", str(init), str(p))
# Just the package directory, the __init__.py module is filtered out.
result = pytester.runpytest("-v", subdir)
result.stdout.fnmatch_lines(
[
"sub/test_file.py::test_file PASSED*",
"*1 passed in*",
]
)

# But it's included if specified directly.
result = pytester.runpytest("-v", init, p)
result.stdout.fnmatch_lines(
[
"sub/__init__.py::test_init PASSED*",
"sub/test_file.py::test_file PASSED*",
"*2 passed in*",
]
)

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

result = pytester.runpytest(str(init))
result = pytester.runpytest(subdir)
result.stdout.fnmatch_lines(["*no tests ran in*"])

result = pytester.runpytest("-v", "-o", "python_files=*.py", str(init))
result = pytester.runpytest("-v", init)
result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"])

result = pytester.runpytest("-v", "-o", "python_files=*.py", subdir)
result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"])


Expand Down
4 changes: 2 additions & 2 deletions testing/test_doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def test_simple_doctestfile(self, pytester: Pytester):
reprec.assertoutcome(failed=1)

def test_importmode(self, pytester: Pytester):
p = pytester.makepyfile(
pytester.makepyfile(
**{
"namespacepkg/innerpkg/__init__.py": "",
"namespacepkg/innerpkg/a.py": """
Expand All @@ -132,7 +132,7 @@ def my_func():
""",
}
)
reprec = pytester.inline_run(p, "--doctest-modules", "--import-mode=importlib")
reprec = pytester.inline_run("--doctest-modules", "--import-mode=importlib")
reprec.assertoutcome(passed=1)

def test_new_pattern(self, pytester: Pytester):
Expand Down
2 changes: 1 addition & 1 deletion testing/test_nose.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ def test_it():
pass
""",
)
result = pytester.runpytest(p, "-p", "nose")
result = pytester.runpytest(p.parent, "-p", "nose")
assert result.ret == 0


Expand Down

0 comments on commit c8b1790

Please sign in to comment.