Skip to content

Commit

Permalink
check tests with mypy
Browse files Browse the repository at this point in the history
  • Loading branch information
amorison committed Apr 15, 2024
1 parent c2bee0d commit e41ea5a
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 72 deletions.
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,9 @@ python_files = ["test_*.py"]

[tool.mypy]
disallow_untyped_defs = true

[[tool.mypy.overrides]]
module = [
"pytest.*",
]
ignore_missing_imports = true
12 changes: 7 additions & 5 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

from pytest import fixture
from pytest import FixtureRequest, fixture

from loam.base import ConfigBase, Section, entry
from loam.cli import CLIManager, Subcmd
Expand Down Expand Up @@ -36,23 +38,23 @@ def conf() -> Conf:


@fixture(params=["subsA"])
def sub_cmds(request):
def sub_cmds(request: FixtureRequest) -> dict[str, Subcmd]:
subs = {}
subs["subsA"] = {
"common_": Subcmd("subsA loam test"),
"bare_": Subcmd(None, "sectionA"),
"bare_": Subcmd("bare command help", "sectionA"),
"sectionB": Subcmd("sectionB subcmd help"),
}
return subs[request.param]


@fixture
def climan(conf, sub_cmds):
def climan(conf: Conf, sub_cmds: dict[str, Subcmd]) -> CLIManager:
return CLIManager(conf, **sub_cmds)


@fixture
def cfile(tmp_path):
def cfile(tmp_path: Path) -> Path:
return tmp_path / "config.toml"


Expand Down
49 changes: 26 additions & 23 deletions tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@

from dataclasses import dataclass
from pathlib import Path
from typing import Optional
from typing import TYPE_CHECKING, Optional

import pytest

from loam.base import ConfigBase, Section, entry

if TYPE_CHECKING:
from conftest import Conf, MyConfig, SectionA, SectionB


class MyMut:
def __init__(self, inner_list):
def __init__(self, inner_list: list):
if not isinstance(inner_list, list):
raise TypeError
self.inner_list = inner_list
Expand All @@ -23,7 +26,7 @@ def from_toml(s: object) -> MyMut:
raise TypeError


def test_with_val():
def test_with_val() -> None:
@dataclass
class MySection(Section):
some_n: int = entry(val=42)
Expand All @@ -32,12 +35,12 @@ class MySection(Section):
assert sec.some_n == 42


def test_two_vals_fail():
def test_two_vals_fail() -> None:
with pytest.raises(ValueError):
entry(val=5, val_factory=lambda: 5)


def test_cast_and_set_type_hint(section_a):
def test_cast_and_set_type_hint(section_a: SectionA) -> None:
assert section_a.some_n == 42
assert section_a.some_str == "foo"
section_a.cast_and_set_("some_n", "5")
Expand All @@ -46,21 +49,21 @@ def test_cast_and_set_type_hint(section_a):
assert section_a.some_str == "bar"


def test_context(section_a):
def test_context(section_a: SectionA) -> None:
with section_a.context_(some_n=5, some_str="bar"):
assert section_a.some_n == 5
assert section_a.some_str == "bar"
assert section_a.some_n == 42
assert section_a.some_str == "foo"


def test_context_cast(section_b):
def test_context_cast(section_b: SectionB) -> None:
with section_b.context_(some_path="my/path"):
assert section_b.some_path == Path("my/path")
assert section_b.some_path == Path()


def test_cast_mutable_protected():
def test_cast_mutable_protected() -> None:
@dataclass
class MySection(Section):
some_mut: MyMut = entry(val_toml="4.5,3.8", from_toml=MyMut.from_toml)
Expand All @@ -69,30 +72,30 @@ class MySection(Section):
assert MySection().some_mut.inner_list == [4.5, 3.8]


def test_type_hint_not_a_class():
def test_type_hint_not_a_class() -> None:
@dataclass
class MySection(Section):
maybe_n: Optional[int] = entry(val_factory=lambda: None, from_toml=int)
maybe_n: Optional[int] = entry(val_factory=lambda: None, from_toml=int) # type: ignore

assert MySection().maybe_n is None
assert MySection("42").maybe_n == "42"
assert MySection("42").maybe_n == "42" # type: ignore


def test_with_obj_no_from_toml():
def test_with_obj_no_from_toml() -> None:
with pytest.raises(ValueError):
entry(val_toml="5")


def test_init_wrong_type():
def test_init_wrong_type() -> None:
@dataclass
class MySection(Section):
some_n: int = 42

with pytest.raises(TypeError):
MySection("bla")
MySection("bla") # type: ignore


def test_missing_from_toml():
def test_missing_from_toml() -> None:
@dataclass
class MySection(Section):
my_mut: MyMut = entry(val_factory=lambda: MyMut([4.5]))
Expand All @@ -103,14 +106,14 @@ class MySection(Section):
sec.cast_and_set_("my_mut", "4.5,3.8")


def test_config_default(my_config):
def test_config_default(my_config: MyConfig) -> None:
assert my_config.section_a.some_n == 42
assert my_config.section_b.some_path == Path()
assert my_config.section_a.some_str == "foo"
assert my_config.section_b.some_str == "bar"


