Skip to content

Commit

Permalink
tests: statically type tests (#894)
Browse files Browse the repository at this point in the history
Signed-off-by: Henry Schreiner <[email protected]>
  • Loading branch information
henryiii authored Nov 22, 2024
1 parent aabdc9b commit f019411
Show file tree
Hide file tree
Showing 23 changed files with 1,144 additions and 638 deletions.
3 changes: 2 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ repos:
rev: v1.13.0
hooks:
- id: mypy
files: ^nox/
files: ^(nox/|tests/)
exclude: ^tests/resources/
args: []
additional_dependencies:
- argcomplete
Expand Down
53 changes: 52 additions & 1 deletion nox/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import subprocess
import sys
from collections.abc import Iterable, Mapping, Sequence
from typing import Literal
from typing import Literal, overload

from nox.logger import logger
from nox.popen import DEFAULT_INTERRUPT_TIMEOUT, DEFAULT_TERMINATE_TIMEOUT, popen
Expand Down Expand Up @@ -73,6 +73,57 @@ def _shlex_join(args: Sequence[str | os.PathLike[str]]) -> str:
return " ".join(shlex.quote(os.fspath(arg)) for arg in args)


@overload
def run(
args: Sequence[str | os.PathLike[str]],
*,
env: Mapping[str, str | None] | None = ...,
silent: Literal[True],
paths: Sequence[str] | None = ...,
success_codes: Iterable[int] | None = ...,
log: bool = ...,
external: ExternalType = ...,
stdout: int | IO[str] | None = ...,
stderr: int | IO[str] | None = ...,
interrupt_timeout: float | None = ...,
terminate_timeout: float | None = ...,
) -> str: ...


@overload
def run(
args: Sequence[str | os.PathLike[str]],
*,
env: Mapping[str, str | None] | None = ...,
silent: Literal[False] = ...,
paths: Sequence[str] | None = ...,
success_codes: Iterable[int] | None = ...,
log: bool = ...,
external: ExternalType = ...,
stdout: int | IO[str] | None = ...,
stderr: int | IO[str] | None = ...,
interrupt_timeout: float | None = ...,
terminate_timeout: float | None = ...,
) -> bool: ...


@overload
def run(
args: Sequence[str | os.PathLike[str]],
*,
env: Mapping[str, str | None] | None = ...,
silent: bool,
paths: Sequence[str] | None = ...,
success_codes: Iterable[int] | None = ...,
log: bool = ...,
external: ExternalType = ...,
stdout: int | IO[str] | None = ...,
stderr: int | IO[str] | None = ...,
interrupt_timeout: float | None = ...,
terminate_timeout: float | None = ...,
) -> str | bool: ...


def run(
args: Sequence[str | os.PathLike[str]],
*,
Expand Down
4 changes: 2 additions & 2 deletions nox/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,8 +419,8 @@ class KeywordLocals(Mapping[str, bool]):
returns False.
"""

def __init__(self, keywords: set[str]) -> None:
self._keywords = keywords
def __init__(self, keywords: Iterable[str]) -> None:
self._keywords = frozenset(keywords)

def __getitem__(self, variable_name: str) -> bool:
return any(variable_name in keyword for keyword in self._keywords)
Expand Down
12 changes: 6 additions & 6 deletions nox/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@
import copy
import functools
from collections.abc import Sequence
from typing import Any, Callable, TypeVar, overload
from typing import Any, Callable, overload

from ._decorators import Func
from ._typing import Python

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

_REGISTRY: collections.OrderedDict[str, Func] = collections.OrderedDict()


@overload
def session_decorator(__func: F) -> F: ...
def session_decorator(__func: RawFunc | Func) -> Func: ...


@overload
Expand All @@ -45,11 +45,11 @@ def session_decorator(
*,
default: bool = ...,
requires: Sequence[str] | None = ...,
) -> Callable[[F], F]: ...
) -> Callable[[RawFunc | Func], Func]: ...


def session_decorator(
func: F | None = None,
func: Callable[..., Any] | Func | None = None,
python: Python | None = None,
py: Python | None = None,
reuse_venv: bool | None = None,
Expand All @@ -60,7 +60,7 @@ def session_decorator(
*,
default: bool = True,
requires: Sequence[str] | None = None,
) -> F | Callable[[F], F]:
) -> Func | Callable[[RawFunc | Func], Func]:
"""Designate the decorated function as a session."""
# If `func` is provided, then this is the decorator call with the function
# being sent as part of the Python syntax (`@nox.session`).
Expand Down
6 changes: 4 additions & 2 deletions nox/virtualenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,11 @@ class ProcessEnv(abc.ABC):
allowed_globals: ClassVar[tuple[Any, ...]] = ()

def __init__(
self, bin_paths: None = None, env: Mapping[str, str | None] | None = None
self,
bin_paths: Sequence[str] | None = None,
env: Mapping[str, str | None] | None = None,
) -> None:
self._bin_paths = bin_paths
self._bin_paths = None if bin_paths is None else list(bin_paths)
self._reused = False

# Filter envs now so `.env` is dict[str, str] (easier to use)
Expand Down
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ lint.typing-modules = [ "nox._typing" ]
max_supported_python = "3.13"

[tool.pytest.ini_options]
minversion = "6.0"
minversion = "7.0"
addopts = [ "-ra", "--strict-markers", "--strict-config" ]
xfail_strict = true
filterwarnings = [ "error" ]
Expand All @@ -128,7 +128,6 @@ run.source_pkgs = [ "nox" ]
report.exclude_also = [ "def __dir__()", "if TYPE_CHECKING:", "@overload" ]

[tool.mypy]
files = [ "nox/**/*.py", "noxfile.py" ]
python_version = "3.8"
strict = true
warn_unreachable = true
Expand Down
28 changes: 14 additions & 14 deletions tests/test__option_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@


class TestOptionSet:
def test_namespace(self):
def test_namespace(self) -> None:
optionset = _option_set.OptionSet()
optionset.add_groups(_option_set.OptionGroup("group_a"))
optionset.add_options(
Expand All @@ -43,7 +43,7 @@ def test_namespace(self):
assert not hasattr(namespace, "non_existent_option")
assert namespace.option_a == "meep"

def test_namespace_values(self):
def test_namespace_values(self) -> None:
optionset = _option_set.OptionSet()
optionset.add_groups(_option_set.OptionGroup("group_a"))
optionset.add_options(
Expand All @@ -56,13 +56,13 @@ def test_namespace_values(self):

assert namespace.option_a == "moop"

def test_namespace_non_existent_options_with_values(self):
def test_namespace_non_existent_options_with_values(self) -> None:
optionset = _option_set.OptionSet()

with pytest.raises(KeyError):
optionset.namespace(non_existent_option="meep")

def test_parser_hidden_option(self):
def test_parser_hidden_option(self) -> None:
optionset = _option_set.OptionSet()
optionset.add_options(
_option_set.Option(
Expand All @@ -76,7 +76,7 @@ def test_parser_hidden_option(self):

assert namespace.oh_boy_i_am_hidden == "meep"

def test_parser_groupless_option(self):
def test_parser_groupless_option(self) -> None:
optionset = _option_set.OptionSet()
optionset.add_options(
_option_set.Option("oh_no_i_have_no_group", group=None, default="meep")
Expand All @@ -85,46 +85,46 @@ def test_parser_groupless_option(self):
with pytest.raises(ValueError):
optionset.parser()

def test_session_completer(self):
def test_session_completer(self) -> None:
parsed_args = _options.options.namespace(
posargs=[],
noxfile=str(RESOURCES.joinpath("noxfile_multiple_sessions.py")),
)
actual_sessions_from_file = _options._session_completer(
prefix=None, parsed_args=parsed_args
prefix="", parsed_args=parsed_args
)

expected_sessions = ["testytest", "lintylint", "typeytype"]
assert expected_sessions == list(actual_sessions_from_file)

def test_session_completer_invalid_sessions(self):
def test_session_completer_invalid_sessions(self) -> None:
parsed_args = _options.options.namespace(
sessions=("baz",), keywords=(), posargs=[]
)
all_nox_sessions = _options._session_completer(
prefix=None, parsed_args=parsed_args
prefix="", parsed_args=parsed_args
)
assert len(all_nox_sessions) == 0
assert len(list(all_nox_sessions)) == 0

def test_python_completer(self):
def test_python_completer(self) -> None:
parsed_args = _options.options.namespace(
posargs=[],
noxfile=str(RESOURCES.joinpath("noxfile_pythons.py")),
)
actual_pythons_from_file = _options._python_completer(
prefix=None, parsed_args=parsed_args
prefix="", parsed_args=parsed_args
)

expected_pythons = {"3.6", "3.12"}
assert expected_pythons == set(actual_pythons_from_file)

def test_tag_completer(self):
def test_tag_completer(self) -> None:
parsed_args = _options.options.namespace(
posargs=[],
noxfile=str(RESOURCES.joinpath("noxfile_tags.py")),
)
actual_tags_from_file = _options._tag_completer(
prefix=None, parsed_args=parsed_args
prefix="", parsed_args=parsed_args
)

expected_tags = {f"tag{n}" for n in range(1, 8)}
Expand Down
Loading

0 comments on commit f019411

Please sign in to comment.