Files
django-stubs/scripts/typecheck_tests.py

123 lines
4.0 KiB
Python

import itertools
import shutil
import subprocess
import sys
from collections import defaultdict
from pathlib import Path
from typing import Dict, List, Pattern, Union
from scripts.enabled_test_modules import (
EXTERNAL_MODULES, IGNORED_ERRORS, IGNORED_MODULES, MOCK_OBJECTS,
)
PROJECT_DIRECTORY = Path(__file__).parent.parent
def get_unused_ignores(ignored_message_freq: Dict[str, Dict[Union[str, Pattern], int]]) -> List[str]:
unused_ignores = []
for root_key, patterns in IGNORED_ERRORS.items():
for pattern in patterns:
if (ignored_message_freq[root_key][pattern] == 0
and pattern not in itertools.chain(EXTERNAL_MODULES, MOCK_OBJECTS)):
unused_ignores.append(f'{root_key}: {pattern}')
return unused_ignores
def is_pattern_fits(pattern: Union[Pattern, str], line: str):
if isinstance(pattern, Pattern):
if pattern.search(line):
return True
else:
if pattern in line:
return True
return False
def is_ignored(line: str, test_folder_name: str, *, ignored_message_freqs: Dict[str, Dict[str, int]]) -> bool:
if 'runtests' in line:
return True
if test_folder_name in IGNORED_MODULES:
return True
for pattern in IGNORED_ERRORS.get(test_folder_name, []):
if is_pattern_fits(pattern, line):
ignored_message_freqs[test_folder_name][pattern] += 1
return True
for pattern in IGNORED_ERRORS['__common__']:
if is_pattern_fits(pattern, line):
ignored_message_freqs['__common__'][pattern] += 1
return True
return False
def replace_with_clickable_location(error: str, abs_test_folder: Path) -> str:
raw_path, _, error_line = error.partition(': ')
fname, _, line_number = raw_path.partition(':')
try:
path = abs_test_folder.joinpath(fname).relative_to(PROJECT_DIRECTORY)
except ValueError:
# fail on travis, just show an error
return error
clickable_location = f'./{path}:{line_number or 1}'
return error.replace(raw_path, clickable_location)
if __name__ == '__main__':
mypy_config_file = (PROJECT_DIRECTORY / 'scripts' / 'mypy.ini').absolute()
repo_directory = PROJECT_DIRECTORY / 'django-sources'
mypy_cache_dir = Path(__file__).parent / '.mypy_cache'
tests_root = repo_directory / 'tests'
global_rc = 0
try:
mypy_options = ['--cache-dir', str(mypy_config_file.parent / '.mypy_cache'),
'--config-file', str(mypy_config_file),
'--show-traceback',
'--no-error-summary',
'--hide-error-context'
]
mypy_options += [str(tests_root)]
import distutils.spawn
mypy_executable = distutils.spawn.find_executable('mypy')
mypy_argv = [mypy_executable, *mypy_options]
completed = subprocess.run(
mypy_argv,
env={'PYTHONPATH': str(tests_root)},
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
output = completed.stdout.decode()
ignored_message_freqs = defaultdict(lambda: defaultdict(int))
sorted_lines = sorted(output.splitlines())
for line in sorted_lines:
try:
path_to_error = line.split(':')[0]
test_folder_name = path_to_error.split('/')[2]
except IndexError:
test_folder_name = 'unknown'
if not is_ignored(line, test_folder_name,
ignored_message_freqs=ignored_message_freqs):
global_rc = 1
print(line)
unused_ignores = get_unused_ignores(ignored_message_freqs)
if unused_ignores:
print('UNUSED IGNORES ------------------------------------------------')
print('\n'.join(unused_ignores))
sys.exit(global_rc)
except BaseException as exc:
shutil.rmtree(mypy_cache_dir, ignore_errors=True)
raise exc