Skip to content
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

python: change Package to no longer be a Module/File #11138

Merged
merged 1 commit into from Jul 28, 2023

Conversation

bluetech
Copy link
Member

Fix #11137.

I'll leave detailed notes on the changes below to hopefully make them easier to understand.

@bluetech bluetech added this to the 8.0 milestone Jun 24, 2023
@@ -228,12 +228,7 @@ def pytest_make_collect_report(self, collector: nodes.Collector):

# Use stable sort to priorize last failed.
def sort_key(node: Union[nodes.Item, nodes.Collector]) -> bool:
# Package.path is the __init__.py file, we need the directory.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplification - previous special case for Package no longer needed

@@ -280,9 +275,7 @@ def __init__(self, lfplugin: "LFPlugin") -> None:
def pytest_make_collect_report(
self, collector: nodes.Collector
) -> Optional[CollectReport]:
# Packages are Files, but we only want to skip test-bearing Files,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplification - previous special case for Package no longer needed

@@ -120,9 +120,8 @@ def get_scope_package(
from _pytest.python import Package

current: Optional[Union[nodes.Item, nodes.Collector]] = node
fixture_package_name = "{}/{}".format(fixturedef.baseid, "__init__.py")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer need special code to adjust to the fact the package's path is the __init__.py.

This change will be relevant for fixing #10993 and other similar bugs.

@@ -571,6 +572,17 @@ def _recurse(self, direntry: "os.DirEntry[str]") -> bool:
return False
return True

def _collectpackage(self, fspath: Path) -> Optional["Package"]:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Package is now no longer collected as a Module, so need to do it manually.

This is temporary, will be entirely revamped using the planned pytest_collect_directory hook (see #7777).


for argpath, names in self._initial_parts:
self.trace("processing argument", (argpath, names))
self.trace.root.indent += 1

# Start with a Session root, and delve to argpath item (dir or file)
# and stack all Packages found on the way.
# No point in finding packages when collecting doctests.
if not self.config.getoption("doctestmodules", False):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't been able to understand this optimization, and it hinders the implementation, so I removed it. It will become irrelevant in upcoming changes anyway.

@@ -521,11 +518,62 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]:
)


