mirror of
https://github.com/home-assistant/core.git
synced 2025-12-20 02:48:57 +00:00
Fix flaky camera stream teardown (#158507)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
This commit is contained in:
@@ -148,6 +148,22 @@ def mock_stream_source_fixture() -> Generator[AsyncMock]:
|
|||||||
yield mock_stream_source
|
yield mock_stream_source
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="mock_create_stream")
|
||||||
|
def mock_create_stream_fixture() -> Generator[Mock]:
|
||||||
|
"""Fixture to mock create_stream and prevent real stream threads."""
|
||||||
|
mock_stream = Mock()
|
||||||
|
mock_stream.add_provider = Mock()
|
||||||
|
mock_stream.start = AsyncMock()
|
||||||
|
mock_stream.endpoint_url = Mock(return_value="http://home.assistant/playlist.m3u8")
|
||||||
|
mock_stream.set_update_callback = Mock()
|
||||||
|
mock_stream.available = True
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.camera.create_stream",
|
||||||
|
return_value=mock_stream,
|
||||||
|
):
|
||||||
|
yield mock_stream
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
async def mock_test_webrtc_cameras(hass: HomeAssistant) -> None:
|
async def mock_test_webrtc_cameras(hass: HomeAssistant) -> None:
|
||||||
"""Initialize test WebRTC cameras with native RTC support."""
|
"""Initialize test WebRTC cameras with native RTC support."""
|
||||||
|
|||||||
@@ -346,20 +346,14 @@ async def test_websocket_stream_no_source(
|
|||||||
|
|
||||||
@pytest.mark.usefixtures("mock_camera", "mock_stream")
|
@pytest.mark.usefixtures("mock_camera", "mock_stream")
|
||||||
async def test_websocket_camera_stream(
|
async def test_websocket_camera_stream(
|
||||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator, mock_create_stream: Mock
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test camera/stream websocket command."""
|
"""Test camera/stream websocket command."""
|
||||||
await async_setup_component(hass, "camera", {})
|
await async_setup_component(hass, "camera", {})
|
||||||
|
|
||||||
with (
|
with patch(
|
||||||
patch(
|
|
||||||
"homeassistant.components.camera.Stream.endpoint_url",
|
|
||||||
return_value="http://home.assistant/playlist.m3u8",
|
|
||||||
) as mock_stream_view_url,
|
|
||||||
patch(
|
|
||||||
"homeassistant.components.demo.camera.DemoCamera.stream_source",
|
"homeassistant.components.demo.camera.DemoCamera.stream_source",
|
||||||
return_value="http://example.com",
|
return_value="http://example.com",
|
||||||
),
|
|
||||||
):
|
):
|
||||||
# Request playlist through WebSocket
|
# Request playlist through WebSocket
|
||||||
client = await hass_ws_client(hass)
|
client = await hass_ws_client(hass)
|
||||||
@@ -369,7 +363,7 @@ async def test_websocket_camera_stream(
|
|||||||
msg = await client.receive_json()
|
msg = await client.receive_json()
|
||||||
|
|
||||||
# Assert WebSocket response
|
# Assert WebSocket response
|
||||||
assert mock_stream_view_url.called
|
assert mock_create_stream.endpoint_url.called
|
||||||
assert msg["id"] == 6
|
assert msg["id"] == 6
|
||||||
assert msg["type"] == TYPE_RESULT
|
assert msg["type"] == TYPE_RESULT
|
||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
@@ -505,21 +499,18 @@ async def test_play_stream_service_no_source(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_camera", "mock_stream")
|
@pytest.mark.usefixtures("mock_camera", "mock_stream")
|
||||||
async def test_handle_play_stream_service(hass: HomeAssistant) -> None:
|
async def test_handle_play_stream_service(
|
||||||
|
hass: HomeAssistant, mock_create_stream: Mock
|
||||||
|
) -> None:
|
||||||
"""Test camera play_stream service."""
|
"""Test camera play_stream service."""
|
||||||
await async_process_ha_core_config(
|
await async_process_ha_core_config(
|
||||||
hass,
|
hass,
|
||||||
{"external_url": "https://example.com"},
|
{"external_url": "https://example.com"},
|
||||||
)
|
)
|
||||||
await async_setup_component(hass, "media_player", {})
|
await async_setup_component(hass, "media_player", {})
|
||||||
with (
|
with patch(
|
||||||
patch(
|
|
||||||
"homeassistant.components.camera.Stream.endpoint_url",
|
|
||||||
) as mock_request_stream,
|
|
||||||
patch(
|
|
||||||
"homeassistant.components.demo.camera.DemoCamera.stream_source",
|
"homeassistant.components.demo.camera.DemoCamera.stream_source",
|
||||||
return_value="http://example.com",
|
return_value="http://example.com",
|
||||||
),
|
|
||||||
):
|
):
|
||||||
# Call service
|
# Call service
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@@ -533,17 +524,14 @@ async def test_handle_play_stream_service(hass: HomeAssistant) -> None:
|
|||||||
)
|
)
|
||||||
# So long as we request the stream, the rest should be covered
|
# So long as we request the stream, the rest should be covered
|
||||||
# by the play_media service tests.
|
# by the play_media service tests.
|
||||||
assert mock_request_stream.called
|
assert mock_create_stream.endpoint_url.called
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_stream")
|
@pytest.mark.usefixtures("mock_stream")
|
||||||
async def test_no_preload_stream(hass: HomeAssistant) -> None:
|
async def test_no_preload_stream(hass: HomeAssistant, mock_create_stream: Mock) -> None:
|
||||||
"""Test camera preload preference."""
|
"""Test camera preload preference."""
|
||||||
demo_settings = camera.DynamicStreamSettings()
|
demo_settings = camera.DynamicStreamSettings()
|
||||||
with (
|
with (
|
||||||
patch(
|
|
||||||
"homeassistant.components.camera.Stream.endpoint_url",
|
|
||||||
) as mock_request_stream,
|
|
||||||
patch(
|
patch(
|
||||||
"homeassistant.components.camera.prefs.CameraPreferences.get_dynamic_stream_settings",
|
"homeassistant.components.camera.prefs.CameraPreferences.get_dynamic_stream_settings",
|
||||||
return_value=demo_settings,
|
return_value=demo_settings,
|
||||||
@@ -557,15 +545,14 @@ async def test_no_preload_stream(hass: HomeAssistant) -> None:
|
|||||||
await async_setup_component(hass, "camera", {DOMAIN: {"platform": "demo"}})
|
await async_setup_component(hass, "camera", {DOMAIN: {"platform": "demo"}})
|
||||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert not mock_request_stream.called
|
assert not mock_create_stream.endpoint_url.called
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_stream")
|
@pytest.mark.usefixtures("mock_stream")
|
||||||
async def test_preload_stream(hass: HomeAssistant) -> None:
|
async def test_preload_stream(hass: HomeAssistant, mock_create_stream: Mock) -> None:
|
||||||
"""Test camera preload preference."""
|
"""Test camera preload preference."""
|
||||||
demo_settings = camera.DynamicStreamSettings(preload_stream=True)
|
demo_settings = camera.DynamicStreamSettings(preload_stream=True)
|
||||||
with (
|
with (
|
||||||
patch("homeassistant.components.camera.create_stream") as mock_create_stream,
|
|
||||||
patch(
|
patch(
|
||||||
"homeassistant.components.camera.prefs.CameraPreferences.get_dynamic_stream_settings",
|
"homeassistant.components.camera.prefs.CameraPreferences.get_dynamic_stream_settings",
|
||||||
return_value=demo_settings,
|
return_value=demo_settings,
|
||||||
@@ -575,14 +562,13 @@ async def test_preload_stream(hass: HomeAssistant) -> None:
|
|||||||
return_value="http://example.com",
|
return_value="http://example.com",
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
mock_create_stream.return_value.start = AsyncMock()
|
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
hass, "camera", {DOMAIN: {"platform": "demo"}}
|
hass, "camera", {DOMAIN: {"platform": "demo"}}
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert mock_create_stream.called
|
assert mock_create_stream.start.called
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_camera")
|
@pytest.mark.usefixtures("mock_camera")
|
||||||
@@ -694,25 +680,16 @@ async def test_state_streaming(hass: HomeAssistant) -> None:
|
|||||||
assert demo_camera.state == camera.CameraState.STREAMING
|
assert demo_camera.state == camera.CameraState.STREAMING
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_camera", "mock_stream")
|
@pytest.mark.usefixtures("mock_camera", "mock_stream", "mock_create_stream")
|
||||||
async def test_stream_unavailable(
|
async def test_stream_unavailable(
|
||||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator, mock_create_stream: Mock
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Camera state."""
|
"""Camera state."""
|
||||||
await async_setup_component(hass, "camera", {})
|
await async_setup_component(hass, "camera", {})
|
||||||
|
|
||||||
with (
|
with patch(
|
||||||
patch(
|
|
||||||
"homeassistant.components.camera.Stream.endpoint_url",
|
|
||||||
return_value="http://home.assistant/playlist.m3u8",
|
|
||||||
),
|
|
||||||
patch(
|
|
||||||
"homeassistant.components.demo.camera.DemoCamera.stream_source",
|
"homeassistant.components.demo.camera.DemoCamera.stream_source",
|
||||||
return_value="http://example.com",
|
return_value="http://example.com",
|
||||||
),
|
|
||||||
patch(
|
|
||||||
"homeassistant.components.camera.Stream.set_update_callback",
|
|
||||||
) as mock_update_callback,
|
|
||||||
):
|
):
|
||||||
# Request playlist through WebSocket. We just want to create the stream
|
# Request playlist through WebSocket. We just want to create the stream
|
||||||
# but don't care about the result.
|
# but don't care about the result.
|
||||||
@@ -721,13 +698,11 @@ async def test_stream_unavailable(
|
|||||||
{"id": 10, "type": "camera/stream", "entity_id": "camera.demo_camera"}
|
{"id": 10, "type": "camera/stream", "entity_id": "camera.demo_camera"}
|
||||||
)
|
)
|
||||||
await client.receive_json()
|
await client.receive_json()
|
||||||
assert mock_update_callback.called
|
assert mock_create_stream.set_update_callback.called
|
||||||
|
|
||||||
# Simulate the stream going unavailable
|
# Simulate the stream going unavailable
|
||||||
callback = mock_update_callback.call_args.args[0]
|
callback = mock_create_stream.set_update_callback.call_args.args[0]
|
||||||
with patch(
|
mock_create_stream.available = False
|
||||||
"homeassistant.components.camera.Stream.available", new_callable=lambda: False
|
|
||||||
):
|
|
||||||
callback()
|
callback()
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
@@ -736,9 +711,7 @@ async def test_stream_unavailable(
|
|||||||
assert demo_camera.state == STATE_UNAVAILABLE
|
assert demo_camera.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
# Simulate stream becomes available
|
# Simulate stream becomes available
|
||||||
with patch(
|
mock_create_stream.available = True
|
||||||
"homeassistant.components.camera.Stream.available", new_callable=lambda: True
|
|
||||||
):
|
|
||||||
callback()
|
callback()
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user