-
-
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
Open
FreerGit
wants to merge
8
commits into
pytest-dev:main
Choose a base branch
from
FreerGit:dont-auto-discover-feat
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 6 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
222457d
Add `discover_imports` in conf
FreerGit fa3b631
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 68ac4a1
`collect_imported_tests` option
FreerGit a6ee0bc
merge: rename from discover to collect
FreerGit eb8592c
update default and add docs
FreerGit 935c06d
WIP: don't collect instead of filtering out
FreerGit 191456e
WIP: modified items, reports and items collected
FreerGit f1821ea
WIP - report collection needs triage
FreerGit File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
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 False will make pytest collect classes/functions from test files only if they are defined in that file (as opposed to imported there). | ||
|
||
-- by :user:`FreerGit` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
from __future__ import annotations | ||
|
||
import textwrap | ||
|
||
from _pytest.pytester import Pytester | ||
|
||
|
||
# Start of tests for classes | ||
|
||
|
||
def run_import_class_test(pytester: Pytester, passed: int = 0, errors: int = 0) -> None: | ||
src_dir = pytester.mkdir("src") | ||
tests_dir = pytester.mkdir("tests") | ||
src_file = src_dir / "foo.py" | ||
|
||
src_file.write_text( | ||
textwrap.dedent("""\ | ||
class Testament(object): | ||
def __init__(self): | ||
super().__init__() | ||
self.collections = ["stamp", "coin"] | ||
|
||
def personal_property(self): | ||
return [f"my {x} collection" for x in self.collections] | ||
"""), | ||
encoding="utf-8", | ||
) | ||
|
||
test_file = tests_dir / "foo_test.py" | ||
test_file.write_text( | ||
textwrap.dedent("""\ | ||
import sys | ||
import os | ||
|
||
current_file = os.path.abspath(__file__) | ||
current_dir = os.path.dirname(current_file) | ||
parent_dir = os.path.abspath(os.path.join(current_dir, '..')) | ||
sys.path.append(parent_dir) | ||
|
||
from src.foo import Testament | ||
|
||
class TestDomain: | ||
def test_testament(self): | ||
testament = Testament() | ||
assert testament.personal_property() | ||
"""), | ||
encoding="utf-8", | ||
) | ||
|
||
result = pytester.runpytest() | ||
result.assert_outcomes(passed=passed, errors=errors) | ||
|
||
|
||
def test_collect_imports_disabled(pytester: Pytester) -> None: | ||
pytester.makeini(""" | ||
[pytest] | ||
testpaths = "tests" | ||
collect_imported_tests = false | ||
""") | ||
|
||
run_import_class_test(pytester, passed=1) | ||
|
||
# Verify that the state of hooks | ||
reprec = pytester.inline_run() | ||
items_collected = reprec.getcalls("pytest_itemcollected") | ||
assert len(items_collected) == 1 | ||
for x in items_collected: | ||
assert x.item._getobj().__name__ == "test_testament" | ||
|
||
|
||
def test_collect_imports_default(pytester: Pytester) -> None: | ||
run_import_class_test(pytester, errors=1) | ||
|
||
# TODO, hooks | ||
|
||
|
||
def test_collect_imports_enabled(pytester: Pytester) -> None: | ||
pytester.makeini(""" | ||
[pytest] | ||
collect_imported_tests = true | ||
""") | ||
|
||
run_import_class_test(pytester, errors=1) | ||
|
||
|
||
# # TODO, hooks | ||
|
||
|
||
# End of tests for classes | ||
################################# | ||
# Start of tests for functions | ||
|
||
|
||
def run_import_functions_test( | ||
pytester: Pytester, passed: int, errors: int, failed: int | ||
) -> None: | ||
src_dir = pytester.mkdir("src") | ||
tests_dir = pytester.mkdir("tests") | ||
|
||
src_file = src_dir / "foo.py" | ||
|
||
# Note that these "tests" should _not_ be treated as tests if `collect_imported_tests = false` | ||
# They are normal functions in that case, that happens to have test_* or *_test in the name. | ||
# Thus should _not_ be collected! | ||
src_file.write_text( | ||
textwrap.dedent("""\ | ||
def test_function(): | ||
some_random_computation = 5 | ||
return some_random_computation | ||
|
||
def test_bar(): | ||
pass | ||
"""), | ||
encoding="utf-8", | ||
) | ||
|
||
test_file = tests_dir / "foo_test.py" | ||
|
||
# Inferred from the comment above, this means that there is _only_ one actual test | ||
# which should result in only 1 passing test being ran. | ||
test_file.write_text( | ||
textwrap.dedent("""\ | ||
import sys | ||
import os | ||
|
||
current_file = os.path.abspath(__file__) | ||
current_dir = os.path.dirname(current_file) | ||
parent_dir = os.path.abspath(os.path.join(current_dir, '..')) | ||
sys.path.append(parent_dir) | ||
|
||
from src.foo import * | ||
|
||
class TestDomain: | ||
def test_important(self): | ||
res = test_function() | ||
if res == 5: | ||
pass | ||
"""), | ||
encoding="utf-8", | ||
) | ||
|
||
result = pytester.runpytest() | ||
result.assert_outcomes(passed=passed, errors=errors, failed=failed) | ||
|
||
|
||
def test_collect_function_imports_enabled(pytester: Pytester) -> None: | ||
pytester.makeini(""" | ||
[pytest] | ||
testpaths = "tests" | ||
collect_imported_tests = true | ||
""") | ||
|
||
run_import_functions_test(pytester, passed=2, errors=0, failed=1) | ||
reprec = pytester.inline_run() | ||
items_collected = reprec.getcalls("pytest_itemcollected") | ||
# Recall that the default is `collect_imported_tests = true`. | ||
# Which means that the normal functions are now interpreted as | ||
# valid tests and `test_function()` will fail. | ||
assert len(items_collected) == 3 | ||
for x in items_collected: | ||
assert x.item._getobj().__name__ in [ | ||
"test_important", | ||
"test_bar", | ||
"test_function", | ||
] | ||
|
||
|
||
def test_behaviour_without_testpaths_set_and_false(pytester: Pytester) -> None: | ||
# Make sure `collect_imported_tests` has no dependence on `testpaths` | ||
pytester.makeini(""" | ||
[pytest] | ||
collect_imported_tests = false | ||
""") | ||
|
||
run_import_functions_test(pytester, passed=1, errors=0, failed=0) | ||
reprec = pytester.inline_run() | ||
items_collected = reprec.getcalls("pytest_itemcollected") | ||
assert len(items_collected) == 1 | ||
for x in items_collected: | ||
assert x.item._getobj().__name__ == "test_important" | ||
|
||
|
||
def test_behaviour_without_testpaths_set_and_true(pytester: Pytester) -> None: | ||
# Make sure `collect_imported_tests` has no dependence on `testpaths` | ||
pytester.makeini(""" | ||
[pytest] | ||
collect_imported_tests = true | ||
""") | ||
|
||
run_import_functions_test(pytester, passed=2, errors=0, failed=1) | ||
reprec = pytester.inline_run() | ||
items_collected = reprec.getcalls("pytest_itemcollected") | ||
assert len(items_collected) == 3 | ||
|
||
|
||
def test_hook_behaviour_when_collect_off(pytester: Pytester) -> None: | ||
pytester.makeini(""" | ||
[pytest] | ||
collect_imported_tests = false | ||
""") | ||
|
||
run_import_functions_test(pytester, passed=1, errors=0, failed=0) | ||
reprec = pytester.inline_run() | ||
|
||
# reports = reprec.getreports("pytest_collectreport") | ||
items_collected = reprec.getcalls("pytest_itemcollected") | ||
modified = reprec.getcalls("pytest_collection_modifyitems") | ||
|
||
# print("Reports: ----------------") | ||
# print(reports) | ||
# for r in reports: | ||
# print(r) | ||
|
||
# TODO this is want I want, I think.... | ||
# <CollectReport '' lenresult=1 outcome='passed'> | ||
# <CollectReport 'tests/foo_test.py::TestDomain' lenresult=1 outcome='passed'> | ||
# <CollectReport 'tests/foo_test.py' lenresult=1 outcome='passed'> | ||
# <CollectReport 'tests' lenresult=1 outcome='passed'> | ||
# <CollectReport '.' lenresult=1 outcome='passed'> | ||
|
||
# TODO | ||
# assert(reports.outcome == "passed") | ||
# assert(len(reports.result) == 1) | ||
|
||
# print("Items collected: ----------------") | ||
# print(items_collected) | ||
# print("Modified : ----------------") | ||
|
||
assert len(items_collected) == 1 | ||
for x in items_collected: | ||
assert x.item._getobj().__name__ == "test_important" | ||
|
||
assert len(modified) == 1 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Im not completely sure what is the correct behaviour yet, I'll try and figure it out. WIP and all.