Skip to content

Commit

Permalink
Add typing to LintModuleOutputUpdate._check_output_text
Browse files Browse the repository at this point in the history
Better typing for test options and fix existing issue
  • Loading branch information
Pierre-Sassoulas committed Dec 3, 2021
1 parent bb3c446 commit 5bb0095
Show file tree
Hide file tree
Showing 13 changed files with 76 additions and 28 deletions.
4 changes: 2 additions & 2 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ Release date: TBA
..
Put new features here and also in 'doc/whatsnew/2.13.rst'

* Some file in ``pylint.testutils`` were deprecated, imports should be done from the
``pylint.testutils`` API directly
* Some files in ``pylint.testutils`` were deprecated. In the future imports should be done from the
``pylint.testutils.functional`` namespace directly.

..
Insert your changelog randomly, it will reduce merge conflicts
Expand Down
2 changes: 1 addition & 1 deletion pylint/testutils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
from pylint.testutils.checker_test_case import CheckerTestCase
from pylint.testutils.constants import UPDATE_FILE, UPDATE_OPTION
from pylint.testutils.decorator import set_config
from pylint.testutils.functional.test_file import FunctionalTestFile
from pylint.testutils.functional import FunctionalTestFile
from pylint.testutils.get_test_info import _get_tests_info
from pylint.testutils.global_test_linter import linter
from pylint.testutils.lint_module_test import LintModuleTest
Expand Down
1 change: 1 addition & 0 deletions pylint/testutils/functional/find_functional_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
def get_functional_test_files_from_directory(
input_dir: Union[Path, str]
) -> List[FunctionalTestFile]:
"""Get all functional tests in the input_dir."""
suite = []
for dirpath, _, filenames in os.walk(input_dir):
if dirpath.endswith("__pycache__"):
Expand Down
19 changes: 15 additions & 4 deletions pylint/testutils/functional/lint_module_output_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@

import csv
import os
from typing import Optional
from typing import List, Optional

from _pytest.config import Config

from pylint.constants import PY38_PLUS
from pylint.testutils.functional.test_file import FunctionalTestFile
from pylint.testutils.lint_module_test import LintModuleTest
from pylint.testutils.lint_module_test import LintModuleTest, MessageCounter
from pylint.testutils.output_line import OutputLine


class LintModuleOutputUpdate(LintModuleTest):
"""If message files should be updated instead of checked."""
"""Class to be used if expected output files should be updated instead of checked."""

class TestDialect(csv.excel):
"""Dialect used by the csv writer."""

delimiter = ":"
lineterminator = "\n"

Expand All @@ -32,11 +35,19 @@ def __init__(
)
super().__init__(test_file, config)

def _check_output_text(self, _, expected_output, actual_output):
def _check_output_text(
self,
_: MessageCounter,
expected_output: List[OutputLine],
actual_output: List[OutputLine],
) -> None:
"""Overwrite or remove the expected output file based on actual output."""
# Remove the file if no output is actually expected and a file exists
if not expected_output and not actual_output:
if os.path.exists(self._test_file.expected_output):
os.remove(self._test_file.expected_output)
return
# Write file with expected output
with open(self._test_file.expected_output, "w", encoding="utf-8") as f:
writer = csv.writer(f, dialect="test")
for line in actual_output:
Expand Down
60 changes: 47 additions & 13 deletions pylint/testutils/functional/test_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,47 @@
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE

import configparser
import sys
from os.path import basename, exists, join
from typing import List, Tuple


def parse_python_version(ver_str):
def parse_python_version(ver_str: str) -> Tuple[int, ...]:
"""Convert python version to a tuple of integers for easy comparison."""
return tuple(int(digit) for digit in ver_str.split("."))


class NoFileError(Exception):
pass


if sys.version_info >= (3, 8):
from typing import TypedDict
else:
from typing_extensions import TypedDict


class TestFileOptions(TypedDict):
min_pyver: Tuple[int, ...]
max_pyver: Tuple[int, ...]
min_pyver_end_position: Tuple[int, ...]
requires: List[str]
except_implementations: str # Type is actually comma separated list of string
exclude_platforms: str # Type is actually comma separated list of string


# mypy need something literal, we can't create this dynamically from TestFileOptions
POSSIBLE_TEST_OPTIONS = {
"min_pyver",
"max_pyver",
"min_pyver_end_position",
"requires",
"except_implementations",
"exclude_platforms",
"exclude_platforms",
}


class FunctionalTestFile:
"""A single functional test case file with options."""

