Skip to content

Commit

Permalink
Merge 602b8ce into be149db
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielNoord committed Nov 25, 2021
2 parents be149db + 602b8ce commit e38be0c
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 27 deletions.
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
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
"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

0 comments on commit e38be0c

Please sign in to comment.