Skip to content
Open
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
29 changes: 19 additions & 10 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,19 +568,23 @@ def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None):
self.fspath = fspath

def setup(self):
if not self._should_import_init():
return

# not using fixtures to call setup_module here because autouse fixtures
# from packages are not called automatically (#4085)
package_obj = self.obj
setup_module = _get_first_non_fixture_func(
self.obj, ("setUpModule", "setup_module")
package_obj, ("setUpModule", "setup_module")
)
if setup_module is not None:
_call_with_optional_argument(setup_module, self.obj)
_call_with_optional_argument(setup_module, package_obj)

teardown_module = _get_first_non_fixture_func(
self.obj, ("tearDownModule", "teardown_module")
package_obj, ("tearDownModule", "teardown_module")
)
if teardown_module is not None:
func = partial(_call_with_optional_argument, teardown_module, self.obj)
func = partial(_call_with_optional_argument, teardown_module, package_obj)
self.addfinalizer(func)

def _recurse(self, dirpath):
Expand Down Expand Up @@ -639,13 +643,9 @@ def isinitpath(self, path):
return path in self.session._initialpaths

def collect(self):
self._mount_obj_if_needed()
this_path = self.fspath.dirpath()
init_module = this_path.join("__init__.py")
if init_module.check(file=1) and path_matches_patterns(
init_module, self.config.getini("python_files")
):
yield Module(init_module, self)
if self._should_import_init():
yield Module(self.fspath, self)
pkg_prefixes = set()
for path in this_path.visit(rec=self._recurse, bf=True, sort=True):
# We will visit our own __init__.py file, in which case we skip it.
Expand All @@ -669,6 +669,15 @@ def collect(self):
elif path.join("__init__.py").check(file=1):
pkg_prefixes.add(path)

def _should_import_init(self):
init_module = self.fspath
if not init_module.check(file=1):
return False
if self.isinitpath(init_module):
return True
python_files = self.config.getini("python_files")
return path_matches_patterns(init_module, python_files)


def _call_with_optional_argument(func, arg):
"""Call the given function with the given argument if func accepts one argument, otherwise
Expand Down
52 changes: 52 additions & 0 deletions testing/python/collect.py
Original file line number Diff line number Diff line change
Expand Up @@ -1336,3 +1336,55 @@ def test_package_ordering(testdir):
# Execute from .
result = testdir.runpytest("-v", "-s")
result.assert_outcomes(passed=3)


def test_package_init_not_imported_when_not_a_test_module(testdir):
testdir.makepyfile(
**{
"pkg/__init__.py": 'assert False, "should not be imported"\n',
"tests/test_mod.py": "def test_ok():\n pass\n",
}
)

result = testdir.runpytest()
result.assert_outcomes(passed=1)
assert "should not be imported" not in result.stdout.str()


def test_package_init_collected_when_enabled_via_python_files(testdir):
testdir.makeini(
"""
[pytest]
python_files = *.py
"""
)
testdir.makepyfile(
**{
"pkg/__init__.py": "def test_from_init():\n assert True\n",
}
)

result = testdir.runpytest("-k", "test_from_init")
result.assert_outcomes(passed=1)


def test_src_layout_package_init_not_imported(testdir):
testdir.makepyfile(
**{
"src/pkg/__init__.py": 'assert False, "should not be imported"\n',
"tests/test_ok.py": "def test_ok():\n pass\n",
}
)

result = testdir.runpytest()
result.assert_outcomes(passed=1)
assert "should not be imported" not in result.stdout.str()


def test_namespace_package_without_init_collects_normally(testdir):
testdir.makepyfile(
**{"ns_pkg/test_mod.py": "def test_namespace():\n pass\n"}
)

result = testdir.runpytest()
result.assert_outcomes(passed=1)
4 changes: 2 additions & 2 deletions testing/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1187,7 +1187,7 @@ def test_collect_pkg_init_and_file_in_args(testdir):
result = testdir.runpytest("-v", str(init), str(p))
result.stdout.fnmatch_lines(
[
"sub/test_file.py::test_file PASSED*",
"sub/__init__.py::test_init PASSED*",
"sub/test_file.py::test_file PASSED*",
"*2 passed in*",
]
Expand All @@ -1209,7 +1209,7 @@ def test_collect_pkg_init_only(testdir):
init.write("def test_init(): pass")

result = testdir.runpytest(str(init))
result.stdout.fnmatch_lines(["*no tests ran in*"])
result.stdout.fnmatch_lines(["*1 passed in*"])

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