mirror of
https://github.com/esphome/esphome.git
synced 2026-08-23 06:36:23 +00:00
111 lines
3.7 KiB
Python
111 lines
3.7 KiB
Python
"""Tests for micro_wake_word local model validation."""
|
|
|
|
import json
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from esphome.components.micro_wake_word import LOCAL_SCHEMA
|
|
from esphome.core import CORE
|
|
|
|
MANIFEST: dict[str, Any] = {
|
|
"type": "micro",
|
|
"model": "hey_jarvis.tflite",
|
|
"author": "someone",
|
|
"version": 2,
|
|
"wake_word": "hey jarvis",
|
|
"trained_languages": ["en"],
|
|
"micro": {
|
|
"feature_step_size": 10,
|
|
"tensor_arena_size": 30000,
|
|
"probability_cutoff": 0.97,
|
|
"sliding_window_size": 5,
|
|
"minimum_esphome_version": "2024.7.0",
|
|
},
|
|
}
|
|
|
|
|
|
def _registered_files() -> list[Path]:
|
|
"""Files components registered for bundling this run."""
|
|
data = CORE.data.get("bundle")
|
|
return list(data.extra_files) if data else []
|
|
|
|
|
|
@pytest.fixture
|
|
def config_dir(tmp_path: Path) -> Path:
|
|
"""A config dir holding a manifest and its model file."""
|
|
(tmp_path / "models").mkdir()
|
|
(tmp_path / "models" / "hey_jarvis.tflite").write_bytes(b"fake model")
|
|
(tmp_path / "models" / "hey_jarvis.json").write_text(json.dumps(MANIFEST))
|
|
CORE.config_path = tmp_path / "test.yaml"
|
|
return tmp_path
|
|
|
|
|
|
def test_local_schema_registers_model_file(config_dir: Path) -> None:
|
|
"""The model file named by the manifest is registered so bundles include it."""
|
|
LOCAL_SCHEMA({"path": "models/hey_jarvis.json"})
|
|
|
|
assert _registered_files() == [config_dir / "models" / "hey_jarvis.tflite"]
|
|
|
|
|
|
def test_local_schema_registers_model_file_in_subdirectory(config_dir: Path) -> None:
|
|
"""The model reference is resolved relative to the manifest, not the config dir."""
|
|
nested = config_dir / "models" / "nested"
|
|
nested.mkdir()
|
|
(nested / "model.tflite").write_bytes(b"fake model")
|
|
(config_dir / "models" / "nested.json").write_text(
|
|
json.dumps({**MANIFEST, "model": "nested/model.tflite"})
|
|
)
|
|
|
|
LOCAL_SCHEMA({"path": "models/nested.json"})
|
|
|
|
assert _registered_files() == [nested / "model.tflite"]
|
|
|
|
|
|
def test_local_schema_leaves_config_untouched(config_dir: Path) -> None:
|
|
"""Registration is a side effect; the model file is not a config key."""
|
|
config = LOCAL_SCHEMA({"path": "models/hey_jarvis.json"})
|
|
|
|
assert config == {"path": config_dir / "models" / "hey_jarvis.json"}
|
|
|
|
|
|
def test_local_schema_missing_model_file_still_validates(config_dir: Path) -> None:
|
|
"""A model file that does not exist is registered, not rejected.
|
|
|
|
Raising here would be swallowed by the shorthand validator, which would then
|
|
report a confusing error about a missing file in a git repository.
|
|
"""
|
|
(config_dir / "models" / "hey_jarvis.tflite").unlink()
|
|
|
|
LOCAL_SCHEMA({"path": "models/hey_jarvis.json"})
|
|
|
|
assert _registered_files() == [config_dir / "models" / "hey_jarvis.tflite"]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"contents",
|
|
[
|
|
pytest.param("{not valid json", id="malformed"),
|
|
pytest.param(json.dumps({"type": "micro"}), id="no_model_key"),
|
|
pytest.param(json.dumps(["a", "list"]), id="not_an_object"),
|
|
pytest.param(json.dumps({"model": 42}), id="model_not_a_string"),
|
|
],
|
|
)
|
|
def test_local_schema_bad_manifest_does_not_raise(
|
|
config_dir: Path, contents: str, caplog: pytest.LogCaptureFixture
|
|
) -> None:
|
|
"""Manifest problems are left to later stages, which report them better.
|
|
|
|
The skipped registration is logged so a bundle built without the model file can
|
|
be diagnosed.
|
|
"""
|
|
(config_dir / "models" / "hey_jarvis.json").write_text(contents)
|
|
|
|
with caplog.at_level(logging.DEBUG):
|
|
LOCAL_SCHEMA({"path": "models/hey_jarvis.json"})
|
|
|
|
assert _registered_files() == []
|
|
assert "Not registering a model file" in caplog.text
|