add tests for the django test suite

This commit is contained in:
Maxim Kurnikov
2019-01-26 17:17:35 +03:00
parent 38e841c4c7
commit 1afa079b0b
18 changed files with 232 additions and 57 deletions

View File

@@ -7,21 +7,21 @@ sudo: required
jobs: jobs:
include: include:
- name: "Run plugin test suite with python 3.7" - name: "Typecheck Django test suite"
python: 3.7 python: 3.7
script: | script: |
set -e xonsh ./scripts/typecheck_django_tests.xsh
pytest
- name: "Lint with black" - name: "Run plugin test suite with python 3.7"
python: 3.7 python: 3.7
script: | script: |
black --check --line-length=120 django-stubs/ set -e
pytest
# - name: "Typecheck Django test suite" - name: "Lint with black"
# python: 3.7 python: 3.7
# script: | script: |
# xonsh ./scripts/typecheck_django_tests.xsh black --check --line-length=120 django-stubs/
before_install: | before_install: |
# Upgrade pip, setuptools, and wheel # Upgrade pip, setuptools, and wheel

View File

@@ -1,4 +1,5 @@
from typing import Any from typing import Any
from django.utils.version import get_version as get_version
VERSION: Any VERSION: Any

View File

@@ -1,7 +1,7 @@
# Stubs for django.conf.urls (Python 3.5) # Stubs for django.conf.urls (Python 3.5)
from typing import Any, Callable, Dict, List, Optional, overload, Tuple, Union from typing import Any, Callable, Dict, List, Optional, overload, Tuple, Union
from django.http.response import HttpResponse from django.http.response import HttpResponse, HttpResponseBase
from django.urls import URLResolver, URLPattern from django.urls import URLResolver, URLPattern
@@ -14,7 +14,9 @@ IncludedURLConf = Tuple[List[URLResolver], Optional[str], Optional[str]]
def include(arg: Any, namespace: str = ..., app_name: str = ...) -> IncludedURLConf: ... def include(arg: Any, namespace: str = ..., app_name: str = ...) -> IncludedURLConf: ...
@overload @overload
def url(regex: str, view: Callable[..., HttpResponse], kwargs: Dict[str, Any] = ..., name: str = ...) -> URLPattern: ... def url(
regex: str, view: Callable[..., HttpResponseBase], kwargs: Dict[str, Any] = ..., name: str = ...
) -> URLPattern: ...
@overload @overload
def url(regex: str, view: IncludedURLConf, kwargs: Dict[str, Any] = ..., name: str = ...) -> URLResolver: ... def url(regex: str, view: IncludedURLConf, kwargs: Dict[str, Any] = ..., name: str = ...) -> URLResolver: ...
@overload @overload

View File

@@ -0,0 +1 @@
from .messages import Warning as Warning, Info as Info, Debug as Debug, Error as Error, Critical as Critical

View File

@@ -1,4 +1,4 @@
from typing import Any, Optional, Union from typing import Any, Optional
DEBUG: int DEBUG: int
INFO: int INFO: int

View File

@@ -1,4 +1,4 @@
from typing import Any, List, Optional from typing import Any, List
from django.core.checks.messages import Warning from django.core.checks.messages import Warning

View File

@@ -31,10 +31,15 @@ class FileSystemStorage(Storage):
file_permissions_mode: Optional[int] = ..., file_permissions_mode: Optional[int] = ...,
directory_permissions_mode: Optional[int] = ..., directory_permissions_mode: Optional[int] = ...,
) -> None: ... ) -> None: ...
@property
def base_location(self) -> str: ... def base_location(self) -> str: ...
@property
def location(self) -> str: ... def location(self) -> str: ...
@property
def base_url(self) -> str: ... def base_url(self) -> str: ...
@property
def file_permissions_mode(self) -> Optional[int]: ... def file_permissions_mode(self) -> Optional[int]: ...
@property
def directory_permissions_mode(self) -> Optional[int]: ... def directory_permissions_mode(self) -> Optional[int]: ...
class DefaultStorage(LazyObject): ... class DefaultStorage(LazyObject): ...

View File

@@ -1,6 +1,6 @@
from typing import Any, Dict, List, Optional, Tuple, Union from typing import Any, Dict, List, Optional, Tuple, Union
from django.core.management.base import BaseCommand as BaseCommand from django.core.management.base import BaseCommand as BaseCommand, CommandError as CommandError
def find_commands(management_dir: str) -> List[str]: ... def find_commands(management_dir: str) -> List[str]: ...
def load_command_class(app_name: str, name: str) -> BaseCommand: ... def load_command_class(app_name: str, name: str) -> BaseCommand: ...

View File

