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
3 changes: 3 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ Release date: TBA
trigger a ``DeprecationWarning``. Expected output files can be easily updated with the
``python tests/test_functional.py --update-functional-output`` command.

* The functional test runner now supports the option ``min_pyver_end_position`` to control on which python
versions the ``end_lineno`` and ``end_column`` attributes should be checked. The default value is 3.8.

* Fix ``accept-no-yields-doc`` and ``accept-no-return-doc`` not allowing missing ``yield`` or
``return`` documentation when a docstring is partially correct

Expand Down
9 changes: 9 additions & 0 deletions doc/development_guide/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ You can also use ``# +n: [`` with n an integer if the above syntax would make th

If you need special control over Pylint's configuration, you can also create a .rc file, which
can have sections of Pylint's configuration.
The .rc file can also contain a section ``[testoptions]`` to pass options for the functional
Copy link
Member

Choose a reason for hiding this comment

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

👌 This is very useful ! I wish I had that when starting out.

test runner. The following options are currently supported:

"min_pyver": Minimal python version required to run the test
"max_pyver": Maximum python version required to run the test
Comment on lines +78 to +79
Copy link
Member

Choose a reason for hiding this comment

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

This isn't completely true. Whereas min_pyver acts inclusively, max_pyver is exclusive due to the way tuple comparisons and sys.version_info work in Python.

I.e. an alpha release would be (3, 11, 0, 'alpha', 0) > (3, 11). Thus setting max_pyver = 3.11 effectively excludes 3.11 and above.

IMO that is the desired behavior as otherwise one would need to say something like 3.10.99 to exclude 3.11+. So just the documentation that should get updated.

https://github.com/PyCQA/pylint/blob/1f7f2e96c39ee89ee9fb18ab23f3dfbb9325c870/pylint/testutils/lint_module_test.py#L92-L96

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks @cdce8p for reviewing even after we already merged, much appreciated!

I'll create a PR that updates the doc.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@cdce8p Any good ideas about how to describe this. I have been trying for 5 minutes but can't come up with a simple but correct sentence 😆

Copy link
Member

Choose a reason for hiding this comment

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

"max_pyver": Starting from that python version for the test won't be run ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

See #5411

"min_pyver_end_position": Minimal python version required to check the end_line and end_column attributes of the message
"requires": Packages required to be installed locally to run the test
"except_implementations": List of python implementations on which the test should not run
"exclude_platforms": List of operating systems on which the test should not run

During development, it's sometimes helpful to run all functional tests in your
current environment in order to have faster feedback. Run from Pylint root directory with::
Expand Down
3 changes: 3 additions & 0 deletions doc/whatsnew/2.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ Other Changes
trigger a ``DeprecationWarning``. Expected output files can be easily updated with the
``python tests/test_functional.py --update-functional-output`` command.

* The functional test runner now supports the option ``min_pyver_end_position`` to control on which python
versions the ``end_lineno`` and ``end_column`` attributes should be checked. The default value is 3.8.

* ``undefined-variable`` now correctly flags variables which only receive a type annotations
and never get assigned a value

Expand Down
2 changes: 2 additions & 0 deletions pylint/testutils/functional_test_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class FunctionalTestFile:
_CONVERTERS = {
"min_pyver": parse_python_version,
"max_pyver": parse_python_version,
"min_pyver_end_position": parse_python_version,
"requires": lambda s: s.split(","),
}

