Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Mock objects
# ============
# Errors
assert my_mock.not_called()
assert my_mock.called_once_with()
Expand All @@ -17,3 +19,25 @@
my_mock.assert_called_once_with()
"""like :meth:`Mock.assert_called_once_with`"""
"""like :meth:`MagicMock.assert_called_once_with`"""

# AsyncMock objects
# =================
# Errors
assert my_mock.not_awaited()
assert my_mock.awaited_once_with()
assert my_mock.not_awaited
assert my_mock.awaited_once_with
my_mock.assert_not_awaited
my_mock.assert_awaited
my_mock.assert_awaited_once_with
my_mock.assert_awaited_once_with
MyMock.assert_awaited_once_with
assert my_mock.awaited

# OK
assert my_mock.await_count == 1
my_mock.assert_not_awaited()
my_mock.assert_awaited()
my_mock.assert_awaited_once_with()
"""like :meth:`Mock.assert_awaited_once_with`"""
"""like :meth:`MagicMock.assert_awaited_once_with`"""
5 changes: 5 additions & 0 deletions crates/ruff_linter/src/preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,8 @@ pub(crate) const fn is_ignore_init_files_in_useless_alias_enabled(
) -> bool {
settings.preview.is_enabled()
}

// https://github.com/astral-sh/ruff/pull/18547
pub(crate) const fn is_invalid_async_mock_access_check_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
19 changes: 19 additions & 0 deletions crates/ruff_linter/src/rules/pygrep_hooks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod tests {

use crate::registry::Rule;

use crate::settings::types::PreviewMode;
use crate::test::test_path;
use crate::{assert_diagnostics, settings};

Expand All @@ -29,4 +30,22 @@ mod tests {
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}

#[test_case(Rule::InvalidMockAccess, Path::new("PGH005_0.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("pygrep_hooks").join(path).as_path(),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
..settings::LinterSettings::for_rule(rule_code)
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use ruff_text_size::Ranged;

use crate::Violation;
use crate::checkers::ast::Checker;
use crate::preview::is_invalid_async_mock_access_check_enabled;

#[derive(Debug, PartialEq, Eq)]
enum Reason {
Expand Down Expand Up @@ -51,7 +52,7 @@ impl Violation for InvalidMockAccess {
/// PGH005
pub(crate) fn uncalled_mock_method(checker: &Checker, expr: &Expr) {
if let Expr::Attribute(ast::ExprAttribute { attr, .. }) = expr {
if matches!(
let is_uncalled_mock_method = matches!(
attr.as_str(),
"assert_any_call"
| "assert_called"
Expand All @@ -60,7 +61,20 @@ pub(crate) fn uncalled_mock_method(checker: &Checker, expr: &Expr) {
| "assert_called_with"
| "assert_has_calls"
| "assert_not_called"
) {
);
let is_uncalled_async_mock_method =
is_invalid_async_mock_access_check_enabled(checker.settings())
&& matches!(
attr.as_str(),
"assert_awaited"
| "assert_awaited_once"
| "assert_awaited_with"
| "assert_awaited_once_with"
| "assert_any_await"
| "assert_has_awaits"
| "assert_not_awaited"
);
if is_uncalled_mock_method || is_uncalled_async_mock_method {
checker.report_diagnostic(
InvalidMockAccess {
reason: Reason::UncalledMethod(attr.to_string()),
Expand All @@ -81,15 +95,28 @@ pub(crate) fn non_existent_mock_method(checker: &Checker, test: &Expr) {
},
_ => return,
};
if matches!(
let is_missing_mock_method = matches!(
attr.as_str(),
"any_call"
| "called_once"
| "called_once_with"
| "called_with"
| "has_calls"
| "not_called"
) {
);
let is_missing_async_mock_method =
is_invalid_async_mock_access_check_enabled(checker.settings())
&& matches!(
attr.as_str(),
"awaited"
| "awaited_once"
| "awaited_with"
| "awaited_once_with"
| "any_await"
| "has_awaits"
| "not_awaited"
);
if is_missing_mock_method || is_missing_async_mock_method {
checker.report_diagnostic(
InvalidMockAccess {
reason: Reason::NonExistentMethod(attr.to_string()),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,90 +1,91 @@
---
source: crates/ruff_linter/src/rules/pygrep_hooks/mod.rs
---
PGH005_0.py:2:8: PGH005 Non-existent mock method: `not_called`
PGH005_0.py:4:8: PGH005 Non-existent mock method: `not_called`
|
1 | # Errors
2 | assert my_mock.not_called()
2 | # ============
3 | # Errors
4 | assert my_mock.not_called()
| ^^^^^^^^^^^^^^^^^^^^ PGH005
3 | assert my_mock.called_once_with()
4 | assert my_mock.not_called
5 | assert my_mock.called_once_with()
6 | assert my_mock.not_called
|

PGH005_0.py:3:8: PGH005 Non-existent mock method: `called_once_with`
PGH005_0.py:5:8: PGH005 Non-existent mock method: `called_once_with`
|
1 | # Errors
2 | assert my_mock.not_called()
3 | assert my_mock.called_once_with()
3 | # Errors
4 | assert my_mock.not_called()
5 | assert my_mock.called_once_with()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
4 | assert my_mock.not_called
5 | assert my_mock.called_once_with
6 | assert my_mock.not_called
7 | assert my_mock.called_once_with
|

PGH005_0.py:4:8: PGH005 Non-existent mock method: `not_called`
PGH005_0.py:6:8: PGH005 Non-existent mock method: `not_called`
|
2 | assert my_mock.not_called()
3 | assert my_mock.called_once_with()
4 | assert my_mock.not_called
4 | assert my_mock.not_called()
5 | assert my_mock.called_once_with()
6 | assert my_mock.not_called
| ^^^^^^^^^^^^^^^^^^ PGH005
5 | assert my_mock.called_once_with
6 | my_mock.assert_not_called
7 | assert my_mock.called_once_with
8 | my_mock.assert_not_called
|

PGH005_0.py:5:8: PGH005 Non-existent mock method: `called_once_with`
PGH005_0.py:7:8: PGH005 Non-existent mock method: `called_once_with`
|
3 | assert my_mock.called_once_with()
4 | assert my_mock.not_called
5 | assert my_mock.called_once_with
5 | assert my_mock.called_once_with()
6 | assert my_mock.not_called
7 | assert my_mock.called_once_with
| ^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
6 | my_mock.assert_not_called
7 | my_mock.assert_called
8 | my_mock.assert_not_called
9 | my_mock.assert_called
|

PGH005_0.py:6:1: PGH005 Mock method should be called: `assert_not_called`
|
4 | assert my_mock.not_called
5 | assert my_mock.called_once_with
6 | my_mock.assert_not_called
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
7 | my_mock.assert_called
8 | my_mock.assert_called_once_with
|
PGH005_0.py:8:1: PGH005 Mock method should be called: `assert_not_called`
|
6 | assert my_mock.not_called
7 | assert my_mock.called_once_with
8 | my_mock.assert_not_called
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
9 | my_mock.assert_called
10 | my_mock.assert_called_once_with
|

PGH005_0.py:7:1: PGH005 Mock method should be called: `assert_called`
|
5 | assert my_mock.called_once_with
6 | my_mock.assert_not_called
7 | my_mock.assert_called
| ^^^^^^^^^^^^^^^^^^^^^ PGH005
8 | my_mock.assert_called_once_with
9 | my_mock.assert_called_once_with
|
PGH005_0.py:9:1: PGH005 Mock method should be called: `assert_called`
|
7 | assert my_mock.called_once_with
8 | my_mock.assert_not_called
9 | my_mock.assert_called
| ^^^^^^^^^^^^^^^^^^^^^ PGH005
10 | my_mock.assert_called_once_with
11 | my_mock.assert_called_once_with
|

PGH005_0.py:8:1: PGH005 Mock method should be called: `assert_called_once_with`
PGH005_0.py:10:1: PGH005 Mock method should be called: `assert_called_once_with`
|
6 | my_mock.assert_not_called
7 | my_mock.assert_called
8 | my_mock.assert_called_once_with
8 | my_mock.assert_not_called
9 | my_mock.assert_called
10 | my_mock.assert_called_once_with
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
9 | my_mock.assert_called_once_with
10 | MyMock.assert_called_once_with
11 | my_mock.assert_called_once_with
12 | MyMock.assert_called_once_with
|

PGH005_0.py:9:1: PGH005 Mock method should be called: `assert_called_once_with`
PGH005_0.py:11:1: PGH005 Mock method should be called: `assert_called_once_with`
|
7 | my_mock.assert_called
8 | my_mock.assert_called_once_with
9 | my_mock.assert_called_once_with
9 | my_mock.assert_called
10 | my_mock.assert_called_once_with
11 | my_mock.assert_called_once_with
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
10 | MyMock.assert_called_once_with
12 | MyMock.assert_called_once_with
|

PGH005_0.py:10:1: PGH005 Mock method should be called: `assert_called_once_with`
PGH005_0.py:12:1: PGH005 Mock method should be called: `assert_called_once_with`
|
8 | my_mock.assert_called_once_with
9 | my_mock.assert_called_once_with
10 | MyMock.assert_called_once_with
10 | my_mock.assert_called_once_with
11 | my_mock.assert_called_once_with
12 | MyMock.assert_called_once_with
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PGH005
11 |
12 | # OK
13 |
14 | # OK
|
Loading
Loading