Support 'reason' argument to pytest.fail (#5040)

## Summary

Per the [API
reference](https://docs.pytest.org/en/7.1.x/reference/reference.html#pytest.fail),
`reason` was added in version 7, and is equivalent to `msg` (but
preferred going forward).

I also grepped for `msg` usages in `flake8_pytest_style`, but found no
others (apart from those that reference `unittest` APIs.)

Closes #3387.
This commit is contained in:
Charlie Marsh 2023-06-12 20:54:07 -04:00 committed by GitHub
parent e2130707f5
commit cbd4c10fdd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 75 additions and 43 deletions

View file

@ -1,17 +1,25 @@
import pytest
def test_xxx():
pytest.fail("this is a failure") # Test OK arg
# OK
def f():
pytest.fail("this is a failure")
def test_xxx():
pytest.fail(msg="this is a failure") # Test OK kwarg
def f():
pytest.fail(msg="this is a failure")
def test_xxx(): # Error
def f():
pytest.fail(reason="this is a failure")
# Errors
def f():
pytest.fail()
pytest.fail("")
pytest.fail(f"")
pytest.fail(msg="")
pytest.fail(msg=f"")
pytest.fail(reason="")
pytest.fail(reason=f"")

View file

@ -21,7 +21,12 @@ impl Violation for PytestFailWithoutMessage {
pub(crate) fn fail_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords: &[Keyword]) {
if is_pytest_fail(checker.semantic_model(), func) {
let call_args = SimpleCallArgs::new(args, keywords);
let msg = call_args.argument("msg", 0);
// Allow either `pytest.fail(reason="...")` (introduced in pytest 7.0) or
// `pytest.fail(msg="...")` (deprecated in pytest 7.0)
let msg = call_args
.argument("reason", 0)
.or_else(|| call_args.argument("msg", 0));
if let Some(msg) = msg {
if is_empty_or_null_string(msg) {

View file

@ -206,9 +206,7 @@ impl UnittestAssert {
keywords: &'a [Keyword],
) -> Result<FxHashMap<&'a str, &'a Expr>> {
// If we have variable-length arguments, abort.
if args.iter().any(|arg| matches!(arg, Expr::Starred(_)))
|| keywords.iter().any(|kw| kw.arg.is_none())
{
if args.iter().any(Expr::is_starred_expr) || keywords.iter().any(|kw| kw.arg.is_none()) {
bail!("Variable-length arguments are not supported");
}
@ -263,14 +261,14 @@ impl UnittestAssert {
.ok_or_else(|| anyhow!("Missing argument `expr`"))?;
let msg = args.get("msg").copied();
Ok(if matches!(self, UnittestAssert::False) {
let node = expr.clone();
let node1 = Expr::UnaryOp(ast::ExprUnaryOp {
op: Unaryop::Not,
operand: Box::new(node),
range: TextRange::default(),
});
let unary_expr = node1;
assert(&unary_expr, msg)
assert(
&Expr::UnaryOp(ast::ExprUnaryOp {
op: Unaryop::Not,
operand: Box::new(expr.clone()),
range: TextRange::default(),
}),
msg,
)
} else {
assert(expr, msg)
})

View file

@ -1,49 +1,70 @@
---
source: crates/ruff/src/rules/flake8_pytest_style/mod.rs
---
PT016.py:13:5: PT016 No message passed to `pytest.fail()`
PT016.py:19:5: PT016 No message passed to `pytest.fail()`
|
12 | def test_xxx(): # Error
13 | pytest.fail()
17 | # Errors
18 | def f():
19 | pytest.fail()
| ^^^^^^^^^^^ PT016
14 | pytest.fail("")
15 | pytest.fail(f"")
20 | pytest.fail("")
21 | pytest.fail(f"")
|
PT016.py:14:5: PT016 No message passed to `pytest.fail()`
PT016.py:20:5: PT016 No message passed to `pytest.fail()`
|
12 | def test_xxx(): # Error
13 | pytest.fail()
14 | pytest.fail("")
18 | def f():
19 | pytest.fail()
20 | pytest.fail("")
| ^^^^^^^^^^^ PT016
15 | pytest.fail(f"")
16 | pytest.fail(msg="")
21 | pytest.fail(f"")
22 | pytest.fail(msg="")
|
PT016.py:15:5: PT016 No message passed to `pytest.fail()`
PT016.py:21:5: PT016 No message passed to `pytest.fail()`
|
13 | pytest.fail()
14 | pytest.fail("")
15 | pytest.fail(f"")
19 | pytest.fail()
20 | pytest.fail("")
21 | pytest.fail(f"")
| ^^^^^^^^^^^ PT016
16 | pytest.fail(msg="")
17 | pytest.fail(msg=f"")
22 | pytest.fail(msg="")
23 | pytest.fail(msg=f"")
|
PT016.py:16:5: PT016 No message passed to `pytest.fail()`
PT016.py:22:5: PT016 No message passed to `pytest.fail()`
|
14 | pytest.fail("")
15 | pytest.fail(f"")
16 | pytest.fail(msg="")
20 | pytest.fail("")
21 | pytest.fail(f"")
22 | pytest.fail(msg="")
| ^^^^^^^^^^^ PT016
17 | pytest.fail(msg=f"")
23 | pytest.fail(msg=f"")
24 | pytest.fail(reason="")
|
PT016.py:17:5: PT016 No message passed to `pytest.fail()`
PT016.py:23:5: PT016 No message passed to `pytest.fail()`
|
15 | pytest.fail(f"")
16 | pytest.fail(msg="")
17 | pytest.fail(msg=f"")
21 | pytest.fail(f"")
22 | pytest.fail(msg="")
23 | pytest.fail(msg=f"")
| ^^^^^^^^^^^ PT016
24 | pytest.fail(reason="")
25 | pytest.fail(reason=f"")
|
PT016.py:24:5: PT016 No message passed to `pytest.fail()`
|
22 | pytest.fail(msg="")
23 | pytest.fail(msg=f"")
24 | pytest.fail(reason="")
| ^^^^^^^^^^^ PT016
25 | pytest.fail(reason=f"")
|
PT016.py:25:5: PT016 No message passed to `pytest.fail()`
|
23 | pytest.fail(msg=f"")
24 | pytest.fail(reason="")
25 | pytest.fail(reason=f"")
| ^^^^^^^^^^^ PT016
|