-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Fix test collection from packages mixed with directories. #3768 and #3789 #3802
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Fix test collection from packages mixed with normal directories. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Fix test collection from packages mixed with normal directories. |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -216,18 +216,6 @@ def pytest_pycollect_makemodule(path, parent): | |||||||||||||||||||||||||||||||||||||||||||||||||
return Module(path, parent) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
def pytest_ignore_collect(path, config): | ||||||||||||||||||||||||||||||||||||||||||||||||||
# Skip duplicate packages. | ||||||||||||||||||||||||||||||||||||||||||||||||||
keepduplicates = config.getoption("keepduplicates") | ||||||||||||||||||||||||||||||||||||||||||||||||||
if keepduplicates: | ||||||||||||||||||||||||||||||||||||||||||||||||||
duplicate_paths = config.pluginmanager._duplicatepaths | ||||||||||||||||||||||||||||||||||||||||||||||||||
if path.basename == "__init__.py": | ||||||||||||||||||||||||||||||||||||||||||||||||||
if path in duplicate_paths: | ||||||||||||||||||||||||||||||||||||||||||||||||||
return True | ||||||||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||||||||
duplicate_paths.add(path) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
@hookimpl(hookwrapper=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||
def pytest_pycollect_makeitem(collector, name, obj): | ||||||||||||||||||||||||||||||||||||||||||||||||||
outcome = yield | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -554,9 +542,7 @@ def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None): | |||||||||||||||||||||||||||||||||||||||||||||||||
self.name = fspath.dirname | ||||||||||||||||||||||||||||||||||||||||||||||||||
self.trace = session.trace | ||||||||||||||||||||||||||||||||||||||||||||||||||
self._norecursepatterns = session._norecursepatterns | ||||||||||||||||||||||||||||||||||||||||||||||||||
for path in list(session.config.pluginmanager._duplicatepaths): | ||||||||||||||||||||||||||||||||||||||||||||||||||
if path.dirname == fspath.dirname and path != fspath: | ||||||||||||||||||||||||||||||||||||||||||||||||||
session.config.pluginmanager._duplicatepaths.remove(path) | ||||||||||||||||||||||||||||||||||||||||||||||||||
self.fspath = fspath | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
def _recurse(self, path): | ||||||||||||||||||||||||||||||||||||||||||||||||||
ihook = self.gethookproxy(path.dirpath()) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -594,6 +580,15 @@ def isinitpath(self, path): | |||||||||||||||||||||||||||||||||||||||||||||||||
return path in self.session._initialpaths | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
def collect(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||
# XXX: HACK! | ||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Strange, I expect Lines 261 to 284 in 4d8903f
If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem is it sees that some modules were already "collected" (at Session level) via the recurse() method. Then when the package goes and tries to collect its children and turn them into nodes it can't because they look like duplicates. Wrong! My first reaction was to try to stop marking them as duplicates at Session level, but everything is so tangled up that I can't find a cleaner way. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JFI: I've moved it in #4241. |
||||||||||||||||||||||||||||||||||||||||||||||||||
# Before starting to collect any files from this package we need | ||||||||||||||||||||||||||||||||||||||||||||||||||
# to cleanup the duplicate paths added by the session's collect(). | ||||||||||||||||||||||||||||||||||||||||||||||||||
# Proper fix is to not track these as duplicates in the first place. | ||||||||||||||||||||||||||||||||||||||||||||||||||
for path in list(self.session.config.pluginmanager._duplicatepaths): | ||||||||||||||||||||||||||||||||||||||||||||||||||
# if path.parts()[:len(self.fspath.dirpath().parts())] == self.fspath.dirpath().parts(): | ||||||||||||||||||||||||||||||||||||||||||||||||||
if path.dirname.startswith(self.name): | ||||||||||||||||||||||||||||||||||||||||||||||||||
self.session.config.pluginmanager._duplicatepaths.remove(path) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
this_path = self.fspath.dirpath() | ||||||||||||||||||||||||||||||||||||||||||||||||||
pkg_prefix = None | ||||||||||||||||||||||||||||||||||||||||||||||||||
for path in this_path.visit(rec=self._recurse, bf=True, sort=True): | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1583,3 +1583,43 @@ def test_package_collection_infinite_recursion(testdir): | |
testdir.copy_example("collect/package_infinite_recursion") | ||
result = testdir.runpytest() | ||
result.stdout.fnmatch_lines("*1 passed*") | ||
|
||
|
||
def test_package_with_modules(testdir): | ||
""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just FYI, you could have created the actual files in The idea is that it is easier to execute the test using pytest as normal during testing/debugging. 😉 This is fine as is, just thought I would mention this for the future. 👍 |
||
. | ||
└── root | ||
├── __init__.py | ||
├── sub1 | ||
│ ├── __init__.py | ||
│ └── sub1_1 | ||
│ ├── __init__.py | ||
│ └── test_in_sub1.py | ||
└── sub2 | ||
└── test | ||
└── test_in_sub2.py | ||
|
||
""" | ||
root = testdir.mkpydir("root") | ||
sub1 = root.mkdir("sub1") | ||
sub1.ensure("__init__.py") | ||
sub1_test = sub1.mkdir("sub1_1") | ||
sub1_test.ensure("__init__.py") | ||
sub2 = root.mkdir("sub2") | ||
sub2_test = sub2.mkdir("sub2") | ||
|
||
sub1_test.join("test_in_sub1.py").write("def test_1(): pass") | ||
sub2_test.join("test_in_sub2.py").write("def test_2(): pass") | ||
|
||
# Execute from . | ||
result = testdir.runpytest("-v", "-s") | ||
result.assert_outcomes(passed=2) | ||
|
||
# Execute from . with one argument "root" | ||
result = testdir.runpytest("-v", "-s", "root") | ||
result.assert_outcomes(passed=2) | ||
|
||
# Chdir into package's root and execute with no args | ||
root.chdir() | ||
result = testdir.runpytest("-v", "-s") | ||
result.assert_outcomes(passed=2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this does not really seem necessary,
main.pytest_ignore_collect
already seems to do the exactly same thing anyway.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initially I copy/pasted the Package class from Session because they both serve very similar functions, and then realized not everything was needed.