@@ -60,7 +60,7 @@ class BaseCommand:
def add_arguments(self, parser: CommandParser) -> None: ... def add_arguments(self, parser: CommandParser) -> None: ...
def print_help(self, prog_name: str, subcommand: str) -> None: ... def print_help(self, prog_name: str, subcommand: str) -> None: ...
def run_from_argv(self, argv: List[str]) -> None: ... def run_from_argv(self, argv: List[str]) -> None: ...
def execute(self, *args: Any, **options: Any) -> Optional[Union[Tuple, str]]: ... def execute(self, *args: Any, **options: Any) -> Any: ...
def check( def check(
self, self,
app_configs: Optional[List[AppConfig]] = ..., app_configs: Optional[List[AppConfig]] = ...,

View File

@@ -11,6 +11,7 @@ from .utils import (
NotSupportedError as NotSupportedError, NotSupportedError as NotSupportedError,
InternalError as InternalError, InternalError as InternalError,
InterfaceError as InterfaceError, InterfaceError as InterfaceError,
ConnectionHandler as ConnectionHandler,
) )
from . import migrations from . import migrations

View File

@@ -1,9 +1,35 @@
from typing import Any, Optional from typing import Any, Optional, Tuple, Iterable, Callable, Dict, Union
from django.db.models.query_utils import RegisterLookupMixin from django.db.models.query_utils import RegisterLookupMixin
_Choice = Tuple[Any, str]
_ChoiceNamedGroup = Tuple[str, Iterable[_Choice]]
_FieldChoices = Iterable[Union[_Choice, _ChoiceNamedGroup]]
_ValidatorCallable = Callable[[...], None]
_ErrorMessagesToOverride = Dict[str, Any]
class Field(RegisterLookupMixin): class Field(RegisterLookupMixin):
def __init__(self, **kwargs: Any): ... def __init__(
self,
verbose_name: Optional[str] = ...,
name: Optional[str] = ...,
primary_key: bool = ...,
unique: bool = ...,
blank: bool = ...,
null: bool = ...,
db_index: bool = ...,
default: Any = ...,
editable: bool = ...,
auto_created: bool = ...,
serialize: bool = ...,
choices: Optional[_FieldChoices] = ...,
help_text: str = ...,
db_column: Optional[str] = ...,
db_tablespace: Optional[str] = ...,
validators: Iterable[_ValidatorCallable] = ...,
error_messages: Optional[_ErrorMessagesToOverride] = ...,
): ...
def __get__(self, instance, owner) -> Any: ... def __get__(self, instance, owner) -> Any: ...
class IntegerField(Field): class IntegerField(Field):
@@ -19,24 +45,83 @@ class BigIntegerField(IntegerField): ...
class FloatField(Field): ... class FloatField(Field): ...
class DecimalField(IntegerField): class DecimalField(IntegerField):
def __init__(self, *, max_digits: Optional[int] = ..., decimal_places: Optional[int] = ..., **kwargs): ... def __init__(
self,
verbose_name: Optional[str] = ...,
name: Optional[str] = ...,
max_digits: Optional[int] = ...,
decimal_places: Optional[int] = ...,
primary_key: bool = ...,
unique: bool = ...,
blank: bool = ...,
null: bool = ...,
db_index: bool = ...,
default: Any = ...,
editable: bool = ...,
auto_created: bool = ...,
serialize: bool = ...,
choices: Optional[_FieldChoices] = ...,
help_text: str = ...,
db_column: Optional[str] = ...,
db_tablespace: Optional[str] = ...,
validators: Iterable[_ValidatorCallable] = ...,
error_messages: Optional[_ErrorMessagesToOverride] = ...,
): ...
class AutoField(Field): class AutoField(Field):
def __get__(self, instance, owner) -> int: ... def __get__(self, instance, owner) -> int: ...
class CharField(Field): class CharField(Field):
def __init__(self, max_length: int = ..., **kwargs: Any): ... def __init__(
self,
verbose_name: Optional[str] = ...,
name: Optional[str] = ...,
primary_key: bool = ...,
max_length: Optional[int] = ...,
unique: bool = ...,
blank: bool = ...,
null: bool = ...,
db_index: bool = ...,
default: Any = ...,
editable: bool = ...,
auto_created: bool = ...,
serialize: bool = ...,
choices: Optional[_FieldChoices] = ...,
help_text: str = ...,
db_column: Optional[str] = ...,
db_tablespace: Optional[str] = ...,
validators: Iterable[_ValidatorCallable] = ...,
error_messages: Optional[_ErrorMessagesToOverride] = ...,
): ...
def __set__(self, instance, value: str) -> None: ... def __set__(self, instance, value: str) -> None: ...
def __get__(self, instance, owner) -> str: ... def __get__(self, instance, owner) -> str: ...
class SlugField(CharField): class SlugField(CharField):
def __init__(self, max_length: int = ..., **kwargs: Any): ... def __init__(
self,
verbose_name: Optional[str] = ...,
name: Optional[str] = ...,
primary_key: bool = ...,
max_length: Optional[int] = ...,
allow_unicode: bool = ...,
unique: bool = ...,
blank: bool = ...,
null: bool = ...,
db_index: bool = ...,
default: Any = ...,
editable: bool = ...,
auto_created: bool = ...,
serialize: bool = ...,
choices: Optional[_FieldChoices] = ...,
help_text: str = ...,
db_column: Optional[str] = ...,
db_tablespace: Optional[str] = ...,
validators: Iterable[_ValidatorCallable] = ...,
error_messages: Optional[_ErrorMessagesToOverride] = ...,
): ...
class EmailField(CharField): class EmailField(CharField): ...
def __init__(self, max_length: int = ..., **kwargs: Any): ... class URLField(CharField): ...
class URLField(CharField):
def __init__(self, max_length: int = ..., **kwargs: Any): ...
class TextField(Field): class TextField(Field):
def __set__(self, instance, value: str) -> None: ... def __set__(self, instance, value: str) -> None: ...
@@ -63,7 +148,21 @@ class GenericIPAddressField(Field):
name: Optional[Any] = ..., name: Optional[Any] = ...,
protocol: str = ..., protocol: str = ...,
unpack_ipv4: bool = ..., unpack_ipv4: bool = ...,
**kwargs: Any primary_key: bool = ...,
unique: bool = ...,
blank: bool = ...,
null: bool = ...,
db_index: bool = ...,
default: Any = ...,
editable: bool = ...,
auto_created: bool = ...,
serialize: bool = ...,
choices: Optional[_FieldChoices] = ...,
help_text: str = ...,
db_column: Optional[str] = ...,
db_tablespace: Optional[str] = ...,
validators: Iterable[_ValidatorCallable] = ...,
error_messages: Optional[_ErrorMessagesToOverride] = ...,
) -> None: ... ) -> None: ...
class DateTimeCheckMixin: ... class DateTimeCheckMixin: ...
@@ -75,7 +174,21 @@ class DateField(DateTimeCheckMixin, Field):
name: Optional[str] = ..., name: Optional[str] = ...,
auto_now: bool = ..., auto_now: bool = ...,
auto_now_add: bool = ..., auto_now_add: bool = ...,
**kwargs primary_key: bool = ...,
unique: bool = ...,
blank: bool = ...,
null: bool = ...,
db_index: bool = ...,
default: Any = ...,
editable: bool = ...,
auto_created: bool = ...,
serialize: bool = ...,
choices: Optional[_FieldChoices] = ...,
help_text: str = ...,
db_column: Optional[str] = ...,
db_tablespace: Optional[str] = ...,
validators: Iterable[_ValidatorCallable] = ...,
error_messages: Optional[_ErrorMessagesToOverride] = ...,
): ... ): ...
class TimeField(DateTimeCheckMixin, Field): ... class TimeField(DateTimeCheckMixin, Field): ...
@@ -92,10 +205,22 @@ class FilePathField(Field):
recursive: bool = ..., recursive: bool = ...,
allow_files: bool = ..., allow_files: bool = ...,
allow_folders: bool = ..., allow_folders: bool = ...,
**kwargs primary_key: bool = ...,
unique: bool = ...,
blank: bool = ...,
null: bool = ...,
db_index: bool = ...,
default: Any = ...,
editable: bool = ...,
auto_created: bool = ...,
serialize: bool = ...,
choices: Optional[_FieldChoices] = ...,
help_text: str = ...,
db_column: Optional[str] = ...,
db_tablespace: Optional[str] = ...,
validators: Iterable[_ValidatorCallable] = ...,
error_messages: Optional[_ErrorMessagesToOverride] = ...,
): ... ): ...
class BinaryField(Field): class BinaryField(Field): ...
def __init__(self, editable: bool = ..., **kwargs: Any): ...
class DurationField(Field): ... class DurationField(Field): ...

