Use event loop scheduling for tracking time patterns (#38021)
* Use event loop scheduling for tracking time patterns * make patching of time targetable * patch time tests since time can tick to match during the test * fix more tests * time can only move forward * time can only move forward * back to 100% coverage * simplify since the event loop time cannot move backwards * simplify some more * revert simplify * Revert "revert simplify" This reverts commitbd42f232f6
. * Revert "simplify some more" This reverts commit2a6c57d514
. * Revert "simplify since the event loop time cannot move backwards" This reverts commit3b13714ef4
. * Attempt another simplify * time does not move backwards in the last two * remove next_time <= now check * fix previous merge error
This commit is contained in:
parent
7bc8caca96
commit
60009ec2f9
9 changed files with 404 additions and 199 deletions
|
@ -1,4 +1,5 @@
|
||||||
"""Helpers for listening to events."""
|
"""Helpers for listening to events."""
|
||||||
|
import asyncio
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import functools as ft
|
import functools as ft
|
||||||
import logging
|
import logging
|
||||||
|
@ -563,6 +564,9 @@ def async_track_sunset(
|
||||||
|
|
||||||
track_sunset = threaded_listener_factory(async_track_sunset)
|
track_sunset = threaded_listener_factory(async_track_sunset)
|
||||||
|
|
||||||
|
# For targeted patching in tests
|
||||||
|
pattern_utc_now = dt_util.utcnow
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@bind_hass
|
@bind_hass
|
||||||
|
@ -590,7 +594,7 @@ def async_track_utc_time_change(
|
||||||
matching_minutes = dt_util.parse_time_expression(minute, 0, 59)
|
matching_minutes = dt_util.parse_time_expression(minute, 0, 59)
|
||||||
matching_hours = dt_util.parse_time_expression(hour, 0, 23)
|
matching_hours = dt_util.parse_time_expression(hour, 0, 23)
|
||||||
|
|
||||||
next_time = None
|
next_time: datetime = dt_util.utcnow()
|
||||||
|
|
||||||
def calculate_next(now: datetime) -> None:
|
def calculate_next(now: datetime) -> None:
|
||||||
"""Calculate and set the next time the trigger should fire."""
|
"""Calculate and set the next time the trigger should fire."""
|
||||||
|
@ -603,29 +607,37 @@ def async_track_utc_time_change(
|
||||||
|
|
||||||
# Make sure rolling back the clock doesn't prevent the timer from
|
# Make sure rolling back the clock doesn't prevent the timer from
|
||||||
# triggering.
|
# triggering.
|
||||||
last_now: Optional[datetime] = None
|
cancel_callback: Optional[asyncio.TimerHandle] = None
|
||||||
|
calculate_next(next_time)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def pattern_time_change_listener(event: Event) -> None:
|
def pattern_time_change_listener() -> None:
|
||||||
"""Listen for matching time_changed events."""
|
"""Listen for matching time_changed events."""
|
||||||
nonlocal next_time, last_now
|
nonlocal next_time, cancel_callback
|
||||||
|
|
||||||
now = event.data[ATTR_NOW]
|
now = pattern_utc_now()
|
||||||
|
hass.async_run_job(action, dt_util.as_local(now) if local else now)
|
||||||
|
|
||||||
if last_now is None or now < last_now:
|
calculate_next(now + timedelta(seconds=1))
|
||||||
# Time rolled back or next time not yet calculated
|
|
||||||
calculate_next(now)
|
|
||||||
|
|
||||||
last_now = now
|
cancel_callback = hass.loop.call_at(
|
||||||
|
hass.loop.time() + next_time.timestamp() - time.time(),
|
||||||
|
pattern_time_change_listener,
|
||||||
|
)
|
||||||
|
|
||||||
if next_time <= now:
|
cancel_callback = hass.loop.call_at(
|
||||||
hass.async_run_job(action, dt_util.as_local(now) if local else now)
|
hass.loop.time() + next_time.timestamp() - time.time(),
|
||||||
calculate_next(now + timedelta(seconds=1))
|
pattern_time_change_listener,
|
||||||
|
)
|
||||||
|
|
||||||
# We can't use async_track_point_in_utc_time here because it would
|
@callback
|
||||||
# break in the case that the system time abruptly jumps backwards.
|
def unsub_pattern_time_change_listener() -> None:
|
||||||
# Our custom last_now logic takes care of resolving that scenario.
|
"""Cancel the call_later."""
|
||||||
return hass.bus.async_listen(EVENT_TIME_CHANGED, pattern_time_change_listener)
|
nonlocal cancel_callback
|
||||||
|
assert cancel_callback is not None
|
||||||
|
cancel_callback.cancel()
|
||||||
|
|
||||||
|
return unsub_pattern_time_change_listener
|
||||||
|
|
||||||
|
|
||||||
track_utc_time_change = threaded_listener_factory(async_track_utc_time_change)
|
track_utc_time_change = threaded_listener_factory(async_track_utc_time_change)
|
||||||
|
|
|
@ -285,7 +285,7 @@ fire_mqtt_message = threadsafe_callback_factory(async_fire_mqtt_message)
|
||||||
|
|
||||||
|
|
||||||
@ha.callback
|
@ha.callback
|
||||||
def async_fire_time_changed(hass, datetime_):
|
def async_fire_time_changed(hass, datetime_, fire_all=False):
|
||||||
"""Fire a time changes event."""
|
"""Fire a time changes event."""
|
||||||
hass.bus.async_fire(EVENT_TIME_CHANGED, {"now": date_util.as_utc(datetime_)})
|
hass.bus.async_fire(EVENT_TIME_CHANGED, {"now": date_util.as_utc(datetime_)})
|
||||||
|
|
||||||
|
@ -298,9 +298,13 @@ def async_fire_time_changed(hass, datetime_):
|
||||||
future_seconds = task.when() - hass.loop.time()
|
future_seconds = task.when() - hass.loop.time()
|
||||||
mock_seconds_into_future = datetime_.timestamp() - time.time()
|
mock_seconds_into_future = datetime_.timestamp() - time.time()
|
||||||
|
|
||||||
if mock_seconds_into_future >= future_seconds:
|
if fire_all or mock_seconds_into_future >= future_seconds:
|
||||||
task._run()
|
with patch(
|
||||||
task.cancel()
|
"homeassistant.helpers.event.pattern_utc_now",
|
||||||
|
return_value=date_util.as_utc(datetime_),
|
||||||
|
):
|
||||||
|
task._run()
|
||||||
|
task.cancel()
|
||||||
|
|
||||||
|
|
||||||
fire_time_changed = threadsafe_callback_factory(async_fire_time_changed)
|
fire_time_changed = threadsafe_callback_factory(async_fire_time_changed)
|
||||||
|
|
|
@ -46,7 +46,11 @@ async def test_if_fires_using_at(hass, calls):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow().replace(hour=5, minute=0, second=0))
|
now = dt_util.utcnow()
|
||||||
|
|
||||||
|
async_fire_time_changed(
|
||||||
|
hass, now.replace(year=now.year + 1, hour=5, minute=0, second=0)
|
||||||
|
)
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"""The tests for the time_pattern automation."""
|
"""The tests for the time_pattern automation."""
|
||||||
|
from asynctest.mock import patch
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import homeassistant.components.automation as automation
|
import homeassistant.components.automation as automation
|
||||||
|
@ -23,53 +24,67 @@ def setup_comp(hass):
|
||||||
|
|
||||||
async def test_if_fires_when_hour_matches(hass, calls):
|
async def test_if_fires_when_hour_matches(hass, calls):
|
||||||
"""Test for firing if hour is matching."""
|
"""Test for firing if hour is matching."""
|
||||||
assert await async_setup_component(
|
now = dt_util.utcnow()
|
||||||
hass,
|
time_that_will_not_match_right_away = dt_util.utcnow().replace(
|
||||||
automation.DOMAIN,
|
year=now.year + 1, hour=3
|
||||||
{
|
|
||||||
automation.DOMAIN: {
|
|
||||||
"trigger": {
|
|
||||||
"platform": "time_pattern",
|
|
||||||
"hours": 0,
|
|
||||||
"minutes": "*",
|
|
||||||
"seconds": "*",
|
|
||||||
},
|
|
||||||
"action": {"service": "test.automation"},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.util.dt.utcnow", return_value=time_that_will_not_match_right_away
|
||||||
|
):
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
automation.DOMAIN,
|
||||||
|
{
|
||||||
|
automation.DOMAIN: {
|
||||||
|
"trigger": {
|
||||||
|
"platform": "time_pattern",
|
||||||
|
"hours": 0,
|
||||||
|
"minutes": "*",
|
||||||
|
"seconds": "*",
|
||||||
|
},
|
||||||
|
"action": {"service": "test.automation"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow().replace(hour=0))
|
async_fire_time_changed(hass, now.replace(year=now.year + 2, hour=0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
|
||||||
await common.async_turn_off(hass)
|
await common.async_turn_off(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow().replace(hour=0))
|
async_fire_time_changed(hass, now.replace(year=now.year + 1, hour=0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
|
||||||
async def test_if_fires_when_minute_matches(hass, calls):
|
async def test_if_fires_when_minute_matches(hass, calls):
|
||||||
"""Test for firing if minutes are matching."""
|
"""Test for firing if minutes are matching."""
|
||||||
assert await async_setup_component(
|
now = dt_util.utcnow()
|
||||||
hass,
|
time_that_will_not_match_right_away = dt_util.utcnow().replace(
|
||||||
automation.DOMAIN,
|
year=now.year + 1, minute=30
|
||||||
{
|
|
||||||
automation.DOMAIN: {
|
|
||||||
"trigger": {
|
|
||||||
"platform": "time_pattern",
|
|
||||||
"hours": "*",
|
|
||||||
"minutes": 0,
|
|
||||||
"seconds": "*",
|
|
||||||
},
|
|
||||||
"action": {"service": "test.automation"},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.util.dt.utcnow", return_value=time_that_will_not_match_right_away
|
||||||
|
):
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
automation.DOMAIN,
|
||||||
|
{
|
||||||
|
automation.DOMAIN: {
|
||||||
|
"trigger": {
|
||||||
|
"platform": "time_pattern",
|
||||||
|
"hours": "*",
|
||||||
|
"minutes": 0,
|
||||||
|
"seconds": "*",
|
||||||
|
},
|
||||||
|
"action": {"service": "test.automation"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow().replace(minute=0))
|
async_fire_time_changed(hass, now.replace(year=now.year + 2, minute=0))
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
@ -77,23 +92,30 @@ async def test_if_fires_when_minute_matches(hass, calls):
|
||||||
|
|
||||||
async def test_if_fires_when_second_matches(hass, calls):
|
async def test_if_fires_when_second_matches(hass, calls):
|
||||||
"""Test for firing if seconds are matching."""
|
"""Test for firing if seconds are matching."""
|
||||||
assert await async_setup_component(
|
now = dt_util.utcnow()
|
||||||
hass,
|
time_that_will_not_match_right_away = dt_util.utcnow().replace(
|
||||||
automation.DOMAIN,
|
year=now.year + 1, second=30
|
||||||
{
|
|
||||||
automation.DOMAIN: {
|
|
||||||
"trigger": {
|
|
||||||
"platform": "time_pattern",
|
|
||||||
"hours": "*",
|
|
||||||
"minutes": "*",
|
|
||||||
"seconds": 0,
|
|
||||||
},
|
|
||||||
"action": {"service": "test.automation"},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.util.dt.utcnow", return_value=time_that_will_not_match_right_away
|
||||||
|
):
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
automation.DOMAIN,
|
||||||
|
{
|
||||||
|
automation.DOMAIN: {
|
||||||
|
"trigger": {
|
||||||
|
"platform": "time_pattern",
|
||||||
|
"hours": "*",
|
||||||
|
"minutes": "*",
|
||||||
|
"seconds": 0,
|
||||||
|
},
|
||||||
|
"action": {"service": "test.automation"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow().replace(second=0))
|
async_fire_time_changed(hass, now.replace(year=now.year + 2, second=0))
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
@ -101,23 +123,32 @@ async def test_if_fires_when_second_matches(hass, calls):
|
||||||
|
|
||||||
async def test_if_fires_when_all_matches(hass, calls):
|
async def test_if_fires_when_all_matches(hass, calls):
|
||||||
"""Test for firing if everything matches."""
|
"""Test for firing if everything matches."""
|
||||||
assert await async_setup_component(
|
now = dt_util.utcnow()
|
||||||
hass,
|
time_that_will_not_match_right_away = dt_util.utcnow().replace(
|
||||||
automation.DOMAIN,
|
year=now.year + 1, hour=4
|
||||||
{
|
|
||||||
automation.DOMAIN: {
|
|
||||||
"trigger": {
|
|
||||||
"platform": "time_pattern",
|
|
||||||
"hours": 1,
|
|
||||||
"minutes": 2,
|
|
||||||
"seconds": 3,
|
|
||||||
},
|
|
||||||
"action": {"service": "test.automation"},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.util.dt.utcnow", return_value=time_that_will_not_match_right_away
|
||||||
|
):
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
automation.DOMAIN,
|
||||||
|
{
|
||||||
|
automation.DOMAIN: {
|
||||||
|
"trigger": {
|
||||||
|
"platform": "time_pattern",
|
||||||
|
"hours": 1,
|
||||||
|
"minutes": 2,
|
||||||
|
"seconds": 3,
|
||||||
|
},
|
||||||
|
"action": {"service": "test.automation"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow().replace(hour=1, minute=2, second=3))
|
async_fire_time_changed(
|
||||||
|
hass, now.replace(year=now.year + 2, hour=1, minute=2, second=3)
|
||||||
|
)
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
@ -125,47 +156,66 @@ async def test_if_fires_when_all_matches(hass, calls):
|
||||||
|
|
||||||
async def test_if_fires_periodic_seconds(hass, calls):
|
async def test_if_fires_periodic_seconds(hass, calls):
|
||||||
"""Test for firing periodically every second."""
|
"""Test for firing periodically every second."""
|
||||||
assert await async_setup_component(
|
now = dt_util.utcnow()
|
||||||
hass,
|
time_that_will_not_match_right_away = dt_util.utcnow().replace(
|
||||||
automation.DOMAIN,
|
year=now.year + 1, second=1
|
||||||
{
|
)
|
||||||
automation.DOMAIN: {
|
with patch(
|
||||||
"trigger": {
|
"homeassistant.util.dt.utcnow", return_value=time_that_will_not_match_right_away
|
||||||
"platform": "time_pattern",
|
):
|
||||||
"hours": "*",
|
assert await async_setup_component(
|
||||||
"minutes": "*",
|
hass,
|
||||||
"seconds": "/2",
|
automation.DOMAIN,
|
||||||
},
|
{
|
||||||
"action": {"service": "test.automation"},
|
automation.DOMAIN: {
|
||||||
}
|
"trigger": {
|
||||||
},
|
"platform": "time_pattern",
|
||||||
|
"hours": "*",
|
||||||
|
"minutes": "*",
|
||||||
|
"seconds": "/10",
|
||||||
|
},
|
||||||
|
"action": {"service": "test.automation"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
async_fire_time_changed(
|
||||||
|
hass, now.replace(year=now.year + 2, hour=0, minute=0, second=10)
|
||||||
)
|
)
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow().replace(hour=0, minute=0, second=2))
|
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(calls) == 1
|
assert len(calls) >= 1
|
||||||
|
|
||||||
|
|
||||||
async def test_if_fires_periodic_minutes(hass, calls):
|
async def test_if_fires_periodic_minutes(hass, calls):
|
||||||
"""Test for firing periodically every minute."""
|
"""Test for firing periodically every minute."""
|
||||||
assert await async_setup_component(
|
|
||||||
hass,
|
|
||||||
automation.DOMAIN,
|
|
||||||
{
|
|
||||||
automation.DOMAIN: {
|
|
||||||
"trigger": {
|
|
||||||
"platform": "time_pattern",
|
|
||||||
"hours": "*",
|
|
||||||
"minutes": "/2",
|
|
||||||
"seconds": "*",
|
|
||||||
},
|
|
||||||
"action": {"service": "test.automation"},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow().replace(hour=0, minute=2, second=0))
|
now = dt_util.utcnow()
|
||||||
|
time_that_will_not_match_right_away = dt_util.utcnow().replace(
|
||||||
|
year=now.year + 1, minute=1
|
||||||
|
)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.util.dt.utcnow", return_value=time_that_will_not_match_right_away
|
||||||
|
):
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
automation.DOMAIN,
|
||||||
|
{
|
||||||
|
automation.DOMAIN: {
|
||||||
|
"trigger": {
|
||||||
|
"platform": "time_pattern",
|
||||||
|
"hours": "*",
|
||||||
|
"minutes": "/2",
|
||||||
|
"seconds": "*",
|
||||||
|
},
|
||||||
|
"action": {"service": "test.automation"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
async_fire_time_changed(
|
||||||
|
hass, now.replace(year=now.year + 2, hour=0, minute=2, second=0)
|
||||||
|
)
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
@ -173,23 +223,32 @@ async def test_if_fires_periodic_minutes(hass, calls):
|
||||||
|
|
||||||
async def test_if_fires_periodic_hours(hass, calls):
|
async def test_if_fires_periodic_hours(hass, calls):
|
||||||
"""Test for firing periodically every hour."""
|
"""Test for firing periodically every hour."""
|
||||||
assert await async_setup_component(
|
now = dt_util.utcnow()
|
||||||
hass,
|
time_that_will_not_match_right_away = dt_util.utcnow().replace(
|
||||||
automation.DOMAIN,
|
year=now.year + 1, hour=1
|
||||||
{
|
|
||||||
automation.DOMAIN: {
|
|
||||||
"trigger": {
|
|
||||||
"platform": "time_pattern",
|
|
||||||
"hours": "/2",
|
|
||||||
"minutes": "*",
|
|
||||||
"seconds": "*",
|
|
||||||
},
|
|
||||||
"action": {"service": "test.automation"},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.util.dt.utcnow", return_value=time_that_will_not_match_right_away
|
||||||
|
):
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
automation.DOMAIN,
|
||||||
|
{
|
||||||
|
automation.DOMAIN: {
|
||||||
|
"trigger": {
|
||||||
|
"platform": "time_pattern",
|
||||||
|
"hours": "/2",
|
||||||
|
"minutes": "*",
|
||||||
|
"seconds": "*",
|
||||||
|
},
|
||||||
|
"action": {"service": "test.automation"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow().replace(hour=2, minute=0, second=0))
|
async_fire_time_changed(
|
||||||
|
hass, now.replace(year=now.year + 2, hour=2, minute=0, second=0)
|
||||||
|
)
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
@ -197,28 +256,41 @@ async def test_if_fires_periodic_hours(hass, calls):
|
||||||
|
|
||||||
async def test_default_values(hass, calls):
|
async def test_default_values(hass, calls):
|
||||||
"""Test for firing at 2 minutes every hour."""
|
"""Test for firing at 2 minutes every hour."""
|
||||||
assert await async_setup_component(
|
now = dt_util.utcnow()
|
||||||
hass,
|
time_that_will_not_match_right_away = dt_util.utcnow().replace(
|
||||||
automation.DOMAIN,
|
year=now.year + 1, minute=1
|
||||||
{
|
)
|
||||||
automation.DOMAIN: {
|
with patch(
|
||||||
"trigger": {"platform": "time_pattern", "minutes": "2"},
|
"homeassistant.util.dt.utcnow", return_value=time_that_will_not_match_right_away
|
||||||
"action": {"service": "test.automation"},
|
):
|
||||||
}
|
assert await async_setup_component(
|
||||||
},
|
hass,
|
||||||
|
automation.DOMAIN,
|
||||||
|
{
|
||||||
|
automation.DOMAIN: {
|
||||||
|
"trigger": {"platform": "time_pattern", "minutes": "2"},
|
||||||
|
"action": {"service": "test.automation"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
async_fire_time_changed(
|
||||||
|
hass, now.replace(year=now.year + 2, hour=1, minute=2, second=0)
|
||||||
)
|
)
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow().replace(hour=1, minute=2, second=0))
|
await hass.async_block_till_done()
|
||||||
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
async_fire_time_changed(
|
||||||
|
hass, now.replace(year=now.year + 2, hour=1, minute=2, second=1)
|
||||||
|
)
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(calls) == 1
|
assert len(calls) == 1
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow().replace(hour=1, minute=2, second=1))
|
async_fire_time_changed(
|
||||||
|
hass, now.replace(year=now.year + 2, hour=2, minute=2, second=0)
|
||||||
await hass.async_block_till_done()
|
)
|
||||||
assert len(calls) == 1
|
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow().replace(hour=2, minute=2, second=0))
|
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(calls) == 2
|
assert len(calls) == 2
|
||||||
|
|
|
@ -17,14 +17,18 @@ from homeassistant.components.recorder.const import DATA_INSTANCE
|
||||||
from homeassistant.components.recorder.models import Events, RecorderRuns, States
|
from homeassistant.components.recorder.models import Events, RecorderRuns, States
|
||||||
from homeassistant.components.recorder.util import session_scope
|
from homeassistant.components.recorder.util import session_scope
|
||||||
from homeassistant.const import MATCH_ALL, STATE_LOCKED, STATE_UNLOCKED
|
from homeassistant.const import MATCH_ALL, STATE_LOCKED, STATE_UNLOCKED
|
||||||
from homeassistant.core import ATTR_NOW, EVENT_TIME_CHANGED, Context, callback
|
from homeassistant.core import Context, callback
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from .common import wait_recording_done
|
from .common import wait_recording_done
|
||||||
|
|
||||||
from tests.async_mock import patch
|
from tests.async_mock import patch
|
||||||
from tests.common import get_test_home_assistant, init_recorder_component
|
from tests.common import (
|
||||||
|
async_fire_time_changed,
|
||||||
|
get_test_home_assistant,
|
||||||
|
init_recorder_component,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestRecorder(unittest.TestCase):
|
class TestRecorder(unittest.TestCase):
|
||||||
|
@ -335,15 +339,15 @@ def test_auto_purge(hass_recorder):
|
||||||
tz = dt_util.get_time_zone("Europe/Copenhagen")
|
tz = dt_util.get_time_zone("Europe/Copenhagen")
|
||||||
dt_util.set_default_time_zone(tz)
|
dt_util.set_default_time_zone(tz)
|
||||||
|
|
||||||
test_time = tz.localize(datetime(2020, 1, 1, 4, 12, 0))
|
now = dt_util.utcnow()
|
||||||
|
test_time = tz.localize(datetime(now.year + 1, 1, 1, 4, 12, 0))
|
||||||
|
async_fire_time_changed(hass, test_time)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.recorder.purge.purge_old_data", return_value=True
|
"homeassistant.components.recorder.purge.purge_old_data", return_value=True
|
||||||
) as purge_old_data:
|
) as purge_old_data:
|
||||||
for delta in (-1, 0, 1):
|
for delta in (-1, 0, 1):
|
||||||
hass.bus.fire(
|
async_fire_time_changed(hass, test_time + timedelta(seconds=delta))
|
||||||
EVENT_TIME_CHANGED, {ATTR_NOW: test_time + timedelta(seconds=delta)}
|
|
||||||
)
|
|
||||||
hass.block_till_done()
|
hass.block_till_done()
|
||||||
hass.data[DATA_INSTANCE].block_till_done()
|
hass.data[DATA_INSTANCE].block_till_done()
|
||||||
|
|
||||||
|
|
|
@ -260,49 +260,49 @@ async def _test_self_reset(hass, config, start_time, expect_reset=True):
|
||||||
assert state.state == "5"
|
assert state.state == "5"
|
||||||
|
|
||||||
|
|
||||||
async def test_self_reset_hourly(hass):
|
async def test_self_reset_hourly(hass, legacy_patchable_time):
|
||||||
"""Test hourly reset of meter."""
|
"""Test hourly reset of meter."""
|
||||||
await _test_self_reset(
|
await _test_self_reset(
|
||||||
hass, gen_config("hourly"), "2017-12-31T23:59:00.000000+00:00"
|
hass, gen_config("hourly"), "2017-12-31T23:59:00.000000+00:00"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_self_reset_daily(hass):
|
async def test_self_reset_daily(hass, legacy_patchable_time):
|
||||||
"""Test daily reset of meter."""
|
"""Test daily reset of meter."""
|
||||||
await _test_self_reset(
|
await _test_self_reset(
|
||||||
hass, gen_config("daily"), "2017-12-31T23:59:00.000000+00:00"
|
hass, gen_config("daily"), "2017-12-31T23:59:00.000000+00:00"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_self_reset_weekly(hass):
|
async def test_self_reset_weekly(hass, legacy_patchable_time):
|
||||||
"""Test weekly reset of meter."""
|
"""Test weekly reset of meter."""
|
||||||
await _test_self_reset(
|
await _test_self_reset(
|
||||||
hass, gen_config("weekly"), "2017-12-31T23:59:00.000000+00:00"
|
hass, gen_config("weekly"), "2017-12-31T23:59:00.000000+00:00"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_self_reset_monthly(hass):
|
async def test_self_reset_monthly(hass, legacy_patchable_time):
|
||||||
"""Test monthly reset of meter."""
|
"""Test monthly reset of meter."""
|
||||||
await _test_self_reset(
|
await _test_self_reset(
|
||||||
hass, gen_config("monthly"), "2017-12-31T23:59:00.000000+00:00"
|
hass, gen_config("monthly"), "2017-12-31T23:59:00.000000+00:00"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_self_reset_quarterly(hass):
|
async def test_self_reset_quarterly(hass, legacy_patchable_time):
|
||||||
"""Test quarterly reset of meter."""
|
"""Test quarterly reset of meter."""
|
||||||
await _test_self_reset(
|
await _test_self_reset(
|
||||||
hass, gen_config("quarterly"), "2017-03-31T23:59:00.000000+00:00"
|
hass, gen_config("quarterly"), "2017-03-31T23:59:00.000000+00:00"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_self_reset_yearly(hass):
|
async def test_self_reset_yearly(hass, legacy_patchable_time):
|
||||||
"""Test yearly reset of meter."""
|
"""Test yearly reset of meter."""
|
||||||
await _test_self_reset(
|
await _test_self_reset(
|
||||||
hass, gen_config("yearly"), "2017-12-31T23:59:00.000000+00:00"
|
hass, gen_config("yearly"), "2017-12-31T23:59:00.000000+00:00"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_self_no_reset_yearly(hass):
|
async def test_self_no_reset_yearly(hass, legacy_patchable_time):
|
||||||
"""Test yearly reset of meter does not occur after 1st January."""
|
"""Test yearly reset of meter does not occur after 1st January."""
|
||||||
await _test_self_reset(
|
await _test_self_reset(
|
||||||
hass,
|
hass,
|
||||||
|
@ -312,7 +312,7 @@ async def test_self_no_reset_yearly(hass):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_reset_yearly_offset(hass):
|
async def test_reset_yearly_offset(hass, legacy_patchable_time):
|
||||||
"""Test yearly reset of meter."""
|
"""Test yearly reset of meter."""
|
||||||
await _test_self_reset(
|
await _test_self_reset(
|
||||||
hass,
|
hass,
|
||||||
|
@ -321,7 +321,7 @@ async def test_reset_yearly_offset(hass):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_no_reset_yearly_offset(hass):
|
async def test_no_reset_yearly_offset(hass, legacy_patchable_time):
|
||||||
"""Test yearly reset of meter."""
|
"""Test yearly reset of meter."""
|
||||||
await _test_self_reset(
|
await _test_self_reset(
|
||||||
hass,
|
hass,
|
||||||
|
|
|
@ -119,7 +119,7 @@ async def test_erronous_network_key_fails_validation(hass, mock_openzwave):
|
||||||
zwave.CONFIG_SCHEMA({"zwave": {"network_key": value}})
|
zwave.CONFIG_SCHEMA({"zwave": {"network_key": value}})
|
||||||
|
|
||||||
|
|
||||||
async def test_auto_heal_midnight(hass, mock_openzwave):
|
async def test_auto_heal_midnight(hass, mock_openzwave, legacy_patchable_time):
|
||||||
"""Test network auto-heal at midnight."""
|
"""Test network auto-heal at midnight."""
|
||||||
await async_setup_component(hass, "zwave", {"zwave": {"autoheal": True}})
|
await async_setup_component(hass, "zwave", {"zwave": {"autoheal": True}})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
"""Set up some common test helper things."""
|
"""Set up some common test helper things."""
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import datetime
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
@ -387,8 +388,71 @@ def legacy_patchable_time():
|
||||||
|
|
||||||
return async_unsub
|
return async_unsub
|
||||||
|
|
||||||
|
@ha.callback
|
||||||
|
@loader.bind_hass
|
||||||
|
def async_track_utc_time_change(
|
||||||
|
hass, action, hour=None, minute=None, second=None, local=False
|
||||||
|
):
|
||||||
|
"""Add a listener that will fire if time matches a pattern."""
|
||||||
|
# We do not have to wrap the function with time pattern matching logic
|
||||||
|
# if no pattern given
|
||||||
|
if all(val is None for val in (hour, minute, second)):
|
||||||
|
|
||||||
|
@ha.callback
|
||||||
|
def time_change_listener(ev) -> None:
|
||||||
|
"""Fire every time event that comes in."""
|
||||||
|
hass.async_run_job(action, ev.data[ATTR_NOW])
|
||||||
|
|
||||||
|
return hass.bus.async_listen(EVENT_TIME_CHANGED, time_change_listener)
|
||||||
|
|
||||||
|
matching_seconds = event.dt_util.parse_time_expression(second, 0, 59)
|
||||||
|
matching_minutes = event.dt_util.parse_time_expression(minute, 0, 59)
|
||||||
|
matching_hours = event.dt_util.parse_time_expression(hour, 0, 23)
|
||||||
|
|
||||||
|
next_time = None
|
||||||
|
|
||||||
|
def calculate_next(now) -> None:
|
||||||
|
"""Calculate and set the next time the trigger should fire."""
|
||||||
|
nonlocal next_time
|
||||||
|
|
||||||
|
localized_now = event.dt_util.as_local(now) if local else now
|
||||||
|
next_time = event.dt_util.find_next_time_expression_time(
|
||||||
|
localized_now, matching_seconds, matching_minutes, matching_hours
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure rolling back the clock doesn't prevent the timer from
|
||||||
|
# triggering.
|
||||||
|
last_now = None
|
||||||
|
|
||||||
|
@ha.callback
|
||||||
|
def pattern_time_change_listener(ev) -> None:
|
||||||
|
"""Listen for matching time_changed events."""
|
||||||
|
nonlocal next_time, last_now
|
||||||
|
|
||||||
|
now = ev.data[ATTR_NOW]
|
||||||
|
|
||||||
|
if last_now is None or now < last_now:
|
||||||
|
# Time rolled back or next time not yet calculated
|
||||||
|
calculate_next(now)
|
||||||
|
|
||||||
|
last_now = now
|
||||||
|
|
||||||
|
if next_time <= now:
|
||||||
|
hass.async_run_job(
|
||||||
|
action, event.dt_util.as_local(now) if local else now
|
||||||
|
)
|
||||||
|
calculate_next(now + datetime.timedelta(seconds=1))
|
||||||
|
|
||||||
|
# We can't use async_track_point_in_utc_time here because it would
|
||||||
|
# break in the case that the system time abruptly jumps backwards.
|
||||||
|
# Our custom last_now logic takes care of resolving that scenario.
|
||||||
|
return hass.bus.async_listen(EVENT_TIME_CHANGED, pattern_time_change_listener)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.helpers.event.async_track_point_in_utc_time",
|
"homeassistant.helpers.event.async_track_point_in_utc_time",
|
||||||
async_track_point_in_utc_time,
|
async_track_point_in_utc_time,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.helpers.event.async_track_utc_time_change",
|
||||||
|
async_track_utc_time_change,
|
||||||
):
|
):
|
||||||
yield
|
yield
|
||||||
|
|
|
@ -748,22 +748,24 @@ async def test_async_track_time_change(hass):
|
||||||
wildcard_runs = []
|
wildcard_runs = []
|
||||||
specific_runs = []
|
specific_runs = []
|
||||||
|
|
||||||
|
now = dt_util.utcnow()
|
||||||
|
|
||||||
unsub = async_track_time_change(hass, lambda x: wildcard_runs.append(1))
|
unsub = async_track_time_change(hass, lambda x: wildcard_runs.append(1))
|
||||||
unsub_utc = async_track_utc_time_change(
|
unsub_utc = async_track_utc_time_change(
|
||||||
hass, lambda x: specific_runs.append(1), second=[0, 30]
|
hass, lambda x: specific_runs.append(1), second=[0, 30]
|
||||||
)
|
)
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 24, 12, 0, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 24, 12, 0, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 1
|
assert len(specific_runs) == 1
|
||||||
assert len(wildcard_runs) == 1
|
assert len(wildcard_runs) == 1
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 24, 12, 0, 15))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 24, 12, 0, 15))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 1
|
assert len(specific_runs) == 1
|
||||||
assert len(wildcard_runs) == 2
|
assert len(wildcard_runs) == 2
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 24, 12, 0, 30))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 24, 12, 0, 30))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 2
|
assert len(specific_runs) == 2
|
||||||
assert len(wildcard_runs) == 3
|
assert len(wildcard_runs) == 3
|
||||||
|
@ -771,7 +773,7 @@ async def test_async_track_time_change(hass):
|
||||||
unsub()
|
unsub()
|
||||||
unsub_utc()
|
unsub_utc()
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 24, 12, 0, 30))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 24, 12, 0, 30))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 2
|
assert len(specific_runs) == 2
|
||||||
assert len(wildcard_runs) == 3
|
assert len(wildcard_runs) == 3
|
||||||
|
@ -781,25 +783,27 @@ async def test_periodic_task_minute(hass):
|
||||||
"""Test periodic tasks per minute."""
|
"""Test periodic tasks per minute."""
|
||||||
specific_runs = []
|
specific_runs = []
|
||||||
|
|
||||||
|
now = dt_util.utcnow()
|
||||||
|
|
||||||
unsub = async_track_utc_time_change(
|
unsub = async_track_utc_time_change(
|
||||||
hass, lambda x: specific_runs.append(1), minute="/5", second=0
|
hass, lambda x: specific_runs.append(1), minute="/5", second=0
|
||||||
)
|
)
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 24, 12, 0, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 24, 12, 0, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 1
|
assert len(specific_runs) == 1
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 24, 12, 3, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 24, 12, 3, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 1
|
assert len(specific_runs) == 1
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 24, 12, 5, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 24, 12, 5, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 2
|
assert len(specific_runs) == 2
|
||||||
|
|
||||||
unsub()
|
unsub()
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 24, 12, 5, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 24, 12, 5, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 2
|
assert len(specific_runs) == 2
|
||||||
|
|
||||||
|
@ -808,33 +812,35 @@ async def test_periodic_task_hour(hass):
|
||||||
"""Test periodic tasks per hour."""
|
"""Test periodic tasks per hour."""
|
||||||
specific_runs = []
|
specific_runs = []
|
||||||
|
|
||||||
|
now = dt_util.utcnow()
|
||||||
|
|
||||||
unsub = async_track_utc_time_change(
|
unsub = async_track_utc_time_change(
|
||||||
hass, lambda x: specific_runs.append(1), hour="/2", minute=0, second=0
|
hass, lambda x: specific_runs.append(1), hour="/2", minute=0, second=0
|
||||||
)
|
)
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 24, 22, 0, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 24, 22, 0, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 1
|
assert len(specific_runs) == 1
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 24, 23, 0, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 24, 23, 0, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 1
|
assert len(specific_runs) == 1
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 25, 0, 0, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 25, 0, 0, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 2
|
assert len(specific_runs) == 2
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 25, 1, 0, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 25, 1, 0, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 2
|
assert len(specific_runs) == 2
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 25, 2, 0, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 25, 2, 0, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 3
|
assert len(specific_runs) == 3
|
||||||
|
|
||||||
unsub()
|
unsub()
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 25, 2, 0, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 25, 2, 0, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 3
|
assert len(specific_runs) == 3
|
||||||
|
|
||||||
|
@ -843,12 +849,14 @@ async def test_periodic_task_wrong_input(hass):
|
||||||
"""Test periodic tasks with wrong input."""
|
"""Test periodic tasks with wrong input."""
|
||||||
specific_runs = []
|
specific_runs = []
|
||||||
|
|
||||||
|
now = dt_util.utcnow()
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
async_track_utc_time_change(
|
async_track_utc_time_change(
|
||||||
hass, lambda x: specific_runs.append(1), hour="/two"
|
hass, lambda x: specific_runs.append(1), hour="/two"
|
||||||
)
|
)
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 2, 0, 0, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 2, 0, 0, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 0
|
assert len(specific_runs) == 0
|
||||||
|
|
||||||
|
@ -857,33 +865,37 @@ async def test_periodic_task_clock_rollback(hass):
|
||||||
"""Test periodic tasks with the time rolling backwards."""
|
"""Test periodic tasks with the time rolling backwards."""
|
||||||
specific_runs = []
|
specific_runs = []
|
||||||
|
|
||||||
|
now = dt_util.utcnow()
|
||||||
|
|
||||||
unsub = async_track_utc_time_change(
|
unsub = async_track_utc_time_change(
|
||||||
hass, lambda x: specific_runs.append(1), hour="/2", minute=0, second=0
|
hass, lambda x: specific_runs.append(1), hour="/2", minute=0, second=0
|
||||||
)
|
)
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 24, 22, 0, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 24, 22, 0, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 1
|
assert len(specific_runs) == 1
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 24, 23, 0, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 24, 23, 0, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 1
|
assert len(specific_runs) == 1
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 24, 22, 0, 0))
|
async_fire_time_changed(
|
||||||
|
hass, datetime(now.year + 1, 5, 24, 22, 0, 0), fire_all=True
|
||||||
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 2
|
assert len(specific_runs) == 2
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 24, 0, 0, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 24, 0, 0, 0), fire_all=True)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 3
|
assert len(specific_runs) == 3
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 25, 2, 0, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 25, 2, 0, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 4
|
assert len(specific_runs) == 4
|
||||||
|
|
||||||
unsub()
|
unsub()
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 25, 2, 0, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 25, 2, 0, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 4
|
assert len(specific_runs) == 4
|
||||||
|
|
||||||
|
@ -892,19 +904,21 @@ async def test_periodic_task_duplicate_time(hass):
|
||||||
"""Test periodic tasks not triggering on duplicate time."""
|
"""Test periodic tasks not triggering on duplicate time."""
|
||||||
specific_runs = []
|
specific_runs = []
|
||||||
|
|
||||||
|
now = dt_util.utcnow()
|
||||||
|
|
||||||
unsub = async_track_utc_time_change(
|
unsub = async_track_utc_time_change(
|
||||||
hass, lambda x: specific_runs.append(1), hour="/2", minute=0, second=0
|
hass, lambda x: specific_runs.append(1), hour="/2", minute=0, second=0
|
||||||
)
|
)
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 24, 22, 0, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 24, 22, 0, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 1
|
assert len(specific_runs) == 1
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 24, 22, 0, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 24, 22, 0, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 1
|
assert len(specific_runs) == 1
|
||||||
|
|
||||||
async_fire_time_changed(hass, datetime(2014, 5, 25, 0, 0, 0))
|
async_fire_time_changed(hass, datetime(now.year + 1, 5, 25, 0, 0, 0))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 2
|
assert len(specific_runs) == 2
|
||||||
|
|
||||||
|
@ -917,23 +931,39 @@ async def test_periodic_task_entering_dst(hass):
|
||||||
dt_util.set_default_time_zone(timezone)
|
dt_util.set_default_time_zone(timezone)
|
||||||
specific_runs = []
|
specific_runs = []
|
||||||
|
|
||||||
unsub = async_track_time_change(
|
now = dt_util.utcnow()
|
||||||
hass, lambda x: specific_runs.append(1), hour=2, minute=30, second=0
|
time_that_will_not_match_right_away = timezone.localize(
|
||||||
|
datetime(now.year + 1, 3, 25, 2, 31, 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
async_fire_time_changed(hass, timezone.localize(datetime(2018, 3, 25, 1, 50, 0)))
|
with patch(
|
||||||
|
"homeassistant.util.dt.utcnow", return_value=time_that_will_not_match_right_away
|
||||||
|
):
|
||||||
|
unsub = async_track_time_change(
|
||||||
|
hass, lambda x: specific_runs.append(1), hour=2, minute=30, second=0
|
||||||
|
)
|
||||||
|
|
||||||
|
async_fire_time_changed(
|
||||||
|
hass, timezone.localize(datetime(now.year + 1, 3, 25, 1, 50, 0))
|
||||||
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 0
|
assert len(specific_runs) == 0
|
||||||
|
|
||||||
async_fire_time_changed(hass, timezone.localize(datetime(2018, 3, 25, 3, 50, 0)))
|
async_fire_time_changed(
|
||||||
|
hass, timezone.localize(datetime(now.year + 1, 3, 25, 3, 50, 0))
|
||||||
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 0
|
assert len(specific_runs) == 0
|
||||||
|
|
||||||
async_fire_time_changed(hass, timezone.localize(datetime(2018, 3, 26, 1, 50, 0)))
|
async_fire_time_changed(
|
||||||
|
hass, timezone.localize(datetime(now.year + 1, 3, 26, 1, 50, 0))
|
||||||
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 0
|
assert len(specific_runs) == 0
|
||||||
|
|
||||||
async_fire_time_changed(hass, timezone.localize(datetime(2018, 3, 26, 2, 50, 0)))
|
async_fire_time_changed(
|
||||||
|
hass, timezone.localize(datetime(now.year + 1, 3, 26, 2, 50, 0))
|
||||||
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 1
|
assert len(specific_runs) == 1
|
||||||
|
|
||||||
|
@ -946,30 +976,45 @@ async def test_periodic_task_leaving_dst(hass):
|
||||||
dt_util.set_default_time_zone(timezone)
|
dt_util.set_default_time_zone(timezone)
|
||||||
specific_runs = []
|
specific_runs = []
|
||||||
|
|
||||||
unsub = async_track_time_change(
|
now = dt_util.utcnow()
|
||||||
hass, lambda x: specific_runs.append(1), hour=2, minute=30, second=0
|
|
||||||
|
time_that_will_not_match_right_away = timezone.localize(
|
||||||
|
datetime(now.year + 1, 10, 28, 2, 28, 0), is_dst=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.util.dt.utcnow", return_value=time_that_will_not_match_right_away
|
||||||
|
):
|
||||||
|
unsub = async_track_time_change(
|
||||||
|
hass, lambda x: specific_runs.append(1), hour=2, minute=30, second=0
|
||||||
|
)
|
||||||
|
|
||||||
async_fire_time_changed(
|
async_fire_time_changed(
|
||||||
hass, timezone.localize(datetime(2018, 10, 28, 2, 5, 0), is_dst=False)
|
hass, timezone.localize(datetime(now.year + 1, 10, 28, 2, 5, 0), is_dst=False)
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 0
|
assert len(specific_runs) == 0
|
||||||
|
|
||||||
async_fire_time_changed(
|
async_fire_time_changed(
|
||||||
hass, timezone.localize(datetime(2018, 10, 28, 2, 55, 0), is_dst=False)
|
hass, timezone.localize(datetime(now.year + 1, 10, 28, 2, 55, 0), is_dst=False)
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 1
|
assert len(specific_runs) == 1
|
||||||
|
|
||||||
async_fire_time_changed(
|
async_fire_time_changed(
|
||||||
hass, timezone.localize(datetime(2018, 10, 28, 2, 5, 0), is_dst=True)
|
hass, timezone.localize(datetime(now.year + 2, 10, 28, 2, 45, 0), is_dst=True)
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 1
|
assert len(specific_runs) == 2
|
||||||
|
|
||||||
async_fire_time_changed(
|
async_fire_time_changed(
|
||||||
hass, timezone.localize(datetime(2018, 10, 28, 2, 55, 0), is_dst=True)
|
hass, timezone.localize(datetime(now.year + 2, 10, 28, 2, 55, 0), is_dst=True)
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(specific_runs) == 2
|
||||||
|
|
||||||
|
async_fire_time_changed(
|
||||||
|
hass, timezone.localize(datetime(now.year + 2, 10, 28, 2, 55, 0), is_dst=True)
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(specific_runs) == 2
|
assert len(specific_runs) == 2
|
||||||
|
|
Loading…
Add table
Reference in a new issue