def test_to_from_toml(my_config, cfile):
def test_to_from_toml(my_config: MyConfig, cfile: Path) -> None:
my_config.section_a.some_n = 5
my_config.section_b.some_path = Path("foo/bar")
my_config.to_file_(cfile)
Expand All @@ -119,28 +122,28 @@ def test_to_from_toml(my_config, cfile):
assert my_config == new_config


def test_to_toml_not_in_file(my_config, cfile):
def test_to_toml_not_in_file(my_config: MyConfig, cfile: Path) -> None:
my_config.section_b.some_str = "ignored"
my_config.to_file_(cfile)
content = cfile.read_text()
assert "ignored" not in content
assert "section_not_in_file" not in content


def test_from_toml_not_in_file(my_config, cfile):
def test_from_toml_not_in_file(my_config: MyConfig, cfile: Path) -> None:
cfile.write_text('[section_b]\nsome_str="ignored"\n')
my_config.default_().update_from_file_(cfile)
assert my_config.section_b.some_str == "bar"


def test_to_file_exist_ok(my_config, cfile):
def test_to_file_exist_ok(my_config: MyConfig, cfile: Path) -> None:
my_config.to_file_(cfile)
with pytest.raises(RuntimeError):
my_config.to_file_(cfile, exist_ok=False)
my_config.to_file_(cfile)


def test_config_with_not_section():
def test_config_with_not_section() -> None:
@dataclass
class MyConfig(ConfigBase):
dummy: int = 5
Expand All @@ -149,11 +152,11 @@ class MyConfig(ConfigBase):
MyConfig.default_()


def test_update_opt(conf):
def test_update_opt(conf: Conf) -> None:
conf.sectionA.update_from_dict_({"optA": 42, "optC": 43})
assert conf.sectionA.optA == 42 and conf.sectionA.optC == 43


def test_update_section(conf):
def test_update_section(conf: Conf) -> None:
conf.update_from_dict_({"sectionA": {"optA": 42}, "sectionB": {"optA": 43}})
assert conf.sectionA.optA == 42 and conf.sectionB.optA == 43
30 changes: 19 additions & 11 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,69 +1,77 @@
from __future__ import annotations

from shlex import split
from typing import TYPE_CHECKING

import pytest

import loam.cli
import loam.error

if TYPE_CHECKING:
from conftest import Conf

from loam.cli import CLIManager


def test_parse_no_args(conf, climan):
def test_parse_no_args(conf: Conf, climan: CLIManager) -> None:
climan.parse_args([])
assert conf == conf.default_()


def test_parse_nosub_common_args(conf, climan):
def test_parse_nosub_common_args(conf: Conf, climan: CLIManager) -> None:
climan.parse_args(split("--optA 42"))
assert conf.sectionA.optA == 42
assert conf.sectionB.optA == 4


def test_parse_short_nosub_common_args(conf, climan):
def test_parse_short_nosub_common_args(conf: Conf, climan: CLIManager) -> None:
climan.parse_args(split("-a 42"))
assert conf.sectionA.optA == 42
assert conf.sectionB.optA == 4


def test_parse_switch_nosub_common_args(conf, climan):
def test_parse_switch_nosub_common_args(conf: Conf, climan: CLIManager) -> None:
climan.parse_args(split("-optBool"))
assert conf.sectionA.optBool is False
assert conf.sectionB.optBool is False


def test_parse_switch_short_nosub_common_args(conf, climan):
def test_parse_switch_short_nosub_common_args(conf: Conf, climan: CLIManager) -> None:
climan.parse_args(split("-o"))
assert conf.sectionA.optBool is False
assert conf.sectionB.optBool is False


def test_parse_sub_common_args(conf, climan):
def test_parse_sub_common_args(conf: Conf, climan: CLIManager) -> None:
climan.parse_args(split("sectionB --optA 42"))
assert conf.sectionA.optA == 1
assert conf.sectionB.optA == 42


def test_parse_switch_sub_common_args(conf, climan):
def test_parse_switch_sub_common_args(conf: Conf, climan: CLIManager) -> None:
climan.parse_args(split("sectionB +optBool"))
assert conf.sectionA.optBool is True
assert conf.sectionB.optBool is True


def test_parse_switch_short_sub_common_args(conf, climan):
def test_parse_switch_short_sub_common_args(conf: Conf, climan: CLIManager) -> None:
climan.parse_args(split("sectionB +o"))
assert conf.sectionA.optBool is True
assert conf.sectionB.optBool is True


def test_parse_no_sub_only_args(conf, climan):
def test_parse_no_sub_only_args(conf: Conf, climan: CLIManager) -> None:
climan.parse_args(split("--optC 42 sectionB"))
assert conf.sectionA.optC == 3
assert conf.sectionB.optC == 6


def test_parse_not_conf_cmd_args(climan):
def test_parse_not_conf_cmd_args(climan: CLIManager) -> None:
with pytest.raises(SystemExit):
climan.parse_args(split("sectionB --optC 42"))


def test_build_climan_invalid_sub(conf):
def test_build_climan_invalid_sub(conf: Conf) -> None:
with pytest.raises(loam.error.SubcmdError):
loam.cli.CLIManager(conf, **{"1invalid_sub": loam.cli.Subcmd("")})
Loading

0 comments on commit e41ea5a

Please sign in to comment.