Skip to content

Check node location attributes in unittests #5383

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

Merged
merged 12 commits into from
Dec 13, 2021
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
3 changes: 3 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ Release date: TBA

Closes #5371

* The ``testutils`` for unittests now accept ``end_lineno`` and ``end_column``. Tests
without these will trigger a ``DeprecationWarning``.

..
Insert your changelog randomly, it will reduce merge conflicts
(Ie. not necessarily at the end)
Expand Down
3 changes: 3 additions & 0 deletions doc/whatsnew/2.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,6 @@ Other Changes

* The ``PyLinter`` class will now be initialiazed with a ``TextReporter``
as its reporter if none is provided.

* The ``testutils`` for unittests now accept ``end_lineno`` and ``end_column``. Tests
without these will trigger a ``DeprecationWarning``.
35 changes: 32 additions & 3 deletions pylint/testutils/checker_test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE

import contextlib
from typing import Dict, Optional, Type
import warnings
from typing import Dict, Generator, Optional, Type

from pylint.constants import PY38_PLUS
from pylint.testutils.global_test_linter import linter
from pylint.testutils.output_line import MessageTest
from pylint.testutils.unittest_linter import UnittestLinter
from pylint.utils import ASTWalker

Expand All @@ -29,7 +32,7 @@ def assertNoMessages(self):
yield

@contextlib.contextmanager
def assertAddsMessages(self, *messages):
def assertAddsMessages(self, *messages: MessageTest) -> Generator[None, None, None]:
"""Assert that exactly the given method adds the given messages.

The list of messages must exactly match *all* the messages added by the
Expand All @@ -45,7 +48,33 @@ def assertAddsMessages(self, *messages):
"Expected messages did not match actual.\n"
f"\nExpected:\n{expected}\n\nGot:\n{got_str}\n"
)
assert got == list(messages), msg

assert len(messages) == len(got), msg

for expected_msg, gotten_msg in zip(messages, got):
assert expected_msg.msg_id == gotten_msg.msg_id, msg
assert expected_msg.line == gotten_msg.line, msg
assert expected_msg.node == gotten_msg.node, msg
assert expected_msg.args == gotten_msg.args, msg
assert expected_msg.confidence == gotten_msg.confidence, msg
assert expected_msg.col_offset == gotten_msg.col_offset, msg
if PY38_PLUS:
# pylint: disable=fixme
# TODO: Require end_line and end_col_offset and remove the warning
if not expected_msg.end_line == gotten_msg.end_line:
warnings.warn(
f"The end_line attribute of {gotten_msg} does not match "
f"the expected value in {expected_msg}. In pylint 3.0 correct end_line "
"attributes will be required for MessageTest.",
DeprecationWarning,
)
if not expected_msg.end_col_offset == gotten_msg.end_col_offset:
warnings.warn(
f"The end_col_offset attribute of {gotten_msg} does not match "
f"the expected value in {expected_msg}. In pylint 3.0 correct end_col_offset "
"attributes will be required for MessageTest.",
DeprecationWarning,
)

def walk(self, node):
"""recursive walk on the given node"""
Expand Down
2 changes: 2 additions & 0 deletions pylint/testutils/output_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class MessageTest(NamedTuple):
args: Optional[Any] = None
confidence: Optional[Confidence] = UNDEFINED
col_offset: Optional[int] = None
end_line: Optional[int] = None
end_col_offset: Optional[int] = None
"""Used to test messages produced by pylint. Class name cannot start with Test as pytest doesn't allow constructors in test classes."""


Expand Down
30 changes: 22 additions & 8 deletions pylint/testutils/unittest_linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ def add_message(
self,
msg_id: str,
line: Optional[int] = None,
# pylint: disable=fixme
# TODO: Make node non optional
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out this is actually because node is optional in pylint itself 😅
I guess we can add that to our mental todo list for 3.0

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeap, that's probably some work too :)

node: Optional[nodes.NodeNG] = None,
args: Any = None,
confidence: Optional[Confidence] = None,
Expand All @@ -41,16 +43,28 @@ def add_message(
# If confidence is None we set it to UNDEFINED as well in PyLinter
if confidence is None:
confidence = UNDEFINED
if not line and node:
line = node.fromlineno
# pylint: disable=fixme
# TODO: Test col_offset
# pylint: disable=fixme
# TODO: Initialize col_offset on every node (can be None) -> astroid
# if col_offset is None and hasattr(node, "col_offset"):
# col_offset = node.col_offset
# pylint: disable=fixme
# TODO: Test end_lineno and end_col_offset :)
# TODO: Initialize col_offset, end_lineno and end_col_offset on every node -> astroid
# See https://github.com/PyCQA/astroid/issues/1273
if col_offset is None and node and hasattr(node, "col_offset"):
col_offset = node.col_offset
if not end_lineno and node and hasattr(node, "end_lineno"):
end_lineno = node.end_lineno
if not end_col_offset and node and hasattr(node, "end_col_offset"):
end_col_offset = node.end_col_offset
self._messages.append(
MessageTest(msg_id, line, node, args, confidence, col_offset)
MessageTest(
msg_id,
line,
node,
args,
confidence,
col_offset,
end_lineno,
end_col_offset,
)
)

@staticmethod
Expand Down
20 changes: 20 additions & 0 deletions tests/checkers/unittest_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ class CLASSC(object): #@
"the `UP` group in the '(?:(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$' pattern",
),
confidence=HIGH,
line=2,
col_offset=0,
end_line=3,
end_col_offset=8,
)
with self.assertAddsMessages(message):
cls = None
Expand Down Expand Up @@ -94,6 +98,10 @@ class CLASSC(object): #@
"'(?:(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$' pattern",
),
confidence=HIGH,
line=2,
col_offset=0,
end_line=3,
end_col_offset=8,
),
MessageTest(
"invalid-name",
Expand All @@ -104,6 +112,10 @@ class CLASSC(object): #@
"the `down` group in the '(?:(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$' pattern",
),
confidence=HIGH,
line=6,
col_offset=0,
end_line=7,
end_col_offset=8,
),
]
with self.assertAddsMessages(*messages):
Expand Down Expand Up @@ -139,6 +151,10 @@ def FUNC(): #@
"the `down` group in the '(?:(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$' pattern",
),
confidence=HIGH,
line=6,
col_offset=0,
end_line=7,
end_col_offset=8,
)
with self.assertAddsMessages(message):
func = None
Expand Down Expand Up @@ -172,6 +188,10 @@ def UPPER(): #@
"the `down` group in the '(?:(?P<ignore>FOO)|(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$' pattern",
),
confidence=HIGH,
line=8,
col_offset=0,
end_line=9,
end_col_offset=8,
)
with self.assertAddsMessages(message):
func = None
Expand Down
Loading