-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Add discover_imports
in conf, don't collect imported classes named Test* closes #12749`
#12810
Conversation
- Allows a user to define whether or not pytest should treat imported (but not in testpaths) as test classes. - Imagine a class is called TestFoo defined in src/ dir, when discover_imports is disabled, TestFoo is not treated as a test class.
for more information, see https://pre-commit.ci
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.
Thanks for getting this started
We have to do some considerations for backwards compatibility and type safety
Also I'm not yet sure if the selection approach is generally intuitive (it solving your usecase nicely
src/_pytest/main.py
Outdated
parser.addini( | ||
"discover_imports", | ||
"Whether to discover tests in imported modules outside `testpaths`", | ||
default=False, |
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.
We need to start with a default of true for backwards compatibility
Alternatively none with a informative warning
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.
If it helps, I'd be happy to argue that the current behavior was a substantial surprise to me, so that defaulting to False would count as removing a bug.
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.
If it helps, I'd be happy to argue that the current behavior was a substantial surprise to me, so that defaulting to False would count as removing a bug.
I understand where you are coming from and I agree this would be the reasonable default if pytest was being born today... but unfortunately in this case I suspect there are test suites which rely on this behavior, so we really should be conservative here.
src/_pytest/python.py
Outdated
@@ -741,6 +741,12 @@ def newinstance(self): | |||
return self.obj() | |||
|
|||
def collect(self) -> Iterable[nodes.Item | nodes.Collector]: | |||
if self.config.getini("discover_imports") == ("false" or False): |
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.
We should ensure this is always a boolean or parsed somewhere else to ensure Separation
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.
I think changing the default value for the option to False
and changing this to if self.config.getinit("discover_imports"):
should be enough.
@RonnyPfannschmidt Hi! Thanks for reviewing :) To be clear if I would set True to default (as you suggested) this would mean that the base case is that a class like "Testament" in a seperate folder would make pytest error out (see #12749). Then a user would opt in (set to false) to make sure that the discovery does not happen for those classes. Your suggestion seems more reasonable I think. I saw some checks failed, which didn't happen locally for some reason. |
src/_pytest/main.py
Outdated
@@ -78,6 +78,11 @@ def pytest_addoption(parser: Parser) -> None: | |||
type="args", | |||
default=[], | |||
) | |||
parser.addini( | |||
"discover_imports", |
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.
Is the idea that discover_imports
is really only active if 'testpaths' is also defined in pytest.ini
?
changelog/12749.feature.rst
Outdated
@@ -0,0 +1,3 @@ | |||
Add :confval:`discover_imports`, when disabled (default) will make sure to not consider classes which are imported by a test file and starts with Test. |
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.
Nice, changelog entry.
We also need to add documentation for that confval in reference.rst
.
I'm not sure about the name too, but at the moment I don't have another suggestion (however we really should strive to find a more appropriate name).
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.
treat_imports_as_tests?
imports_are_tests?
crawl_imports?
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.
As per my comment, perhaps we should focus on being more "strict" about what we collect from modules.
collect_?something? = "all"/"local-only"
But we should wait to see what other maintainers think.
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.
Alright, now after a some hours (more than id like to admit) I now have something that works. Both for functions and classes. Let me know how we wanna handle the toggle of the feature. Have a good weekend!
I also wonder if we should be using a more general approach, and not only classes: shouldn't we also account for |
Thought about this some more: I think this should actually be expanded to the general idea/feature of "pytest will only collect objects from modules if they are defined in that module" (by inspecting its |
Another thing that occurred to me: @pytest-dev/core how do you feel about this feature, should we go ahead with it or are there other concerns to discuss? |
I'm completely unsurprised that classes or functions imported into a test file are collected, but I'm also obviously atypical of pytest users, and so having a setting for this could make sense. If we do, I think the setting should be named
Agree with all of this, Finally, I'm concerned that this opens up another avenue for the worst kind of testing bug, where you think you have tests but they can't actually fail (because they're not collected!) and so the whole exercise is silently useless. I'm unsure whether the benefits are worth this risk, given that there are plenty of other workarounds based on our existing features - including collection patterns, plugins, etc. |
I'm not sure about this hiding bugs etc (the behavior seems somewhat more sane for those not used to how pytest works I think), but of course I might be wrong. However I do agree with the general sentiment of "yet another flag" that this brings. In this case however seems reasonable to me (but not that strongly TBH), because if I were designing pytest from the ground up today, without backward compatibility concerns, I would vote that it should behave this way (only collecting classes/functions defined in their own module). |
Yeah, as the report of this issue, I found it wildly surprising, and spent a lot of time trying to find a fix. I'd rather have there be a clear option for this, as that's what I first looked for. But failing that, "plenty of other workarounds" was part of the problem for me, as I had to try a lot of stuff and couldn't find one that let me fix exactly the issue once in a clear and clean way. I am fine with me having to fix it once, but didn't want it to be something every engineer had to learn and remember every time they created a new domain class (or function) with test in the name. So if something like this doesn't make it in, then I'd at least suggest a clear, official workaround recommendation the docs where people can solve it in one spot permanently for their project. |
There seems to be some disagreement about the issue, either way I pushed the new implementation. Maybe it helps in the discussion :) |
src/_pytest/main.py
Outdated
self.trace("genitems", node) | ||
if isinstance(node, nodes.Item): | ||
node.ihook.pytest_itemcollected(item=node) | ||
if self.config.getini("collect_imported_tests"): |
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.
The logic for the variable is inverted: if collect_imported_tests
is True, we are actually excluding imported tests instead of collecting them.
So the default for collect_imported_tests
should be True for backward compatibility as discussed previously, and the logic here should be inverted:
if self.config.getini("collect_imported_tests"): | |
if not self.config.getini("collect_imported_tests"): |
src/_pytest/main.py
Outdated
yield node | ||
else: | ||
assert isinstance(node, nodes.Collector) | ||
keepduplicates = self.config.getoption("keepduplicates") | ||
# For backward compat, dedup only applies to files. | ||
handle_dupes = not (keepduplicates and isinstance(node, nodes.File)) | ||
rep, duplicate = self._collect_one_node(node, handle_dupes) | ||
|
||
if self.config.getini("collect_imported_tests"): |
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.
My other comment applies here as well.
changelog/12749.feature.rst
Outdated
@@ -0,0 +1,3 @@ | |||
Add :confval:`collect_imported_tests`, when enabled (default is disabled) will make sure to not consider classes/functions which are imported by a test file and contains Test/test_*/*_test. |
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.
As per my comment, this description needs an update:
Add :confval:`collect_imported_tests`, when enabled (default is disabled) will make sure to not consider classes/functions which are imported by a test file and contains Test/test_*/*_test. | |
New :confval:`collect_imported_tests`: when enabled (the default) pytest will collect classes/functions in test modules even if they are imported from another file. | |
Setting this to True will make pytest collect classes/functions from test files only if they are defined in that file (as opposed to imported there). |
Also, we should describe this option in https://github.com/pytest-dev/pytest/blob/main/doc/en/reference/reference.rst in a new .. conf-val::
block.
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.
Thanks @Bjwebb,
Unfortunately we are not quite there yet.
However feel free to hold on to updating the PR in case other maintainers want to comment further before we move this along.
- pytest_collectreport hook currently gets more reports (TODO) - `pytest_collection_modifyitems` and `pytest_itemcollected` is correct.
We should not collect classes at the module level when the config is set. Doing so is not only the correct thing, as we will not collect the class, but also lets us avoid having the same logic at the Class collector (as it will not be collected at all now).
Thanks @FreerGit for the patience and sorry for the delay! I changed your original implementation a bit: we only need to prevent the collection of classes/functions at the module level, because at that point they will not even appear as a Also improved the docs a bit providing an example for easier understanding. Thanks again! @RonnyPfannschmidt could you review it too? Thanks. |
Pinging @Zac-HD in case he wants to review too. 👍 |
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.
Looks great - thanks everyone!
@Zac-HD please remember to squash next time. 😢 Also I would have liked for @RonnyPfannschmidt's review before merging. |
Gah, sorry! I was rushing out and should have left it for when I had more time 😓 |
No worries, it happens. 👍 |
Great to see this PR getting merged! Thanks for all the help and appreciate the cleanup @nicoddemus , admittedly the PR required more understanding of the internals than I first thought... Anyhow, great to see it move along :) |
closes #12749