Skip to content

Commit fa7a84f

Browse files
authored
Add min_pyver_end_position option (#5386)
* Add ``min_pyver_end_position`` option
1 parent be149db commit fa7a84f

File tree

7 files changed

+105
-27
lines changed

7 files changed

+105
-27
lines changed

Diff for: ChangeLog

+3
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

Diff for: doc/development_guide/testing.rst

+9
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::

Diff for: doc/whatsnew/2.12.rst

+3
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

Diff for: pylint/testutils/functional_test_file.py

+2
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": [],

Diff for: pylint/testutils/lint_module_test.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ def __init__(
5757
pass
5858
self._test_file = test_file
5959
self._config = config
60+
self._check_end_position = (
61+
sys.version_info >= self._test_file.options["min_pyver_end_position"]
62+
)
6063

6164
def setUp(self) -> None:
6265
if self._should_be_skipped_due_to_version():
@@ -166,7 +169,8 @@ def _get_expected(self) -> Tuple["MessageCounter", List[OutputLine]]:
166169
expected_msgs = Counter()
167170
with self._open_expected_file() as f:
168171
expected_output_lines = [
169-
OutputLine.from_csv(row) for row in csv.reader(f, "test")
172+
OutputLine.from_csv(row, self._check_end_position)
173+
for row in csv.reader(f, "test")
170174
]
171175
return expected_msgs, expected_output_lines
172176

@@ -180,7 +184,9 @@ def _get_actual(self) -> Tuple["MessageCounter", List[OutputLine]]:
180184
msg.symbol != "fatal"
181185
), f"Pylint analysis failed because of '{msg.msg}'"
182186
received_msgs[msg.line, msg.symbol] += 1
183-
received_output_lines.append(OutputLine.from_msg(msg))
187+
received_output_lines.append(
188+
OutputLine.from_msg(msg, self._check_end_position)
189+
)
184190
return received_msgs, received_output_lines
185191

186192
def _runTest(self) -> None:

Diff for: pylint/testutils/output_line.py

+12-10
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 = True) -> "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 = True
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]),

Diff for: tests/testutils/test_output_line.py

+68-15
Original file line numberDiff line numberDiff line change
@@ -55,35 +55,66 @@ 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
58+
6059
output_line = OutputLine.from_msg(message())
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_with_end = OutputLine.from_msg(message(), True)
70+
assert output_line_with_end.symbol == "missing-docstring"
71+
assert output_line_with_end.lineno == 1
72+
assert output_line_with_end.column == expected_column
73+
assert output_line_with_end.end_lineno == 1
74+
assert output_line_with_end.end_column == 3
75+
assert output_line_with_end.object == "obj"
76+
assert output_line_with_end.msg == "msg"
77+
assert output_line_with_end.confidence == "HIGH"
78+
79+
output_line_without_end = OutputLine.from_msg(message(), False)
80+
assert output_line_without_end.symbol == "missing-docstring"
81+
assert output_line_without_end.lineno == 1
82+
assert output_line_without_end.column == expected_column
83+
assert output_line_without_end.end_lineno is None
84+
assert output_line_without_end.end_column is None
85+
assert output_line_without_end.object == "obj"
86+
assert output_line_without_end.msg == "msg"
87+
assert output_line_without_end.confidence == "HIGH"
88+
7089

7190
@pytest.mark.parametrize("confidence", [HIGH, INFERENCE])
7291
def test_output_line_to_csv(confidence: Confidence, message: Callable) -> None:
7392
"""Test that the OutputLine NamedTuple is instantiated correctly with from_msg
7493
and then converted to csv.
7594
"""
76-
output_line = OutputLine.from_msg(message(confidence))
95+
output_line = OutputLine.from_msg(message(confidence), True)
7796
csv = output_line.to_csv()
7897
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"
8198
assert csv == (
8299
"missing-docstring",
83100
"1",
84101
expected_column,
85-
expected_end_lineno,
86-
expected_end_column,
102+
"1",
103+
"3",
104+
"obj",
105+
"msg",
106+
confidence.name,
107+
)
108+
109+
output_line_without_end = OutputLine.from_msg(message(confidence), False)
110+
csv = output_line_without_end.to_csv()
111+
expected_column = "2" if PY38_PLUS else "0"
112+
assert csv == (
113+
"missing-docstring",
114+
"1",
115+
expected_column,
116+
"None",
117+
"None",
87118
"obj",
88119
"msg",
89120
confidence.name,
@@ -96,12 +127,12 @@ def test_output_line_from_csv_error() -> None:
96127
MalformedOutputLineException,
97128
match="msg-symbolic-name:42:27:MyClass.my_function:The message",
98129
):
99-
OutputLine.from_csv("'missing-docstring', 'line', 'column', 'obj', 'msg'")
130+
OutputLine.from_csv("'missing-docstring', 'line', 'column', 'obj', 'msg'", True)
100131
with pytest.raises(
101132
MalformedOutputLineException, match="symbol='missing-docstring' ?"
102133
):
103134
csv = ("missing-docstring", "line", "column", "obj", "msg")
104-
OutputLine.from_csv(csv)
135+
OutputLine.from_csv(csv, True)
105136

106137

107138
@pytest.mark.parametrize(
@@ -125,7 +156,7 @@ def test_output_line_from_csv_deprecated(
125156
else:
126157
proper_csv = ["missing-docstring", "1", "2", "obj", "msg"]
127158
with pytest.warns(DeprecationWarning) as records:
128-
output_line = OutputLine.from_csv(proper_csv)
159+
output_line = OutputLine.from_csv(proper_csv, True)
129160
assert len(records) == 1
130161

131162
expected_column = 2 if PY38_PLUS else 0
@@ -155,14 +186,36 @@ def test_output_line_from_csv() -> None:
155186
"msg",
156187
"HIGH",
157188
]
158-
output_line = OutputLine.from_csv(proper_csv)
159189
expected_column = 2 if PY38_PLUS else 0
160-
expected_end_lineno = 1 if PY38_PLUS else None
190+
191+
output_line = OutputLine.from_csv(proper_csv)
161192
assert output_line == OutputLine(
162193
symbol="missing-docstring",
163194
lineno=1,
164195
column=expected_column,
165-
end_lineno=expected_end_lineno,
196+
end_lineno=1,
197+
end_column=None,
198+
object="obj",
199+
msg="msg",
200+
confidence="HIGH",
201+
)
202+
output_line_with_end = OutputLine.from_csv(proper_csv, True)
203+
assert output_line_with_end == OutputLine(
204+
symbol="missing-docstring",
205+
lineno=1,
206+
column=expected_column,
207+
end_lineno=1,
208+
end_column=None,
209+
object="obj",
210+
msg="msg",
211+
confidence="HIGH",
212+
)
213+
output_line_without_end = OutputLine.from_csv(proper_csv, False)
214+
assert output_line_without_end == OutputLine(
215+
symbol="missing-docstring",
216+
lineno=1,
217+
column=expected_column,
218+
end_lineno=None,
166219
end_column=None,
167220
object="obj",
168221
msg="msg",

0 commit comments

Comments
 (0)