Upgrade pytest-aiohttp (#82475)

* Upgrade pytest-aiohttp

* Make sure executors, tasks and timers are closed

Some test will trigger warnings on garbage collect, these warnings
spills over into next test.

Some test trigger tasks that raise errors on shutdown, these spill
over into next test.

This is to mimic older pytest-aiohttp and it's behaviour on test
cleanup.

Discussions on similar changes for pytest-aiohttp are here:
https://github.com/pytest-dev/pytest-asyncio/pull/309

* Replace loop with event_loop

* Make sure time is frozen for tests

* Make sure the ConditionType is not async

  /home-assistant/homeassistant/helpers/template.py:2082: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
    def wrapper(*args, **kwargs):
  Enable tracemalloc to get traceback where the object was allocated.
  See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

* Increase litejet press tests with a factor 10

The times are simulated anyway, and we can't stop the normal
event from occuring.

* Use async handlers for aiohttp

tests/components/motioneye/test_camera.py::test_get_still_image_from_camera
tests/components/motioneye/test_camera.py::test_get_still_image_from_camera
tests/components/motioneye/test_camera.py::test_get_stream_from_camera
tests/components/motioneye/test_camera.py::test_get_stream_from_camera
tests/components/motioneye/test_camera.py::test_camera_option_stream_url_template
tests/components/motioneye/test_camera.py::test_camera_option_stream_url_template
  /Users/joakim/src/hass/home-assistant/venv/lib/python3.9/site-packages/aiohttp/web_urldispatcher.py:189: DeprecationWarning: Bare functions are deprecated, use async ones
    warnings.warn(

* Switch to freezegun in modbus tests

The tests allowed clock to tick in between steps

* Make sure skybell object are fully mocked

Old tests would trigger attempts to post to could services:

```
DEBUG:aioskybell:HTTP post https://cloud.myskybell.com/api/v3/login/ Request with headers: {'content-type': 'application/json', 'accept': '*/*', 'x-skybell-app-id': 'd2b542c7-a7e4-4e1e-b77d-2b76911c7c46', 'x-skybell-client-id': '1f36a3c0-6dee-4997-a6db-4e1c67338e57'}
```

* Fix sorting that broke after rebase
This commit is contained in:
Joakim Plate 2022-11-29 22:36:36 +01:00 committed by GitHub
parent b7652c78ee
commit c576a68d33
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 263 additions and 226 deletions

View file

@ -98,9 +98,6 @@ hyperframe>=5.2.0
# Ensure we run compatible with musllinux build env
numpy==1.23.2
# pytest_asyncio breaks our test suite. We rely on pytest-aiohttp instead
pytest_asyncio==1000000000.0.0
# Prevent dependency conflicts between sisyphus-control and aioambient
# until upper bounds for sisyphus-control have been updated
# https://github.com/jkeljo/sisyphus-control/issues/6

View file

@ -227,3 +227,4 @@ norecursedirs = [
]
log_format = "%(asctime)s.%(msecs)03d %(levelname)-8s %(threadName)s %(name)s:%(filename)s:%(lineno)s %(message)s"
log_date_format = "%Y-%m-%d %H:%M:%S"
asyncio_mode = "auto"

View file

@ -16,7 +16,8 @@ mypy==0.991
pre-commit==2.20.0
pylint==2.15.6
pipdeptree==2.3.1
pytest-aiohttp==0.3.0
pytest-asyncio==0.20.2
pytest-aiohttp==1.0.4
pytest-cov==3.0.0
pytest-freezegun==0.4.2
pytest-socket==0.5.1

View file

@ -110,9 +110,6 @@ hyperframe>=5.2.0
# Ensure we run compatible with musllinux build env
numpy==1.23.2
# pytest_asyncio breaks our test suite. We rely on pytest-aiohttp instead
pytest_asyncio==1000000000.0.0
# Prevent dependency conflicts between sisyphus-control and aioambient
# until upper bounds for sisyphus-control have been updated
# https://github.com/jkeljo/sisyphus-control/issues/6

View file

@ -28,7 +28,7 @@ from tests.common import (
@pytest.fixture
def mock_hass(loop):
def mock_hass(event_loop):
"""Home Assistant mock with minimum amount of data set to make it work with auth."""
hass = Mock()
hass.config.skip_pip = True

View file

@ -160,7 +160,7 @@ def get_test_home_assistant():
# pylint: disable=protected-access
async def async_test_home_assistant(loop, load_registries=True):
async def async_test_home_assistant(event_loop, load_registries=True):
"""Return a Home Assistant object pointing at test config dir."""
hass = ha.HomeAssistant()
store = auth_store.AuthStore(hass)

View file

@ -21,8 +21,9 @@ NPR_NEWS_MP3_URL = "https://pd.npr.org/anon.npr-mp3/npr/news/newscast.mp3"
@pytest.fixture
def alexa_client(loop, hass, hass_client):
def alexa_client(event_loop, hass, hass_client):
"""Initialize a Home Assistant server for testing this module."""
loop = event_loop
@callback
def mock_service(call):

View file

@ -27,8 +27,9 @@ NPR_NEWS_MP3_URL = "https://pd.npr.org/anon.npr-mp3/npr/news/newscast.mp3"
@pytest.fixture
def alexa_client(loop, hass, hass_client):
def alexa_client(event_loop, hass, hass_client):
"""Initialize a Home Assistant server for testing this module."""
loop = event_loop
@callback
def mock_service(call):

View file

@ -3,6 +3,6 @@ import pytest
@pytest.fixture
def aiohttp_client(loop, aiohttp_client, socket_enabled):
def aiohttp_client(event_loop, aiohttp_client, socket_enabled):
"""Return aiohttp_client and allow opening sockets."""
return aiohttp_client

View file

@ -2,7 +2,7 @@
from homeassistant.setup import async_setup_component
async def test_config_setup(hass, loop):
async def test_config_setup(hass, event_loop):
"""Test it sets up hassbian."""
await async_setup_component(hass, "config", {})
assert "config" in hass.config.components

View file

@ -31,7 +31,7 @@ class MockTransport:
@pytest.fixture
def aiohttp_client(loop, aiohttp_client, socket_enabled):
def aiohttp_client(event_loop, aiohttp_client, socket_enabled):
"""Return aiohttp_client and allow opening sockets."""
return aiohttp_client

View file

@ -25,7 +25,7 @@ async def test_events_fired_properly(hass):
roku_event_handler = None
def instantiate(
loop,
event_loop,
handler,
roku_usn,
host_ip,

View file

@ -3,6 +3,7 @@ import copy
from datetime import timedelta
from unittest.mock import patch
from freezegun import freeze_time
import pytest
from homeassistant.components.energy import data
@ -41,6 +42,13 @@ async def setup_integration(recorder_mock):
return setup_integration
@pytest.fixture(autouse=True)
@freeze_time("2022-04-19 07:53:05")
def frozen_time():
"""Freeze clock for tests."""
yield
def get_statistics_for_entity(statistics_results, entity_id):
"""Get statistics for a certain entity, or None if there is none."""
for statistics_result in statistics_results:

View file

@ -38,7 +38,7 @@ class FidoClientMockError(FidoClientMock):
raise PyFidoError("Fake Error")
async def test_fido_sensor(loop, hass):
async def test_fido_sensor(event_loop, hass):
"""Test the Fido number sensor."""
with patch("homeassistant.components.fido.sensor.FidoClient", new=FidoClientMock):
config = {

View file

@ -80,7 +80,7 @@ async def frontend_themes(hass):
@pytest.fixture
def aiohttp_client(loop, aiohttp_client, socket_enabled):
def aiohttp_client(event_loop, aiohttp_client, socket_enabled):
"""Return aiohttp_client and allow opening sockets."""
return aiohttp_client

View file

@ -117,7 +117,7 @@ def mock_dev_track(mock_device_tracker_conf):
@pytest.fixture
async def geofency_client(loop, hass, hass_client_no_auth):
async def geofency_client(event_loop, hass, hass_client_no_auth):
"""Geofency mock client (unauthenticated)."""
assert await async_setup_component(
@ -130,7 +130,7 @@ async def geofency_client(loop, hass, hass_client_no_auth):
@pytest.fixture(autouse=True)
async def setup_zones(loop, hass):
async def setup_zones(event_loop, hass):
"""Set up Zone config in HA."""
assert await async_setup_component(
hass,

View file

@ -42,8 +42,9 @@ def auth_header(hass_access_token):
@pytest.fixture
def assistant_client(loop, hass, hass_client_no_auth):
def assistant_client(event_loop, hass, hass_client_no_auth):
"""Create web client for the Google Assistant API."""
loop = event_loop
loop.run_until_complete(
setup.async_setup_component(
hass,
@ -66,8 +67,10 @@ def assistant_client(loop, hass, hass_client_no_auth):
@pytest.fixture
def hass_fixture(loop, hass):
def hass_fixture(event_loop, hass):
"""Set up a Home Assistant instance for these tests."""
loop = event_loop
# We need to do this to get access to homeassistant/turn_(on,off)
loop.run_until_complete(setup.async_setup_component(hass, core.DOMAIN, {}))

View file

@ -26,7 +26,7 @@ def mock_dev_track(mock_device_tracker_conf):
@pytest.fixture
async def gpslogger_client(loop, hass, hass_client_no_auth):
async def gpslogger_client(event_loop, hass, hass_client_no_auth):
"""Mock client for GPSLogger (unauthenticated)."""
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
@ -38,7 +38,7 @@ async def gpslogger_client(loop, hass, hass_client_no_auth):
@pytest.fixture(autouse=True)
async def setup_zones(loop, hass):
async def setup_zones(event_loop, hass):
"""Set up Zone config in HA."""
assert await async_setup_component(
hass,

View file

@ -21,7 +21,7 @@ def iid_storage(hass):
@pytest.fixture()
def run_driver(hass, loop, iid_storage):
def run_driver(hass, event_loop, iid_storage):
"""Return a custom AccessoryDriver instance for HomeKit accessory init.
This mock does not mock async_stop, so the driver will not be stopped
@ -41,12 +41,12 @@ def run_driver(hass, loop, iid_storage):
bridge_name=BRIDGE_NAME,
iid_storage=iid_storage,
address="127.0.0.1",
loop=loop,
loop=event_loop,
)
@pytest.fixture
def hk_driver(hass, loop, iid_storage):
def hk_driver(hass, event_loop, iid_storage):
"""Return a custom AccessoryDriver instance for HomeKit accessory init."""
with patch("pyhap.accessory_driver.AsyncZeroconf"), patch(
"pyhap.accessory_driver.AccessoryEncoder"
@ -65,12 +65,12 @@ def hk_driver(hass, loop, iid_storage):
bridge_name=BRIDGE_NAME,
iid_storage=iid_storage,
address="127.0.0.1",
loop=loop,
loop=event_loop,
)
@pytest.fixture
def mock_hap(hass, loop, iid_storage, mock_zeroconf):
def mock_hap(hass, event_loop, iid_storage, mock_zeroconf):
"""Return a custom AccessoryDriver instance for HomeKit accessory init."""
with patch("pyhap.accessory_driver.AsyncZeroconf"), patch(
"pyhap.accessory_driver.AccessoryEncoder"
@ -93,7 +93,7 @@ def mock_hap(hass, loop, iid_storage, mock_zeroconf):
bridge_name=BRIDGE_NAME,
iid_storage=iid_storage,
address="127.0.0.1",
loop=loop,
loop=event_loop,
)

View file

@ -3,6 +3,6 @@ import pytest
@pytest.fixture
def aiohttp_client(loop, aiohttp_client, socket_enabled):
def aiohttp_client(event_loop, aiohttp_client, socket_enabled):
"""Return aiohttp_client and allow opening sockets."""
return aiohttp_client

View file

@ -49,12 +49,12 @@ async def mock_handler(request):
@pytest.fixture
def client(loop, aiohttp_client):
def client(event_loop, aiohttp_client):
"""Fixture to set up a web.Application."""
app = web.Application()
setup_cors(app, [TRUSTED_ORIGIN])
app["allow_configured_cors"](app.router.add_get("/", mock_handler))
return loop.run_until_complete(aiohttp_client(app))
return event_loop.run_until_complete(aiohttp_client(app))
async def test_cors_requests(client):

View file

@ -15,7 +15,7 @@ from tests.common import assert_setup_component, async_capture_events
@pytest.fixture
def aiohttp_unused_port(loop, aiohttp_unused_port, socket_enabled):
def aiohttp_unused_port(event_loop, aiohttp_unused_port, socket_enabled):
"""Return aiohttp_unused_port and allow opening sockets."""
return aiohttp_unused_port

View file

@ -113,12 +113,12 @@ async def test_held_more_than_short(hass, calls, mock_litejet):
{
"platform": "litejet",
"number": ENTITY_OTHER_SWITCH_NUMBER,
"held_more_than": {"milliseconds": "200"},
"held_more_than": {"milliseconds": "2000"},
},
)
await simulate_press(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER)
await simulate_time(hass, mock_litejet, timedelta(seconds=0.1))
await simulate_time(hass, mock_litejet, timedelta(seconds=1))
await simulate_release(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER)
assert len(calls) == 0
@ -130,13 +130,13 @@ async def test_held_more_than_long(hass, calls, mock_litejet):
{
"platform": "litejet",
"number": ENTITY_OTHER_SWITCH_NUMBER,
"held_more_than": {"milliseconds": "200"},
"held_more_than": {"milliseconds": "2000"},
},
)
await simulate_press(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER)
assert len(calls) == 0
await simulate_time(hass, mock_litejet, timedelta(seconds=0.3))
await simulate_time(hass, mock_litejet, timedelta(seconds=3))
assert len(calls) == 1
assert calls[0].data["id"] == 0
await simulate_release(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER)
@ -150,12 +150,12 @@ async def test_held_less_than_short(hass, calls, mock_litejet):
{
"platform": "litejet",
"number": ENTITY_OTHER_SWITCH_NUMBER,
"held_less_than": {"milliseconds": "200"},
"held_less_than": {"milliseconds": "2000"},
},
)
await simulate_press(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER)
await simulate_time(hass, mock_litejet, timedelta(seconds=0.1))
await simulate_time(hass, mock_litejet, timedelta(seconds=1))
assert len(calls) == 0
await simulate_release(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER)
assert len(calls) == 1
@ -169,13 +169,13 @@ async def test_held_less_than_long(hass, calls, mock_litejet):
{
"platform": "litejet",
"number": ENTITY_OTHER_SWITCH_NUMBER,
"held_less_than": {"milliseconds": "200"},
"held_less_than": {"milliseconds": "2000"},
},
)
await simulate_press(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER)
assert len(calls) == 0
await simulate_time(hass, mock_litejet, timedelta(seconds=0.3))
await simulate_time(hass, mock_litejet, timedelta(seconds=3))
assert len(calls) == 0
await simulate_release(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER)
assert len(calls) == 0
@ -188,13 +188,13 @@ async def test_held_in_range_short(hass, calls, mock_litejet):
{
"platform": "litejet",
"number": ENTITY_OTHER_SWITCH_NUMBER,
"held_more_than": {"milliseconds": "100"},
"held_less_than": {"milliseconds": "300"},
"held_more_than": {"milliseconds": "1000"},
"held_less_than": {"milliseconds": "3000"},
},
)
await simulate_press(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER)
await simulate_time(hass, mock_litejet, timedelta(seconds=0.05))
await simulate_time(hass, mock_litejet, timedelta(seconds=0.5))
await simulate_release(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER)
assert len(calls) == 0
@ -206,14 +206,14 @@ async def test_held_in_range_just_right(hass, calls, mock_litejet):
{
"platform": "litejet",
"number": ENTITY_OTHER_SWITCH_NUMBER,
"held_more_than": {"milliseconds": "100"},
"held_less_than": {"milliseconds": "300"},
"held_more_than": {"milliseconds": "1000"},
"held_less_than": {"milliseconds": "3000"},
},
)
await simulate_press(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER)
assert len(calls) == 0
await simulate_time(hass, mock_litejet, timedelta(seconds=0.2))
await simulate_time(hass, mock_litejet, timedelta(seconds=2))
assert len(calls) == 0
await simulate_release(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER)
assert len(calls) == 1
@ -227,14 +227,14 @@ async def test_held_in_range_long(hass, calls, mock_litejet):
{
"platform": "litejet",
"number": ENTITY_OTHER_SWITCH_NUMBER,
"held_more_than": {"milliseconds": "100"},
"held_less_than": {"milliseconds": "300"},
"held_more_than": {"milliseconds": "1000"},
"held_less_than": {"milliseconds": "3000"},
},
)
await simulate_press(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER)
assert len(calls) == 0
await simulate_time(hass, mock_litejet, timedelta(seconds=0.4))
await simulate_time(hass, mock_litejet, timedelta(seconds=4))
assert len(calls) == 0
await simulate_release(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER)
assert len(calls) == 0
@ -247,8 +247,8 @@ async def test_reload(hass, calls, mock_litejet):
{
"platform": "litejet",
"number": ENTITY_OTHER_SWITCH_NUMBER,
"held_more_than": {"milliseconds": "100"},
"held_less_than": {"milliseconds": "300"},
"held_more_than": {"milliseconds": "1000"},
"held_less_than": {"milliseconds": "3000"},
},
)
@ -260,7 +260,7 @@ async def test_reload(hass, calls, mock_litejet):
"trigger": {
"platform": "litejet",
"number": ENTITY_OTHER_SWITCH_NUMBER,
"held_more_than": {"milliseconds": "1000"},
"held_more_than": {"milliseconds": "10000"},
},
"action": {"service": "test.automation"},
}
@ -275,7 +275,7 @@ async def test_reload(hass, calls, mock_litejet):
await simulate_press(hass, mock_litejet, ENTITY_OTHER_SWITCH_NUMBER)
assert len(calls) == 0
await simulate_time(hass, mock_litejet, timedelta(seconds=0.5))
await simulate_time(hass, mock_litejet, timedelta(seconds=5))
assert len(calls) == 0
await simulate_time(hass, mock_litejet, timedelta(seconds=1.25))
await simulate_time(hass, mock_litejet, timedelta(seconds=12.5))
assert len(calls) == 1

View file

@ -21,7 +21,7 @@ def mock_dev_track(mock_device_tracker_conf):
@pytest.fixture
async def locative_client(loop, hass, hass_client):
async def locative_client(event_loop, hass, hass_client):
"""Locative mock client."""
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
await hass.async_block_till_done()

View file

@ -15,8 +15,9 @@ from homeassistant.setup import async_setup_component
@pytest.fixture
def meraki_client(loop, hass, hass_client):
def meraki_client(event_loop, hass, hass_client):
"""Meraki mock client."""
loop = event_loop
assert loop.run_until_complete(
async_setup_component(
hass,

View file

@ -16,6 +16,7 @@ from datetime import timedelta
import logging
from unittest import mock
from freezegun.api import FrozenDateTimeFactory
from pymodbus.exceptions import ModbusException
from pymodbus.pdu import ExceptionResponse, IllegalFunctionRequest
import pytest
@ -542,6 +543,7 @@ async def mock_modbus_read_pymodbus_fixture(
do_exception,
caplog,
mock_pymodbus,
freezer: FrozenDateTimeFactory,
):
"""Load integration modbus using mocked pymodbus."""
caplog.clear()
@ -573,16 +575,13 @@ async def mock_modbus_read_pymodbus_fixture(
}
],
}
now = dt_util.utcnow()
with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now):
assert await async_setup_component(hass, DOMAIN, config) is True
await hass.async_block_till_done()
assert await async_setup_component(hass, DOMAIN, config) is True
await hass.async_block_till_done()
assert DOMAIN in hass.config.components
assert caplog.text == ""
now = now + timedelta(seconds=DEFAULT_SCAN_INTERVAL + 60)
with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now):
async_fire_time_changed(hass, now)
await hass.async_block_till_done()
freezer.tick(timedelta(seconds=DEFAULT_SCAN_INTERVAL + 60))
async_fire_time_changed(hass)
await hass.async_block_till_done()
yield mock_pymodbus
@ -690,7 +689,7 @@ async def test_pymodbus_connect_fail(hass, caplog, mock_pymodbus):
assert ExceptionMessage in caplog.text
async def test_delay(hass, mock_pymodbus):
async def test_delay(hass, mock_pymodbus, freezer: FrozenDateTimeFactory):
"""Run test for startup delay."""
# the purpose of this test is to test startup delay
@ -720,11 +719,8 @@ async def test_delay(hass, mock_pymodbus):
}
mock_pymodbus.read_coils.return_value = ReadResult([0x01])
start_time = dt_util.utcnow()
with mock.patch(
"homeassistant.helpers.event.dt_util.utcnow", return_value=start_time
):
assert await async_setup_component(hass, DOMAIN, config) is True
await hass.async_block_till_done()
assert await async_setup_component(hass, DOMAIN, config) is True
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == STATE_UNKNOWN
time_sensor_active = start_time + timedelta(seconds=2)
@ -736,19 +732,15 @@ async def test_delay(hass, mock_pymodbus):
# This test assumed listeners are always fired at 0
# microseconds which is impossible in production so
# we use 999999 microseconds to simulate the real world.
now += timedelta(seconds=1, microseconds=999999)
with mock.patch(
"homeassistant.helpers.event.dt_util.utcnow",
return_value=now,
autospec=True,
):
async_fire_time_changed(hass, now)
await hass.async_block_till_done()
if now > time_sensor_active:
if now <= time_after_delay:
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
elif now > time_after_scan:
assert hass.states.get(entity_id).state == STATE_ON
freezer.tick(timedelta(seconds=1, microseconds=999999))
now = dt_util.utcnow()
async_fire_time_changed(hass, now)
await hass.async_block_till_done()
if now > time_sensor_active:
if now <= time_after_delay:
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
elif now > time_after_scan:
assert hass.states.get(entity_id).state == STATE_ON
@pytest.mark.parametrize(
@ -853,19 +845,20 @@ async def test_write_no_client(hass, mock_modbus):
@pytest.mark.parametrize("do_config", [{}])
async def test_integration_reload(hass, caplog, mock_modbus):
async def test_integration_reload(
hass, caplog, mock_modbus, freezer: FrozenDateTimeFactory
):
"""Run test for integration reload."""
caplog.set_level(logging.INFO)
caplog.clear()
yaml_path = get_fixture_path("configuration.yaml", "modbus")
now = dt_util.utcnow()
with mock.patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path):
await hass.services.async_call(DOMAIN, SERVICE_RELOAD, blocking=True)
await hass.async_block_till_done()
for i in range(4):
now = now + timedelta(seconds=1)
async_fire_time_changed(hass, now)
freezer.tick(timedelta(seconds=1))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert "Modbus reloading" in caplog.text

View file

@ -62,7 +62,7 @@ from tests.common import async_fire_time_changed
@pytest.fixture
def aiohttp_server(loop, aiohttp_server, socket_enabled):
def aiohttp_server(event_loop, aiohttp_server, socket_enabled):
"""Return aiohttp_server and allow opening sockets."""
return aiohttp_server
@ -218,7 +218,7 @@ async def test_get_still_image_from_camera(
) -> None:
"""Test getting a still image."""
image_handler = Mock(return_value="")
image_handler = AsyncMock(return_value="")
app = web.Application()
app.add_routes(
@ -258,7 +258,7 @@ async def test_get_still_image_from_camera(
async def test_get_stream_from_camera(aiohttp_server: Any, hass: HomeAssistant) -> None:
"""Test getting a stream."""
stream_handler = Mock(return_value="")
stream_handler = AsyncMock(return_value="")
app = web.Application()
app.add_routes([web.get("/", stream_handler)])
stream_server = await aiohttp_server(app)
@ -341,7 +341,7 @@ async def test_camera_option_stream_url_template(
"""Verify camera with a stream URL template option."""
client = create_mock_motioneye_client()
stream_handler = Mock(return_value="")
stream_handler = AsyncMock(return_value="")
app = web.Application()
app.add_routes([web.get(f"/{TEST_CAMERA_NAME}/{TEST_CAMERA_ID}", stream_handler)])
stream_server = await aiohttp_server(app)
@ -371,7 +371,7 @@ async def test_camera_option_stream_url_template(
# the expected exception, then verify the right handler was called.
with pytest.raises(HTTPBadGateway):
await async_get_mjpeg_stream(hass, Mock(), TEST_CAMERA_ENTITY_ID)
assert stream_handler.called
assert AsyncMock.called
assert not client.get_camera_stream_url.called

View file

@ -82,7 +82,7 @@ class FakeAuth(AbstractAuth):
@pytest.fixture
def aiohttp_client(loop, aiohttp_client, socket_enabled):
def aiohttp_client(event_loop, aiohttp_client, socket_enabled):
"""Return aiohttp_client and allow opening sockets."""
return aiohttp_client

View file

@ -53,7 +53,9 @@ async def test_auth(hass, aioclient_mock):
# Prepare to capture credentials for Subscriber
captured_creds = None
async def async_new_subscriber(creds, subscription_name, loop, async_callback):
async def async_new_subscriber(
creds, subscription_name, event_loop, async_callback
):
"""Capture credentials for tests."""
nonlocal captured_creds
captured_creds = creds
@ -112,7 +114,9 @@ async def test_auth_expired_token(hass, aioclient_mock):
# Prepare to capture credentials for Subscriber
captured_creds = None
async def async_new_subscriber(creds, subscription_name, loop, async_callback):
async def async_new_subscriber(
creds, subscription_name, event_loop, async_callback
):
"""Capture credentials for tests."""
nonlocal captured_creds
captured_creds = creds

View file

@ -110,7 +110,7 @@ def test_validate_or_move_away_sqlite_database(hass, tmpdir, caplog):
async def test_last_run_was_recently_clean(
loop, async_setup_recorder_instance: SetupRecorderInstanceT, tmp_path
event_loop, async_setup_recorder_instance: SetupRecorderInstanceT, tmp_path
):
"""Test we can check if the last recorder run was recently clean."""
config = {

View file

@ -8,8 +8,9 @@ from homeassistant.setup import async_setup_component
@pytest.fixture
def mock_http_client(loop, hass, hass_client):
def mock_http_client(event_loop, hass, hass_client):
"""Set up test fixture."""
loop = event_loop
config = {
"rss_feed_template": {
"testfeed": {

View file

@ -1,7 +1,5 @@
"""Tests for the SkyBell integration."""
from unittest.mock import AsyncMock, patch
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
USERNAME = "user"
@ -12,19 +10,3 @@ CONF_CONFIG_FLOW = {
CONF_EMAIL: USERNAME,
CONF_PASSWORD: PASSWORD,
}
def _patch_skybell_devices() -> None:
mocked_skybell = AsyncMock()
mocked_skybell.user_id = USER_ID
return patch(
"homeassistant.components.skybell.config_flow.Skybell.async_get_devices",
return_value=[mocked_skybell],
)
def _patch_skybell() -> None:
return patch(
"homeassistant.components.skybell.config_flow.Skybell.async_send_request",
return_value={"id": USER_ID},
)

View file

@ -0,0 +1,25 @@
"""Test setup for the SkyBell integration."""
from unittest.mock import AsyncMock, patch
from aioskybell import Skybell, SkybellDevice
from pytest import fixture
from . import USER_ID
@fixture(autouse=True)
def skybell_mock():
"""Fixture for our skybell tests."""
mocked_skybell_device = AsyncMock(spec=SkybellDevice)
mocked_skybell = AsyncMock(spec=Skybell)
mocked_skybell.async_get_devices.return_value = [mocked_skybell_device]
mocked_skybell.async_send_request.return_value = {"id": USER_ID}
mocked_skybell.user_id = USER_ID
with patch(
"homeassistant.components.skybell.config_flow.Skybell",
return_value=mocked_skybell,
), patch("homeassistant.components.skybell.Skybell", return_value=mocked_skybell):
yield mocked_skybell

View file

@ -2,6 +2,7 @@
from unittest.mock import patch
from aioskybell import exceptions
from pytest import fixture
from homeassistant import config_entries
from homeassistant.components.skybell.const import DOMAIN
@ -10,43 +11,39 @@ from homeassistant.const import CONF_PASSWORD, CONF_SOURCE
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from . import (
CONF_CONFIG_FLOW,
PASSWORD,
USER_ID,
_patch_skybell,
_patch_skybell_devices,
)
from . import CONF_CONFIG_FLOW, PASSWORD, USER_ID
from tests.common import MockConfigEntry
def _patch_setup_entry() -> None:
return patch(
@fixture(autouse=True)
def setup_entry() -> None:
"""Make sure component doesn't initialize."""
with patch(
"homeassistant.components.skybell.async_setup_entry",
return_value=True,
)
):
yield
async def test_flow_user(hass: HomeAssistant) -> None:
"""Test that the user step works."""
with _patch_skybell(), _patch_skybell_devices(), _patch_setup_entry():
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=CONF_CONFIG_FLOW,
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=CONF_CONFIG_FLOW,
)
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "user"
assert result["data"] == CONF_CONFIG_FLOW
assert result["result"].unique_id == USER_ID
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "user"
assert result["data"] == CONF_CONFIG_FLOW
assert result["result"].unique_id == USER_ID
async def test_flow_user_already_configured(hass: HomeAssistant) -> None:
@ -57,50 +54,48 @@ async def test_flow_user_already_configured(hass: HomeAssistant) -> None:
)
entry.add_to_hass(hass)
with _patch_skybell(), _patch_skybell_devices():
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONF_CONFIG_FLOW
)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONF_CONFIG_FLOW
)
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "already_configured"
async def test_flow_user_cannot_connect(hass: HomeAssistant) -> None:
async def test_flow_user_cannot_connect(hass: HomeAssistant, skybell_mock) -> None:
"""Test user initialized flow with unreachable server."""
with _patch_skybell() as skybell_mock:
skybell_mock.side_effect = exceptions.SkybellException(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONF_CONFIG_FLOW
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {"base": "cannot_connect"}
skybell_mock.async_initialize.side_effect = exceptions.SkybellException(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONF_CONFIG_FLOW
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {"base": "cannot_connect"}
async def test_invalid_credentials(hass: HomeAssistant) -> None:
async def test_invalid_credentials(hass: HomeAssistant, skybell_mock) -> None:
"""Test that invalid credentials throws an error."""
with patch("homeassistant.components.skybell.Skybell.async_login") as skybell_mock:
skybell_mock.side_effect = exceptions.SkybellAuthenticationException(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONF_CONFIG_FLOW
)
skybell_mock.async_initialize.side_effect = (
exceptions.SkybellAuthenticationException(hass)
)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONF_CONFIG_FLOW
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {"base": "invalid_auth"}
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {"base": "invalid_auth"}
async def test_flow_user_unknown_error(hass: HomeAssistant) -> None:
async def test_flow_user_unknown_error(hass: HomeAssistant, skybell_mock) -> None:
"""Test user initialized flow with unreachable server."""
with _patch_skybell_devices() as skybell_mock:
skybell_mock.side_effect = Exception
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONF_CONFIG_FLOW
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {"base": "unknown"}
skybell_mock.async_initialize.side_effect = Exception
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONF_CONFIG_FLOW
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {"base": "unknown"}
async def test_step_reauth(hass: HomeAssistant) -> None:
@ -121,17 +116,15 @@ async def test_step_reauth(hass: HomeAssistant) -> None:
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
with _patch_skybell(), _patch_skybell_devices(), _patch_setup_entry():
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_PASSWORD: PASSWORD},
)
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_PASSWORD: PASSWORD},
)
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
async def test_step_reauth_failed(hass: HomeAssistant) -> None:
async def test_step_reauth_failed(hass: HomeAssistant, skybell_mock) -> None:
"""Test the reauth flow fails and recovers."""
entry = MockConfigEntry(domain=DOMAIN, unique_id=USER_ID, data=CONF_CONFIG_FLOW)
entry.add_to_hass(hass)
@ -149,21 +142,22 @@ async def test_step_reauth_failed(hass: HomeAssistant) -> None:
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
with patch("homeassistant.components.skybell.Skybell.async_login") as skybell_mock:
skybell_mock.side_effect = exceptions.SkybellAuthenticationException(hass)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_PASSWORD: PASSWORD},
)
skybell_mock.async_initialize.side_effect = (
exceptions.SkybellAuthenticationException(hass)
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_PASSWORD: PASSWORD},
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": "invalid_auth"}
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": "invalid_auth"}
with _patch_skybell(), _patch_skybell_devices(), _patch_setup_entry():
skybell_mock.async_initialize.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_PASSWORD: PASSWORD},
)
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_PASSWORD: PASSWORD},
)
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "reauth_successful"

