Skip to content

Commit

Permalink
Bump version
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthewFlamm committed Aug 29, 2024
1 parent 096e785 commit 76a7c48
Show file tree
Hide file tree
Showing 9 changed files with 68 additions and 66 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.8.3&labelColor=blue)
![HA core version](https://img.shields.io/static/v1?label=HA+core+version&message=2024.9.0b0&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.8.3
2024.9.0b0
2 changes: 1 addition & 1 deletion requirements_test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ respx==0.21.1
syrupy==4.6.1
tqdm==4.66.4
uv==0.2.27
homeassistant==2024.8.3
homeassistant==2024.9.0b0
SQLAlchemy==2.0.31

paho-mqtt==1.6.1
Expand Down
87 changes: 36 additions & 51 deletions src/pytest_homeassistant_custom_component/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import logging
import os
import pathlib
import threading
import time
import traceback
from types import FrameType, ModuleType
Expand All @@ -52,8 +51,8 @@
from homeassistant.components.device_automation import ( # noqa: F401
_async_get_device_automation_capabilities as async_get_device_automation_capabilities,
)
from homeassistant.config import async_process_component_config
from homeassistant.config_entries import ConfigEntry, ConfigFlow
from homeassistant.config import IntegrationConfigInfo, async_process_component_config
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
from homeassistant.const import (
DEVICE_DEFAULT_NAME,
EVENT_HOMEASSISTANT_CLOSE,
Expand Down Expand Up @@ -98,6 +97,7 @@
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util.async_ import (
_SHUTDOWN_RUN_CALLBACK_THREADSAFE,
get_scheduled_timer_handles,
run_callback_threadsafe,
)
import homeassistant.util.dt as dt_util
Expand Down Expand Up @@ -178,48 +178,6 @@ def get_test_config_dir(*add_path):
return os.path.join(os.path.dirname(__file__), "testing_config", *add_path)


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

loop_stop_event = threading.Event()

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

loop._thread_ident = threading.get_ident()
hass.loop_thread_id = loop._thread_ident
loop.run_forever()
loop_stop_event.set()

orig_stop = hass.stop
hass._stopped = Mock(set=loop.stop)

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

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

hass.start = start_hass
hass.stop = stop_hass

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

try:
yield hass
finally:
loop.run_until_complete(context_manager.__aexit__(None, None, None))
loop.close()


class StoreWithoutWriteLoad[_T: (Mapping[str, Any] | Sequence[Any])](storage.Store[_T]):
"""Fake store that does not write or load. Used for testing."""

Expand All @@ -242,6 +200,7 @@ async def async_test_home_assistant(
event_loop: asyncio.AbstractEventLoop | None = None,
load_registries: bool = True,
config_dir: str | None = None,
initial_state: CoreState = CoreState.running,
) -> AsyncGenerator[HomeAssistant]:
"""Return a Home Assistant object pointing at test config dir."""
hass = HomeAssistant(config_dir or get_test_config_dir())
Expand Down Expand Up @@ -369,7 +328,7 @@ def async_create_task_internal(coroutine, name=None, eager_start=True):
await rs.async_load(hass)
hass.data[bootstrap.DATA_REGISTRIES_LOADED] = None

hass.set_state(CoreState.running)
hass.set_state(initial_state)

@callback
def clear_instance(event):
Expand Down Expand Up @@ -430,14 +389,16 @@ def mock_service_log(call):


@callback
def async_mock_intent(hass, intent_typ):
def async_mock_intent(hass: HomeAssistant, intent_typ: str) -> list[intent.Intent]:
"""Set up a fake intent handler."""
intents = []
intents: list[intent.Intent] = []

class MockIntentHandler(intent.IntentHandler):
intent_type = intent_typ

async def async_handle(self, intent_obj):
async def async_handle(
self, intent_obj: intent.Intent
) -> intent.IntentResponse:
"""Handle the intent."""
intents.append(intent_obj)
return intent_obj.create_response()
Expand Down Expand Up @@ -536,7 +497,7 @@ def _async_fire_time_changed(
hass: HomeAssistant, utc_datetime: datetime | None, fire_all: bool
) -> None:
timestamp = dt_util.utc_to_timestamp(utc_datetime)
for task in list(hass.loop._scheduled):
for task in list(get_scheduled_timer_handles(hass.loop)):
if not isinstance(task, asyncio.TimerHandle):
continue
if task.cancelled():
Expand Down Expand Up @@ -1099,6 +1060,25 @@ def mock_state(
"""
self._async_set_state(hass, state, reason)

async def start_reauth_flow(
self,
hass: HomeAssistant,
context: dict[str, Any] | None = None,
data: dict[str, Any] | None = None,
) -> ConfigFlowResult:
"""Start a reauthentication flow."""
return await hass.config_entries.flow.async_init(
self.domain,
context={
"source": config_entries.SOURCE_REAUTH,
"entry_id": self.entry_id,
"title_placeholders": {"name": self.title},
"unique_id": self.unique_id,
}
| (context or {}),
data=self.data | (data or {}),
)


def patch_yaml_files(files_dict, endswith=True):
"""Patch load_yaml with a dictionary of yaml files."""
Expand Down Expand Up @@ -1151,7 +1131,12 @@ def assert_setup_component(count, domain=None):
"""
config = {}

async def mock_psc(hass, config_input, integration, component=None):
async def mock_psc(
hass: HomeAssistant,
config_input: ConfigType,
integration: loader.Integration,
component: loader.ComponentProtocol | None = None,
) -> IntegrationConfigInfo:
"""Mock the prepare_setup_component to capture config."""
domain_input = integration.domain
integration_config_info = await async_process_component_config(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,12 +269,16 @@ def assert_dict_of_states_equal_without_context_and_last_changed(
)


async def async_record_states(hass: HomeAssistant):
async def async_record_states(
hass: HomeAssistant,
) -> tuple[datetime, datetime, dict[str, list[State | None]]]:
"""Record some test states."""
return await hass.async_add_executor_job(record_states, hass)


def record_states(hass):
def record_states(
hass: HomeAssistant,
) -> tuple[datetime, datetime, dict[str, list[State | None]]]:
"""Record some test states.
We inject a bunch of state updates temperature sensors.
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 TYPE_CHECKING, Final
MAJOR_VERSION: Final = 2024
MINOR_VERSION: Final = 8
PATCH_VERSION: Final = "3"
MINOR_VERSION: Final = 9
PATCH_VERSION: Final = "0b0"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
17 changes: 14 additions & 3 deletions src/pytest_homeassistant_custom_component/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
from homeassistant.helpers.typing import ConfigType
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util, location
from homeassistant.util.async_ import create_eager_task
from homeassistant.util.async_ import create_eager_task, get_scheduled_timer_handles
from homeassistant.util.json import json_loads

from .ignore_uncaught_exceptions import IGNORE_UNCAUGHT_EXCEPTIONS
Expand Down Expand Up @@ -376,7 +376,7 @@ def verify_cleanup(
if tasks:
event_loop.run_until_complete(asyncio.wait(tasks))

for handle in event_loop._scheduled: # type: ignore[attr-defined]
for handle in get_scheduled_timer_handles(event_loop):
if not handle.cancelled():
with long_repr_strings():
if expected_lingering_timers:
Expand Down Expand Up @@ -1266,6 +1266,16 @@ def enable_statistics() -> bool:
return False


@pytest.fixture
def enable_missing_statistics() -> bool:
"""Fixture to control enabling of recorder's statistics compilation.
To enable statistics, tests can be marked with:
@pytest.mark.parametrize("enable_missing_statistics", [True])
"""
return False


@pytest.fixture
def enable_schema_validation() -> bool:
"""Fixture to control enabling of recorder's statistics table validation.
Expand Down Expand Up @@ -1457,6 +1467,7 @@ async def async_test_recorder(
recorder_db_url: str,
enable_nightly_purge: bool,
enable_statistics: bool,
enable_missing_statistics: bool,
enable_schema_validation: bool,
enable_migrate_context_ids: bool,
enable_migrate_event_type_ids: bool,
Expand Down Expand Up @@ -1515,7 +1526,7 @@ def debug_session_scope(
)
compile_missing = (
recorder.Recorder._schedule_compile_missing_statistics
if enable_statistics
if enable_missing_statistics
else None
)
migrate_states_context_ids = (
Expand Down
10 changes: 6 additions & 4 deletions src/pytest_homeassistant_custom_component/test_util/aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from contextlib import contextmanager
from http import HTTPStatus
import re
from typing import Any
from unittest import mock
from urllib.parse import parse_qs

Expand All @@ -23,6 +24,7 @@
from yarl import URL

from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
from homeassistant.core import HomeAssistant
from homeassistant.helpers.json import json_dumps
from homeassistant.util.json import json_loads

Expand All @@ -41,7 +43,7 @@ def mock_stream(data):
class AiohttpClientMocker:
"""Mock Aiohttp client requests."""

def __init__(self):
def __init__(self) -> None:
"""Initialize the request mocker."""
self._mocks = []
self._cookies = {}
Expand Down Expand Up @@ -178,7 +180,7 @@ def __init__(
headers=None,
side_effect=None,
closing=None,
):
) -> None:
"""Initialize a fake response."""
if json is not None:
text = json_dumps(json)
Expand Down Expand Up @@ -305,7 +307,7 @@ def mock_aiohttp_client() -> Iterator[AiohttpClientMocker]:
"""Context manager to mock aiohttp client."""
mocker = AiohttpClientMocker()

def create_session(hass, *args, **kwargs):
def create_session(hass: HomeAssistant, *args: Any, **kwargs: Any) -> ClientSession:
session = mocker.create_session(hass.loop)

async def close_session(event):
Expand All @@ -331,7 +333,7 @@ class MockLongPollSideEffect:
If queue is empty, will await until done.
"""

def __init__(self):
def __init__(self) -> None:
"""Initialize the queue."""
self.semaphore = asyncio.Semaphore(0)
self.response_list = []
Expand Down
2 changes: 1 addition & 1 deletion version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.13.155
0.13.156

0 comments on commit 76a7c48

Please sign in to comment.