Skip to content

Commit 3615219

Browse files
committed
Add min_pyver_end_position option
1 parent be149db commit 3615219

File tree

7 files changed

+91
-32
lines changed

7 files changed

+91
-32
lines changed

ChangeLog

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ Release date: TBA
5353
trigger a ``DeprecationWarning``. Expected output files can be easily updated with the
5454
``python tests/test_functional.py --update-functional-output`` command.
5555

56+
* The functional test runner now supports the option ``min_pyver_end_position`` to control on which python
57+
versions the ``end_lineno`` and ``end_column`` attributes should be checked. The default value is 3.8.
58+
5659
* Fix ``accept-no-yields-doc`` and ``accept-no-return-doc`` not allowing missing ``yield`` or
5760
``return`` documentation when a docstring is partially correct
5861

doc/development_guide/testing.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@ You can also use ``# +n: [`` with n an integer if the above syntax would make th
7272

7373
If you need special control over Pylint's configuration, you can also create a .rc file, which
7474
can have sections of Pylint's configuration.
75+
The .rc file can also contain a section ``[testoptions]`` to pass options for the functional
76+
test runner. The following options are currently supported:
77+
78+
"min_pyver": Minimal python version required to run the test
79+
"max_pyver": Maximum python version required to run the test
80+
"min_pyver_end_position": Minimal python version required to check the end_line and end_column attributes of the message
81+
"requires": Packages required to be installed locally to run the test
82+
"except_implementations": List of python implementations on which the test should not run
83+
"exclude_platforms": List of operating systems on which the test should not run
7584

7685
During development, it's sometimes helpful to run all functional tests in your
7786
current environment in order to have faster feedback. Run from Pylint root directory with::

doc/whatsnew/2.12.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ Other Changes
132132
trigger a ``DeprecationWarning``. Expected output files can be easily updated with the
133133
``python tests/test_functional.py --update-functional-output`` command.
134134

135+
* The functional test runner now supports the option ``min_pyver_end_position`` to control on which python
136+
versions the ``end_lineno`` and ``end_column`` attributes should be checked. The default value is 3.8.
137+
135138
* ``undefined-variable`` now correctly flags variables which only receive a type annotations
136139
and never get assigned a value
137140

pylint/testutils/functional_test_file.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class FunctionalTestFile:
1919
_CONVERTERS = {
2020
"min_pyver": parse_python_version,
2121
"max_pyver": parse_python_version,
22+
"min_pyver_end_position": parse_python_version,
2223
"requires": lambda s: s.split(","),
2324
}
2425

@@ -28,6 +29,7 @@ def __init__(self, directory, filename):
2829
self.options = {
2930
"min_pyver": (2, 5),
3031
"max_pyver": (4, 0),
32+
"min_pyver_end_position": (3, 8),
3133
"requires": [],
3234
"except_implementations": [],
3335
"exclude_platforms": [],

pylint/testutils/lint_module_test.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,18 +159,23 @@ def _open_source_file(self) -> TextIO:
159159
return open(self._test_file.source, encoding="latin1")
160160
return open(self._test_file.source, encoding="utf8")
161161

162-
def _get_expected(self) -> Tuple["MessageCounter", List[OutputLine]]:
162+
def _get_expected(
163+
self, check_end_position: bool
164+
) -> Tuple["MessageCounter", List[OutputLine]]:
163165
with self._open_source_file() as f:
164166
expected_msgs = self.get_expected_messages(f)
165167
if not expected_msgs:
166168
expected_msgs = Counter()
167169
with self._open_expected_file() as f:
168170
expected_output_lines = [
169-
OutputLine.from_csv(row) for row in csv.reader(f, "test")
171+
OutputLine.from_csv(row, check_end_position)
172+
for row in csv.reader(f, "test")
170173
]
171174
return expected_msgs, expected_output_lines
172175

173-
def _get_actual(self) -> Tuple["MessageCounter", List[OutputLine]]:
176+
def _get_actual(
177+
self, check_end_position: bool
178+
) -> Tuple["MessageCounter", List[OutputLine]]:
174179
messages: List[Message] = self._linter.reporter.messages
175180
messages.sort(key=lambda m: (m.line, m.symbol, m.msg))
176181
received_msgs: "MessageCounter" = Counter()
@@ -180,15 +185,18 @@ def _get_actual(self) -> Tuple["MessageCounter", List[OutputLine]]:
180185
msg.symbol != "fatal"
181186
), f"Pylint analysis failed because of '{msg.msg}'"
182187
received_msgs[msg.line, msg.symbol] += 1
183-
received_output_lines.append(OutputLine.from_msg(msg))
188+
received_output_lines.append(OutputLine.from_msg(msg, check_end_position))
184189
return received_msgs, received_output_lines
185190