Expand All @@ -23,23 +53,23 @@ class FunctionalTestFile:
"requires": lambda s: s.split(","),
}

def __init__(self, directory, filename):
def __init__(self, directory: str, filename: str) -> None:
self._directory = directory
self.base = filename.replace(".py", "")
self.options = {
self.options: TestFileOptions = {
"min_pyver": (2, 5),
"max_pyver": (4, 0),
"min_pyver_end_position": (3, 8),
"requires": [],
"except_implementations": [],
"exclude_platforms": [],
"except_implementations": "",
"exclude_platforms": "",
}
self._parse_options()

def __repr__(self):
def __repr__(self) -> str:
return f"FunctionalTest:{self.base}"

def _parse_options(self):
def _parse_options(self) -> None:
cp = configparser.ConfigParser()
cp.add_section("testoptions")
try:
Expand All @@ -49,26 +79,30 @@ def _parse_options(self):

for name, value in cp.items("testoptions"):
conv = self._CONVERTERS.get(name, lambda v: v)
self.options[name] = conv(value)

assert (
name in POSSIBLE_TEST_OPTIONS
), f"[testoptions]' can only contains one of {POSSIBLE_TEST_OPTIONS}"
self.options[name] = conv(value) # type: ignore[misc]

@property
def option_file(self):
def option_file(self) -> str:
return self._file_type(".rc")

@property
def module(self):
def module(self) -> str:
package = basename(self._directory)
return ".".join([package, self.base])

@property
def expected_output(self):
def expected_output(self) -> str:
return self._file_type(".txt", check_exists=False)

@property
def source(self):
def source(self) -> str:
return self._file_type(".py")

def _file_type(self, ext, check_exists=True):
def _file_type(self, ext: str, check_exists: bool = True) -> str:
name = join(self._directory, self.base + ext)
if not check_exists or exists(name):
return name
Expand Down
2 changes: 1 addition & 1 deletion pylint/testutils/lint_module_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from pylint.lint import PyLinter
from pylint.message.message import Message
from pylint.testutils.constants import _EXPECTED_RE, _OPERATORS, UPDATE_OPTION
from pylint.testutils.functional.test_file import (
from pylint.testutils.functional.test_file import ( # need to import from functional.test_file to avoid cyclic import
FunctionalTestFile,
NoFileError,
parse_python_version,
Expand Down
4 changes: 3 additions & 1 deletion tests/functional/f/fixme.rc
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
[testoptions]
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=XXX,TODO,./TODO
# Regular expression of note tags to take in consideration.
notes-rgx=FIXME(?!.*ISSUE-\d+)|TO.*DO
2 changes: 1 addition & 1 deletion tests/functional/r/regression/regression_issue_4631.rc
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[testoptions]
[MASTER]
limit-inference-results=0
2 changes: 1 addition & 1 deletion tests/functional/t/too/too_few_public_methods_excluded.rc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[testoptions]
[DESIGN]
min-public-methods=10 # to combat inherited methods

exclude-too-few-public-methods=json.*,^.*Control$
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[testoptions]
[DESIGN]
max-parents=2
ignored-parents=functional.t.too.too_many_ancestors_ignored_parents.E
2 changes: 1 addition & 1 deletion tests/functional/t/too/too_many_statements.rc
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[testoptions]
[DESIGN]
max-statements=5
2 changes: 1 addition & 1 deletion tests/test_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@
from pylint.testutils import UPDATE_FILE, UPDATE_OPTION
from pylint.testutils.functional import (
FunctionalTestFile,
LintModuleOutputUpdate,
get_functional_test_files_from_directory,
)
from pylint.testutils.functional.lint_module_output_update import LintModuleOutputUpdate
from pylint.utils import HAS_ISORT_5

# TODOs
Expand Down
2 changes: 1 addition & 1 deletion tests/testutils/test_lint_module_output_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def inner(base: str) -> Tuple[Path, Path, LintModuleOutputUpdate]:

@pytest.mark.skipif(PY38_PLUS, reason="Requires python 3.7 or lower")
def test_not_py38(tmp_path: Path) -> None:
with pytest.raises(RuntimeError, match="new, better AST in python 3.8"):
with pytest.raises(RuntimeError, match="new AST parser"):
LintModuleOutputUpdate(
test_file=FunctionalTestFile(str(tmp_path), str(tmp_path / "filename.py"))
)
Expand Down

0 comments on commit 5bb0095

Please sign in to comment.