Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add tests for type info #27

Merged
merged 7 commits into from
Jul 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging

import nox # noqa
from nox.command import CommandFailed
from pathlib import Path # noqa
import sys

Expand All @@ -28,7 +29,6 @@
PY37: {"coverage": True, "pkg_specs": {"pip": ">19"}}, # , "pytest-html": "1.9.0"
}


# set the default activated sessions, minimal for CI
nox.options.sessions = ["tests", "flake8"] # , "docs", "gh_pages"
nox.options.reuse_existing_virtualenvs = True # this can be done using -r
Expand Down Expand Up @@ -98,7 +98,10 @@ def tests(session: PowerSession, coverage, pkg_specs):
conda_prefix = Path(session.bin)
if conda_prefix.name == "bin":
conda_prefix = conda_prefix.parent
session.run2("conda list", env={"CONDA_PREFIX": str(conda_prefix), "CONDA_DEFAULT_ENV": session.get_session_id()})
try:
session.run2("conda list", env={"CONDA_PREFIX": str(conda_prefix), "CONDA_DEFAULT_ENV": session.get_session_id()})
except CommandFailed:
pass

# Fail if the assumed python version is not the actual one
session.run2("python ci_tools/check_python_version.py %s" % session.python)
Expand Down
4 changes: 4 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,16 @@ setup_requires =
pytest-runner
install_requires =
makefun>=1.5.0
typing-extensions>=4.2;python_version>'3.6' and python_version<'3.10'
# note: do not use double quotes in these, this triggers a weird bug in PyCharm in debug mode only
funcsigs;python_version<'3.3'
enum34;python_version<'3.4'
tests_require =
pytest
pytest_cases
# syrupy and pyright is used for testing type correctness.
syrupy>2;python_version>'3.6'
pyright;python_version>'3.6'
# for some reason these pytest dependencies were not declared in old versions of pytest
six;python_version<'3.6'
attr;python_version<'3.6'
Expand Down
12 changes: 7 additions & 5 deletions src/decopatch/main.pyi
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from typing import Any, Callable, Optional, Protocol, TypeVar, overload

from decopatch.utils_disambiguation import FirstArgDisambiguation
from decopatch.utils_modes import SignatureInfo

try:
from typing import ParamSpec
except ImportError:
# We're importing typing_extensions version first, becouse it will
# detect best available implementation depending on python version.
from typing_extensions import ParamSpec
except ImportError:
from typing import ParamSpec

from decopatch.utils_disambiguation import FirstArgDisambiguation
from decopatch.utils_modes import SignatureInfo

P = ParamSpec("P")
F = TypeVar("F", bound=Callable[..., Any])
Expand Down
Empty file added tests/pyright/__init__.py
Empty file.
61 changes: 61 additions & 0 deletions tests/pyright/__snapshots__/test_typing.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# This file is generated by [syrupy](https://github.com/tophat/syrupy),
# do not edit manually.
# To update - run `pytest --snapshot-update`
# name: test_typing
list([
dict({
'message': '''
No overloads for "function_decorator" match the provided arguments
  Argument types: (Literal[True])
''',
'range': dict({
'end': dict({
'character': 47,
'line': 15,
}),
'start': dict({
'character': 9,
'line': 15,
}),
}),
'rule': 'reportGeneralTypeIssues',
'severity': 'error',
}),
dict({
'message': '''
Argument of type "Literal[2]" cannot be assigned to parameter "scope" of type "str" in function "__call__"
  "Literal[2]" is incompatible with "str"
''',
'range': dict({
'end': dict({
'character': 22,
'line': 36,
}),
'start': dict({
'character': 21,
'line': 36,
}),
}),
'rule': 'reportGeneralTypeIssues',
'severity': 'error',
}),
dict({
'message': '''
Argument of type "Literal[2]" cannot be assigned to parameter "scope" of type "str" in function "__call__"
  "Literal[2]" is incompatible with "str"
''',
'range': dict({
'end': dict({
'character': 34,
'line': 52,
}),
'start': dict({
'character': 33,
'line': 52,
}),
}),
'rule': 'reportGeneralTypeIssues',
'severity': 'error',
}),
])
# ---
35 changes: 35 additions & 0 deletions tests/pyright/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import json
import shutil
import subprocess

__all__ = ["run_pyright", "pyright_installed"]

try:
pyright_bin = shutil.which("pyright")
pyright_installed = pyright_bin is not None
except AttributeError:
# shutil.which works from python 3.3 onward
pyright_bin = None
pyright_installed = False


def run_pyright(filename):
"""
Executes pyright type checker against a file, and returns json output.

Used together with syrupy snapshot to check if typing is working as expected.
"""
result = subprocess.run(
[pyright_bin, "--outputjson", filename],
capture_output=True,
text=True,
)
assert result.stdout, result.stderr
output = json.loads(result.stdout)

def format_row(data):
# Remove "file" from json report, it has no use here.
del data["file"]
return data

return [format_row(row) for row in output["generalDiagnostics"]]
30 changes: 14 additions & 16 deletions tests/test_typing.py → tests/pyright/test_file.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# This is test file for typing,
# No automatic testing is used at the moment. Just use your type checker and see if it works.
# Pytest here is used to make sure that runtime behavir matches with type checker expecter errors.
"""
Tests in this file do almost nothing at runtime, but serve as a source for
testing with pyright from test_typing.py
"""
from typing import Any, Callable

import pytest
Expand All @@ -10,37 +11,32 @@

def test_invalid_parameter():
with pytest.raises(TypeError):
# Error, invalid argument
# Error, invalid argument.
# This triggers error in type checking and in runtime.
@function_decorator(invalid_param=True)
def decorator_wint_invalid_param(fn=DECORATED):
def decorator_with_invalid_param(fn=DECORATED):
return fn


def test_normal_decorator():
@function_decorator
def decorator(scope="test", fn=DECORATED): # type: (str, Any) -> Callable[..., Any]
assert isinstance(scope, str)
return fn

# Ok
@decorator
def decorated_flat():
pass

assert decorated_flat

with pytest.raises(AssertionError):
# Error, Literal[2] is incompatible with str
@decorator(scope=2)
def decorated_with_invalid_options():
pass

# Ok, should reveal correct type for `scope`
@decorator(scope="success")
def decorated_with_valid_options():
pass

assert decorated_with_valid_options
# Error, Literal[2] is incompatible with str
@decorator(scope=2)
def decorated_with_invalid_options():
pass


def test_function_decorator_with_params():
Expand All @@ -54,4 +50,6 @@ def decorator_with_params(scope = "test", fn=DECORATED): # type: (str, Any) ->
def decorated_with_valid_options():
pass

assert decorated_with_valid_options
@decorator_with_params(scope=2)
def decorated_with_invalid_options():
pass
19 changes: 19 additions & 0 deletions tests/pyright/test_typing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import sys

import pytest

from .base import run_pyright, pyright_installed


@pytest.mark.skipif(
sys.version_info < (3, 7),
reason="Requires Python 3.7+",
)
@pytest.mark.skipif(
not pyright_installed,
reason="Pyright not installed",
)
def test_typing(snapshot):
"""Test that pyright detects the typing issues on `test_file` correctly."""
actual = run_pyright("tests/pyright/test_file.py")
assert actual == snapshot