186191
def _runTest(self) -> None:
187192
__tracebackhide__ = True # pylint: disable=unused-variable
188193
modules_to_check = [self._test_file.source]
189194
self._linter.check(modules_to_check)
190-
expected_messages, expected_output = self._get_expected()
191-
actual_messages, actual_output = self._get_actual()
195+
check_end_position = (
196+
sys.version_info >= self._test_file.options["min_pyver_end_position"]
197+
)
198+
expected_messages, expected_output = self._get_expected(check_end_position)
199+
actual_messages, actual_output = self._get_actual(check_end_position)
192200
assert (
193201
expected_messages == actual_messages
194202
), self.error_msg_for_unequal_messages(

pylint/testutils/output_line.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,11 @@ class OutputLine(NamedTuple):
7373
confidence: str
7474

7575
@classmethod
76-
def from_msg(cls, msg: Message) -> "OutputLine":
76+
def from_msg(cls, msg: Message, check_endline: bool) -> "OutputLine":
7777
"""Create an OutputLine from a Pylint Message"""
7878
column = cls._get_column(msg.column)
79-
end_line = cls._get_py38_none_value(msg.end_line)
80-
end_column = cls._get_py38_none_value(msg.end_column)
79+
end_line = cls._get_py38_none_value(msg.end_line, check_endline)
80+
end_column = cls._get_py38_none_value(msg.end_column, check_endline)
8181
return cls(
8282
msg.symbol,
8383
msg.line,
@@ -100,15 +100,17 @@ def _get_column(column: str) -> int:
100100
return int(column)
101101

102102
@staticmethod
103-
def _get_py38_none_value(value: T) -> Optional[T]:
104-
"""Handle attributes that are always None on pylint < 3.8 similar to _get_column."""
105-
if not PY38_PLUS:
106-
# We check the value only for the new better ast parser introduced in python 3.8
103+
def _get_py38_none_value(value: T, check_endline: bool) -> Optional[T]:
104+
"""Used to make end_line and end_column None as indicated by our version compared to
105+
`min_pyver_end_position`."""
106+
if not check_endline:
107107
return None # pragma: no cover
108108
return value
109109

110110
@classmethod
111-
def from_csv(cls, row: Union[Sequence[str], str]) -> "OutputLine":
111+
def from_csv(
112+
cls, row: Union[Sequence[str], str], check_endline: bool
113+
) -> "OutputLine":
112114
"""Create an OutputLine from a comma separated list (the functional tests expected
113115
output .txt files).
114116
"""
@@ -143,8 +145,8 @@ def from_csv(cls, row: Union[Sequence[str], str]) -> "OutputLine":
143145
row[0], int(row[1]), column, None, None, row[3], row[4], row[5]
144146
)
145147
if len(row) == 8:
146-
end_line = cls._get_py38_none_value(row[3])
147-
end_column = cls._get_py38_none_value(row[4])
148+
end_line = cls._get_py38_none_value(row[3], check_endline)
149+
end_column = cls._get_py38_none_value(row[4], check_endline)
148150
return cls(
149151
row[0],
150152
int(row[1]),

tests/testutils/test_output_line.py

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,35 +55,56 @@ def test_output_line() -> None:
5555
def test_output_line_from_message(message: Callable) -> None:
5656
"""Test that the OutputLine NamedTuple is instantiated correctly with from_msg."""
5757
expected_column = 2 if PY38_PLUS else 0
58-
expected_end_lineno = 1 if PY38_PLUS else None
59-
expected_end_column = 3 if PY38_PLUS else None
60-
output_line = OutputLine.from_msg(message())
58+
59+
output_line = OutputLine.from_msg(message(), True)
6160
assert output_line.symbol == "missing-docstring"
6261
assert output_line.lineno == 1
6362
assert output_line.column == expected_column
64-
assert output_line.end_lineno == expected_end_lineno
65-
assert output_line.end_column == expected_end_column
63+
assert output_line.end_lineno == 1
64+
assert output_line.end_column == 3
6665
assert output_line.object == "obj"
6766
assert output_line.msg == "msg"
6867
assert output_line.confidence == "HIGH"
6968

69+
output_line_without_end = OutputLine.from_msg(message(), False)
70+
assert output_line_without_end.symbol == "missing-docstring"
71+
assert output_line_without_end.lineno == 1
72+
assert output_line_without_end.column == expected_column
73+
assert output_line_without_end.end_lineno is None
74+
assert output_line_without_end.end_column is None
75+
assert output_line_without_end.object == "obj"
76+
assert output_line_without_end.msg == "msg"
77+
assert output_line_without_end.confidence == "HIGH"
78+
7079

7180
@pytest.mark.parametrize("confidence", [HIGH, INFERENCE])
7281
def test_output_line_to_csv(confidence: Confidence, message: Callable) -> None:
7382
"""Test that the OutputLine NamedTuple is instantiated correctly with from_msg
7483
and then converted to csv.
7584
"""
76-
output_line = OutputLine.from_msg(message(confidence))
85+
output_line = OutputLine.from_msg(message(confidence), True)
7786
csv = output_line.to_csv()
7887
expected_column = "2" if PY38_PLUS else "0"
79-
expected_end_lineno = "1" if PY38_PLUS else "None"
80-
expected_end_column = "3" if PY38_PLUS else "None"
8188
assert csv == (
8289
"missing-docstring",
8390
"1",
8491
expected_column,
85-
expected_end_lineno,
86-
expected_end_column,
92+
"1",
93+
"3",
94+
"obj",
95+
"msg",
96+
confidence.name,
97+
)
98+
99+
output_line_without_end = OutputLine.from_msg(message(confidence), False)
100+
csv = output_line_without_end.to_csv()
101+
expected_column = "2" if PY38_PLUS else "0"
102+
assert csv == (
103+
"missing-docstring",
104+
"1",
105+
expected_column,
106+
"None",
107+
"None",
87108
"obj",
88109
"msg",
89110
confidence.name,
@@ -96,12 +117,12 @@ def test_output_line_from_csv_error() -> None:
96117
MalformedOutputLineException,
97118
match="msg-symbolic-name:42:27:MyClass.my_function:The message",
98119
):
99-
OutputLine.from_csv("'missing-docstring', 'line', 'column', 'obj', 'msg'")
120+
OutputLine.from_csv("'missing-docstring', 'line', 'column', 'obj', 'msg'", True)
100121
with pytest.raises(
101122
MalformedOutputLineException, match="symbol='missing-docstring' ?"
102123
):
103124
csv = ("missing-docstring", "line", "column", "obj", "msg")
104-
OutputLine.from_csv(csv)
125+
OutputLine.from_csv(csv, True)
105126

106127

107128
@pytest.mark.parametrize(
@@ -125,7 +146,7 @@ def test_output_line_from_csv_deprecated(
125146
else:
126147
proper_csv = ["missing-docstring", "1", "2", "obj", "msg"]
127148
with pytest.warns(DeprecationWarning) as records:
128-
output_line = OutputLine.from_csv(proper_csv)
149+
output_line = OutputLine.from_csv(proper_csv, True)
129150
assert len(records) == 1
130151

131152
expected_column = 2 if PY38_PLUS else 0
@@ -155,14 +176,25 @@ def test_output_line_from_csv() -> None:
155176
"msg",
156177
"HIGH",
157178
]
158-
output_line = OutputLine.from_csv(proper_csv)
159179
expected_column = 2 if PY38_PLUS else 0
160-
expected_end_lineno = 1 if PY38_PLUS else None
180+
181+
output_line = OutputLine.from_csv(proper_csv, True)
182+
assert output_line == OutputLine(
183+
symbol="missing-docstring",
184+
lineno=1,
185+
column=expected_column,
186+
end_lineno=1,
187+
end_column=None,
188+
object="obj",
189+
msg="msg",
190+
confidence="HIGH",
191+
)
192+
output_line = OutputLine.from_csv(proper_csv, False)
161193
assert output_line == OutputLine(
162194
symbol="missing-docstring",
163195
lineno=1,
164196
column=expected_column,
165-
end_lineno=expected_end_lineno,
197+
end_lineno=None,
166198
end_column=None,
167199
object="obj",
168200
msg="msg",

0 commit comments

Comments
 (0)