mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-01 20:30:49 +00:00
[ruff] Check for non-context-manager use of pytest.raises, pytest.warns, and pytest.deprecated_call (RUF061) (#17368)
<!-- Thank you for contributing to Ruff! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? - Does this pull request include references to any relevant issues? --> This PR aims to close #16605. ## Summary This PR introduces a new rule (`RUF061`) that detects non-contextmanager usage of `pytest.raises`, `pytest.warns`, and `pytest.deprecated_call`. This pattern is discouraged and [was proposed in flake8-pytest-style](https://github.com/m-burst/flake8-pytest-style/pull/332), but the corresponding PR has been open for over a month without activity. Additionally, this PR provides an unsafe fix for simple cases where the non-contextmanager form can be transformed into the context manager form. Examples of supported patterns are listed in `RUF061_raises.py`, `RUF061_warns.py`, and `RUF061_deprecated_call.py` test files. The more complex case from the original issue (involving two separate statements): ```python excinfo = pytest.raises(ValueError, int, "hello") assert excinfo.match("^invalid literal") ``` is getting fixed like this: ```python with pytest.raises(ValueError) as excinfo: int("hello") assert excinfo.match("^invalid literal") ``` Putting match in the raises call requires multi-statement transformation, which I am not sure how to implement. ## Test Plan <!-- How was it tested? --> New test files were added to cover various usages of the non-contextmanager form of pytest.raises, warns, and deprecated_call.
This commit is contained in:
parent
c5b58187da
commit
c3aa965546
12 changed files with 635 additions and 0 deletions
25
crates/ruff_linter/resources/test/fixtures/ruff/RUF061_deprecated_call.py
vendored
Normal file
25
crates/ruff_linter/resources/test/fixtures/ruff/RUF061_deprecated_call.py
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import warnings
|
||||
import pytest
|
||||
|
||||
|
||||
def raise_deprecation_warning(s):
|
||||
warnings.warn(s, DeprecationWarning)
|
||||
return s
|
||||
|
||||
|
||||
def test_ok():
|
||||
with pytest.deprecated_call():
|
||||
raise_deprecation_warning("")
|
||||
|
||||
|
||||
def test_error_trivial():
|
||||
pytest.deprecated_call(raise_deprecation_warning, "deprecated")
|
||||
|
||||
|
||||
def test_error_assign():
|
||||
s = pytest.deprecated_call(raise_deprecation_warning, "deprecated")
|
||||
print(s)
|
||||
|
||||
|
||||
def test_error_lambda():
|
||||
pytest.deprecated_call(lambda: warnings.warn("", DeprecationWarning))
|
||||
40
crates/ruff_linter/resources/test/fixtures/ruff/RUF061_raises.py
vendored
Normal file
40
crates/ruff_linter/resources/test/fixtures/ruff/RUF061_raises.py
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import pytest
|
||||
|
||||
|
||||
def func(a, b):
|
||||
return a / b
|
||||
|
||||
|
||||
def test_ok():
|
||||
with pytest.raises(ValueError):
|
||||
raise ValueError
|
||||
|
||||
|
||||
def test_ok_as():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
raise ValueError
|
||||
|
||||
|
||||
def test_error_trivial():
|
||||
pytest.raises(ZeroDivisionError, func, 1, b=0)
|
||||
|
||||
|
||||
def test_error_match():
|
||||
pytest.raises(ZeroDivisionError, func, 1, b=0).match("division by zero")
|
||||
|
||||
|
||||
def test_error_assign():
|
||||
excinfo = pytest.raises(ZeroDivisionError, func, 1, b=0)
|
||||
|
||||
|
||||
def test_error_kwargs():
|
||||
pytest.raises(func=func, expected_exception=ZeroDivisionError)
|
||||
|
||||
|
||||
def test_error_multi_statement():
|
||||
excinfo = pytest.raises(ValueError, int, "hello")
|
||||
assert excinfo.match("^invalid literal")
|
||||
|
||||
|
||||
def test_error_lambda():
|
||||
pytest.raises(ZeroDivisionError, lambda: 1 / 0)
|
||||
25
crates/ruff_linter/resources/test/fixtures/ruff/RUF061_warns.py
vendored
Normal file
25
crates/ruff_linter/resources/test/fixtures/ruff/RUF061_warns.py
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import warnings
|
||||
import pytest
|
||||
|
||||
|
||||
def raise_user_warning(s):
|
||||
warnings.warn(s, UserWarning)
|
||||
return s
|
||||
|
||||
|
||||
def test_ok():
|
||||
with pytest.warns(UserWarning):
|
||||
raise_user_warning("")
|
||||
|
||||
|
||||
def test_error_trivial():
|
||||
pytest.warns(UserWarning, raise_user_warning, "warning")
|
||||
|
||||
|
||||
def test_error_assign():
|
||||
s = pytest.warns(UserWarning, raise_user_warning, "warning")
|
||||
print(s)
|
||||
|
||||
|
||||
def test_error_lambda():
|
||||
pytest.warns(UserWarning, lambda: warnings.warn("", UserWarning))
|
||||
Loading…
Add table
Add a link
Reference in a new issue