-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add assertDoesNotAddMessages to CheckerTestCase
#10930
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
base: main
Are you sure you want to change the base?
Changes from 6 commits
03cd23c
3299943
8a426ba
4c98679
faedc02
0f15cc2
5bbba3f
8a3aaec
9f088f4
12d10c4
7d08354
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| Add ``assertDoesNotAddMessages`` to ``CheckerTestCase`` to assert that | ||
| specific messages are not emitted, while allowing other messages to be | ||
| present. This complements ``assertNoMessages`` which asserts that no | ||
| messages at all are emitted. | ||
|
|
||
| Refs #9598 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,63 @@ | |
| with self.assertAddsMessages(): | ||
| yield | ||
|
|
||
| @contextlib.contextmanager | ||
| def assertDoesNotAddMessages( | ||
| self, *messages: MessageTest, ignore_position: bool = False | ||
| ) -> Generator[None]: | ||
| """Assert that the given messages are not added by the given method. | ||
|
|
||
| This is different from ``assertNoMessages`` which asserts that no | ||
| messages at all are added. ``assertDoesNotAddMessages`` checks that | ||
| none of the *specific* messages passed as arguments are emitted, while | ||
| other messages may still be present. | ||
| """ | ||
| if not messages: | ||
| raise TypeError( | ||
| "assertDoesNotAddMessages requires at least one MessageTest argument" | ||
| ) | ||
| try: | ||
| yield | ||
| except Exception: | ||
| self.linter.release_messages() | ||
| raise | ||
| else: | ||
| got = self.linter.release_messages() | ||
| for unwanted in messages: | ||
| for gotten_msg in got: | ||
| if not self._messages_match(unwanted, gotten_msg, ignore_position): | ||
| continue | ||
| got_str = "\n".join(repr(m) for m in got) | ||
| msg = ( | ||
| "Expected the following message to not be raised:\n" | ||
| f"\n {unwanted!r}\n\n" | ||
| f"but it was found among the actual messages:\n\n{got_str}\n" | ||
| ) | ||
| raise AssertionError(msg) | ||
|
|
||
| @staticmethod | ||
| def _messages_match( | ||
| expected: MessageTest, actual: MessageTest, ignore_position: bool | ||
| ) -> bool: | ||
| if expected.msg_id != actual.msg_id: | ||
| return False | ||
| if expected.node != actual.node: | ||
| return False | ||
| if expected.args != actual.args: | ||
| return False | ||
| if expected.confidence != actual.confidence: | ||
| return False | ||
| if not ignore_position: | ||
| if expected.line != actual.line: | ||
| return False | ||
| if expected.col_offset != actual.col_offset: | ||
| return False | ||
| if expected.end_line != actual.end_line: | ||
| return False | ||
| if expected.end_col_offset != actual.end_col_offset: | ||
| return False | ||
| return True | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we only care about the msgid ?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right. The method now accepts plain message ID strings instead of full |
||
|
|
||
| @contextlib.contextmanager | ||
| def assertAddsMessages( | ||
| self, *messages: MessageTest, ignore_position: bool = False | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html | ||
| # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE | ||
| # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt | ||
|
|
||
| """Tests for CheckerTestCase assertion methods.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import pytest | ||
|
|
||
| from pylint.checkers.base_checker import BaseChecker | ||
| from pylint.interfaces import UNDEFINED | ||
| from pylint.testutils import CheckerTestCase, MessageTest | ||
|
|
||
|
|
||
| class _DummyChecker(BaseChecker): | ||
| """A minimal checker used only for testing the test infrastructure.""" | ||
|
|
||
| name = "dummy-for-testcase" | ||
| msgs = { | ||
| "W9901": ("Dummy message A", "dummy-msg-a", "Dummy message A."), | ||
| "W9902": ("Dummy message B", "dummy-msg-b", "Dummy message B."), | ||
| } | ||
|
|
||
|
|
||
| _MSG_A = MessageTest("W9901", line=1, node=None, args=None, confidence=UNDEFINED) | ||
| _MSG_B = MessageTest("W9902", line=2, node=None, args=None, confidence=UNDEFINED) | ||
|
|
||
|
|
||
| class TestCheckerTestCase(CheckerTestCase): | ||
| CHECKER_CLASS = _DummyChecker | ||
|
|
||
| # -- assertAddsMessages scenarios (1-3) ----------------------------------- | ||
|
Pierre-Sassoulas marked this conversation as resolved.
Outdated
|
||
|
|
||
| def test_assert_adds_messages_success(self) -> None: | ||
| """Scenario 1: expected raised / actual raised.""" | ||
| with self.assertAddsMessages(_MSG_A): | ||
| self.linter.add_message("W9901", line=1) | ||
|
|
||
| def test_assert_adds_messages_failure_not_raised(self) -> None: | ||
| """Scenario 2: expected raised / actual not raised.""" | ||
| with pytest.raises(AssertionError): | ||
|
Pierre-Sassoulas marked this conversation as resolved.
Outdated
|
||
| with self.assertAddsMessages(_MSG_A): | ||
| pass # nothing emitted | ||
|
|
||
| def test_assert_adds_messages_failure_wrong_message(self) -> None: | ||
| """Scenario 3: expected raised / actual not raised but another one raised.""" | ||
| with pytest.raises(AssertionError): | ||
| with self.assertAddsMessages(_MSG_A): | ||
| self.linter.add_message("W9902", line=2) | ||
|
|
||
| # -- assertDoesNotAddMessages scenarios (4-6) ----------------------------- | ||
|
Pierre-Sassoulas marked this conversation as resolved.
Outdated
|
||
|
|
||
| def test_assert_does_not_add_messages_failure(self) -> None: | ||
| """Scenario 4: expected not raised / actual raised.""" | ||
| with pytest.raises(AssertionError): | ||
|
Pierre-Sassoulas marked this conversation as resolved.
Outdated
|
||
| with self.assertDoesNotAddMessages(_MSG_A): | ||
| self.linter.add_message("W9901", line=1) | ||
|
|
||
| def test_assert_does_not_add_messages_success(self) -> None: | ||
| """Scenario 5: expected not raised / actual not raised.""" | ||
| with self.assertDoesNotAddMessages(_MSG_A): | ||
| pass # nothing emitted | ||
|
|
||
| def test_assert_does_not_add_messages_success_other_raised(self) -> None: | ||
| """Scenario 6: expected not raised / actual not raised but another one raised.""" | ||
| with self.assertDoesNotAddMessages(_MSG_A): | ||
| self.linter.add_message("W9902", line=2) | ||
|
|
||
| # -- additional edge cases ------------------------------------------------ | ||
|
Pierre-Sassoulas marked this conversation as resolved.
Outdated
|
||
|
|
||
| def test_assert_does_not_add_messages_ignore_position(self) -> None: | ||
| """Position mismatch means no match when ignore_position=False.""" | ||
| # Same msg_id but different line: should pass (not a match) | ||
| msg_different_line = MessageTest( | ||
| "W9901", line=99, node=None, args=None, confidence=UNDEFINED | ||
| ) | ||
| with self.assertDoesNotAddMessages(msg_different_line): | ||
| self.linter.add_message("W9901", line=1) | ||
|
|
||
| def test_assert_does_not_add_messages_ignore_position_true(self) -> None: | ||
| """With ignore_position=True, position differences are ignored.""" | ||
| msg_different_line = MessageTest( | ||
| "W9901", line=99, node=None, args=None, confidence=UNDEFINED | ||
| ) | ||
| with pytest.raises(AssertionError): | ||
| with self.assertDoesNotAddMessages( | ||
| msg_different_line, ignore_position=True | ||
| ): | ||
| self.linter.add_message("W9901", line=1) | ||
|
|
||
| def test_assert_does_not_add_messages_multiple_unwanted(self) -> None: | ||
| """Fails when any of several unwanted messages is found.""" | ||
| with pytest.raises(AssertionError): | ||
| with self.assertDoesNotAddMessages(_MSG_A, _MSG_B): | ||
| self.linter.add_message("W9902", line=2) | ||
|
|
||
| def test_assert_does_not_add_messages_no_args_raises(self) -> None: | ||
| """Calling with no arguments must raise TypeError.""" | ||
| with pytest.raises(TypeError, match="requires at least one"): | ||
| with self.assertDoesNotAddMessages(): | ||
| pass | ||
|
|
||
| def test_assert_does_not_add_messages_exception_in_body_drains_messages( | ||
| self, | ||
| ) -> None: | ||
| """An exception in the with-block must not leak messages to later tests.""" | ||
| with pytest.raises(RuntimeError): | ||
| with self.assertDoesNotAddMessages(_MSG_A): | ||
| self.linter.add_message("W9901", line=1) | ||
| raise RuntimeError("something went wrong") | ||
| # Messages must have been drained; a subsequent assertNoMessages should pass. | ||
| with self.assertNoMessages(): | ||
| pass | ||
|
|
||
| # -- _messages_match branch coverage -------------------------------------- | ||
|
|
||
| def test_messages_match_node_mismatch(self) -> None: | ||
| expected = MessageTest("W9901", line=1, node="sentinel", args=None) | ||
| actual = MessageTest("W9901", line=1, node=None, args=None) | ||
|
ShehabSherif0 marked this conversation as resolved.
Outdated
|
||
| assert not self._messages_match(expected, actual, ignore_position=False) | ||
|
|
||
| def test_messages_match_args_mismatch(self) -> None: | ||
| expected = MessageTest("W9901", line=1, node=None, args=("x",)) | ||
| actual = MessageTest("W9901", line=1, node=None, args=None) | ||
| assert not self._messages_match(expected, actual, ignore_position=False) | ||
|
|
||
| def test_messages_match_confidence_mismatch(self) -> None: | ||
| from pylint.interfaces import HIGH | ||
|
|
||
| expected = MessageTest("W9901", line=1, node=None, args=None, confidence=HIGH) | ||
| actual = MessageTest( | ||
| "W9901", line=1, node=None, args=None, confidence=UNDEFINED | ||
| ) | ||
| assert not self._messages_match(expected, actual, ignore_position=False) | ||
|
|
||
| def test_messages_match_col_offset_mismatch(self) -> None: | ||
| expected = MessageTest("W9901", line=1, node=None, args=None, col_offset=5) | ||
| actual = MessageTest("W9901", line=1, node=None, args=None, col_offset=10) | ||
| assert not self._messages_match(expected, actual, ignore_position=False) | ||
|
|
||
| def test_messages_match_end_line_mismatch(self) -> None: | ||
| expected = MessageTest("W9901", line=1, node=None, args=None, end_line=5) | ||
| actual = MessageTest("W9901", line=1, node=None, args=None, end_line=10) | ||
| assert not self._messages_match(expected, actual, ignore_position=False) | ||
|
|
||
| def test_messages_match_end_col_offset_mismatch(self) -> None: | ||
| expected = MessageTest("W9901", line=1, node=None, args=None, end_col_offset=5) | ||
| actual = MessageTest("W9901", line=1, node=None, args=None, end_col_offset=10) | ||
| assert not self._messages_match(expected, actual, ignore_position=False) | ||
Uh oh!
There was an error while loading. Please reload this page.