Skip to content

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/13403.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Disable assertion for modules outside current working dir(cwd)
1 change: 1 addition & 0 deletions changelog/13492.doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed outdated warning about ``faulthandler`` not working on Windows.
1 change: 1 addition & 0 deletions src/_pytest/assertion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ class AssertionState:
def __init__(self, config: Config, mode) -> None:
self.mode = mode
self.trace = config.trace.root.get("assertion")
self.invocation_path = str(config.invocation_params.dir)
self.hook: rewrite.AssertionRewritingHook | None = None


Expand Down
4 changes: 3 additions & 1 deletion src/_pytest/assertion/rewrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,9 @@ def _should_rewrite(self, name: str, fn: str, state: AssertionState) -> bool:
# rewritten if they match the naming convention for test files
fn_path = PurePath(fn)
for pat in self.fnpats:
if fnmatch_ex(pat, fn_path):
if fnmatch_ex(pat, fn_path) and fn_path.is_relative_to(
state.invocation_path
):
state.trace(f"matched test file {fn!r}")
return True

Expand Down
4 changes: 4 additions & 0 deletions src/_pytest/pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

from _pytest import timing
from _pytest._code import Source
from _pytest.assertion.rewrite import assertstate_key
from _pytest.capture import _get_multicapture
from _pytest.compat import NOTSET
from _pytest.compat import NotSetType
Expand Down Expand Up @@ -751,6 +752,9 @@ def chdir(self) -> None:
This is done automatically upon instantiation.
"""
self._monkeypatch.chdir(self.path)
self._monkeypatch.setattr(
self._request.config.stash[assertstate_key], "invocation_path", self.path
)

def _makefile(
self,
Expand Down
2 changes: 2 additions & 0 deletions testing/example_scripts/rewrite/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
python_files = *.py
11 changes: 11 additions & 0 deletions testing/example_scripts/rewrite/some_plugin/__init__.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
Empty file.
9 changes: 9 additions & 0 deletions testing/example_scripts/rewrite/src/main.py
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
Empty file.
16 changes: 16 additions & 0 deletions testing/example_scripts/rewrite/tests/conftest.py
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
13 changes: 13 additions & 0 deletions testing/example_scripts/rewrite/tests/test_main.py
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)
59 changes: 59 additions & 0 deletions testing/test_assertrewrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@
from _pytest.assertion.rewrite import _get_maxsize_for_saferepr
from _pytest.assertion.rewrite import _saferepr
from _pytest.assertion.rewrite import AssertionRewritingHook
from _pytest.assertion.rewrite import assertstate_key
from _pytest.assertion.rewrite import get_cache_dir
from _pytest.assertion.rewrite import PYC_TAIL
from _pytest.assertion.rewrite import PYTEST_TAG
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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -1294,6 +1297,36 @@ def test_meta_path():
)
assert pytester.runpytest().ret == 0

def test_invocation_dir(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
"""Test get invocation param from AssertionState"""
from _pytest.assertion import AssertionState

config = pytester.parseconfig()
state = AssertionState(config, "rewrite")

assert state.invocation_path == str(config.invocation_params.dir)

new_rootpath = pytester.path / "test"
if not os.path.exists(new_rootpath):
os.mkdir(new_rootpath)
monkeypatch.setattr(
config,
"invocation_params",
Config.InvocationParams(
args=(),
plugins=(),
dir=new_rootpath,
),
)
state = AssertionState(config, "rewrite")
assert state.invocation_path == str(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"
)
def test_write_pyc(self, pytester: Pytester, tmp_path) -> None:
from _pytest.assertion import AssertionState
from _pytest.assertion.rewrite import _write_pyc
Expand Down Expand Up @@ -1971,6 +2004,32 @@ def test_simple_failure():
assert hook.find_spec("file") is not None
assert self.find_spec_calls == ["file"]

def test_assert_rewrites_only_invocation_path(
self, pytester: Pytester, hook: AssertionRewritingHook, monkeypatch
) -> None:
"""Do not rewrite assertions in tests outside `AssertState.rootpath` (#13403)."""
pytester.makepyfile(
**{
"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

invocation_path = f"{os.getcwd()}/tests"
monkeypatch.setattr(
pytester._request.config.stash[assertstate_key],
"invocation_path",
invocation_path,
)

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"
)
Expand Down
Loading