def importtestmodule(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor: moving this code from a method to a standalone function because we need to reuse it. No logic changes.

@@ -681,22 +682,24 @@ def __init__(
session=session,
nodeid=nodeid,
)
self.name = self.path.parent.name
self.name = self.path.name
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path is now the directory, not the __init__.py. The name is the name of the directory, so no need for the parent anymore.


def setup(self) -> None:
init_mod = importtestmodule(self.path / "__init__.py", self.config)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was previously handled by Module importing its module file (the __init__.py file), now we need to do it ad-hoc.

As discussed in the issue, I think we should consider deprecating setup_module/teardown_module in __init__.py files for Packages, it's pretty obscure, but for now I retained full compat.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes me wonder if we want to consider deprecating xunit style setup methods alltogether as they are practically better served by fixtures

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it will be too disruptive at this point, but maybe with nose gone we can start considering this as well. Overall though I think it's not worth deprecating, I know a lot of people use them.

This particular case though (setup_module xunit fixtures in the __init__.py file being package scoped) is more dubious than the others, I'd be surprised to learn that anyone knows about it at all. So we might want to consider deprecating it specifically.

# Always collect the __init__ first.
if self.session.isinitpath(self.path) or path_matches_patterns(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since __init__.py is no longer treated special by pytest_pycollect_makemodule, can just use _collectfile for it.

@@ -733,21 +736,16 @@ def _collectfile(
return ihook.pytest_collect_file(file_path=fspath, parent=self) # type: ignore[no-any-return]

def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
this_path = self.path.parent
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.path is now this path (the directory)

@bluetech
Copy link
Member Author

  • Rebased to fix conflicts
  • Added the change in get_parametrized_fixture_keys, which I had missed before

Copy link
Member

@nicoddemus nicoddemus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, thanks @bluetech!

@bluetech
Copy link
Member Author

Thanks a lot for the review @nicoddemus!

I'll try to get the coverage passing (in separate PRs) then merge.

Copy link
Member

@RonnyPfannschmidt RonnyPfannschmidt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i love how much this cleans things up, awesome 👍

@bluetech bluetech merged commit 485c555 into pytest-dev:main Jul 28, 2023
24 of 25 checks passed
@bluetech bluetech deleted the pkg-mod branch July 28, 2023 20:15
@Pierre-Sassoulas
Copy link
Contributor

This commit prevents upgrading pytest above 8.0.0 in pylint (pylint-dev/pylint#9576) . Am I correct in assuming that the order the tests are discovered and launched changed (with the change to sort_key) ? I assume it could have revealed an existing test contamination issue in pylint's test suite. I see a visit call and occurrence of nodes.Collector but pytest does not modify the ast during test collection, right ?

@RonnyPfannschmidt
Copy link
Member

AST modification may occur during collection in case assertion rewriting needs to act

@bluetech
Copy link
Member Author

bluetech commented May 1, 2024

@Pierre-Sassoulas from a quick look it looks like maybe something changed with regards to the AudioTime precedence test: https://github.com/search?q=repo%3Apylint-dev%2Fpylint%20AudioTime&type=code

Are you able to run this test https://github.com/pylint-dev/pylint/blob/82ef6475e665ca8ae1542822a485c98a067e1834/tests/test_regr.py#L59 "manually" (outside of pytest) and see if it passes?

Pierre-Sassoulas added a commit to pylint-dev/pylint that referenced this pull request May 1, 2024
Suggestion from pytest-dev/pytest#11138 (comment)

With 'pytest':
FAILED tests/test_precedence.py::test_package - AssertionError: E: 21: Module 'package.AudioTime' has no 'DECIMAL' member<function Equals.<locals>.<lambda> at 0x76c566741750>

With 'python tests/test_precedence.py':
Checked ['package.__init__'] successfully
Checked ['precedence_test'] successfully
Checked ['import_package_subpackage_module'] successfully
Checked ['pylint.checkers.__init__'] successfully
Checked ['/home/pierre/pylint/tests/regrtest_data/classdoc_usage.py'] successfully
Checked ['/home/pierre/pylint/tests/regrtest_data/module_global.py'] successfully
Checked ['/home/pierre/pylint/tests/regrtest_data/decimal_inference.py'] successfully
Checked ['/home/pierre/pylint/tests/regrtest_data/absimp/string.py'] successfully
Checked ['/home/pierre/pylint/tests/regrtest_data/bad_package'] successfully
Pierre-Sassoulas added a commit to pylint-dev/pylint that referenced this pull request May 1, 2024
Suggestion from pytest-dev/pytest#11138 (comment)

With 'pytest' (launching the whole pylint test suite):
FAILED tests/test_precedence.py::test_package - AssertionError: E: 21: Module 'package.AudioTime' has no 'DECIMAL' member<function Equals.<locals>.<lambda> at 0x76c566741750>

With 'pytest tests/test_precedence.py':
tests/test_precedence.py .                                                                                                                                                                           [100%]

============================================================================================ 1 passed in 1.04s =============================================================================================

With 'python tests/test_precedence.py':
Checked ['package.__init__'] successfully
Checked ['precedence_test'] successfully
Checked ['import_package_subpackage_module'] successfully
Checked ['pylint.checkers.__init__'] successfully
Checked ['/home/pierre/pylint/tests/regrtest_data/classdoc_usage.py'] successfully
Checked ['/home/pierre/pylint/tests/regrtest_data/module_global.py'] successfully
Checked ['/home/pierre/pylint/tests/regrtest_data/decimal_inference.py'] successfully
Checked ['/home/pierre/pylint/tests/regrtest_data/absimp/string.py'] successfully
Checked ['/home/pierre/pylint/tests/regrtest_data/bad_package'] successfully
@Pierre-Sassoulas
Copy link
Contributor

Thank you for the quick answers, very helpful !

AST modification may occur during collection in case assertion rewriting needs to act

Interesting, I wonder if we have some pylint features that do not work without a pytest test collection prior to the analysis 😄

Are you able to run this test https://github.com/pylint-dev/pylint/blob/82ef6475e665ca8ae1542822a485c98a067e1834/tests/test_regr.py#L59 "manually" (outside of pytest) and see if it passes?

Tried that in pylint-dev/pylint@49a7a61, it seems this is indeed not passing due to pytest collection (but only when it collects the whole pylint test suite).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Package should not be a Module/File
4 participants