Expand All @@ -28,6 +29,7 @@ def __init__(self, directory, filename):
self.options = {
"min_pyver": (2, 5),
"max_pyver": (4, 0),
"min_pyver_end_position": (3, 8),
"requires": [],
"except_implementations": [],
"exclude_platforms": [],
Expand Down
10 changes: 8 additions & 2 deletions pylint/testutils/lint_module_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ def __init__(
pass
self._test_file = test_file
self._config = config
self._check_end_position = (
sys.version_info >= self._test_file.options["min_pyver_end_position"]
)

def setUp(self) -> None:
if self._should_be_skipped_due_to_version():
Expand Down Expand Up @@ -166,7 +169,8 @@ def _get_expected(self) -> Tuple["MessageCounter", List[OutputLine]]:
expected_msgs = Counter()
with self._open_expected_file() as f:
expected_output_lines = [
OutputLine.from_csv(row) for row in csv.reader(f, "test")
OutputLine.from_csv(row, self._check_end_position)
for row in csv.reader(f, "test")
]
return expected_msgs, expected_output_lines

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

def _runTest(self) -> None:
Expand Down
22 changes: 12 additions & 10 deletions pylint/testutils/output_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@ class OutputLine(NamedTuple):
confidence: str

@classmethod
def from_msg(cls, msg: Message) -> "OutputLine":
def from_msg(cls, msg: Message, check_endline: bool = True) -> "OutputLine":
"""Create an OutputLine from a Pylint Message"""
column = cls._get_column(msg.column)
end_line = cls._get_py38_none_value(msg.end_line)
end_column = cls._get_py38_none_value(msg.end_column)
end_line = cls._get_py38_none_value(msg.end_line, check_endline)
end_column = cls._get_py38_none_value(msg.end_column, check_endline)
return cls(
msg.symbol,
msg.line,
Expand All @@ -100,15 +100,17 @@ def _get_column(column: str) -> int:
return int(column)

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

@classmethod
def from_csv(cls, row: Union[Sequence[str], str]) -> "OutputLine":
def from_csv(
cls, row: Union[Sequence[str], str], check_endline: bool = True
) -> "OutputLine":
"""Create an OutputLine from a comma separated list (the functional tests expected
output .txt files).
"""
Expand Down Expand Up @@ -143,8 +145,8 @@ def from_csv(cls, row: Union[Sequence[str], str]) -> "OutputLine":
row[0], int(row[1]), column, None, None, row[3], row[4], row[5]
)
if len(row) == 8:
end_line = cls._get_py38_none_value(row[3])
end_column = cls._get_py38_none_value(row[4])
end_line = cls._get_py38_none_value(row[3], check_endline)
end_column = cls._get_py38_none_value(row[4], check_endline)
return cls(
row[0],
int(row[1]),
Expand Down
83 changes: 68 additions & 15 deletions tests/testutils/test_output_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,35 +55,66 @@ def test_output_line() -> None:
def test_output_line_from_message(message: Callable) -> None:
"""Test that the OutputLine NamedTuple is instantiated correctly with from_msg."""
expected_column = 2 if PY38_PLUS else 0
expected_end_lineno = 1 if PY38_PLUS else None
expected_end_column = 3 if PY38_PLUS else None

output_line = OutputLine.from_msg(message())
assert output_line.symbol == "missing-docstring"
assert output_line.lineno == 1
assert output_line.column == expected_column
assert output_line.end_lineno == expected_end_lineno
assert output_line.end_column == expected_end_column
assert output_line.end_lineno == 1
assert output_line.end_column == 3
assert output_line.object == "obj"
assert output_line.msg == "msg"
assert output_line.confidence == "HIGH"

output_line_with_end = OutputLine.from_msg(message(), True)
assert output_line_with_end.symbol == "missing-docstring"
assert output_line_with_end.lineno == 1
assert output_line_with_end.column == expected_column
assert output_line_with_end.end_lineno == 1
assert output_line_with_end.end_column == 3
assert output_line_with_end.object == "obj"
assert output_line_with_end.msg == "msg"
assert output_line_with_end.confidence == "HIGH"

output_line_without_end = OutputLine.from_msg(message(), False)
assert output_line_without_end.symbol == "missing-docstring"
assert output_line_without_end.lineno == 1
assert output_line_without_end.column == expected_column
assert output_line_without_end.end_lineno is None
assert output_line_without_end.end_column is None
assert output_line_without_end.object == "obj"
assert output_line_without_end.msg == "msg"
assert output_line_without_end.confidence == "HIGH"


@pytest.mark.parametrize("confidence", [HIGH, INFERENCE])
def test_output_line_to_csv(confidence: Confidence, message: Callable) -> None:
"""Test that the OutputLine NamedTuple is instantiated correctly with from_msg
and then converted to csv.
"""
output_line = OutputLine.from_msg(message(confidence))
output_line = OutputLine.from_msg(message(confidence), True)
csv = output_line.to_csv()
expected_column = "2" if PY38_PLUS else "0"
expected_end_lineno = "1" if PY38_PLUS else "None"
expected_end_column = "3" if PY38_PLUS else "None"
assert csv == (
"missing-docstring",
"1",
expected_column,
expected_end_lineno,
expected_end_column,
"1",
"3",
"obj",
"msg",
confidence.name,
)

output_line_without_end = OutputLine.from_msg(message(confidence), False)
csv = output_line_without_end.to_csv()
expected_column = "2" if PY38_PLUS else "0"
assert csv == (
"missing-docstring",
"1",
expected_column,
"None",
"None",
"obj",
"msg",
confidence.name,
Expand All @@ -96,12 +127,12 @@ def test_output_line_from_csv_error() -> None:
MalformedOutputLineException,
match="msg-symbolic-name:42:27:MyClass.my_function:The message",
):
OutputLine.from_csv("'missing-docstring', 'line', 'column', 'obj', 'msg'")
OutputLine.from_csv("'missing-docstring', 'line', 'column', 'obj', 'msg'", True)
with pytest.raises(
MalformedOutputLineException, match="symbol='missing-docstring' ?"
):
csv = ("missing-docstring", "line", "column", "obj", "msg")
OutputLine.from_csv(csv)
OutputLine.from_csv(csv, True)


@pytest.mark.parametrize(
Expand All @@ -125,7 +156,7 @@ def test_output_line_from_csv_deprecated(
else:
proper_csv = ["missing-docstring", "1", "2", "obj", "msg"]
with pytest.warns(DeprecationWarning) as records:
output_line = OutputLine.from_csv(proper_csv)
output_line = OutputLine.from_csv(proper_csv, True)
assert len(records) == 1

expected_column = 2 if PY38_PLUS else 0
Expand Down Expand Up @@ -155,14 +186,36 @@ def test_output_line_from_csv() -> None:
"msg",
"HIGH",
]
output_line = OutputLine.from_csv(proper_csv)
expected_column = 2 if PY38_PLUS else 0
expected_end_lineno = 1 if PY38_PLUS else None

output_line = OutputLine.from_csv(proper_csv)
assert output_line == OutputLine(
symbol="missing-docstring",
lineno=1,
column=expected_column,
end_lineno=expected_end_lineno,
end_lineno=1,
end_column=None,
object="obj",
msg="msg",
confidence="HIGH",
)
output_line_with_end = OutputLine.from_csv(proper_csv, True)
assert output_line_with_end == OutputLine(
symbol="missing-docstring",
lineno=1,
column=expected_column,
end_lineno=1,
end_column=None,
object="obj",
msg="msg",
confidence="HIGH",
)
output_line_without_end = OutputLine.from_csv(proper_csv, False)
assert output_line_without_end == OutputLine(
symbol="missing-docstring",
lineno=1,
column=expected_column,
end_lineno=None,
end_column=None,
object="obj",
msg="msg",
Expand Down