Skip to content

Commit

Permalink
Bump version
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthewFlamm committed Mar 1, 2024
1 parent 409ab5b commit edb9749
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 148 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# pytest-homeassistant-custom-component

![HA core version](https://img.shields.io/static/v1?label=HA+core+version&message=2024.2.4&labelColor=blue)
![HA core version](https://img.shields.io/static/v1?label=HA+core+version&message=2024.3.0b3&labelColor=blue)

[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/MatthewFlamm/pytest-homeassistant-custom-component)

Expand Down
2 changes: 1 addition & 1 deletion ha_version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2024.2.4
2024.3.0b3
8 changes: 4 additions & 4 deletions requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# This file is originally from homeassistant/core and modified by pytest-homeassistant-custom-component.
astroid==3.0.1
astroid==3.1.0
mypy==1.8.0
pre-commit==3.6.0
pylint==3.0.3
pre-commit==3.6.2
pylint==3.1.0
types-aiofiles==23.2.0.20240106
types-atomicwrites==1.4.5.1
types-croniter==1.0.6
Expand All @@ -15,7 +15,7 @@ types-pillow==10.2.0.20240111
types-protobuf==4.24.0.20240106
types-psutil==5.9.5.20240106
types-python-dateutil==2.8.19.20240106
types-python-slugify==8.0.0.3
types-python-slugify==8.0.2.20240127
types-pytz==2023.3.1.1
types-PyYAML==6.0.12.12
types-requests==2.31.0.3
Expand Down
26 changes: 13 additions & 13 deletions requirements_test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,30 @@

-c homeassistant/package_constraints.txt
-r requirements_test_pre_commit.txt
coverage==7.4.1
freezegun==1.3.1
coverage==7.4.3
freezegun==1.4.0
mock-open==1.4.0
pydantic==1.10.12
pylint-per-file-ignores==1.2.1
pipdeptree==2.13.2
pytest-asyncio==0.23.4
pylint-per-file-ignores==1.3.2
pipdeptree==2.15.1
pytest-asyncio==0.23.5
pytest-aiohttp==1.0.5
pytest-cov==4.1.0
pytest-freezer==0.4.8
pytest-socket==0.6.0
pytest-socket==0.7.0
pytest-test-groups==1.0.3
pytest-sugar==0.9.7
pytest-timeout==2.1.0
pytest-sugar==1.0.0
pytest-timeout==2.2.0
pytest-unordered==0.5.2
pytest-picked==0.5.0
pytest-xdist==3.3.1
pytest==7.4.4
pytest==8.0.2
requests-mock==1.11.0
respx==0.20.2
syrupy==4.6.0
tqdm==4.66.1
homeassistant==2024.2.4
SQLAlchemy==2.0.25
syrupy==4.6.1
tqdm==4.66.2
homeassistant==2024.3.0b3
SQLAlchemy==2.0.27

paho-mqtt==1.6.1

Expand Down
133 changes: 106 additions & 27 deletions src/pytest_homeassistant_custom_component/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

import asyncio
from collections import OrderedDict
from collections.abc import Generator, Mapping, Sequence
from contextlib import contextmanager
from collections.abc import AsyncGenerator, Generator, Mapping, Sequence
from contextlib import asynccontextmanager, contextmanager
from datetime import UTC, datetime, timedelta
from enum import Enum
import functools as ft
Expand All @@ -22,7 +22,7 @@
import time
import traceback
from types import ModuleType
from typing import Any, NoReturn
from typing import Any, NoReturn, TypeVar
from unittest.mock import AsyncMock, Mock, patch

from aiohttp.test_utils import unused_port as get_test_instance_port # noqa: F401
Expand Down Expand Up @@ -68,12 +68,15 @@
entity_platform,
entity_registry as er,
event,
floor_registry as fr,
intent,
issue_registry as ir,
label_registry as lr,
recorder as recorder_helper,
restore_state,
restore_state as rs,
storage,
translation,
)
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
Expand Down Expand Up @@ -157,15 +160,17 @@ def get_test_config_dir(*add_path):
return os.path.join(os.path.dirname(__file__), "testing_config", *add_path)


def get_test_home_assistant():
@contextmanager
def get_test_home_assistant() -> Generator[HomeAssistant, None, None]:
"""Return a Home Assistant object pointing at test config directory."""
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
hass = loop.run_until_complete(async_test_home_assistant(loop))
context_manager = async_test_home_assistant(loop)
hass = loop.run_until_complete(context_manager.__aenter__())

loop_stop_event = threading.Event()

def run_loop():
def run_loop() -> None:
"""Run event loop."""

loop._thread_ident = threading.get_ident()
Expand All @@ -175,27 +180,55 @@ def run_loop():
orig_stop = hass.stop
hass._stopped = Mock(set=loop.stop)

def start_hass(*mocks):
def start_hass(*mocks: Any) -> None:
"""Start hass."""
asyncio.run_coroutine_threadsafe(hass.async_start(), loop).result()

def stop_hass():
def stop_hass() -> None:
"""Stop hass."""
orig_stop()
loop_stop_event.wait()
loop.close()

hass.start = start_hass
hass.stop = stop_hass

threading.Thread(name="LoopThread", target=run_loop, daemon=False).start()

return hass
yield hass
loop.run_until_complete(context_manager.__aexit__(None, None, None))
loop.close()


_T = TypeVar("_T", bound=Mapping[str, Any] | Sequence[Any])


class StoreWithoutWriteLoad(storage.Store[_T]):
"""Fake store that does not write or load. Used for testing."""

async def async_test_home_assistant(event_loop, load_registries=True):
async def async_save(self, *args: Any, **kwargs: Any) -> None:
"""Save the data.
This function is mocked out in .
"""

@callback
def async_save_delay(self, *args: Any, **kwargs: Any) -> None:
"""Save data with an optional delay.
This function is mocked out in .
"""


@asynccontextmanager
async def async_test_home_assistant(
event_loop: asyncio.AbstractEventLoop | None = None,
load_registries: bool = True,
storage_dir: str | None = None,
) -> AsyncGenerator[HomeAssistant, None]:
"""Return a Home Assistant object pointing at test config dir."""
hass = HomeAssistant(get_test_config_dir())
if storage_dir:
hass.config.config_dir = storage_dir
store = auth_store.AuthStore(hass)
hass.auth = auth.AuthManager(hass, store, {}, {})
ensure_auth_manager_loaded(hass.auth)
Expand All @@ -204,6 +237,7 @@ async def async_test_home_assistant(event_loop, load_registries=True):
orig_async_add_job = hass.async_add_job
orig_async_add_executor_job = hass.async_add_executor_job
orig_async_create_task = hass.async_create_task
orig_tz = dt_util.DEFAULT_TIME_ZONE

def async_add_job(target, *args):
"""Add job."""
Expand Down Expand Up @@ -231,14 +265,14 @@ def async_add_executor_job(target, *args):

return orig_async_add_executor_job(target, *args)

def async_create_task(coroutine, name=None):
def async_create_task(coroutine, name=None, eager_start=False):
"""Create task."""
if isinstance(coroutine, Mock) and not isinstance(coroutine, AsyncMock):
fut = asyncio.Future()
fut.set_result(None)
return fut

return orig_async_create_task(coroutine, name)
return orig_async_create_task(coroutine, name, eager_start)

hass.async_add_job = async_add_job
hass.async_add_executor_job = async_add_executor_job
Expand Down Expand Up @@ -272,22 +306,42 @@ def async_create_task(coroutine, name=None):
# Load the registries
entity.async_setup(hass)
loader.async_setup(hass)

# setup translation cache instead of calling translation.async_setup(hass)
hass.data[translation.TRANSLATION_FLATTEN_CACHE] = translation._TranslationCache(
hass
)
if load_registries:
with patch(
"homeassistant.helpers.storage.Store.async_load", return_value=None
with patch.object(
StoreWithoutWriteLoad, "async_load", return_value=None
), patch(
"homeassistant.helpers.area_registry.AreaRegistryStore",
StoreWithoutWriteLoad,
), patch(
"homeassistant.helpers.device_registry.DeviceRegistryStore",
StoreWithoutWriteLoad,
), patch(
"homeassistant.helpers.entity_registry.EntityRegistryStore",
StoreWithoutWriteLoad,
), patch(
"homeassistant.helpers.storage.Store", # Floor & label registry are different
StoreWithoutWriteLoad,
), patch(
"homeassistant.helpers.issue_registry.IssueRegistryStore",
StoreWithoutWriteLoad,
), patch(
"homeassistant.helpers.restore_state.RestoreStateData.async_setup_dump",
return_value=None,
), patch(
"homeassistant.helpers.restore_state.start.async_at_start",
):
await asyncio.gather(
ar.async_load(hass),
dr.async_load(hass),
er.async_load(hass),
ir.async_load(hass),
rs.async_load(hass),
)
await ar.async_load(hass)
await dr.async_load(hass)
await er.async_load(hass)
await fr.async_load(hass)
await ir.async_load(hass)
await lr.async_load(hass)
await rs.async_load(hass)
hass.data[bootstrap.DATA_REGISTRIES_LOADED] = None

hass.set_state(CoreState.running)
Expand All @@ -299,7 +353,10 @@ def clear_instance(event):

hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, clear_instance)

return hass
yield hass

# Restore timezone, it is set when creating the hass object
dt_util.DEFAULT_TIME_ZONE = orig_tz


def async_mock_service(
Expand Down Expand Up @@ -844,8 +901,9 @@ def __init__(
entity_namespace=entity_namespace,
)

async def _async_on_stop(_: Event) -> None:
await self.async_shutdown()
@callback
def _async_on_stop(_: Event) -> None:
self.async_shutdown()

hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_on_stop)

Expand Down Expand Up @@ -940,7 +998,7 @@ def __init__(
kwargs["state"] = state
super().__init__(**kwargs)
if reason is not None:
self.reason = reason
object.__setattr__(self, "reason", reason)

def add_to_hass(self, hass: HomeAssistant) -> None:
"""Test helper to add entry to hass."""
Expand All @@ -950,6 +1008,27 @@ def add_to_manager(self, manager: config_entries.ConfigEntries) -> None:
"""Test helper to add entry to entry manager."""
manager._entries[self.entry_id] = self

def mock_state(
self,
hass: HomeAssistant,
state: config_entries.ConfigEntryState,
reason: str | None = None,
) -> None:
"""Mock the state of a config entry to be used in .
Currently this is a wrapper around _async_set_state, but it may
change in the future.
It is preferable to get the config entry into the desired state
by using the normal config entry methods, and this helper
is only intended to be used in cases where that is not possible.
When in doubt, this helper should not be used in new code
and is only intended for backwards compatibility with existing
.
"""
self._async_set_state(hass, state, reason)


def patch_yaml_files(files_dict, endswith=True):
"""Patch load_yaml with a dictionary of yaml files."""
Expand Down Expand Up @@ -1343,7 +1422,7 @@ def mock_integration(
f"{loader.PACKAGE_BUILTIN}.{module.DOMAIN}"
if built_in
else f"{loader.PACKAGE_CUSTOM_COMPONENTS}.{module.DOMAIN}",
None,
pathlib.Path(""),
module.mock_manifest(),
)

Expand Down
4 changes: 2 additions & 2 deletions src/pytest_homeassistant_custom_component/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"""
from typing import Final
MAJOR_VERSION: Final = 2024
MINOR_VERSION: Final = 2
PATCH_VERSION: Final = "4"
MINOR_VERSION: Final = 3
PATCH_VERSION: Final = "0b3"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
Loading

0 comments on commit edb9749

Please sign in to comment.