View file

@ -24,7 +24,7 @@ def mock_dev_track(mock_device_tracker_conf):
@pytest.fixture(name="client")
async def traccar_client(loop, hass, hass_client_no_auth):
async def traccar_client(event_loop, hass, hass_client_no_auth):
"""Mock client for Traccar (unauthenticated)."""
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
@ -36,7 +36,7 @@ async def traccar_client(loop, hass, hass_client_no_auth):
@pytest.fixture(autouse=True)
async def setup_zones(loop, hass):
async def setup_zones(event_loop, hass):
"""Set up Zone config in HA."""
assert await async_setup_component(
hass,

View file

@ -5,6 +5,7 @@ import asyncio
from collections.abc import AsyncGenerator, Callable, Generator
from contextlib import asynccontextmanager
import functools
import gc
import itertools
from json import JSONDecoder, loads
import logging
@ -13,6 +14,7 @@ import ssl
import threading
from typing import Any
from unittest.mock import AsyncMock, MagicMock, Mock, patch
import warnings
from aiohttp import client
from aiohttp.pytest_plugin import AiohttpClient
@ -188,12 +190,14 @@ util.get_local_ip = lambda: "127.0.0.1"
@pytest.fixture(autouse=True)
def verify_cleanup():
def verify_cleanup(event_loop: asyncio.AbstractEventLoop):
"""Verify that the test has cleaned up resources correctly."""
threads_before = frozenset(threading.enumerate())
tasks_before = asyncio.all_tasks(event_loop)
yield
event_loop.run_until_complete(event_loop.shutdown_default_executor())
if len(INSTANCES) >= 2:
count = len(INSTANCES)
for inst in INSTANCES:
@ -204,6 +208,26 @@ def verify_cleanup():
for thread in threads:
assert isinstance(thread, threading._DummyThread)
# Warn and clean-up lingering tasks and timers
# before moving on to the next test.
tasks = asyncio.all_tasks(event_loop) - tasks_before
for task in tasks:
warnings.warn(f"Linger task after test {task}")
task.cancel()
if tasks:
event_loop.run_until_complete(asyncio.wait(tasks))
for handle in event_loop._scheduled: # pylint: disable=protected-access
if not handle.cancelled():
warnings.warn(f"Lingering timer after test {handle}")
handle.cancel()
# Make sure garbage collect run in same test as allocation
# this is to mimic the behavior of pytest-aiohttp, and is
# required to avoid warnings from spilling over into next
# test case.
gc.collect()
@pytest.fixture(autouse=True)
def bcrypt_cost():
@ -278,7 +302,7 @@ def aiohttp_client_cls():
@pytest.fixture
def aiohttp_client(
loop: asyncio.AbstractEventLoop,
event_loop: asyncio.AbstractEventLoop,
) -> Generator[AiohttpClient, None, None]:
"""Override the default aiohttp_client since 3.x does not support aiohttp_client_cls.
@ -289,6 +313,7 @@ def aiohttp_client(
aiohttp_client(server, **kwargs)
aiohttp_client(raw_server, **kwargs)
"""
loop = event_loop
clients = []
async def go(
@ -335,9 +360,10 @@ def hass_fixture_setup():
@pytest.fixture
def hass(hass_fixture_setup, loop, load_registries, hass_storage, request):
def hass(hass_fixture_setup, event_loop, load_registries, hass_storage, request):
"""Fixture to provide a test instance of Home Assistant."""
loop = event_loop
hass_fixture_setup.append(True)
orig_tz = dt_util.DEFAULT_TIME_ZONE
@ -382,7 +408,7 @@ def hass(hass_fixture_setup, loop, load_registries, hass_storage, request):
@pytest.fixture
async def stop_hass():
async def stop_hass(event_loop):
"""Make sure all hass are stopped."""
orig_hass = ha.HomeAssistant
@ -403,6 +429,7 @@ async def stop_hass():
with patch.object(hass_inst.loop, "stop"):
await hass_inst.async_block_till_done()
await hass_inst.async_stop(force=True)
await event_loop.shutdown_default_executor()
@pytest.fixture

View file

@ -8,7 +8,7 @@ import logging
import operator
from types import MappingProxyType
from unittest import mock
from unittest.mock import AsyncMock, patch
from unittest.mock import AsyncMock, MagicMock, patch
from async_timeout import timeout
import pytest
@ -1734,6 +1734,7 @@ async def test_condition_created_once(async_from_config, hass):
)
async_from_config.reset_mock()
async_from_config.return_value = MagicMock()
hass.states.async_set("test.entity", "hello")
await script_obj.async_run(context=Context())

View file

@ -108,7 +108,7 @@ async def test_change_password_invalid_user(hass, provider, capsys, hass_storage
data.validate_login("invalid-user", "new-pass")
def test_parsing_args(loop):
def test_parsing_args(event_loop):
"""Test we parse args correctly."""
called = False

View file

@ -43,7 +43,7 @@ def normalize_yaml_files(check_dict):
return [key.replace(root, "...") for key in sorted(check_dict["yaml_files"].keys())]
def test_bad_core_config(mock_is_file, loop):
def test_bad_core_config(mock_is_file, event_loop):
"""Test a bad core config setup."""
files = {YAML_CONFIG_FILE: BAD_CORE_CONFIG}
with patch_yaml_files(files):
@ -52,7 +52,7 @@ def test_bad_core_config(mock_is_file, loop):
assert res["except"]["homeassistant"][1] == {"unit_system": "bad"}
def test_config_platform_valid(mock_is_file, loop):
def test_config_platform_valid(mock_is_file, event_loop):
"""Test a valid platform setup."""
files = {YAML_CONFIG_FILE: BASE_CONFIG + "light:\n platform: demo"}
with patch_yaml_files(files):
@ -65,7 +65,7 @@ def test_config_platform_valid(mock_is_file, loop):
assert len(res["yaml_files"]) == 1
def test_component_platform_not_found(mock_is_file, loop):
def test_component_platform_not_found(mock_is_file, event_loop):
"""Test errors if component or platform not found."""
# Make sure they don't exist
files = {YAML_CONFIG_FILE: BASE_CONFIG + "beer:"}
@ -96,7 +96,7 @@ def test_component_platform_not_found(mock_is_file, loop):
assert len(res["yaml_files"]) == 1
def test_secrets(mock_is_file, loop):
def test_secrets(mock_is_file, event_loop):
"""Test secrets config checking method."""
secrets_path = get_test_config_dir("secrets.yaml")
@ -127,7 +127,7 @@ def test_secrets(mock_is_file, loop):
]
def test_package_invalid(mock_is_file, loop):
def test_package_invalid(mock_is_file, event_loop):
"""Test an invalid package."""
files = {
YAML_CONFIG_FILE: BASE_CONFIG + (" packages:\n p1:\n" ' group: ["a"]')
@ -145,7 +145,7 @@ def test_package_invalid(mock_is_file, loop):
assert len(res["yaml_files"]) == 1
def test_bootstrap_error(loop):
def test_bootstrap_error(event_loop):
"""Test a valid platform setup."""
files = {YAML_CONFIG_FILE: BASE_CONFIG + "automation: !include no.yaml"}
with patch_yaml_files(files):

View file

@ -460,7 +460,7 @@ async def test_setup_hass(
mock_ensure_config_exists,
mock_process_ha_config_upgrade,
caplog,
loop,
event_loop,
):
"""Test it works."""
verbose = Mock()
@ -511,7 +511,7 @@ async def test_setup_hass_takes_longer_than_log_slow_startup(
mock_ensure_config_exists,
mock_process_ha_config_upgrade,
caplog,
loop,
event_loop,
):
"""Test it works."""
verbose = Mock()
@ -553,7 +553,7 @@ async def test_setup_hass_invalid_yaml(
mock_mount_local_lib_path,
mock_ensure_config_exists,
mock_process_ha_config_upgrade,
loop,
event_loop,
):
"""Test it works."""
with patch(
@ -581,7 +581,7 @@ async def test_setup_hass_config_dir_nonexistent(
mock_mount_local_lib_path,
mock_ensure_config_exists,
mock_process_ha_config_upgrade,
loop,
event_loop,
):
"""Test it works."""
mock_ensure_config_exists.return_value = False
@ -608,7 +608,7 @@ async def test_setup_hass_safe_mode(
mock_mount_local_lib_path,
mock_ensure_config_exists,
mock_process_ha_config_upgrade,
loop,
event_loop,
):
"""Test it works."""
with patch("homeassistant.components.browser.setup") as browser_setup, patch(
@ -641,7 +641,7 @@ async def test_setup_hass_invalid_core_config(
mock_mount_local_lib_path,
mock_ensure_config_exists,
mock_process_ha_config_upgrade,
loop,
event_loop,
):
"""Test it works."""
with patch(
@ -669,7 +669,7 @@ async def test_setup_safe_mode_if_no_frontend(
mock_mount_local_lib_path,
mock_ensure_config_exists,
mock_process_ha_config_upgrade,
loop,
event_loop,
):
"""Test we setup safe mode if frontend didn't load."""
verbose = Mock()

View file

@ -87,9 +87,9 @@ def test_async_add_hass_job_schedule_partial_callback():
assert len(hass.add_job.mock_calls) == 0
def test_async_add_hass_job_schedule_coroutinefunction(loop):
def test_async_add_hass_job_schedule_coroutinefunction(event_loop):
"""Test that we schedule coroutines and add jobs to the job pool."""
hass = MagicMock(loop=MagicMock(wraps=loop))
hass = MagicMock(loop=MagicMock(wraps=event_loop))
async def job():
pass
@ -100,9 +100,9 @@ def test_async_add_hass_job_schedule_coroutinefunction(loop):
assert len(hass.add_job.mock_calls) == 0
def test_async_add_hass_job_schedule_partial_coroutinefunction(loop):
def test_async_add_hass_job_schedule_partial_coroutinefunction(event_loop):
"""Test that we schedule partial coros and add jobs to the job pool."""
hass = MagicMock(loop=MagicMock(wraps=loop))
hass = MagicMock(loop=MagicMock(wraps=event_loop))
async def job():
pass
@ -128,9 +128,9 @@ def test_async_add_job_add_hass_threaded_job_to_pool():
assert len(hass.loop.run_in_executor.mock_calls) == 1
def test_async_create_task_schedule_coroutine(loop):
def test_async_create_task_schedule_coroutine(event_loop):
"""Test that we schedule coroutines and add jobs to the job pool."""
hass = MagicMock(loop=MagicMock(wraps=loop))
hass = MagicMock(loop=MagicMock(wraps=event_loop))
async def job():
pass
@ -1079,7 +1079,7 @@ async def test_bad_timezone_raises_value_error(hass):
await hass.config.async_update(time_zone="not_a_timezone")
async def test_start_taking_too_long(loop, caplog):
async def test_start_taking_too_long(event_loop, caplog):
"""Test when async_start takes too long."""
hass = ha.HomeAssistant()
caplog.set_level(logging.WARNING)
@ -1098,7 +1098,7 @@ async def test_start_taking_too_long(loop, caplog):
assert hass.state == ha.CoreState.stopped
async def test_track_task_functions(loop):
async def test_track_task_functions(event_loop):
"""Test function to start/stop track task and initial state."""
hass = ha.HomeAssistant()
try:

View file

@ -32,7 +32,7 @@ async def session(hass):
@pytest.fixture
async def raising_session(loop):
async def raising_session(event_loop):
"""Return an aioclient session that only fails."""
return Mock(get=Mock(side_effect=aiohttp.ClientError))