Validate requirements format in hassfest (#55094)
Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
This commit is contained in:
parent
cac486440f
commit
c8f584f4ef
5 changed files with 123 additions and 9 deletions
|
@ -2,7 +2,7 @@
|
|||
"domain": "gc100",
|
||||
"name": "Global Cach\u00e9 GC-100",
|
||||
"documentation": "https://www.home-assistant.io/integrations/gc100",
|
||||
"requirements": ["python-gc100==1.0.3a"],
|
||||
"requirements": ["python-gc100==1.0.3a0"],
|
||||
"codeowners": [],
|
||||
"iot_class": "local_polling"
|
||||
}
|
||||
|
|
|
@ -1853,7 +1853,7 @@ python-forecastio==1.4.0
|
|||
# python-gammu==3.1
|
||||
|
||||
# homeassistant.components.gc100
|
||||
python-gc100==1.0.3a
|
||||
python-gc100==1.0.3a0
|
||||
|
||||
# homeassistant.components.gitlab_ci
|
||||
python-gitlab==1.6.0
|
||||
|
|
|
@ -24,18 +24,19 @@ from . import (
|
|||
from .model import Config, Integration
|
||||
|
||||
INTEGRATION_PLUGINS = [
|
||||
json,
|
||||
codeowners,
|
||||
config_flow,
|
||||
dependencies,
|
||||
dhcp,
|
||||
json,
|
||||
manifest,
|
||||
mqtt,
|
||||
requirements,
|
||||
services,
|
||||
ssdp,
|
||||
translations,
|
||||
zeroconf,
|
||||
dhcp,
|
||||
usb,
|
||||
zeroconf,
|
||||
]
|
||||
HASS_PLUGINS = [
|
||||
coverage,
|
||||
|
@ -103,9 +104,6 @@ def main():
|
|||
|
||||
plugins = [*INTEGRATION_PLUGINS]
|
||||
|
||||
if config.requirements:
|
||||
plugins.append(requirements)
|
||||
|
||||
if config.specific_integrations:
|
||||
integrations = {}
|
||||
|
||||
|
@ -122,7 +120,11 @@ def main():
|
|||
try:
|
||||
start = monotonic()
|
||||
print(f"Validating {plugin.__name__.split('.')[-1]}...", end="", flush=True)
|
||||
if plugin is requirements and not config.specific_integrations:
|
||||
if (
|
||||
plugin is requirements
|
||||
and config.requirements
|
||||
and not config.specific_integrations
|
||||
):
|
||||
print()
|
||||
plugin.validate(integrations, config)
|
||||
print(f" done in {monotonic() - start:.2f}s")
|
||||
|
|
|
@ -9,6 +9,7 @@ import re
|
|||
import subprocess
|
||||
import sys
|
||||
|
||||
from awesomeversion import AwesomeVersion, AwesomeVersionStrategy
|
||||
from stdlib_list import stdlib_list
|
||||
from tqdm import tqdm
|
||||
|
||||
|
@ -61,6 +62,12 @@ def normalize_package_name(requirement: str) -> str:
|
|||
|
||||
def validate(integrations: dict[str, Integration], config: Config):
|
||||
"""Handle requirements for integrations."""
|
||||
# Check if we are doing format-only validation.
|
||||
if not config.requirements:
|
||||
for integration in integrations.values():
|
||||
validate_requirements_format(integration)
|
||||
return
|
||||
|
||||
ensure_cache()
|
||||
|
||||
# check for incompatible requirements
|
||||
|
@ -74,8 +81,45 @@ def validate(integrations: dict[str, Integration], config: Config):
|
|||
validate_requirements(integration)
|
||||
|
||||
|
||||
def validate_requirements_format(integration: Integration) -> bool:
|
||||
"""Validate requirements format.
|
||||
|
||||
Returns if valid.
|
||||
"""
|
||||
start_errors = len(integration.errors)
|
||||
|
||||
for req in integration.requirements:
|
||||
if " " in req:
|
||||
integration.add_error(
|
||||
"requirements",
|
||||
f'Requirement "{req}" contains a space',
|
||||
)
|
||||
continue
|
||||
|
||||
pkg, sep, version = req.partition("==")
|
||||
|
||||
if not sep and integration.core:
|
||||
integration.add_error(
|
||||
"requirements",
|
||||
f'Requirement {req} need to be pinned "<pkg name>==<version>".',
|
||||
)
|
||||
continue
|
||||
|
||||
if AwesomeVersion(version).strategy == AwesomeVersionStrategy.UNKNOWN:
|
||||
integration.add_error(
|
||||
"requirements",
|
||||
f"Unable to parse package version ({version}) for {pkg}.",
|
||||
)
|
||||
continue
|
||||
|
||||
return len(integration.errors) == start_errors
|
||||
|
||||
|
||||
def validate_requirements(integration: Integration):
|
||||
"""Validate requirements."""
|
||||
if not validate_requirements_format(integration):
|
||||
return
|
||||
|
||||
# Some integrations have not been fixed yet so are allowed to have violations.
|
||||
if integration.domain in IGNORE_VIOLATIONS:
|
||||
return
|
||||
|
|
68
tests/hassfest/test_requirements.py
Normal file
68
tests/hassfest/test_requirements.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
"""Tests for hassfest requirements."""
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from script.hassfest.model import Integration
|
||||
from script.hassfest.requirements import validate_requirements_format
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def integration():
|
||||
"""Fixture for hassfest integration model."""
|
||||
integration = Integration(
|
||||
path=Path("homeassistant/components/test"),
|
||||
manifest={
|
||||
"domain": "test",
|
||||
"documentation": "https://example.com",
|
||||
"name": "test",
|
||||
"codeowners": ["@awesome"],
|
||||
"requirements": [],
|
||||
},
|
||||
)
|
||||
yield integration
|
||||
|
||||
|
||||
def test_validate_requirements_format_with_space(integration: Integration):
|
||||
"""Test validate requirement with space around separator."""
|
||||
integration.manifest["requirements"] = ["test_package == 1"]
|
||||
assert not validate_requirements_format(integration)
|
||||
assert len(integration.errors) == 1
|
||||
assert 'Requirement "test_package == 1" contains a space' in [
|
||||
x.error for x in integration.errors
|
||||
]
|
||||
|
||||
|
||||
def test_validate_requirements_format_wrongly_pinned(integration: Integration):
|
||||
"""Test requirement with loose pin."""
|
||||
integration.manifest["requirements"] = ["test_package>=1"]
|
||||
assert not validate_requirements_format(integration)
|
||||
assert len(integration.errors) == 1
|
||||
assert 'Requirement test_package>=1 need to be pinned "<pkg name>==<version>".' in [
|
||||
x.error for x in integration.errors
|
||||
]
|
||||
|
||||
|
||||
def test_validate_requirements_format_ignore_pin_for_custom(integration: Integration):
|
||||
"""Test requirement ignore pinning for custom."""
|
||||
integration.manifest["requirements"] = ["test_package>=1"]
|
||||
integration.path = Path("")
|
||||
assert validate_requirements_format(integration)
|
||||
assert len(integration.errors) == 0
|
||||
|
||||
|
||||
def test_validate_requirements_format_invalid_version(integration: Integration):
|
||||
"""Test requirement with invalid version."""
|
||||
integration.manifest["requirements"] = ["test_package==invalid"]
|
||||
assert not validate_requirements_format(integration)
|
||||
assert len(integration.errors) == 1
|
||||
assert "Unable to parse package version (invalid) for test_package." in [
|
||||
x.error for x in integration.errors
|
||||
]
|
||||
|
||||
|
||||
def test_validate_requirements_format_successful(integration: Integration):
|
||||
"""Test requirement with successful result."""
|
||||
integration.manifest["requirements"] = ["test_package==1.2.3"]
|
||||
assert validate_requirements_format(integration)
|
||||
assert len(integration.errors) == 0
|
Loading…
Add table
Reference in a new issue