Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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