Skip to content

Commit b9ed3e3

Browse files
committed
[flake8-pytest-style] Allow for loops with empty bodies (PT012, PT031) (#16678)
## Summary This PR stabilizes the behavior change introduced in #15542 to allow for statements with an empty body in `pytest.raises` and `pytest.warns` with statements. This raised an error before but is now allowed: ```py with pytest.raises(KeyError, match='unknown'): async for _ in gpt.generate(gpt_request): pass ``` The same applies to ```py with pytest.raises(KeyError, match='unknown'): async for _ in gpt.generate(gpt_request): ... ``` There have been now new issues or PRs related to PT012 or PT031 since this behavior change was introduced in ruff 0.9.3 (January 23rd).
1 parent e740286 commit b9ed3e3

7 files changed

+11
-392
lines changed

crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ mod tests {
1111
use test_case::test_case;
1212

1313
use crate::registry::Rule;
14-
use crate::settings::types::{IdentifierPattern, PreviewMode};
14+
use crate::settings::types::IdentifierPattern;
1515
use crate::test::test_path;
1616
use crate::{assert_messages, settings};
1717

@@ -358,36 +358,6 @@ mod tests {
358358
Ok(())
359359
}
360360

361-
#[test_case(
362-
Rule::PytestRaisesWithMultipleStatements,
363-
Path::new("PT012.py"),
364-
Settings::default(),
365-
"PT012_preview"
366-
)]
367-
#[test_case(
368-
Rule::PytestWarnsWithMultipleStatements,
369-
Path::new("PT031.py"),
370-
Settings::default(),
371-
"PT031_preview"
372-
)]
373-
fn test_pytest_style_preview(
374-
rule_code: Rule,
375-
path: &Path,
376-
plugin_settings: Settings,
377-
name: &str,
378-
) -> Result<()> {
379-
let diagnostics = test_path(
380-
Path::new("flake8_pytest_style").join(path).as_path(),
381-
&settings::LinterSettings {
382-
preview: PreviewMode::Enabled,
383-
flake8_pytest_style: plugin_settings,
384-
..settings::LinterSettings::for_rule(rule_code)
385-
},
386-
)?;
387-
assert_messages!(name, diagnostics);
388-
Ok(())
389-
}
390-
391361
/// This test ensure that PT006 and PT007 don't conflict when both of them suggest a fix that
392362
/// edits `argvalues` for `pytest.mark.parametrize`.
393363
#[test]

crates/ruff_linter/src/rules/flake8_pytest_style/rules/raises.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ use super::helpers::is_empty_or_null_string;
1313
/// ## What it does
1414
/// Checks for `pytest.raises` context managers with multiple statements.
1515
///
16+
/// This rule allows `pytest.raises` bodies to contain `for`
17+
/// loops with empty bodies (e.g., `pass` or `...` statements), to test
18+
/// iterator behavior.
19+
///
1620
/// ## Why is this bad?
1721
/// When a `pytest.raises` is used as a context manager and contains multiple
1822
/// statements, it can lead to the test passing when it actually should fail.
1923
///
2024
/// A `pytest.raises` context manager should only contain a single simple
2125
/// statement that raises the expected exception.
2226
///
23-
/// In [preview], this rule allows `pytest.raises` bodies to contain `for`
24-
/// loops with empty bodies (e.g., `pass` or `...` statements), to test
25-
/// iterator behavior.
26-
///
2727
/// ## Example
2828
/// ```python
2929
/// import pytest
@@ -50,8 +50,6 @@ use super::helpers::is_empty_or_null_string;
5050
///
5151
/// ## References
5252
/// - [`pytest` documentation: `pytest.raises`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-raises)
53-
///
54-
/// [preview]: https://docs.astral.sh/ruff/preview/
5553
#[derive(ViolationMetadata)]
5654
pub(crate) struct PytestRaisesWithMultipleStatements;
5755

