Buckets:
| import importlib.util | |
| import os | |
| import re | |
| import shutil | |
| import textwrap | |
| from collections import defaultdict | |
| from typing import TYPE_CHECKING | |
| import pytest | |
| # Only trigger a full `mypy` run if this environment variable is set | |
| # Note that these tests tend to take over a minute even on a macOS M1 CPU, | |
| # and more than that in CI. | |
| RUN_MYPY = "NPY_RUN_MYPY_IN_TESTSUITE" in os.environ | |
| if RUN_MYPY and RUN_MYPY not in ('0', '', 'false'): | |
| RUN_MYPY = True | |
| # Skips all functions in this file | |
| pytestmark = pytest.mark.skipif( | |
| not RUN_MYPY, | |
| reason="`NPY_RUN_MYPY_IN_TESTSUITE` not set" | |
| ) | |
| try: | |
| from mypy import api | |
| except ImportError: | |
| NO_MYPY = True | |
| else: | |
| NO_MYPY = False | |
| if TYPE_CHECKING: | |
| from collections.abc import Iterator | |
| # We need this as annotation, but it's located in a private namespace. | |
| # As a compromise, do *not* import it during runtime | |
| from _pytest.mark.structures import ParameterSet | |
| DATA_DIR = os.path.join(os.path.dirname(__file__), "data") | |
| PASS_DIR = os.path.join(DATA_DIR, "pass") | |
| FAIL_DIR = os.path.join(DATA_DIR, "fail") | |
| REVEAL_DIR = os.path.join(DATA_DIR, "reveal") | |
| MISC_DIR = os.path.join(DATA_DIR, "misc") | |
| MYPY_INI = os.path.join(DATA_DIR, "mypy.ini") | |
| CACHE_DIR = os.path.join(DATA_DIR, ".mypy_cache") | |
| #: A dictionary with file names as keys and lists of the mypy stdout as values. | |
| #: To-be populated by `run_mypy`. | |
| OUTPUT_MYPY: defaultdict[str, list[str]] = defaultdict(list) | |
| def _key_func(key: str) -> str: | |
| """Split at the first occurrence of the ``:`` character. | |
| Windows drive-letters (*e.g.* ``C:``) are ignored herein. | |
| """ | |
| drive, tail = os.path.splitdrive(key) | |
| return os.path.join(drive, tail.split(":", 1)[0]) | |
| def _strip_filename(msg: str) -> tuple[int, str]: | |
| """Strip the filename and line number from a mypy message.""" | |
| _, tail = os.path.splitdrive(msg) | |
| _, lineno, msg = tail.split(":", 2) | |
| return int(lineno), msg.strip() | |
| def strip_func(match: re.Match[str]) -> str: | |
| """`re.sub` helper function for stripping module names.""" | |
| return match.groups()[1] | |
| def run_mypy() -> None: | |
| """Clears the cache and run mypy before running any of the typing tests. | |
| The mypy results are cached in `OUTPUT_MYPY` for further use. | |
| The cache refresh can be skipped using | |
| NUMPY_TYPING_TEST_CLEAR_CACHE=0 pytest numpy/typing/tests | |
| """ | |
| if ( | |
| os.path.isdir(CACHE_DIR) | |
| and bool(os.environ.get("NUMPY_TYPING_TEST_CLEAR_CACHE", True)) # noqa: PLW1508 | |
| ): | |
| shutil.rmtree(CACHE_DIR) | |
| split_pattern = re.compile(r"(\s+)?\^(\~+)?") | |
| for directory in (PASS_DIR, REVEAL_DIR, FAIL_DIR, MISC_DIR): | |
| # Run mypy | |
| stdout, stderr, exit_code = api.run([ | |
| "--config-file", | |
| MYPY_INI, | |
| "--cache-dir", | |
| CACHE_DIR, | |
| directory, | |
| ]) | |
| if stderr: | |
| pytest.fail(f"Unexpected mypy standard error\n\n{stderr}", False) | |
| elif exit_code not in {0, 1}: | |
| pytest.fail(f"Unexpected mypy exit code: {exit_code}\n\n{stdout}", False) | |
| str_concat = "" | |
| filename: str | None = None | |
| for i in stdout.split("\n"): | |
| if "note:" in i: | |
| continue | |
| if filename is None: | |
| filename = _key_func(i) | |
| str_concat += f"{i}\n" | |
| if split_pattern.match(i) is not None: | |
| OUTPUT_MYPY[filename].append(str_concat) | |
| str_concat = "" | |
| filename = None | |
| def get_test_cases(*directories: str) -> "Iterator[ParameterSet]": | |
| for directory in directories: | |
| for root, _, files in os.walk(directory): | |
| for fname in files: | |
| short_fname, ext = os.path.splitext(fname) | |
| if ext not in (".pyi", ".py"): | |
| continue | |
| fullpath = os.path.join(root, fname) | |
| yield pytest.param(fullpath, id=short_fname) | |
| _FAIL_INDENT = " " * 4 | |
| _FAIL_SEP = "\n" + "_" * 79 + "\n\n" | |
| _FAIL_MSG_REVEAL = """{}:{} - reveal mismatch: | |
| {}""" | |
| def test_pass(path) -> None: | |
| # Alias `OUTPUT_MYPY` so that it appears in the local namespace | |
| output_mypy = OUTPUT_MYPY | |
| if path not in output_mypy: | |
| return | |
| relpath = os.path.relpath(path) | |
| # collect any reported errors, and clean up the output | |
| messages = [] | |
| for message in output_mypy[path]: | |
| lineno, content = _strip_filename(message) | |
| content = content.removeprefix("error:").lstrip() | |
| messages.append(f"{relpath}:{lineno} - {content}") | |
| if messages: | |
| pytest.fail("\n".join(messages), pytrace=False) | |
| def test_reveal(path: str) -> None: | |
| """Validate that mypy correctly infers the return-types of | |
| the expressions in `path`. | |
| """ | |
| __tracebackhide__ = True | |
| output_mypy = OUTPUT_MYPY | |
| if path not in output_mypy: | |
| return | |
| relpath = os.path.relpath(path) | |
| # collect any reported errors, and clean up the output | |
| failures = [] | |
| for error_line in output_mypy[path]: | |
| lineno, error_msg = _strip_filename(error_line) | |
| error_msg = textwrap.indent(error_msg, _FAIL_INDENT) | |
| reason = _FAIL_MSG_REVEAL.format(relpath, lineno, error_msg) | |
| failures.append(reason) | |
| if failures: | |
| reasons = _FAIL_SEP.join(failures) | |
| pytest.fail(reasons, pytrace=False) | |
| def test_code_runs(path: str) -> None: | |
| """Validate that the code in `path` properly during runtime.""" | |
| path_without_extension, _ = os.path.splitext(path) | |
| dirname, filename = path.split(os.sep)[-2:] | |
| spec = importlib.util.spec_from_file_location( | |
| f"{dirname}.{filename}", path | |
| ) | |
| assert spec is not None | |
| assert spec.loader is not None | |
| test_module = importlib.util.module_from_spec(spec) | |
| spec.loader.exec_module(test_module) | |
Xet Storage Details
- Size:
- 6.29 kB
- Xet hash:
- 78d8cc98b19b37bd6172412efb27aa31bfb79caa36dfc3c7e7a00c629ab8ce04
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.