164 lines
4.4 KiB
Python
164 lines
4.4 KiB
Python
"""Validate coverage files."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from .model import Config, Integration
|
|
|
|
DONT_IGNORE = (
|
|
"config_flow.py",
|
|
"device_action.py",
|
|
"device_condition.py",
|
|
"device_trigger.py",
|
|
"diagnostics.py",
|
|
"group.py",
|
|
"intent.py",
|
|
"logbook.py",
|
|
"media_source.py",
|
|
"recorder.py",
|
|
"scene.py",
|
|
)
|
|
|
|
PREFIX = """# Sorted by hassfest.
|
|
#
|
|
# To sort, run python3 -m script.hassfest -p coverage
|
|
|
|
[run]
|
|
source = homeassistant
|
|
omit =
|
|
homeassistant/__main__.py
|
|
homeassistant/helpers/signal.py
|
|
homeassistant/scripts/__init__.py
|
|
homeassistant/scripts/check_config.py
|
|
homeassistant/scripts/ensure_config.py
|
|
homeassistant/scripts/benchmark/__init__.py
|
|
homeassistant/scripts/macos/__init__.py
|
|
|
|
# omit pieces of code that rely on external devices being present
|
|
"""
|
|
|
|
SUFFIX = """[report]
|
|
# Regexes for lines to exclude from consideration
|
|
exclude_lines =
|
|
# Have to re-enable the standard pragma
|
|
pragma: no cover
|
|
|
|
# Don't complain about missing debug-only code:
|
|
def __repr__
|
|
|
|
# Don't complain if tests don't hit defensive assertion code:
|
|
raise AssertionError
|
|
raise NotImplementedError
|
|
|
|
# TYPE_CHECKING and @overload blocks are never executed during pytest run
|
|
if TYPE_CHECKING:
|
|
@overload
|
|
"""
|
|
|
|
|
|
def validate(integrations: dict[str, Integration], config: Config) -> None:
|
|
"""Validate coverage."""
|
|
coverage_path = config.root / ".coveragerc"
|
|
|
|
not_found: list[str] = []
|
|
checking = False
|
|
|
|
previous_line = ""
|
|
with coverage_path.open("rt") as fp:
|
|
for line in fp:
|
|
line = line.strip()
|
|
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
|
|
if not checking:
|
|
if line == "omit =":
|
|
checking = True
|
|
continue
|
|
|
|
# Finished
|
|
if line == "[report]":
|
|
break
|
|
|
|
path = Path(line)
|
|
|
|
# Discard wildcard
|
|
path_exists = path
|
|
while "*" in path_exists.name:
|
|
path_exists = path_exists.parent
|
|
|
|
if not path_exists.exists():
|
|
not_found.append(line)
|
|
continue
|
|
|
|
if not line.startswith("homeassistant/components/"):
|
|
continue
|
|
|
|
integration_path = path.parent
|
|
while len(integration_path.parts) > 3:
|
|
integration_path = integration_path.parent
|
|
|
|
integration = integrations[integration_path.name]
|
|
|
|
# Ensure sorted
|
|
if line < previous_line:
|
|
integration.add_error(
|
|
"coverage",
|
|
f"{line} is unsorted in .coveragerc file",
|
|
)
|
|
previous_line = line
|
|
|
|
# Ignore sub-directories for further checks
|
|
if len(path.parts) > 4:
|
|
continue
|
|
|
|
if (
|
|
path.parts[-1] == "*"
|
|
and Path(f"tests/components/{integration.domain}/__init__.py").exists()
|
|
):
|
|
integration.add_error(
|
|
"coverage",
|
|
"has tests and should not use wildcard in .coveragerc file",
|
|
)
|
|
|
|
for check in DONT_IGNORE:
|
|
if path.parts[-1] not in {"*", check}:
|
|
continue
|
|
|
|
if (integration_path / check).exists():
|
|
integration.add_error(
|
|
"coverage",
|
|
f"{check} must not be ignored by the .coveragerc file",
|
|
)
|
|
|
|
if not_found:
|
|
raise RuntimeError(
|
|
f".coveragerc references files that don't exist: {', '.join(not_found)}."
|
|
)
|
|
|
|
|
|
def generate(integrations: dict[str, Integration], config: Config) -> None:
|
|
"""Sort coverage."""
|
|
coverage_path = config.root / ".coveragerc"
|
|
lines = []
|
|
start = False
|
|
|
|
with coverage_path.open("rt") as fp:
|
|
for line in fp:
|
|
if (
|
|
not start
|
|
and line
|
|
== " # omit pieces of code that rely on external devices being present\n"
|
|
):
|
|
start = True
|
|
elif line == "[report]\n":
|
|
break
|
|
elif start and line != "\n":
|
|
lines.append(line)
|
|
|
|
content = f"{PREFIX}{"".join(sorted(lines))}\n\n{SUFFIX}"
|
|
|
|
with coverage_path.open("w") as fp:
|
|
fp.write(content)
|