Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
coverage.py 3.72 KiB
"""Validate coverage files."""
from pathlib import Path
from typing import Dict

from .model import Config, Integration

DONT_IGNORE = (
    "config_flow.py",
    "device_action.py",
    "device_condition.py",
    "device_trigger.py",
    "group.py",
    "intent.py",
    "logbook.py",
    "media_source.py",
    "scene.py",
)

# They were violating when we introduced this check
# Need to be fixed in a future PR.
ALLOWED_IGNORE_VIOLATIONS = {
    ("ambient_station", "config_flow.py"),
    ("cast", "config_flow.py"),
    ("daikin", "config_flow.py"),
    ("doorbird", "config_flow.py"),
    ("doorbird", "logbook.py"),
    ("elkm1", "config_flow.py"),
    ("elkm1", "scene.py"),
    ("fibaro", "scene.py"),
    ("flume", "config_flow.py"),
    ("hangouts", "config_flow.py"),
    ("harmony", "config_flow.py"),
    ("hisense_aehw4a1", "config_flow.py"),
    ("home_connect", "config_flow.py"),
    ("huawei_lte", "config_flow.py"),
    ("ifttt", "config_flow.py"),
    ("ios", "config_flow.py"),
    ("iqvia", "config_flow.py"),
    ("knx", "scene.py"),
    ("konnected", "config_flow.py"),
    ("lcn", "scene.py"),
    ("life360", "config_flow.py"),
    ("lifx", "config_flow.py"),
    ("lutron", "scene.py"),
    ("mobile_app", "config_flow.py"),
    ("nest", "config_flow.py"),
    ("plaato", "config_flow.py"),
    ("point", "config_flow.py"),
    ("rachio", "config_flow.py"),
    ("sense", "config_flow.py"),
    ("sms", "config_flow.py"),
    ("solarlog", "config_flow.py"),
    ("somfy", "config_flow.py"),
    ("sonos", "config_flow.py"),
    ("speedtestdotnet", "config_flow.py"),
    ("spider", "config_flow.py"),
    ("starline", "config_flow.py"),
    ("tado", "config_flow.py"),
    ("tahoma", "scene.py"),
    ("totalconnect", "config_flow.py"),
    ("tradfri", "config_flow.py"),
    ("tuya", "config_flow.py"),
    ("tuya", "scene.py"),
    ("upnp", "config_flow.py"),
    ("velux", "scene.py"),
    ("wemo", "config_flow.py"),
    ("wiffi", "config_flow.py"),
    ("wink", "scene.py"),
}


def validate(integrations: Dict[str, Integration], config: Config):
    """Validate coverage."""
    coverage_path = config.root / ".coveragerc"

    not_found = []
    checking = False

    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/")
                or not len(path.parts) == 4
                or not path.parts[-1] == "*"
            ):
                continue

            integration_path = path.parent

            integration = integrations[integration_path.name]

            for check in DONT_IGNORE:
                if (integration_path.name, check) in ALLOWED_IGNORE_VIOLATIONS:
                    continue

                if (integration_path / check).exists():
                    integration.add_error(
                        "coverage",
                        f"{check} must not be ignored by the .coveragerc file",
                    )

    if not not_found:
        return

    errors = []

    if not_found:
        errors.append(
            f".coveragerc references files that don't exist: {', '.join(not_found)}."
        )

    raise RuntimeError(" ".join(errors))