View File

@@ -1,3 +1,5 @@
from django.core.exceptions import ValidationError as ValidationError
from .forms import Form as Form, BaseForm as BaseForm from .forms import Form as Form, BaseForm as BaseForm
from .models import ModelForm as ModelForm from .models import ModelForm as ModelForm

View File

@@ -1,6 +1,6 @@
from typing import Any, Dict, Iterator, Optional, List from typing import Any, Dict, Iterator, Optional, List
from django.template.base import Template from django.template.base import Template as Template
from django.template.exceptions import TemplateDoesNotExist from django.template.exceptions import TemplateDoesNotExist
from django.template.engine import Engine from django.template.engine import Engine

View File

@@ -1,6 +1,6 @@
from typing import Callable, Dict, List, Optional, Tuple, Any from typing import Callable, Dict, List, Optional, Tuple, Any
from django.template.base import Template from django.template.base import Template as Template
from django.template.exceptions import TemplateSyntaxError from django.template.exceptions import TemplateSyntaxError
from .base import BaseEngine from .base import BaseEngine

View File

@@ -51,5 +51,5 @@ class Engine:
) -> Tuple[Template, Origin]: ... ) -> Tuple[Template, Origin]: ...
def from_string(self, template_code: str) -> Template: ... def from_string(self, template_code: str) -> Template: ...
def get_template(self, template_name: str) -> Template: ... def get_template(self, template_name: str) -> Template: ...
def render_to_string(self, template_name: str, context: Any = ...) -> SafeText: ... def render_to_string(self, template_name: str, context: Optional[Dict[str, Any]] = ...) -> SafeText: ...
def select_template(self, template_name_list: List[str]) -> Template: ... def select_template(self, template_name_list: List[str]) -> Template: ...

