mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 20:27:08 +00:00
Clean up translations for mocked integrations inbetween tests (#138732)
* Clean up translations for mocked integrations inbetween tests * Adjust code, add test * Fix docstring * Improve cleanup, add test * Fix test
This commit is contained in:
parent
1733f5d3fb
commit
af0a862aab
@ -1867,23 +1867,6 @@ async def snapshot_platform(
|
|||||||
assert state == snapshot(name=f"{entity_entry.entity_id}-state")
|
assert state == snapshot(name=f"{entity_entry.entity_id}-state")
|
||||||
|
|
||||||
|
|
||||||
def reset_translation_cache(hass: HomeAssistant, components: list[str]) -> None:
|
|
||||||
"""Reset translation cache for specified components.
|
|
||||||
|
|
||||||
Use this if you are mocking a core component (for example via
|
|
||||||
mock_integration), to ensure that the mocked translations are not
|
|
||||||
persisted in the shared session cache.
|
|
||||||
"""
|
|
||||||
translations_cache = translation._async_get_translations_cache(hass)
|
|
||||||
for loaded_components in translations_cache.cache_data.loaded.values():
|
|
||||||
for component_to_unload in components:
|
|
||||||
loaded_components.discard(component_to_unload)
|
|
||||||
for loaded_categories in translations_cache.cache_data.cache.values():
|
|
||||||
for loaded_components in loaded_categories.values():
|
|
||||||
for component_to_unload in components:
|
|
||||||
loaded_components.pop(component_to_unload, None)
|
|
||||||
|
|
||||||
|
|
||||||
@lru_cache
|
@lru_cache
|
||||||
def get_quality_scale(integration: str) -> dict[str, QualityScaleStatus]:
|
def get_quality_scale(integration: str) -> dict[str, QualityScaleStatus]:
|
||||||
"""Load quality scale for integration."""
|
"""Load quality scale for integration."""
|
||||||
|
@ -34,7 +34,6 @@ from tests.common import (
|
|||||||
mock_integration,
|
mock_integration,
|
||||||
mock_platform,
|
mock_platform,
|
||||||
mock_restore_cache,
|
mock_restore_cache,
|
||||||
reset_translation_cache,
|
|
||||||
)
|
)
|
||||||
from tests.typing import ClientSessionGenerator, WebSocketGenerator
|
from tests.typing import ClientSessionGenerator, WebSocketGenerator
|
||||||
|
|
||||||
@ -519,9 +518,6 @@ async def test_default_engine_prefer_cloud_entity(
|
|||||||
assert provider_engine.name == "test"
|
assert provider_engine.name == "test"
|
||||||
assert async_default_engine(hass) == "stt.cloud_stt_entity"
|
assert async_default_engine(hass) == "stt.cloud_stt_entity"
|
||||||
|
|
||||||
# Reset the `cloud` translations cache to avoid flaky translation checks
|
|
||||||
reset_translation_cache(hass, ["cloud"])
|
|
||||||
|
|
||||||
|
|
||||||
async def test_get_engine_legacy(
|
async def test_get_engine_legacy(
|
||||||
hass: HomeAssistant, tmp_path: Path, mock_provider: MockSTTProvider
|
hass: HomeAssistant, tmp_path: Path, mock_provider: MockSTTProvider
|
||||||
|
@ -44,7 +44,6 @@ from tests.common import (
|
|||||||
mock_integration,
|
mock_integration,
|
||||||
mock_platform,
|
mock_platform,
|
||||||
mock_restore_cache,
|
mock_restore_cache,
|
||||||
reset_translation_cache,
|
|
||||||
)
|
)
|
||||||
from tests.typing import ClientSessionGenerator, WebSocketGenerator
|
from tests.typing import ClientSessionGenerator, WebSocketGenerator
|
||||||
|
|
||||||
@ -1987,6 +1986,3 @@ async def test_default_engine_prefer_cloud_entity(
|
|||||||
provider_engine = tts.async_resolve_engine(hass, "test")
|
provider_engine = tts.async_resolve_engine(hass, "test")
|
||||||
assert provider_engine == "test"
|
assert provider_engine == "test"
|
||||||
assert tts.async_default_engine(hass) == "tts.cloud_tts_entity"
|
assert tts.async_default_engine(hass) == "tts.cloud_tts_entity"
|
||||||
|
|
||||||
# Reset the `cloud` translations cache to avoid flaky translation checks
|
|
||||||
reset_translation_cache(hass, ["cloud"])
|
|
||||||
|
@ -11,6 +11,7 @@ import gc
|
|||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
import reprlib
|
import reprlib
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
import sqlite3
|
import sqlite3
|
||||||
@ -49,7 +50,7 @@ from . import patch_recorder
|
|||||||
# Setup patching of dt_util time functions before any other Home Assistant imports
|
# Setup patching of dt_util time functions before any other Home Assistant imports
|
||||||
from . import patch_time # noqa: F401, isort:skip
|
from . import patch_time # noqa: F401, isort:skip
|
||||||
|
|
||||||
from homeassistant import core as ha, loader, runner
|
from homeassistant import components, core as ha, loader, runner
|
||||||
from homeassistant.auth.const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY
|
from homeassistant.auth.const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY
|
||||||
from homeassistant.auth.models import Credentials
|
from homeassistant.auth.models import Credentials
|
||||||
from homeassistant.auth.providers import homeassistant
|
from homeassistant.auth.providers import homeassistant
|
||||||
@ -85,6 +86,7 @@ from homeassistant.helpers import (
|
|||||||
issue_registry as ir,
|
issue_registry as ir,
|
||||||
label_registry as lr,
|
label_registry as lr,
|
||||||
recorder as recorder_helper,
|
recorder as recorder_helper,
|
||||||
|
translation as translation_helper,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
from homeassistant.helpers.translation import _TranslationsCacheData
|
from homeassistant.helpers.translation import _TranslationsCacheData
|
||||||
@ -1234,9 +1236,8 @@ def mock_get_source_ip() -> Generator[_patch]:
|
|||||||
def translations_once() -> Generator[_patch]:
|
def translations_once() -> Generator[_patch]:
|
||||||
"""Only load translations once per session.
|
"""Only load translations once per session.
|
||||||
|
|
||||||
Warning: having this as a session fixture can cause issues with tests that
|
Note: To avoid issues with tests that mock integrations, translations for
|
||||||
create mock integrations, overriding the real integration translations
|
mocked integrations are cleaned up by the evict_faked_translations fixture.
|
||||||
with empty ones. Translations should be reset after such tests (see #131628)
|
|
||||||
"""
|
"""
|
||||||
cache = _TranslationsCacheData({}, {})
|
cache = _TranslationsCacheData({}, {})
|
||||||
patcher = patch(
|
patcher = patch(
|
||||||
@ -1250,6 +1251,30 @@ def translations_once() -> Generator[_patch]:
|
|||||||
patcher.stop()
|
patcher.stop()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True, scope="module")
|
||||||
|
def evict_faked_translations(translations_once) -> Generator[_patch]:
|
||||||
|
"""Clear translations for mocked integrations from the cache after each module."""
|
||||||
|
real_component_strings = translation_helper._async_get_component_strings
|
||||||
|
with patch(
|
||||||
|
"homeassistant.helpers.translation._async_get_component_strings",
|
||||||
|
wraps=real_component_strings,
|
||||||
|
) as mock_component_strings:
|
||||||
|
yield
|
||||||
|
cache: _TranslationsCacheData = translations_once.kwargs["return_value"]
|
||||||
|
component_paths = components.__path__
|
||||||
|
|
||||||
|
for call in mock_component_strings.mock_calls:
|
||||||
|
integrations: dict[str, loader.Integration] = call.args[3]
|
||||||
|
for domain, integration in integrations.items():
|
||||||
|
if any(
|
||||||
|
pathlib.Path(f"{component_path}/{domain}") == integration.file_path
|
||||||
|
for component_path in component_paths
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
for loaded_for_lang in cache.loaded.values():
|
||||||
|
loaded_for_lang.discard(domain)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def disable_translations_once(
|
def disable_translations_once(
|
||||||
translations_once: _patch,
|
translations_once: _patch,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
"""Test test fixture configuration."""
|
"""Test test fixture configuration."""
|
||||||
|
|
||||||
|
from collections.abc import Generator
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
import pathlib
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
@ -9,8 +11,11 @@ import pytest_socket
|
|||||||
|
|
||||||
from homeassistant.components.http import HomeAssistantView
|
from homeassistant.components.http import HomeAssistantView
|
||||||
from homeassistant.core import HomeAssistant, async_get_hass
|
from homeassistant.core import HomeAssistant, async_get_hass
|
||||||
|
from homeassistant.helpers import translation
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from .common import MockModule, mock_integration
|
||||||
|
from .conftest import evict_faked_translations
|
||||||
from .typing import ClientSessionGenerator
|
from .typing import ClientSessionGenerator
|
||||||
|
|
||||||
|
|
||||||
@ -70,3 +75,46 @@ async def test_aiohttp_client_frozen_router_view(
|
|||||||
assert response.status == HTTPStatus.OK
|
assert response.status == HTTPStatus.OK
|
||||||
result = await response.json()
|
result = await response.json()
|
||||||
assert result["test"] is True
|
assert result["test"] is True
|
||||||
|
|
||||||
|
|
||||||
|
async def test_evict_faked_translations_assumptions(hass: HomeAssistant) -> None:
|
||||||
|
"""Test assumptions made when detecting translations for mocked integrations.
|
||||||
|
|
||||||
|
If this test fails, the evict_faked_translations may need to be updated.
|
||||||
|
"""
|
||||||
|
integration = mock_integration(hass, MockModule("test"), built_in=True)
|
||||||
|
assert integration.file_path == pathlib.Path("")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_evict_faked_translations(hass: HomeAssistant, translations_once) -> None:
|
||||||
|
"""Test the evict_faked_translations fixture."""
|
||||||
|
cache: translation._TranslationsCacheData = translations_once.kwargs["return_value"]
|
||||||
|
fake_domain = "test"
|
||||||
|
real_domain = "homeassistant"
|
||||||
|
|
||||||
|
# Evict the real domain from the cache in case it's been loaded before
|
||||||
|
cache.loaded["en"].discard(real_domain)
|
||||||
|
|
||||||
|
assert fake_domain not in cache.loaded["en"]
|
||||||
|
assert real_domain not in cache.loaded["en"]
|
||||||
|
|
||||||
|
# The evict_faked_translations fixture has module scope, so we set it up and
|
||||||
|
# tear it down manually
|
||||||
|
real_func = evict_faked_translations.__pytest_wrapped__.obj
|
||||||
|
gen: Generator = real_func(translations_once)
|
||||||
|
|
||||||
|
# Set up the evict_faked_translations fixture
|
||||||
|
next(gen)
|
||||||
|
|
||||||
|
mock_integration(hass, MockModule(fake_domain), built_in=True)
|
||||||
|
await translation.async_load_integrations(hass, {fake_domain, real_domain})
|
||||||
|
assert fake_domain in cache.loaded["en"]
|
||||||
|
assert real_domain in cache.loaded["en"]
|
||||||
|
|
||||||
|
# Tear down the evict_faked_translations fixture
|
||||||
|
with pytest.raises(StopIteration):
|
||||||
|
next(gen)
|
||||||
|
|
||||||
|
# The mock integration should be removed from the cache, the real domain should still be there
|
||||||
|
assert fake_domain not in cache.loaded["en"]
|
||||||
|
assert real_domain in cache.loaded["en"]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user