@@ -206,14 +204,12 @@ pub(crate) fn complex_raises(checker: &Checker, stmt: &Stmt, items: &[WithItem],
206204
// Check body for `pytest.raises` context manager
207205
if raises_called {
208206
let is_too_complex = if let [stmt] = body {
209-
let in_preview = checker.settings.preview.is_enabled();
210-
211207
match stmt {
212208
Stmt::With(ast::StmtWith { body, .. }) => is_non_trivial_with_body(body),
213209
// Allow function and class definitions to test decorators.
214210
Stmt::ClassDef(_) | Stmt::FunctionDef(_) => false,
215211
// Allow empty `for` loops to test iterators.
216-
Stmt::For(ast::StmtFor { body, .. }) if in_preview => match &body[..] {
212+
Stmt::For(ast::StmtFor { body, .. }) => match &body[..] {
217213
[Stmt::Pass(_)] => false,
218214
[Stmt::Expr(ast::StmtExpr { value, .. })] => !value.is_ellipsis_literal_expr(),
219215
_ => true,

crates/ruff_linter/src/rules/flake8_pytest_style/rules/warns.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,17 @@ use super::helpers::is_empty_or_null_string;
1313
/// ## What it does
1414
/// Checks for `pytest.warns` context managers with multiple statements.
1515
///
16+
/// This rule allows `pytest.warns` bodies to contain `for`
17+
/// loops with empty bodies (e.g., `pass` or `...` statements), to test
18+
/// iterator behavior.
19+
///
1620
/// ## Why is this bad?
1721
/// When `pytest.warns` is used as a context manager and contains multiple
1822
/// statements, it can lead to the test passing when it should instead fail.
1923
///
2024
/// A `pytest.warns` context manager should only contain a single
2125
/// simple statement that triggers the expected warning.
2226
///
23-
/// In [preview], this rule allows `pytest.warns` bodies to contain `for`
24-
/// loops with empty bodies (e.g., `pass` or `...` statements), to test
25-
/// iterator behavior.
2627
///
2728
/// ## Example
2829
/// ```python
@@ -48,8 +49,6 @@ use super::helpers::is_empty_or_null_string;
4849
///
4950
/// ## References
5051
/// - [`pytest` documentation: `pytest.warns`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-warns)
51-
///
52-
/// [preview]: https://docs.astral.sh/ruff/preview/
5352
#[derive(ViolationMetadata)]
5453
pub(crate) struct PytestWarnsWithMultipleStatements;
5554

@@ -206,14 +205,12 @@ pub(crate) fn complex_warns(checker: &Checker, stmt: &Stmt, items: &[WithItem],
206205
// Check body for `pytest.warns` context manager
207206
if warns_called {
208207
let is_too_complex = if let [stmt] = body {
209-
let in_preview = checker.settings.preview.is_enabled();
210-
211208
match stmt {
212209
Stmt::With(ast::StmtWith { body, .. }) => is_non_trivial_with_body(body),
213210
// Allow function and class definitions to test decorators.
214211
Stmt::ClassDef(_) | Stmt::FunctionDef(_) => false,
215212
// Allow empty `for` loops to test iterators.
216-
Stmt::For(ast::StmtFor { body, .. }) if in_preview => match &body[..] {
213+
Stmt::For(ast::StmtFor { body, .. }) => match &body[..] {
217214
[Stmt::Pass(_)] => false,
218215
[Stmt::Expr(ast::StmtExpr { value, .. })] => !value.is_ellipsis_literal_expr(),
219216
_ => true,

crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT012.snap

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -124,49 +124,3 @@ PT012.py:95:5: PT012 `pytest.raises()` block should contain a single simple stat
124124
97 | | assert foo
125125
| |______________________^ PT012
126126
|
127-
128-
PT012.py:102:5: PT012 `pytest.raises()` block should contain a single simple statement
129-
|
130-
100 | ## No errors in preview
131-
101 |
132-
102 | / with pytest.raises(RuntimeError):
133-
103 | | for a in b:
134-
104 | | pass
135-
| |________________^ PT012
136-
105 |
137-
106 | with pytest.raises(RuntimeError):
138-
|
139-
140-
PT012.py:106:5: PT012 `pytest.raises()` block should contain a single simple statement
141-
|
142-
104 | pass
143-
105 |
144-
106 | / with pytest.raises(RuntimeError):
145-
107 | | for a in b:
146-
108 | | ...
147-
| |_______________^ PT012
148-
109 |
149-
110 | with pytest.raises(RuntimeError):
150-
|
151-
152-
PT012.py:110:5: PT012 `pytest.raises()` block should contain a single simple statement
153-
|
154-
108 | ...
155-
109 |
156-
110 | / with pytest.raises(RuntimeError):
157-
111 | | async for a in b:
158-
112 | | pass
159-
| |________________^ PT012
160-
113 |
161-
114 | with pytest.raises(RuntimeError):
162-
|
163-
164-
PT012.py:114:5: PT012 `pytest.raises()` block should contain a single simple statement
165-
|
166-
112 | pass
167-
113 |
168-
114 | / with pytest.raises(RuntimeError):
169-
115 | | async for a in b:
170-
116 | | ...
171-
| |_______________^ PT012
172-
|

crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT012_preview.snap

Lines changed: 0 additions & 126 deletions
This file was deleted.

crates/ruff_linter/src/rules/flake8_pytest_style/snapshots/ruff_linter__rules__flake8_pytest_style__tests__PT031.snap

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -124,49 +124,3 @@ PT031.py:95:5: PT031 `pytest.warns()` block should contain a single simple state
124124
97 | | assert foo
125125
| |______________________^ PT031
126126
|
127-
128-
PT031.py:102:5: PT031 `pytest.warns()` block should contain a single simple statement
129-
|
130-
100 | ## No errors in preview
131-
101 |
132-
102 | / with pytest.warns(RuntimeError):
133-
103 | | for a in b:
134-
104 | | pass
135-
| |________________^ PT031
136-
105 |
137-
106 | with pytest.warns(RuntimeError):
138-
|
139-
140-
PT031.py:106:5: PT031 `pytest.warns()` block should contain a single simple statement
141-
|
142-
104 | pass
143-
105 |
144-
106 | / with pytest.warns(RuntimeError):
145-
107 | | for a in b:
146-
108 | | ...
147-
| |_______________^ PT031
148-
109 |
149-
110 | with pytest.warns(RuntimeError):
150-
|
151-
152-
PT031.py:110:5: PT031 `pytest.warns()` block should contain a single simple statement
153-
|
154-
108 | ...
155-
109 |
156-
110 | / with pytest.warns(RuntimeError):
157-
111 | | async for a in b:
158-
112 | | pass
159-
| |________________^ PT031
160-
113 |
161-
114 | with pytest.warns(RuntimeError):
162-
|
163-
164-
PT031.py:114:5: PT031 `pytest.warns()` block should contain a single simple statement
165-
|
166-
112 | pass
167-
113 |
168-
114 | / with pytest.warns(RuntimeError):
169-
115 | | async for a in b:
170-
116 | | ...
171-
| |_______________^ PT031
172-
|

0 commit comments

Comments
 (0)