| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
- # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
- # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
- from __future__ import annotations
- import configparser
- import sys
- from collections.abc import Callable
- from os.path import basename, exists, join, split
- from pathlib import Path
- from typing import Final, TypedDict
- _CURRENT_VERSION: Final = sys.version_info[:2]
- 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
- class TestFileOptions(TypedDict):
- min_pyver: tuple[int, ...]
- max_pyver: tuple[int, ...]
- min_pyver_end_position: tuple[int, ...]
- requires: list[str]
- except_implementations: list[str]
- exclude_platforms: list[str]
- exclude_from_minimal_messages_config: bool
- # 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_from_minimal_messages_config",
- }
- class FunctionalTestFile:
- """A single functional test case file with options."""
- _CONVERTERS: dict[str, Callable[[str], tuple[int, ...] | list[str]]] = {
- "min_pyver": parse_python_version,
- "max_pyver": parse_python_version,
- "min_pyver_end_position": parse_python_version,
- "requires": lambda s: [i.strip() for i in s.split(",")],
- "except_implementations": lambda s: [i.strip() for i in s.split(",")],
- "exclude_platforms": lambda s: [i.strip() for i in s.split(",")],
- }
- def __init__(self, directory: str, filename: str) -> None:
- self._directory = directory
- self.base = filename.replace(".py", "")
- # TODO:4.0: Deprecate FunctionalTestFile.options and related code
- # We should just parse these options like a normal configuration file.
- self.options: TestFileOptions = {
- "min_pyver": (2, 5),
- "max_pyver": (4, 0),
- "min_pyver_end_position": (3, 8),
- "requires": [],
- "except_implementations": [],
- "exclude_platforms": [],
- "exclude_from_minimal_messages_config": False,
- }
- self._parse_options()
- def __repr__(self) -> str:
- return f"FunctionalTest:{self.base}"
- def _parse_options(self) -> None:
- cp = configparser.ConfigParser()
- cp.add_section("testoptions")
- try:
- cp.read(self.option_file)
- except NoFileError:
- pass
- for name, value in cp.items("testoptions"):
- conv = self._CONVERTERS.get(name, lambda v: v)
- assert (
- name in POSSIBLE_TEST_OPTIONS
- ), f"[testoptions]' can only contains one of {POSSIBLE_TEST_OPTIONS} and had '{name}'"
- self.options[name] = conv(value) # type: ignore[literal-required]
- @property
- def option_file(self) -> str:
- return self._file_type(".rc")
- @property
- def module(self) -> str:
- package = basename(self._directory)
- return ".".join([package, self.base])
- @property
- def expected_output(self) -> str:
- files = [
- p.stem
- for p in Path(self._directory).glob(f"{split(self.base)[-1]}.[0-9]*.txt")
- ]
- output_options = [
- (int(version[0]), int(version[1:]))
- for s in files
- if (version := s.rpartition(".")[2]).isalnum()
- ]
- for opt in sorted(output_options, reverse=True):
- if _CURRENT_VERSION >= opt:
- str_opt = "".join([str(s) for s in opt])
- return join(self._directory, f"{self.base}.{str_opt}.txt")
- return join(self._directory, self.base + ".txt")
- @property
- def source(self) -> str:
- return self._file_type(".py")
- 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
- raise NoFileError(f"Cannot find '{name}'.")
|