Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 875f0ca

Browse files
authored
mypy_test.py: Move type-checking of our tests and scripts into a different test (#8587)
1 parent 5ea1b1e commit 875f0ca

5 files changed

Lines changed: 171 additions & 88 deletions

File tree

.github/workflows/tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
- run: ./tests/pytype_test.py --print-stderr
6262

6363
mypy:
64-
name: Run mypy against typeshed
64+
name: Test the stubs with mypy
6565
runs-on: ubuntu-latest
6666
strategy:
6767
matrix:
@@ -77,7 +77,7 @@ jobs:
7777
- run: ./tests/mypy_test.py --platform=${{ matrix.platform }} --python-version=${{ matrix.python-version }}
7878

7979
pyright:
80-
name: Run pyright against typeshed
80+
name: Test the stubs with pyright
8181
runs-on: ubuntu-latest
8282
strategy:
8383
matrix:
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Typecheck-typeshed-code
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- main
8+
- master
9+
pull_request:
10+
paths:
11+
- 'scripts/**'
12+
- 'tests/**'
13+
- '.github/workflows/typecheck_typeshed_code.yml'
14+
- 'requirements-tests.txt'
15+
16+
permissions:
17+
contents: read
18+
19+
env:
20+
PIP_DISABLE_PIP_VERSION_CHECK: 1
21+
22+
jobs:
23+
mypy:
24+
name: Run mypy against the scripts and tests directories
25+
runs-on: ubuntu-latest
26+
strategy:
27+
matrix:
28+
platform: ["linux", "win32"]
29+
fail-fast: false
30+
steps:
31+
- uses: actions/checkout@v3
32+
- uses: actions/setup-python@v4
33+
with:
34+
python-version: 3.9
35+
- run: pip install -r requirements-tests.txt
36+
- run: python ./tests/typecheck_typeshed.py --platform=${{ matrix.platform }}

tests/README.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
This directory contains several tests:
22
- `tests/mypy_test.py`
3-
tests typeshed with [mypy](https://github.com/python/mypy/)
4-
- `tests/pytype_test.py` tests typeshed with
3+
tests the stubs with [mypy](https://github.com/python/mypy/)
4+
- `tests/pytype_test.py` tests the stubs with
55
[pytype](https://github.com/google/pytype/).
6-
- `tests/pyright_test.py` tests typeshed with
6+
- `tests/pyright_test.py` tests the stubs with
77
[pyright](https://github.com/microsoft/pyright).
88
- `tests/check_consistent.py` checks certain files in typeshed remain
99
consistent with each other.
1010
- `tests/stubtest_stdlib.py` checks standard library stubs against the
1111
objects at runtime.
1212
- `tests/stubtest_third_party.py` checks third-party stubs against the
1313
objects at runtime.
14+
- `tests/typecheck_typeshed.py` runs mypy against typeshed's own code
15+
in the `tests` and `scripts` directories.
1416

1517
To run the tests, follow the [setup instructions](../CONTRIBUTING.md#preparing-the-environment)
1618
in the `CONTRIBUTING.md` document. In particular, we recommend running with Python 3.9+.
@@ -22,10 +24,9 @@ Run using:
2224
(.venv3)$ python3 tests/mypy_test.py
2325
```
2426

25-
The test has four parts. Each part uses mypy with slightly different configuration options:
27+
The test has three parts. Each part uses mypy with slightly different configuration options:
2628
- Running mypy on the stdlib stubs
2729
- Running mypy on the third-party stubs
28-
- Running mypy `--strict` on the scripts in the `tests` directory
2930
- Running mypy `--strict` on the regression tests in the `test_cases` directory.
3031

3132
When running mypy on the stubs, this test is shallow — it verifies that all stubs can be
@@ -118,3 +119,14 @@ check on the command line:
118119
For each distribution, stubtest ignores definitions listed in a `@tests/stubtest_allowlist.txt` file,
119120
relative to the distribution. Additional packages that are needed to run stubtest for a
120121
distribution can be added to `@tests/requirements-stubtest.txt`.
122+
123+
## typecheck\_typeshed.py
124+
125+
Run using
126+
```
127+
(.venv3)$ python3 tests/typecheck_typeshed.py
128+
```
129+
130+
This is a small wrapper script that uses mypy to typecheck typeshed's own code in the
131+
`scripts` and `tests` directories. Run `python tests/typecheck_typeshed.py --help` for
132+
information on the various configuration options.

tests/mypy_test.py

Lines changed: 14 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,14 @@
2929
from colors import colored, print_error, print_success_msg
3030

3131
SUPPORTED_VERSIONS = [(3, 11), (3, 10), (3, 9), (3, 8), (3, 7)]
32-
SUPPORTED_PLATFORMS = frozenset({"linux", "win32", "darwin"})
33-
TYPESHED_DIRECTORIES = frozenset({"stdlib", "stubs", "tests", "test_cases", "scripts"})
32+
SUPPORTED_PLATFORMS = ("linux", "win32", "darwin")
33+
TYPESHED_DIRECTORIES = frozenset({"stdlib", "stubs", "test_cases"})
3434

35+
ReturnCode: TypeAlias = int
3536
MajorVersion: TypeAlias = int
3637
MinorVersion: TypeAlias = int
38+
MinVersion: TypeAlias = tuple[MajorVersion, MinorVersion]
39+
MaxVersion: TypeAlias = tuple[MajorVersion, MinorVersion]
3740
Platform: TypeAlias = Annotated[str, "Must be one of the entries in SUPPORTED_PLATFORMS"]
3841
Directory: TypeAlias = Annotated[str, "Must be one of the entries in TYPESHED_DIRECTORIES"]
3942

@@ -122,8 +125,6 @@ def match(fn: str, args: TestConfig) -> bool:
122125

123126

124127
_VERSION_LINE_RE = re.compile(r"^([a-zA-Z_][a-zA-Z0-9_.]*): ([23]\.\d{1,2})-([23]\.\d{1,2})?$")
125-
MinVersion: TypeAlias = tuple[MajorVersion, MinorVersion]
126-
MaxVersion: TypeAlias = tuple[MajorVersion, MinorVersion]
127128

128129

129130
def parse_versions(fname: StrPath) -> dict[str, tuple[MinVersion, MaxVersion]]:
@@ -210,7 +211,7 @@ def add_configuration(configurations: list[MypyDistConf], distribution: str) ->
210211
configurations.append(MypyDistConf(module_name, values.copy()))
211212

212213

213-
def run_mypy(args: TestConfig, configurations: list[MypyDistConf], files: list[str], *, custom_typeshed: bool = False) -> int:
214+
def run_mypy(args: TestConfig, configurations: list[MypyDistConf], files: list[str]) -> ReturnCode:
214215
try:
215216
from mypy.api import run as mypy_run
216217
except ImportError:
@@ -225,7 +226,7 @@ def run_mypy(args: TestConfig, configurations: list[MypyDistConf], files: list[s
225226
temp.write(f"{k} = {v}\n")
226227
temp.flush()
227228

228-
flags = get_mypy_flags(args, temp.name, custom_typeshed=custom_typeshed)
229+
flags = get_mypy_flags(args, temp.name)
229230
mypy_args = [*flags, *files]
230231
if args.verbose:
231232
print("running mypy", " ".join(mypy_args))
@@ -253,9 +254,6 @@ def run_mypy(args: TestConfig, configurations: list[MypyDistConf], files: list[s
253254
return exit_code
254255

255256

256-
ReturnCode: TypeAlias = int
257-
258-
259257
def run_mypy_as_subprocess(directory: StrPath, flags: Iterable[str]) -> ReturnCode:
260258
result = subprocess.run([sys.executable, "-m", "mypy", directory, *flags], capture_output=True)
261259
stdout, stderr = result.stdout, result.stderr
@@ -267,14 +265,7 @@ def run_mypy_as_subprocess(directory: StrPath, flags: Iterable[str]) -> ReturnCo
267265

268266

269267
def get_mypy_flags(
270-
args: TestConfig,
271-
temp_name: str | None,
272-
*,
273-
custom_typeshed: bool = False,
274-
strict: bool = False,
275-
test_suite_run: bool = False,
276-
enforce_error_codes: bool = True,
277-
ignore_missing_imports: bool = False,
268+
args: TestConfig, temp_name: str | None, *, strict: bool = False, enforce_error_codes: bool = True
278269
) -> list[str]:
279270
flags = [
280271
"--python-version",
@@ -285,27 +276,18 @@ def get_mypy_flags(
285276
"--no-error-summary",
286277
"--platform",
287278
args.platform,
279+
"--no-site-packages",
280+
"--custom-typeshed-dir",
281+
os.path.dirname(os.path.dirname(__file__)),
288282
]
289283
if strict:
290284
flags.append("--strict")
291285
else:
292286
flags.extend(["--no-implicit-optional", "--disallow-untyped-decorators", "--disallow-any-generics", "--strict-equality"])
293287
if temp_name is not None:
294288
flags.extend(["--config-file", temp_name])
295-
if custom_typeshed:
296-
# Setting custom typeshed dir prevents mypy from falling back to its bundled
297-
# typeshed in case of stub deletions
298-
flags.extend(["--custom-typeshed-dir", os.path.dirname(os.path.dirname(__file__))])
299-
if test_suite_run:
300-
flags.append("--namespace-packages")
301-
if args.platform == "win32":
302-
flags.extend(["--exclude", "tests/pytype_test.py"])
303-
else:
304-
flags.append("--no-site-packages")
305289
if enforce_error_codes:
306290
flags.extend(["--enable-error-code", "ignore-without-code"])
307-
if ignore_missing_imports:
308-
flags.append("--ignore-missing-imports")
309291
return flags
310292

311293

@@ -389,8 +371,8 @@ def test_stdlib(code: int, args: TestConfig) -> TestResults:
389371

390372
if files:
391373
print(f"Testing stdlib ({len(files)} files)...")
392-
print("Running mypy " + " ".join(get_mypy_flags(args, "/tmp/...", custom_typeshed=True)))
393-
this_code = run_mypy(args, [], files, custom_typeshed=True)
374+
print("Running mypy " + " ".join(get_mypy_flags(args, "/tmp/...")))
375+
this_code = run_mypy(args, [], files)
394376
code = max(code, this_code)
395377

396378
return TestResults(code, len(files))
@@ -414,44 +396,10 @@ def test_third_party_stubs(code: int, args: TestConfig) -> TestResults:
414396
return TestResults(code, files_checked)
415397

416398

417-
def test_the_test_scripts(code: int, args: TestConfig) -> TestResults:
418-
files_to_test = list(Path("tests").rglob("*.py"))
419-
if args.platform == "win32":
420-
files_to_test.remove(Path("tests/pytype_test.py"))
421-
num_test_files_to_test = len(files_to_test)
422-
flags = get_mypy_flags(args, None, strict=True, test_suite_run=True)
423-
print(f"Testing the test suite ({num_test_files_to_test} files)...")
424-
print("Running mypy " + " ".join(flags))
425-
if args.dry_run:
426-
this_code = 0
427-
else:
428-
this_code = run_mypy_as_subprocess("tests", flags)
429-
if not this_code:
430-
print_success_msg()
431-
code = max(code, this_code)
432-
return TestResults(code, num_test_files_to_test)
433-
434-
435-
def test_scripts_directory(code: int, args: TestConfig) -> TestResults:
436-
files_to_test = list(Path("scripts").rglob("*.py"))
437-
num_test_files_to_test = len(files_to_test)
438-
flags = get_mypy_flags(args, None, strict=True, ignore_missing_imports=True)
439-
print(f"Testing the scripts directory ({num_test_files_to_test} files)...")
440-
print("Running mypy " + " ".join(flags))
441-
if args.dry_run:
442-
this_code = 0
443-
else:
444-
this_code = run_mypy_as_subprocess("scripts", flags)
445-
if not this_code:
446-
print_success_msg()
447-
code = max(code, this_code)
448-
return TestResults(code, num_test_files_to_test)
449-
450-
451399
def test_the_test_cases(code: int, args: TestConfig) -> TestResults:
452400
test_case_files = list(map(str, Path("test_cases").rglob("*.py")))
453401
num_test_case_files = len(test_case_files)
454-
flags = get_mypy_flags(args, None, strict=True, custom_typeshed=True, enforce_error_codes=False)
402+
flags = get_mypy_flags(args, None, strict=True, enforce_error_codes=False)
455403
print(f"Running mypy on the test_cases directory ({num_test_case_files} files)...")
456404
print("Running mypy " + " ".join(flags))
457405
if args.dry_run:
@@ -481,21 +429,6 @@ def test_typeshed(code: int, args: TestConfig) -> TestResults:
481429
files_checked_this_version += third_party_files_checked
482430
print()
483431

484-
if args.minor >= 9:
485-
# Run mypy against our own test suite and the scripts directory
486-
#
487-
# Skip this on earlier Python versions,
488-
# as we're using new syntax and new functions in some test files
489-
if "tests" in args.directories:
490-
code, test_script_files_checked = test_the_test_scripts(code, args)
491-
files_checked_this_version += test_script_files_checked
492-
print()
493-
494-
if "scripts" in args.directories:
495-
code, script_files_checked = test_scripts_directory(code, args)
496-
files_checked_this_version += script_files_checked
497-
print()
498-
499432
if "test_cases" in args.directories:
500433
code, test_case_files_checked = test_the_test_cases(code, args)
501434
files_checked_this_version += test_case_files_checked

tests/typecheck_typeshed.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env python3
2+
"""Run mypy on the "tests" and "scripts" directories."""
3+
from __future__ import annotations
4+
5+
import argparse
6+
import subprocess
7+
import sys
8+
from itertools import product
9+
from typing_extensions import TypeAlias
10+
11+
from colors import colored, print_error
12+
13+
ReturnCode: TypeAlias = int
14+
15+
SUPPORTED_PLATFORMS = ("linux", "darwin", "win32")
16+
SUPPORTED_VERSIONS = ("3.11", "3.10", "3.9")
17+
DIRECTORIES_TO_TEST = ("scripts", "tests")
18+
19+
parser = argparse.ArgumentParser(description="Run mypy on typeshed's own code in the `scripts` and `tests` directories.")
20+
parser.add_argument(
21+
"--platform",
22+
choices=SUPPORTED_PLATFORMS,
23+
nargs="*",
24+
action="extend",
25+
help="Run mypy for certain OS platforms (defaults to sys.platform)",
26+
)
27+
parser.add_argument(
28+
"-p",
29+
"--python-version",
30+
choices=SUPPORTED_VERSIONS,
31+
nargs="*",
32+
action="extend",
33+
help="Run mypy for certain Python versions (defaults to sys.version_info[:2])",
34+
)
35+
parser.add_argument(
36+
"-d",
37+
"--dir",
38+
choices=DIRECTORIES_TO_TEST,
39+
nargs="*",
40+
action="extend",
41+
help=f"Test only these top-level typeshed directories (defaults to {DIRECTORIES_TO_TEST!r})",
42+
)
43+
44+
45+
def run_mypy_as_subprocess(directory: str, platform: str, version: str) -> ReturnCode:
46+
command = [
47+
sys.executable,
48+
"-m",
49+
"mypy",
50+
directory,
51+
"--platform",
52+
platform,
53+
"--python-version",
54+
version,
55+
"--strict",
56+
"--show-traceback",
57+
"--show-error-codes",
58+
"--no-error-summary",
59+
"--enable-error-code",
60+
"ignore-without-code",
61+
"--namespace-packages",
62+
]
63+
if directory == "tests":
64+
if platform == "win32":
65+
command.extend(["--exclude", "tests/pytype_test.py"])
66+
else:
67+
command.append("--ignore-missing-imports")
68+
result = subprocess.run(command, capture_output=True)
69+
stdout, stderr = result.stdout, result.stderr
70+
if stderr:
71+
print_error(stderr.decode())
72+
if stdout:
73+
print_error(stdout.decode())
74+
return result.returncode
75+
76+
77+
def main() -> ReturnCode:
78+
args = parser.parse_args()
79+
directories = args.dir or DIRECTORIES_TO_TEST
80+
platforms = args.platform or [sys.platform]
81+
versions = args.python_version or [f"3.{sys.version_info[1]}"]
82+
83+
code = 0
84+
85+
for directory, platform, version in product(directories, platforms, versions):
86+
print(f'Running "mypy --platform {platform} --python-version {version}" on the "{directory}" directory...')
87+
code = max(code, run_mypy_as_subprocess(directory, platform, version))
88+
89+
if code:
90+
print_error("Test completed with errors")
91+
else:
92+
print(colored("Test completed successfully!", "green"))
93+
return code
94+
95+
96+
if __name__ == "__main__":
97+
try:
98+
code = main()
99+
except KeyboardInterrupt:
100+
print_error("\n\n!!!\nTest aborted due to KeyboardInterrupt\n!!!")
101+
code = 1
102+
raise SystemExit(code)

0 commit comments

Comments
 (0)