Skip to content

Commit

Permalink
Clean up camera and demo camera (home-assistant#34058)
Browse files Browse the repository at this point in the history
* Clean up demo camera

* Complete test_motion_detection

* Clean up image reading

* Clean up camera integration async methods

* Fix and clean camera integration tests

* Fix image processing patch
  • Loading branch information
MartinHjelmare authored Apr 12, 2020
1 parent 4f519a2 commit 20aa089
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 63 deletions.
16 changes: 7 additions & 9 deletions homeassistant/components/camera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ def camera_image(self):

async def async_camera_image(self):
"""Return bytes of camera image."""
return await self.hass.async_add_job(self.camera_image)
return await self.hass.async_add_executor_job(self.camera_image)

async def handle_async_still_stream(self, request, interval):
"""Generate an HTTP MJPEG stream from camera images."""
Expand Down Expand Up @@ -409,33 +409,31 @@ def turn_off(self):

async def async_turn_off(self):
"""Turn off camera."""
await self.hass.async_add_job(self.turn_off)
await self.hass.async_add_executor_job(self.turn_off)

def turn_on(self):
"""Turn off camera."""
raise NotImplementedError()

async def async_turn_on(self):
"""Turn off camera."""
await self.hass.async_add_job(self.turn_on)
await self.hass.async_add_executor_job(self.turn_on)

def enable_motion_detection(self):
"""Enable motion detection in the camera."""
raise NotImplementedError()

@callback
def async_enable_motion_detection(self):
async def async_enable_motion_detection(self):
"""Call the job and enable motion detection."""
return self.hass.async_add_job(self.enable_motion_detection)
await self.hass.async_add_executor_job(self.enable_motion_detection)

def disable_motion_detection(self):
"""Disable motion detection in camera."""
raise NotImplementedError()

@callback
def async_disable_motion_detection(self):
async def async_disable_motion_detection(self):
"""Call the job and disable motion detection."""
return self.hass.async_add_job(self.disable_motion_detection)
await self.hass.async_add_executor_job(self.disable_motion_detection)

@property
def state_attributes(self):
Expand Down
30 changes: 13 additions & 17 deletions homeassistant/components/demo/camera.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Demo camera platform that has a fake camera."""
import logging
import os
from pathlib import Path

from homeassistant.components.camera import SUPPORT_ON_OFF, Camera

Expand Down Expand Up @@ -28,16 +28,12 @@ def __init__(self, name):
self.is_streaming = True
self._images_index = 0

def camera_image(self):
async def async_camera_image(self):
"""Return a faked still image response."""
self._images_index = (self._images_index + 1) % 4
image_path = Path(__file__).parent / f"demo_{self._images_index}.jpg"

image_path = os.path.join(
os.path.dirname(__file__), f"demo_{self._images_index}.jpg"
)
_LOGGER.debug("Loading camera_image: %s", image_path)
with open(image_path, "rb") as file:
return file.read()
return await self.hass.async_add_executor_job(image_path.read_bytes)

@property
def name(self):
Expand All @@ -48,7 +44,7 @@ def name(self):
def should_poll(self):
"""Demo camera doesn't need poll.
Need explicitly call schedule_update_ha_state() after state changed.
Need explicitly call async_write_ha_state() after state changed.
"""
return False

Expand All @@ -67,22 +63,22 @@ def motion_detection_enabled(self):
"""Camera Motion Detection Status."""
return self._motion_status

def enable_motion_detection(self):
async def async_enable_motion_detection(self):
"""Enable the Motion detection in base station (Arm)."""
self._motion_status = True
self.schedule_update_ha_state()
self.async_write_ha_state()

def disable_motion_detection(self):
async def async_disable_motion_detection(self):
"""Disable the motion detection in base station (Disarm)."""
self._motion_status = False
self.schedule_update_ha_state()
self.async_write_ha_state()

def turn_off(self):
async def async_turn_off(self):
"""Turn off camera."""
self.is_streaming = False
self.schedule_update_ha_state()
self.async_write_ha_state()

def turn_on(self):
async def async_turn_on(self):
"""Turn on camera."""
self.is_streaming = True
self.schedule_update_ha_state()
self.async_write_ha_state()
40 changes: 19 additions & 21 deletions tests/components/camera/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import asyncio
import base64
import io
from unittest.mock import PropertyMock, mock_open, patch
from unittest.mock import PropertyMock, mock_open

from asynctest import patch
import pytest

from homeassistant.components import camera
Expand All @@ -14,40 +15,38 @@
from homeassistant.exceptions import HomeAssistantError
from homeassistant.setup import async_setup_component

from tests.common import mock_coro
from tests.components.camera import common


@pytest.fixture
def mock_camera(hass):
@pytest.fixture(name="mock_camera")
def mock_camera_fixture(hass):
"""Initialize a demo camera platform."""
assert hass.loop.run_until_complete(
async_setup_component(hass, "camera", {camera.DOMAIN: {"platform": "demo"}})
)

with patch(
"homeassistant.components.demo.camera.DemoCamera.camera_image",
return_value=b"Test",
"homeassistant.components.demo.camera.Path.read_bytes", return_value=b"Test",
):
yield


@pytest.fixture
def mock_stream(hass):
@pytest.fixture(name="mock_stream")
def mock_stream_fixture(hass):
"""Initialize a demo camera platform with streaming."""
assert hass.loop.run_until_complete(
async_setup_component(hass, "stream", {"stream": {}})
)


@pytest.fixture
def setup_camera_prefs(hass):
@pytest.fixture(name="setup_camera_prefs")
def setup_camera_prefs_fixture(hass):
"""Initialize HTTP API."""
return common.mock_camera_prefs(hass, "camera.demo_camera")


@pytest.fixture
async def image_mock_url(hass):
@pytest.fixture(name="image_mock_url")
async def image_mock_url_fixture(hass):
"""Fixture for get_image tests."""
await async_setup_component(
hass, camera.DOMAIN, {camera.DOMAIN: {"platform": "demo"}}
Expand All @@ -58,7 +57,7 @@ async def test_get_image_from_camera(hass, image_mock_url):
"""Grab an image from camera entity."""

with patch(
"homeassistant.components.demo.camera.DemoCamera.camera_image",
"homeassistant.components.demo.camera.Path.read_bytes",
autospec=True,
return_value=b"Test",
) as mock_camera:
Expand All @@ -80,7 +79,7 @@ async def test_get_image_without_exists_camera(hass, image_mock_url):
async def test_get_image_with_timeout(hass, image_mock_url):
"""Try to get image with timeout."""
with patch(
"homeassistant.components.camera.Camera.async_camera_image",
"homeassistant.components.demo.camera.DemoCamera.async_camera_image",
side_effect=asyncio.TimeoutError,
), pytest.raises(HomeAssistantError):
await camera.async_get_image(hass, "camera.demo_camera")
Expand All @@ -89,8 +88,8 @@ async def test_get_image_with_timeout(hass, image_mock_url):
async def test_get_image_fails(hass, image_mock_url):
"""Try to get image with timeout."""
with patch(
"homeassistant.components.camera.Camera.async_camera_image",
return_value=mock_coro(None),
"homeassistant.components.demo.camera.DemoCamera.async_camera_image",
return_value=None,
), pytest.raises(HomeAssistantError):
await camera.async_get_image(hass, "camera.demo_camera")

Expand Down Expand Up @@ -169,7 +168,7 @@ async def test_websocket_camera_stream(hass, hass_ws_client, mock_camera, mock_s
return_value="http://home.assistant/playlist.m3u8",
) as mock_request_stream, patch(
"homeassistant.components.demo.camera.DemoCamera.stream_source",
return_value=mock_coro("http://example.com"),
return_value="http://example.com",
):
# Request playlist through WebSocket
client = await hass_ws_client(hass)
Expand Down Expand Up @@ -248,7 +247,7 @@ async def test_handle_play_stream_service(hass, mock_camera, mock_stream):
"homeassistant.components.camera.request_stream"
) as mock_request_stream, patch(
"homeassistant.components.demo.camera.DemoCamera.stream_source",
return_value=mock_coro("http://example.com"),
return_value="http://example.com",
):
# Call service
await hass.services.async_call(
Expand Down Expand Up @@ -294,7 +293,7 @@ async def test_preload_stream(hass, mock_stream):
return_value=demo_prefs,
), patch(
"homeassistant.components.demo.camera.DemoCamera.stream_source",
return_value=mock_coro("http://example.com"),
return_value="http://example.com",
):
await async_setup_component(hass, "camera", {DOMAIN: {"platform": "demo"}})
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
Expand Down Expand Up @@ -323,10 +322,9 @@ async def test_record_service(hass, mock_camera, mock_stream):
"""Test record service."""
with patch(
"homeassistant.components.demo.camera.DemoCamera.stream_source",
return_value=mock_coro("http://example.com"),
return_value="http://example.com",
), patch(
"homeassistant.components.stream.async_handle_record_service",
return_value=mock_coro(),
) as mock_record_service, patch.object(
hass.config, "is_allowed_path", return_value=True
):
Expand Down
28 changes: 18 additions & 10 deletions tests/components/demo/test_camera.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""The tests for local file camera component."""
from unittest.mock import mock_open, patch
from unittest.mock import patch

import pytest

from homeassistant.components.camera import (
DOMAIN as CAMERA_DOMAIN,
SERVICE_DISABLE_MOTION,
SERVICE_ENABLE_MOTION,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
Expand Down Expand Up @@ -35,16 +36,11 @@ async def test_init_state_is_streaming(hass):
state = hass.states.get(ENTITY_CAMERA)
assert state.state == STATE_STREAMING

mock_on_img = mock_open(read_data=b"ON")
with patch("homeassistant.components.demo.camera.open", mock_on_img, create=True):
with patch(
"homeassistant.components.demo.camera.Path.read_bytes", return_value=b"ON"
) as mock_read_bytes:
image = await async_get_image(hass, ENTITY_CAMERA)
assert mock_on_img.called
assert mock_on_img.call_args_list[0][0][0][-6:] in [
"_0.jpg",
"_1.jpg",
"_2.jpg",
"_3.jpg",
]
assert mock_read_bytes.call_count == 1
assert image.content == b"ON"


Expand Down Expand Up @@ -113,3 +109,15 @@ async def test_motion_detection(hass):
# Check if state has been updated.
state = hass.states.get(ENTITY_CAMERA)
assert state.attributes.get("motion_detection")

# Call service to turn off motion detection
await hass.services.async_call(
CAMERA_DOMAIN,
SERVICE_DISABLE_MOTION,
{ATTR_ENTITY_ID: ENTITY_CAMERA},
blocking=True,
)

# Check if state has been updated.
state = hass.states.get(ENTITY_CAMERA)
assert not state.attributes.get("motion_detection")
12 changes: 6 additions & 6 deletions tests/components/image_processing/test_init.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""The tests for the image_processing component."""
from unittest.mock import PropertyMock, patch
from unittest.mock import PropertyMock

from asynctest import patch

import homeassistant.components.http as http
import homeassistant.components.image_processing as ip
Expand Down Expand Up @@ -69,18 +71,16 @@ def teardown_method(self):
self.hass.stop()

@patch(
"homeassistant.components.demo.camera.DemoCamera.camera_image",
autospec=True,
return_value=b"Test",
"homeassistant.components.demo.camera.Path.read_bytes", return_value=b"Test",
)
def test_get_image_from_camera(self, mock_camera):
def test_get_image_from_camera(self, mock_camera_read):
"""Grab an image from camera entity."""
common.scan(self.hass, entity_id="image_processing.test")
self.hass.block_till_done()

state = self.hass.states.get("image_processing.test")

assert mock_camera.called
assert mock_camera_read.called
assert state.state == "1"
assert state.attributes["image"] == b"Test"

Expand Down

0 comments on commit 20aa089

Please sign in to comment.