Repository navigation
Expand file tree
/
Copy pathpyproject.toml
More file actions
147 lines (139 loc) · 6.43 KB
/
Copy pathpyproject.toml
File metadata and controls
147 lines (139 loc) · 6.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
[tool.coverage.run]
omit = ["custom_components/rainpoint/debug.py"]
[tool.coverage.report]
fail_under = 100
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = "test_*.py"
python_classes = "Test*"
python_functions = "test_*"
asyncio_mode = "auto"
addopts = [
"--cov=custom_components.rainpoint",
"--cov-branch",
"--cov-report=term-missing",
"--cov-report=html",
]
# Declared here rather than in requirements-test.txt so CI, which installs that
# file on every pull request, never pays to download a tool no job runs. The
# major is capped because mutmut 3 changed its configuration format from mutmut
# 2, so a 4.x could invalidate the [tool.mutmut] block below without failing
# loudly.
[dependency-groups]
mutation = ["mutmut>=3.8.0,<4"]
# Mutation testing, run by hand rather than in CI. mutmut copies the source and
# everything listed in also_copy into ./mutants/, mutates the copy, and runs the
# suite from there, so anything the tests open by path has to be copied too:
# README.md and scripts/ are read directly by tests, and pyproject.toml carries
# the pytest configuration the suite needs to run at all.
#
# --no-cov is what keeps the results honest. This project's pytest run enables
# coverage with a fail_under gate, and a mutant only ever exercises part of the
# tree, so every run would exit non-zero on the gate and every mutant would be
# reported killed whether the tests noticed it or not.
#
# debug.py is left alone for the same reason it is left out of coverage: nothing
# imports it in a shipped build.
#
# The deselected tests inspect source that mutmut rewrites with every mutant inlined
# (the decoders.py digest pin and the mqtt.py call-graph pair), so they fail on the
# unmutated baseline. They still run in every ordinary pytest run. A renamed test
# leaves a stale node id here, which shows up as "failed to collect stats" before
# the first mutant runs.
#
# on_dependency_change defaults to "warn", which keeps a cache built under the
# previous config when one of the copied inputs changes. That cache is what
# feeds the test-to-function map, and a stale one can end up emptied, at which
# point every mutant is skipped as uncovered and the run finishes green at
# 0.00 mutations/second having tested nothing. "rerun" throws the cache away
# instead. It costs a full re-run whenever one of those inputs changes, which is
# the cheaper of the two failure modes.
#
# also_copy only copies; it registers nothing for change detection, and neither
# of mutmut's two detectors sees these files on its own. The git detector skips
# every .py file, because the per-function source hashes are meant to cover
# those, but those hashes only span source_paths, so scripts/ is invisible to
# both. Markdown is then dropped as documentation noise, which hides README.md.
# cache_invalidation_files is the escape hatch: a registered path is hashed every
# run and is never dropped as noise. Keep the globs one level deep. mutmut
# matches them with fnmatch as well as glob, and fnmatch's "*" already spans
# separators, so "scripts/**/*" silently matches nothing there.
#
# tests/ is deliberately left unregistered. mutmut re-collects stats for tests it
# has not seen before, so a new test is picked up without discarding anything,
# and registering the directory would throw the whole cache away on every
# assertion added while chasing a surviving mutant.
[tool.mutmut]
source_paths = ["custom_components/"]
also_copy = [
"tests/",
"scripts/",
"pyproject.toml",
"README.md",
".github/ISSUE_TEMPLATE/",
]
cache_invalidation_files = [
"scripts/*",
"README.md",
".github/ISSUE_TEMPLATE/*",
]
do_not_mutate = ["*/debug.py"]
# Mutated log calls were a third of all survivors and test nothing but log text.
# Only the line a statement starts on is matched, so continuation lines still mutate.
# mutmut keeps existing mutant files, so delete mutants/ after changing this.
do_not_mutate_patterns = ['_LOGGER\.', 'debug_with_version\(']
# At the default of 15, a mutant that breaks a loop keeps growing for about a minute,
# enough memory to hang WSL. A slow mutant under load becomes a timeout, not a survivor.
timeout_multiplier = 5.0
# Vale lints prose and cannot affect the tests, but the git detector counts any
# non-Python edit as a dependency change, and "rerun" then discards the whole
# cache at the start of the next run.
cache_invalidation_exclude = [".vale.ini", ".vale/*"]
on_dependency_change = "rerun"
pytest_add_cli_args = [
"--no-cov",
"--deselect",
"tests/api/test_generic_decoder.py::TestAsciiFramingNonRegression::test_decoders_py_digest_is_pinned",
"--deselect",
"tests/api/test_mqtt.py::TestPushDispatchNeverRunsOnPahoThread",
]
pytest_add_cli_args_test_selection = ["tests/"]
[tool.ruff]
# The oldest Python this integration must run on, not the newest it is tested
# against. hacs.json declares Home Assistant 2026.2.0 as the minimum and that
# release runs Python 3.13, so 3.13 syntax is the ceiling the code has to hold
# to even though CI's primary leg is 3.14. Home Assistant moved to 3.14 in
# 2026.3, so raise this only when the declared minimum reaches that release.
target-version = "py313"
line-length = 130
[tool.ruff.lint]
select = [
"E",
"F",
"I",
"UP",
"B",
"SIM",
"RUF",
]
[tool.ruff.lint.isort]
known-first-party = ["custom_components.rainpoint"]
# Editor-only. No CI job runs a type checker, and ruff remains the enforced
# linter. This exists so an editor's Pyright reports real source rather than
# the test suite: the tests deliberately pass SimpleNamespace and MagicMock
# fakes where Home Assistant types are annotated, and conftest.py stubs whole
# modules by assigning attributes onto them. Both are documented patterns, not
# defects, and they account for the large majority of the findings. Including
# tests here would bury the source findings under them.
#
# scripts/ is real maintainer source rather than a test fake, so it is in
# scope for the same reason custom_components is, and its presence here keeps
# this block consistent with the maintainer's own pyrightconfig.json.
[tool.pyright]
include = ["custom_components", "scripts"]
exclude = ["**/__pycache__"]
# Pinned rather than inherited from whichever interpreter the editor happens to
# select, so diagnostics match ruff's target-version across every contributor's
# setup. Like that setting this is the oldest supported Python rather than the
# newest tested one, so the editor flags anything the support floor cannot run.
pythonVersion = "3.13"