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 2 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
5 changes: 5 additions & 0 deletions .github/workflows/base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ jobs:
steps:
- uses: actions/checkout@v2

# Install pyright
- uses: actions/setup-node@v2
with:
node-version: '16'

last-partizan marked this conversation as resolved.
Show resolved Hide resolved
# Conda install
- name: Install conda v3.7
uses: conda-incubator/setup-miniconda@v2
Expand Down
14 changes: 12 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:
last-partizan marked this conversation as resolved.
Show resolved Hide resolved
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 All @@ -107,6 +110,13 @@ def tests(session: PowerSession, coverage, pkg_specs):
# Important: do not surround the command into double quotes as in the shell !
# session.run('python', '-c', 'import os; os.chdir(\'./docs/\'); import %s' % pkg_name)

# Type checking is supported from python 3.7
if float(session.python) >= 3.7:
try:
session.run2("npm install -g pyright@1.1.247")
except CommandFailed:
print("Failed to install pyright, typing tests would be skipped")
last-partizan marked this conversation as resolved.
Show resolved Hide resolved

# finally run all tests
if not coverage:
# install self so that it is recognized by pytest
Expand Down
8 changes: 8 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,20 @@ setup_requires =
pytest-runner
install_requires =
makefun>=1.5.0
# we're using typing_extensions even for new versions of python, so we don't pollute our code
# with try/except every time we use new feature.
# typing_extensions will use version from typing module if it detects new enough python.
typing-extensions>=4.2;python_version>='3.6'
last-partizan marked this conversation as resolved.
Show resolved Hide resolved
# 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 is used for snapshot testing with pyright,
# pyright must be installed separately, we're not using https://pypi.org/project/pyright/
# becouse it's just slow wrapper around node version.
syrupy>2;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
7 changes: 2 additions & 5 deletions src/decopatch/main.pyi
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
from typing import Any, Callable, Optional, Protocol, TypeVar, overload

from typing_extensions import ParamSpec

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

try:
from typing import ParamSpec
except ImportError:
from typing_extensions import ParamSpec

P = ParamSpec("P")
F = TypeVar("F", bound=Callable[..., Any])

Expand Down
Empty file added tests/pyright/__init__.py
Empty file.
58 changes: 58 additions & 0 deletions tests/pyright/__snapshots__/test_typing.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# name: test_typing
last-partizan marked this conversation as resolved.
Show resolved Hide resolved
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.
last-partizan marked this conversation as resolved.
Show resolved Hide resolved
"""
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")
last-partizan marked this conversation as resolved.
Show resolved Hide resolved
assert actual == snapshot