mirror of
https://github.com/davidhalter/typeshed.git
synced 2026-06-24 09:48:39 +08:00
Apply mypy-tests custom config to other mypy-based tests (#13825)
This commit is contained in:
@@ -166,6 +166,7 @@ _KNOWN_METADATA_FIELDS: Final = frozenset(
|
||||
"tool",
|
||||
"partial_stub",
|
||||
"requires_python",
|
||||
"mypy-tests",
|
||||
}
|
||||
)
|
||||
_KNOWN_METADATA_TOOL_FIELDS: Final = {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator, Iterable
|
||||
from contextlib import contextmanager
|
||||
from typing import Any, NamedTuple
|
||||
|
||||
import tomli
|
||||
|
||||
from ts_utils.metadata import metadata_path
|
||||
from ts_utils.utils import NamedTemporaryFile, TemporaryFileWrapper
|
||||
|
||||
|
||||
class MypyDistConf(NamedTuple):
|
||||
module_name: str
|
||||
values: dict[str, dict[str, Any]]
|
||||
|
||||
|
||||
# The configuration section in the metadata file looks like the following, with multiple module sections possible
|
||||
# [mypy-tests]
|
||||
# [mypy-tests.yaml]
|
||||
# module_name = "yaml"
|
||||
# [mypy-tests.yaml.values]
|
||||
# disallow_incomplete_defs = true
|
||||
# disallow_untyped_defs = true
|
||||
|
||||
|
||||
def mypy_configuration_from_distribution(distribution: str) -> list[MypyDistConf]:
|
||||
with metadata_path(distribution).open("rb") as f:
|
||||
data = tomli.load(f)
|
||||
|
||||
# TODO: This could be added to ts_utils.metadata
|
||||
mypy_tests_conf: dict[str, dict[str, Any]] = data.get("mypy-tests", {})
|
||||
if not mypy_tests_conf:
|
||||
return []
|
||||
|
||||
def validate_configuration(section_name: str, mypy_section: dict[str, Any]) -> MypyDistConf:
|
||||
assert isinstance(mypy_section, dict), f"{section_name} should be a section"
|
||||
module_name = mypy_section.get("module_name")
|
||||
|
||||
assert module_name is not None, f"{section_name} should have a module_name key"
|
||||
assert isinstance(module_name, str), f"{section_name} should be a key-value pair"
|
||||
|
||||
assert "values" in mypy_section, f"{section_name} should have a values section"
|
||||
values: dict[str, dict[str, Any]] = mypy_section["values"]
|
||||
assert isinstance(values, dict), "values should be a section"
|
||||
return MypyDistConf(module_name, values.copy())
|
||||
|
||||
assert isinstance(mypy_tests_conf, dict), "mypy-tests should be a section"
|
||||
return [validate_configuration(section_name, mypy_section) for section_name, mypy_section in mypy_tests_conf.items()]
|
||||
|
||||
|
||||
@contextmanager
|
||||
def temporary_mypy_config_file(configurations: Iterable[MypyDistConf]) -> Generator[TemporaryFileWrapper[str]]:
|
||||
temp = NamedTemporaryFile("w+")
|
||||
try:
|
||||
for dist_conf in configurations:
|
||||
temp.write(f"[mypy-{dist_conf.module_name}]\n")
|
||||
for k, v in dist_conf.values.items():
|
||||
temp.write(f"{k} = {v}\n")
|
||||
temp.write("[mypy]\n")
|
||||
temp.flush()
|
||||
yield temp
|
||||
finally:
|
||||
temp.close()
|
||||
+30
-4
@@ -3,16 +3,24 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
from collections.abc import Iterable, Mapping
|
||||
from pathlib import Path
|
||||
from typing import Any, Final, NamedTuple
|
||||
from types import MethodType
|
||||
from typing import TYPE_CHECKING, Any, Final, NamedTuple
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
import pathspec
|
||||
from packaging.requirements import Requirement
|
||||
|
||||
from .paths import REQUIREMENTS_PATH, STDLIB_PATH, STUBS_PATH, TEST_CASES_DIR, allowlists_path, test_cases_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _typeshed import OpenTextMode
|
||||
|
||||
try:
|
||||
from termcolor import colored as colored # pyright: ignore[reportAssignmentType]
|
||||
except ImportError:
|
||||
@@ -21,8 +29,6 @@ except ImportError:
|
||||
return text
|
||||
|
||||
|
||||
from .paths import REQUIREMENTS_PATH, STDLIB_PATH, STUBS_PATH, TEST_CASES_DIR, allowlists_path, test_cases_path
|
||||
|
||||
PYTHON_VERSION: Final = f"{sys.version_info.major}.{sys.version_info.minor}"
|
||||
|
||||
|
||||
@@ -196,6 +202,26 @@ def allowlists(distribution_name: str) -> list[str]:
|
||||
return ["stubtest_allowlist.txt", platform_allowlist]
|
||||
|
||||
|
||||
# Re-exposing as a public name to avoid many pyright reportPrivateUsage
|
||||
TemporaryFileWrapper = tempfile._TemporaryFileWrapper # pyright: ignore[reportPrivateUsage]
|
||||
|
||||
# We need to work around a limitation of tempfile.NamedTemporaryFile on Windows
|
||||
# For details, see https://github.com/python/typeshed/pull/13620#discussion_r1990185997
|
||||
# Python 3.12 added a cross-platform solution with `tempfile.NamedTemporaryFile("w+", delete_on_close=False)`
|
||||
if sys.platform != "win32":
|
||||
NamedTemporaryFile = tempfile.NamedTemporaryFile # noqa: TID251
|
||||
else:
|
||||
|
||||
def NamedTemporaryFile(mode: OpenTextMode) -> TemporaryFileWrapper[str]: # noqa: N802
|
||||
def close(self: TemporaryFileWrapper[str]) -> None:
|
||||
TemporaryFileWrapper.close(self) # pyright: ignore[reportUnknownMemberType]
|
||||
os.remove(self.name)
|
||||
|
||||
temp = tempfile.NamedTemporaryFile(mode, delete=False) # noqa: SIM115, TID251
|
||||
temp.close = MethodType(close, temp) # type: ignore[method-assign]
|
||||
return temp
|
||||
|
||||
|
||||
# ====================================================================
|
||||
# Parsing .gitignore
|
||||
# ====================================================================
|
||||
@@ -215,7 +241,7 @@ def spec_matches_path(spec: pathspec.PathSpec, path: Path) -> bool:
|
||||
|
||||
|
||||
# ====================================================================
|
||||
# mypy/stubtest call
|
||||
# stubtest call
|
||||
# ====================================================================
|
||||
|
||||
|
||||
|
||||
@@ -139,6 +139,8 @@ select = [
|
||||
"TC005", # Found empty type-checking block
|
||||
# "TC008", # TODO: Enable when out of preview
|
||||
"TC010", # Invalid string member in `X | Y`-style union type
|
||||
# Used for lint.flake8-import-conventions.aliases
|
||||
"TID251", # `{name}` is banned: {message}
|
||||
]
|
||||
extend-safe-fixes = [
|
||||
"UP036", # Remove unnecessary `sys.version_info` blocks
|
||||
@@ -235,6 +237,9 @@ convention = "pep257" # https://docs.astral.sh/ruff/settings/#lint_pydocstyle_co
|
||||
typing_extensions = "typing_extensions"
|
||||
typing = "typing"
|
||||
|
||||
[tool.ruff.lint.flake8-tidy-imports.banned-api]
|
||||
"tempfile.NamedTemporaryFile".msg = "Use `ts_util.util.NamedTemporaryFile` instead."
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
split-on-trailing-comma = false
|
||||
combine-as-imports = true
|
||||
|
||||
+29
-93
@@ -5,14 +5,12 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import concurrent.futures
|
||||
import functools
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from collections.abc import Generator
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from itertools import product
|
||||
@@ -21,10 +19,10 @@ from threading import Lock
|
||||
from typing import Annotated, Any, NamedTuple
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
import tomli
|
||||
from packaging.requirements import Requirement
|
||||
|
||||
from ts_utils.metadata import PackageDependencies, get_recursive_requirements, metadata_path, read_metadata
|
||||
from ts_utils.metadata import PackageDependencies, get_recursive_requirements, read_metadata
|
||||
from ts_utils.mypy import MypyDistConf, mypy_configuration_from_distribution, temporary_mypy_config_file
|
||||
from ts_utils.paths import STDLIB_PATH, STUBS_PATH, TESTS_DIR, TS_BASE_PATH, distribution_path
|
||||
from ts_utils.utils import (
|
||||
PYTHON_VERSION,
|
||||
@@ -46,24 +44,6 @@ except ImportError:
|
||||
print_error("Cannot import mypy. Did you install it?")
|
||||
sys.exit(1)
|
||||
|
||||
# We need to work around a limitation of tempfile.NamedTemporaryFile on Windows
|
||||
# For details, see https://github.com/python/typeshed/pull/13620#discussion_r1990185997
|
||||
# Python 3.12 added a workaround with `tempfile.NamedTemporaryFile("w+", delete_on_close=False)`
|
||||
if sys.platform != "win32":
|
||||
_named_temporary_file = functools.partial(tempfile.NamedTemporaryFile, "w+")
|
||||
else:
|
||||
from contextlib import contextmanager
|
||||
|
||||
@contextmanager
|
||||
def _named_temporary_file() -> Generator[tempfile._TemporaryFileWrapper[str]]: # pyright: ignore[reportPrivateUsage]
|
||||
temp = tempfile.NamedTemporaryFile("w+", delete=False) # noqa: SIM115
|
||||
try:
|
||||
yield temp
|
||||
finally:
|
||||
temp.close()
|
||||
os.remove(temp.name)
|
||||
|
||||
|
||||
SUPPORTED_VERSIONS = ["3.13", "3.12", "3.11", "3.10", "3.9"]
|
||||
SUPPORTED_PLATFORMS = ("linux", "win32", "darwin")
|
||||
DIRECTORIES_TO_TEST = [STDLIB_PATH, STUBS_PATH]
|
||||
@@ -177,49 +157,20 @@ def add_files(files: list[Path], module: Path, args: TestConfig) -> None:
|
||||
files.extend(sorted(file for file in module.rglob("*.pyi") if match(file, args)))
|
||||
|
||||
|
||||
class MypyDistConf(NamedTuple):
|
||||
module_name: str
|
||||
values: dict[str, dict[str, Any]]
|
||||
|
||||
|
||||
# The configuration section in the metadata file looks like the following, with multiple module sections possible
|
||||
# [mypy-tests]
|
||||
# [mypy-tests.yaml]
|
||||
# module_name = "yaml"
|
||||
# [mypy-tests.yaml.values]
|
||||
# disallow_incomplete_defs = true
|
||||
# disallow_untyped_defs = true
|
||||
|
||||
|
||||
def add_configuration(configurations: list[MypyDistConf], distribution: str) -> None:
|
||||
with metadata_path(distribution).open("rb") as f:
|
||||
data = tomli.load(f)
|
||||
|
||||
# TODO: This could be added to ts_utils.metadata, but is currently unused
|
||||
mypy_tests_conf: dict[str, dict[str, Any]] = data.get("mypy-tests", {})
|
||||
if not mypy_tests_conf:
|
||||
return
|
||||
|
||||
assert isinstance(mypy_tests_conf, dict), "mypy-tests should be a section"
|
||||
for section_name, mypy_section in mypy_tests_conf.items():
|
||||
assert isinstance(mypy_section, dict), f"{section_name} should be a section"
|
||||
module_name = mypy_section.get("module_name")
|
||||
|
||||
assert module_name is not None, f"{section_name} should have a module_name key"
|
||||
assert isinstance(module_name, str), f"{section_name} should be a key-value pair"
|
||||
|
||||
assert "values" in mypy_section, f"{section_name} should have a values section"
|
||||
values: dict[str, dict[str, Any]] = mypy_section["values"]
|
||||
assert isinstance(values, dict), "values should be a section"
|
||||
|
||||
configurations.append(MypyDistConf(module_name, values.copy()))
|
||||
|
||||
|
||||
class MypyResult(Enum):
|
||||
SUCCESS = 0
|
||||
FAILURE = 1
|
||||
CRASH = 2
|
||||
|
||||
@staticmethod
|
||||
def from_process_result(result: subprocess.CompletedProcess[Any]) -> MypyResult:
|
||||
if result.returncode == 0:
|
||||
return MypyResult.SUCCESS
|
||||
elif result.returncode == 1:
|
||||
return MypyResult.FAILURE
|
||||
else:
|
||||
return MypyResult.CRASH
|
||||
|
||||
|
||||
def run_mypy(
|
||||
args: TestConfig,
|
||||
@@ -234,15 +185,7 @@ def run_mypy(
|
||||
env_vars = dict(os.environ)
|
||||
if mypypath is not None:
|
||||
env_vars["MYPYPATH"] = mypypath
|
||||
|
||||
with _named_temporary_file() as temp:
|
||||
temp.write("[mypy]\n")
|
||||
for dist_conf in configurations:
|
||||
temp.write(f"[mypy-{dist_conf.module_name}]\n")
|
||||
for k, v in dist_conf.values.items():
|
||||
temp.write(f"{k} = {v}\n")
|
||||
temp.flush()
|
||||
|
||||
with temporary_mypy_config_file(configurations) as temp:
|
||||
flags = [
|
||||
"--python-version",
|
||||
args.version,
|
||||
@@ -278,29 +221,23 @@ def run_mypy(
|
||||
if args.verbose:
|
||||
print(colored(f"running {' '.join(mypy_command)}", "blue"))
|
||||
result = subprocess.run(mypy_command, capture_output=True, text=True, env=env_vars, check=False)
|
||||
if result.returncode:
|
||||
print_error(f"failure (exit code {result.returncode})\n")
|
||||
if result.stdout:
|
||||
print_error(result.stdout)
|
||||
if result.stderr:
|
||||
print_error(result.stderr)
|
||||
if non_types_dependencies and args.verbose:
|
||||
print("Ran with the following environment:")
|
||||
subprocess.run(["uv", "pip", "freeze"], env={**os.environ, "VIRTUAL_ENV": str(venv_dir)}, check=False)
|
||||
print()
|
||||
else:
|
||||
print_success_msg()
|
||||
if result.returncode == 0:
|
||||
return MypyResult.SUCCESS
|
||||
elif result.returncode == 1:
|
||||
return MypyResult.FAILURE
|
||||
else:
|
||||
return MypyResult.CRASH
|
||||
if result.returncode:
|
||||
print_error(f"failure (exit code {result.returncode})\n")
|
||||
if result.stdout:
|
||||
print_error(result.stdout)
|
||||
if result.stderr:
|
||||
print_error(result.stderr)
|
||||
if non_types_dependencies and args.verbose:
|
||||
print("Ran with the following environment:")
|
||||
subprocess.run(["uv", "pip", "freeze"], env={**os.environ, "VIRTUAL_ENV": str(venv_dir)}, check=False)
|
||||
print()
|
||||
else:
|
||||
print_success_msg()
|
||||
|
||||
return MypyResult.from_process_result(result)
|
||||
|
||||
|
||||
def add_third_party_files(
|
||||
distribution: str, files: list[Path], args: TestConfig, configurations: list[MypyDistConf], seen_dists: set[str]
|
||||
) -> None:
|
||||
def add_third_party_files(distribution: str, files: list[Path], args: TestConfig, seen_dists: set[str]) -> None:
|
||||
typeshed_reqs = get_recursive_requirements(distribution).typeshed_pkgs
|
||||
if distribution in seen_dists:
|
||||
return
|
||||
@@ -311,7 +248,6 @@ def add_third_party_files(
|
||||
if name.startswith("."):
|
||||
continue
|
||||
add_files(files, (root / name), args)
|
||||
add_configuration(configurations, distribution)
|
||||
|
||||
|
||||
class TestResult(NamedTuple):
|
||||
@@ -328,9 +264,9 @@ def test_third_party_distribution(
|
||||
and the second element is the number of checked files.
|
||||
"""
|
||||
files: list[Path] = []
|
||||
configurations: list[MypyDistConf] = []
|
||||
seen_dists: set[str] = set()
|
||||
add_third_party_files(distribution, files, args, configurations, seen_dists)
|
||||
add_third_party_files(distribution, files, args, seen_dists)
|
||||
configurations = mypy_configuration_from_distribution(distribution)
|
||||
|
||||
if not files and args.filter:
|
||||
return TestResult(MypyResult.SUCCESS, 0)
|
||||
|
||||
+61
-51
@@ -22,6 +22,7 @@ from pathlib import Path
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
from ts_utils.metadata import get_recursive_requirements, read_metadata
|
||||
from ts_utils.mypy import mypy_configuration_from_distribution, temporary_mypy_config_file
|
||||
from ts_utils.paths import STDLIB_PATH, TEST_CASES_DIR, TS_BASE_PATH, distribution_path
|
||||
from ts_utils.utils import (
|
||||
PYTHON_VERSION,
|
||||
@@ -169,62 +170,71 @@ def run_testcases(
|
||||
env_vars = dict(os.environ)
|
||||
new_test_case_dir = tempdir / TEST_CASES_DIR
|
||||
|
||||
# "--enable-error-code ignore-without-code" is purposefully omitted.
|
||||
# See https://github.com/python/typeshed/pull/8083
|
||||
flags = [
|
||||
"--python-version",
|
||||
version,
|
||||
"--show-traceback",
|
||||
"--no-error-summary",
|
||||
"--platform",
|
||||
platform,
|
||||
"--strict",
|
||||
"--pretty",
|
||||
# Avoid race conditions when reading the cache
|
||||
# (https://github.com/python/typeshed/issues/11220)
|
||||
"--no-incremental",
|
||||
# Not useful for the test cases
|
||||
"--disable-error-code=empty-body",
|
||||
]
|
||||
|
||||
if package.is_stdlib:
|
||||
python_exe = sys.executable
|
||||
custom_typeshed = TS_BASE_PATH
|
||||
flags.append("--no-site-packages")
|
||||
configurations = []
|
||||
else:
|
||||
custom_typeshed = tempdir / TYPESHED
|
||||
env_vars["MYPYPATH"] = os.pathsep.join(map(str, custom_typeshed.glob("stubs/*")))
|
||||
has_non_types_dependencies = (tempdir / VENV_DIR).exists()
|
||||
if has_non_types_dependencies:
|
||||
python_exe = str(venv_python(tempdir / VENV_DIR))
|
||||
else:
|
||||
configurations = mypy_configuration_from_distribution(package.name)
|
||||
|
||||
with temporary_mypy_config_file(configurations) as temp:
|
||||
|
||||
# "--enable-error-code ignore-without-code" is purposefully omitted.
|
||||
# See https://github.com/python/typeshed/pull/8083
|
||||
flags = [
|
||||
"--python-version",
|
||||
version,
|
||||
"--show-traceback",
|
||||
"--no-error-summary",
|
||||
"--platform",
|
||||
platform,
|
||||
"--strict",
|
||||
"--pretty",
|
||||
"--config-file",
|
||||
temp.name,
|
||||
# Avoid race conditions when reading the cache
|
||||
# (https://github.com/python/typeshed/issues/11220)
|
||||
"--no-incremental",
|
||||
# Not useful for the test cases
|
||||
"--disable-error-code=empty-body",
|
||||
]
|
||||
|
||||
if package.is_stdlib:
|
||||
python_exe = sys.executable
|
||||
custom_typeshed = TS_BASE_PATH
|
||||
flags.append("--no-site-packages")
|
||||
|
||||
flags.extend(["--custom-typeshed-dir", str(custom_typeshed)])
|
||||
|
||||
# If the test-case filename ends with -py39,
|
||||
# only run the test if --python-version was set to 3.9 or higher (for example)
|
||||
for path in new_test_case_dir.rglob("*.py"):
|
||||
if match := re.fullmatch(r".*-py3(\d{1,2})", path.stem):
|
||||
minor_version_required = int(match[1])
|
||||
assert f"3.{minor_version_required}" in SUPPORTED_VERSIONS
|
||||
python_minor_version = int(version.split(".")[1])
|
||||
if minor_version_required > python_minor_version:
|
||||
continue
|
||||
flags.append(str(path))
|
||||
|
||||
mypy_command = [python_exe, "-m", "mypy", *flags]
|
||||
if verbosity is Verbosity.VERBOSE:
|
||||
description = f"{package.name}/{version}/{platform}"
|
||||
msg = f"{description}: {mypy_command=}\n"
|
||||
if "MYPYPATH" in env_vars:
|
||||
msg += f"{description}: {env_vars['MYPYPATH']=}"
|
||||
else:
|
||||
msg += f"{description}: MYPYPATH not set"
|
||||
msg += "\n"
|
||||
verbose_log(msg)
|
||||
return subprocess.run(mypy_command, capture_output=True, text=True, env=env_vars, check=False)
|
||||
custom_typeshed = tempdir / TYPESHED
|
||||
env_vars["MYPYPATH"] = os.pathsep.join(map(str, custom_typeshed.glob("stubs/*")))
|
||||
has_non_types_dependencies = (tempdir / VENV_DIR).exists()
|
||||
if has_non_types_dependencies:
|
||||
python_exe = str(venv_python(tempdir / VENV_DIR))
|
||||
else:
|
||||
python_exe = sys.executable
|
||||
flags.append("--no-site-packages")
|
||||
|
||||
flags.extend(["--custom-typeshed-dir", str(custom_typeshed)])
|
||||
|
||||
# If the test-case filename ends with -py39,
|
||||
# only run the test if --python-version was set to 3.9 or higher (for example)
|
||||
for path in new_test_case_dir.rglob("*.py"):
|
||||
if match := re.fullmatch(r".*-py3(\d{1,2})", path.stem):
|
||||
minor_version_required = int(match[1])
|
||||
assert f"3.{minor_version_required}" in SUPPORTED_VERSIONS
|
||||
python_minor_version = int(version.split(".")[1])
|
||||
if minor_version_required > python_minor_version:
|
||||
continue
|
||||
flags.append(str(path))
|
||||
|
||||
mypy_command = [python_exe, "-m", "mypy", *flags]
|
||||
if verbosity is Verbosity.VERBOSE:
|
||||
description = f"{package.name}/{version}/{platform}"
|
||||
msg = f"{description}: {mypy_command=}\n"
|
||||
if "MYPYPATH" in env_vars:
|
||||
msg += f"{description}: {env_vars['MYPYPATH']=}"
|
||||
else:
|
||||
msg += f"{description}: MYPYPATH not set"
|
||||
msg += "\n"
|
||||
verbose_log(msg)
|
||||
return subprocess.run(mypy_command, capture_output=True, text=True, env=env_vars, check=False)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
@@ -16,6 +16,7 @@ from time import time
|
||||
from typing import NoReturn
|
||||
|
||||
from ts_utils.metadata import NoSuchStubError, get_recursive_requirements, read_metadata
|
||||
from ts_utils.mypy import mypy_configuration_from_distribution, temporary_mypy_config_file
|
||||
from ts_utils.paths import STUBS_PATH, allowlists_path, tests_path
|
||||
from ts_utils.utils import (
|
||||
PYTHON_VERSION,
|
||||
@@ -95,89 +96,95 @@ def run_stubtest(
|
||||
print_command_failure("Failed to install", e)
|
||||
return False
|
||||
|
||||
ignore_missing_stub = ["--ignore-missing-stub"] if stubtest_settings.ignore_missing_stub else []
|
||||
packages_to_check = [d.name for d in dist.iterdir() if d.is_dir() and d.name.isidentifier()]
|
||||
modules_to_check = [d.stem for d in dist.iterdir() if d.is_file() and d.suffix == ".pyi"]
|
||||
stubtest_cmd = [
|
||||
python_exe,
|
||||
"-m",
|
||||
"mypy.stubtest",
|
||||
# Use --custom-typeshed-dir in case we make linked changes to stdlib or _typeshed
|
||||
"--custom-typeshed-dir",
|
||||
str(dist.parent.parent),
|
||||
*ignore_missing_stub,
|
||||
*packages_to_check,
|
||||
*modules_to_check,
|
||||
*allowlist_stubtest_arguments(dist_name),
|
||||
]
|
||||
mypy_configuration = mypy_configuration_from_distribution(dist_name)
|
||||
with temporary_mypy_config_file(mypy_configuration) as temp:
|
||||
ignore_missing_stub = ["--ignore-missing-stub"] if stubtest_settings.ignore_missing_stub else []
|
||||
packages_to_check = [d.name for d in dist.iterdir() if d.is_dir() and d.name.isidentifier()]
|
||||
modules_to_check = [d.stem for d in dist.iterdir() if d.is_file() and d.suffix == ".pyi"]
|
||||
stubtest_cmd = [
|
||||
python_exe,
|
||||
"-m",
|
||||
"mypy.stubtest",
|
||||
"--mypy-config-file",
|
||||
temp.name,
|
||||
# Use --custom-typeshed-dir in case we make linked changes to stdlib or _typeshed
|
||||
"--custom-typeshed-dir",
|
||||
str(dist.parent.parent),
|
||||
*ignore_missing_stub,
|
||||
*packages_to_check,
|
||||
*modules_to_check,
|
||||
*allowlist_stubtest_arguments(dist_name),
|
||||
]
|
||||
|
||||
stubs_dir = dist.parent
|
||||
mypypath_items = [str(dist)] + [str(stubs_dir / pkg.name) for pkg in requirements.typeshed_pkgs]
|
||||
mypypath = os.pathsep.join(mypypath_items)
|
||||
# For packages that need a display, we need to pass at least $DISPLAY
|
||||
# to stubtest. $DISPLAY is set by xvfb-run in CI.
|
||||
#
|
||||
# It seems that some other environment variables are needed too,
|
||||
# because the CI fails if we pass only os.environ["DISPLAY"]. I didn't
|
||||
# "bisect" to see which variables are actually needed.
|
||||
stubtest_env = os.environ | {"MYPYPATH": mypypath, "MYPY_FORCE_COLOR": "1"}
|
||||
stubs_dir = dist.parent
|
||||
mypypath_items = [str(dist)] + [str(stubs_dir / pkg.name) for pkg in requirements.typeshed_pkgs]
|
||||
mypypath = os.pathsep.join(mypypath_items)
|
||||
# For packages that need a display, we need to pass at least $DISPLAY
|
||||
# to stubtest. $DISPLAY is set by xvfb-run in CI.
|
||||
#
|
||||
# It seems that some other environment variables are needed too,
|
||||
# because the CI fails if we pass only os.environ["DISPLAY"]. I didn't
|
||||
# "bisect" to see which variables are actually needed.
|
||||
stubtest_env = os.environ | {"MYPYPATH": mypypath, "MYPY_FORCE_COLOR": "1"}
|
||||
|
||||
# Perform some black magic in order to run stubtest inside uWSGI
|
||||
if dist_name == "uWSGI":
|
||||
if not setup_uwsgi_stubtest_command(dist, venv_dir, stubtest_cmd):
|
||||
return False
|
||||
# Perform some black magic in order to run stubtest inside uWSGI
|
||||
if dist_name == "uWSGI":
|
||||
if not setup_uwsgi_stubtest_command(dist, venv_dir, stubtest_cmd):
|
||||
return False
|
||||
|
||||
if dist_name == "gdb":
|
||||
if not setup_gdb_stubtest_command(venv_dir, stubtest_cmd):
|
||||
return False
|
||||
if dist_name == "gdb":
|
||||
if not setup_gdb_stubtest_command(venv_dir, stubtest_cmd):
|
||||
return False
|
||||
|
||||
try:
|
||||
subprocess.run(stubtest_cmd, env=stubtest_env, check=True, capture_output=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print_time(time() - t)
|
||||
print_error("fail")
|
||||
try:
|
||||
subprocess.run(stubtest_cmd, env=stubtest_env, check=True, capture_output=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print_time(time() - t)
|
||||
print_error("fail")
|
||||
|
||||
print_divider()
|
||||
print("Commands run:")
|
||||
print_commands(pip_cmd, stubtest_cmd, mypypath)
|
||||
print_divider()
|
||||
print("Commands run:")
|
||||
print_commands(pip_cmd, stubtest_cmd, mypypath)
|
||||
|
||||
print_divider()
|
||||
print("Command output:\n")
|
||||
print_command_output(e)
|
||||
print_divider()
|
||||
print("Command output:\n")
|
||||
print_command_output(e)
|
||||
|
||||
print_divider()
|
||||
print("Python version: ", end="", flush=True)
|
||||
ret = subprocess.run([sys.executable, "-VV"], capture_output=True, check=False)
|
||||
print_command_output(ret)
|
||||
|
||||
print("\nRan with the following environment:")
|
||||
ret = subprocess.run([pip_exe, "freeze", "--all"], capture_output=True, check=False)
|
||||
print_command_output(ret)
|
||||
if keep_tmp_dir:
|
||||
print("Path to virtual environment:", venv_dir, flush=True)
|
||||
|
||||
print_divider()
|
||||
main_allowlist_path = allowlists_path(dist_name) / "stubtest_allowlist.txt"
|
||||
if main_allowlist_path.exists():
|
||||
print(f'To fix "unused allowlist" errors, remove the corresponding entries from {main_allowlist_path}')
|
||||
print()
|
||||
else:
|
||||
print(f"Re-running stubtest with --generate-allowlist.\nAdd the following to {main_allowlist_path}:")
|
||||
ret = subprocess.run([*stubtest_cmd, "--generate-allowlist"], env=stubtest_env, capture_output=True, check=False)
|
||||
print_divider()
|
||||
print("Python version: ", end="", flush=True)
|
||||
ret = subprocess.run([sys.executable, "-VV"], capture_output=True, check=False)
|
||||
print_command_output(ret)
|
||||
|
||||
print_divider()
|
||||
print(f"Upstream repository: {metadata.upstream_repository}")
|
||||
print(f"Typeshed source code: https://github.com/python/typeshed/tree/main/stubs/{dist.name}")
|
||||
print("\nRan with the following environment:")
|
||||
ret = subprocess.run([pip_exe, "freeze", "--all"], capture_output=True, check=False)
|
||||
print_command_output(ret)
|
||||
if keep_tmp_dir:
|
||||
print("Path to virtual environment:", venv_dir, flush=True)
|
||||
|
||||
print_divider()
|
||||
print_divider()
|
||||
main_allowlist_path = allowlists_path(dist_name) / "stubtest_allowlist.txt"
|
||||
if main_allowlist_path.exists():
|
||||
print(f'To fix "unused allowlist" errors, remove the corresponding entries from {main_allowlist_path}')
|
||||
print()
|
||||
else:
|
||||
print(f"Re-running stubtest with --generate-allowlist.\nAdd the following to {main_allowlist_path}:")
|
||||
ret = subprocess.run(
|
||||
[*stubtest_cmd, "--generate-allowlist"], env=stubtest_env, capture_output=True, check=False
|
||||
)
|
||||
print_command_output(ret)
|
||||
|
||||
return False
|
||||
else:
|
||||
print_time(time() - t)
|
||||
print_success_msg()
|
||||
if keep_tmp_dir:
|
||||
print_info(f"Virtual environment kept at: {venv_dir}")
|
||||
print_divider()
|
||||
print(f"Upstream repository: {metadata.upstream_repository}")
|
||||
print(f"Typeshed source code: https://github.com/python/typeshed/tree/main/stubs/{dist.name}")
|
||||
|
||||
print_divider()
|
||||
|
||||
return False
|
||||
else:
|
||||
print_time(time() - t)
|
||||
print_success_msg()
|
||||
if keep_tmp_dir:
|
||||
print_info(f"Virtual environment kept at: {venv_dir}")
|
||||
finally:
|
||||
if not keep_tmp_dir:
|
||||
rmtree(venv_dir)
|
||||
|
||||
Reference in New Issue
Block a user