-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Disable assertion rewriting external modules #13421
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
base: main
Are you sure you want to change the base?
Changes from all commits
231cb63
bdc096c
0701fcb
0d0551e
3ec4da8
f03f24e
b6db388
1346500
023dfdb
1fee11d
ba41963
253497e
67e7a69
ee836ad
e7a98bb
c947f9a
abd69ac
fd59d87
3da821d
48cfbc3
eceae2e
8c95bcd
1d5a612
c1f3772
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 @@ | ||
Disable assertion for modules outside current working dir(cwd) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -110,8 +110,14 @@ class AssertionState: | |
def __init__(self, config: Config, mode) -> None: | ||
self.mode = mode | ||
self.trace = config.trace.root.get("assertion") | ||
self.config = config | ||
self.hook: rewrite.AssertionRewritingHook | None = None | ||
|
||
@property | ||
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. this must mot use getwd instead use the invoction params 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. then we cannot change it on runtime as far as invocation param for pytester changes rootpath 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. sorry for the type - this code shoud use the rootdir or the invocationdir from the invocation params of the config 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. My error, I mean as far as Invocation Param is frozen - it can't be changed on runtime. Pytester starts after the config has been loaded. So to change the rootpath for pytester I need to rewrite the rootpath in someway. I could mock all invocation params or I could use getcwd alongside with config rootdir for the testing purpose. 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 complete object can be replaced with a changed one 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. ok. would be done) 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. But is I feel we should use 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. indeed - the rootpath - else we miss imports to rewrite |
||
def invocation_path(self): | ||
"""Get current root path (current working dir)""" | ||
return str(self.config.invocation_params.dir) | ||
|
||
|
||
def install_importhook(config: Config) -> rewrite.AssertionRewritingHook: | ||
"""Try to install the rewrite hook, raise SystemError if it fails.""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
from collections.abc import Iterable | ||
from collections.abc import Sequence | ||
import contextlib | ||
import dataclasses | ||
from fnmatch import fnmatch | ||
import gc | ||
import importlib | ||
|
@@ -749,6 +750,13 @@ def chdir(self) -> None: | |
This is done automatically upon instantiation. | ||
""" | ||
self._monkeypatch.chdir(self.path) | ||
self._monkeypatch.setattr( | ||
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. do i gather correct that this bit is to alter the assertion rewtiter state we should alter only that state, not the full pytest config of the surrounding pytest - this looks like a assertion rewriter shortcoming that needs a followup after this but we certainly need to alter the assertion hook in sys metapath instead of a pytest config |
||
self._request.config, | ||
"invocation_params", | ||
dataclasses.replace( | ||
self._request.config.invocation_params, dir=Path(self._path) | ||
), | ||
) | ||
|
||
def _makefile( | ||
self, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[pytest] | ||
python_files = *.py |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from __future__ import annotations | ||
|
||
import pytest | ||
|
||
|
||
@pytest.fixture | ||
def special_asserter(): | ||
def special_assert(x, y): | ||
assert {"x": x} == {"x": y} | ||
|
||
return special_assert |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
"""Stub file for testing""" | ||
|
||
from __future__ import annotations | ||
|
||
|
||
def func(x: int, y: int) -> int: | ||
"""Stub function""" | ||
assert (x) > 0 | ||
return 0 if x == y else 1 if x > y else -1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from __future__ import annotations | ||
|
||
from _pytest.fixtures import fixture | ||
|
||
|
||
pytest_plugins = ["pytester", "some_plugin"] | ||
|
||
|
||
@fixture | ||
def b(): | ||
return 1 | ||
|
||
|
||
@fixture | ||
def a(): | ||
return 2 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from __future__ import annotations | ||
|
||
from typing import Callable | ||
|
||
from testing.example_scripts.rewrite.src.main import func | ||
|
||
|
||
def test_plugin(a: int, b: int, special_asserter: Callable[[int, int], bool]): | ||
special_asserter(a, b) | ||
|
||
|
||
def test_func(a: int, b: int, special_asserter: Callable[[int, int], bool]): | ||
assert {"res": func(a, b)} == {"res": 0} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
def some_check(a: int): | ||
assert abs(a)<2 | ||
return a in set(0, 1, -1) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
import inspect | ||
import marshal | ||
import os | ||
from os import mkdir | ||
from pathlib import Path | ||
import py_compile | ||
import re | ||
|
@@ -35,6 +36,7 @@ | |
from _pytest.assertion.rewrite import rewrite_asserts | ||
from _pytest.config import Config | ||
from _pytest.config import ExitCode | ||
from _pytest.monkeypatch import MonkeyPatch | ||
from _pytest.pathlib import make_numbered_dir | ||
from _pytest.pytester import Pytester | ||
import pytest | ||
|
@@ -370,6 +372,7 @@ def test_rewrites_plugin_as_a_package(self, pytester: Pytester) -> None: | |
pytester.makeconftest('pytest_plugins = ["plugin"]') | ||
pytester.makepyfile("def test(special_asserter): special_asserter(1, 2)\n") | ||
result = pytester.runpytest() | ||
|
||
result.stdout.fnmatch_lines(["*assert 1 == 2*"]) | ||
|
||
def test_honors_pep_235(self, pytester: Pytester, monkeypatch) -> None: | ||
|
@@ -1294,6 +1297,34 @@ def test_meta_path(): | |
) | ||
assert pytester.runpytest().ret == 0 | ||
|
||
def test_invocation_dir(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> None: | ||
"""Test get invocation param afrom AssertionState""" | ||
from _pytest.assertion import AssertionState | ||
|
||
config = pytester.parseconfig() | ||
state = AssertionState(config, "rewrite") | ||
assert state.invocation_path == str(config.invocation_params.dir) | ||
new_rootpath = str(pytester.path / "test") | ||
if not os.path.exists(new_rootpath): | ||
os.mkdir(new_rootpath) | ||
monkeypatch.setattr( | ||
config, | ||
"invocation_params", | ||
Config.InvocationParams( | ||
args=(), | ||
plugins=(), | ||
dir=Path(new_rootpath), | ||
), | ||
) | ||
state = AssertionState(config, "rewrite") | ||
assert state.invocation_path == new_rootpath | ||
|
||
@pytest.mark.skipif( | ||
sys.platform.startswith("win32"), reason="cannot remove cwd on Windows" | ||
) | ||
@pytest.mark.skipif( | ||
sys.platform.startswith("sunos5"), reason="cannot remove cwd on Solaris" | ||
) | ||
Comment on lines
+1322
to
+1327
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. Are these 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. Is sunos still a supported python platform As far as I'm aware sun is down |
||
def test_write_pyc(self, pytester: Pytester, tmp_path) -> None: | ||
from _pytest.assertion import AssertionState | ||
from _pytest.assertion.rewrite import _write_pyc | ||
|
@@ -1971,6 +2002,36 @@ def test_simple_failure(): | |
assert hook.find_spec("file") is not None | ||
assert self.find_spec_calls == ["file"] | ||
|
||
def test_assert_rewrites_only_rootpath( | ||
self, pytester: Pytester, hook: AssertionRewritingHook, monkeypatch | ||
) -> None: | ||
"""Do not rewrite assertions in tests outside `AssertState.rootpath` (#13403).""" | ||
pytester.makepyfile( | ||
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. I would prefer to avoid mocking here, instead using a real scenario to ensure Python files outside of the rootpath do not have their assertions rewritten. Please create a simple but real-world project using
Then execute If I understand the objective of the issue correctly:
I think the scenario above should cover what needs to be tested. 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. We need to include an external plugin here too, to test the same scenario as in 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. I've tried to create the structure along with a some conftest plugin. Even witout changes it doen't rewrite conftest plugins assertions Example: However it sees only top-level plugins: Does it an expected behavior? How does pytest provide plugins which are needed to be rewritten at the import stage? May be I do something wrong |
||
**{ | ||
"file.py": """\ | ||
def test_simple_failure(): | ||
assert 1 + 1 == 3 | ||
""" | ||
} | ||
) | ||
with mock.patch.object(hook, "fnpats", ["*.py"]): | ||
assert hook.find_spec("file") is not None | ||
|
||
rootpath = f"{os.getcwd()}/tests" | ||
if not os.path.exists(rootpath): | ||
mkdir(rootpath) | ||
monkeypatch.setattr( | ||
pytester._request.config, | ||
"invocation_params", | ||
Config.InvocationParams( | ||
args=(), | ||
plugins=(), | ||
dir=Path(rootpath), | ||
), | ||
) | ||
with mock.patch.object(hook, "fnpats", ["*.py"]): | ||
assert hook.find_spec("file") is None | ||
|
||
@pytest.mark.skipif( | ||
sys.platform.startswith("win32"), reason="cannot remove cwd on Windows" | ||
) | ||
|
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.
Let's perhaps just pass in the invocation details
Id like to avoid full config in more objects