View File

@@ -1,19 +1,12 @@
from typing import Dict, List, Optional, Union from typing import Dict, List, Optional, Union, Any
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
from django.template.backends.django import Template as DjangoTemplate
from django.template.backends.dummy import Template as DummyTemplate
from django.template.backends.jinja2 import Template as Jinja2Template
def get_template( def get_template(template_name: str, using: Optional[str] = ...) -> Any: ...
template_name: str, using: Optional[str] = ... def select_template(template_name_list: Union[List[str], str], using: Optional[str] = ...) -> Any: ...
) -> Union[DjangoTemplate, DummyTemplate, Jinja2Template]: ...
def select_template(
template_name_list: Union[List[str], str], using: Optional[str] = ...
) -> Union[DjangoTemplate, DummyTemplate, Jinja2Template]: ...
def render_to_string( def render_to_string(
template_name: Union[List[str], str], template_name: Union[List[str], str],
context: Optional[Union[Dict[str, bool], Dict[str, str]]] = ..., context: Optional[Dict[str, Any]] = ...,
request: Optional[WSGIRequest] = ..., request: Optional[WSGIRequest] = ...,
using: Optional[str] = ..., using: Optional[str] = ...,
) -> str: ... ) -> str: ...

View File

@@ -5,6 +5,6 @@ from .testcases import (
LiveServerTestCase as LiveServerTestCase, LiveServerTestCase as LiveServerTestCase,
) )
from .utils import override_settings as override_settings from .utils import override_settings as override_settings, modify_settings as modify_settings
from .client import Client as Client from .client import Client as Client

View File

@@ -2,10 +2,55 @@ import os
if not os.path.exists('./django-sources'): if not os.path.exists('./django-sources'):
git clone -b stable/2.1.x https://github.com/django/django.git django-sources git clone -b stable/2.1.x https://github.com/django/django.git django-sources
ignored_error_patterns = ["Need type annotation for", "already defined on", "Cannot assign to a"] IGNORED_ERROR_PATTERNS = [
for line in $(mypy --config-file ./scripts/mypy.ini ./django-sources/tests).split('\n'): 'Need type annotation for',
for pattern in ignored_error_patterns: 'already defined on',
if pattern in line: 'Cannot assign to a',
break 'cannot perform relative import',
else: 'broken_app',
print(line) 'LazySettings',
'Cannot infer type of lambda',
'Incompatible types in assignment (expression has type "Callable[',
'"Callable[[Any], Any]" has no attribute',
'Invalid value for a to= parameter'
]
TESTS_DIRS = [
'absolute_url_overrides',
'admin_*',
'aggregation*',
'annotations'
]
def check_file_in_the_current_directory(directory, fname):
cd @(directory)
with ${...}.swap(FNAME=fname):
for line in $(mypy --config-file ../../../scripts/mypy.ini $FNAME).split('\n'):
for pattern in IGNORED_ERROR_PATTERNS:
if pattern in line:
break
else:
if line:
print(line)
cd -
def parse_ls_output_into_fnames(output):
fnames = []
for line in output.splitlines()[1:]:
fnames.append(line.split()[-1])
return fnames
all_tests_dirs = []
for test_dir in TESTS_DIRS:
with ${...}.swap(TEST_DIR=test_dir):
dirs = g`django-sources/tests/$TEST_DIR`
all_tests_dirs.extend(dirs)
for tests_dir in all_tests_dirs:
print('Checking ' + tests_dir)
abs_dir = os.path.join(os.getcwd(), tests_dir)
with ${...}.swap(ABS_DIR=abs_dir):
ls_output = $(ls $ABS_DIR)
for fname in parse_ls_output_into_fnames(ls_output):
path_to_check = os.path.join(abs_dir, fname)
check_file_in_the_current_directory(abs_dir, fname)