async def test_stream_stopped_while_decoding(hass): """Tests that worker quits when stop() is called while decodign.""" # Add some synchronization so that the test can pause the background # worker. When the worker is stopped, the test invokes stop() which # will cause the worker thread to exit once it enters the decode # loop worker_open = threading.Event() worker_wake = threading.Event() stream = Stream(hass, STREAM_SOURCE, {}, hass.data[DOMAIN][ATTR_SETTINGS]) stream.add_provider(HLS_PROVIDER) py_av = MockPyAv() py_av.container.packets = PacketSequence(TEST_SEQUENCE_LENGTH) def blocking_open(stream_source, *args, **kwargs): # Let test know the thread is running worker_open.set() # Block worker thread until test wakes up worker_wake.wait() return py_av.open(stream_source, args, kwargs) with patch("av.open", new=blocking_open): await stream.start() assert worker_open.wait(TIMEOUT) # Note: There is a race here where the worker could start as soon # as the wake event is sent, completing all decode work. worker_wake.set() await stream.stop() # Stream is still considered available when the worker was still active and asked to stop assert stream.available
async def async_decode_stream(hass, packets, py_av=None, stream_settings=None): """Start a stream worker that decodes incoming stream packets into output segments.""" stream = Stream( hass, STREAM_SOURCE, {}, stream_settings or hass.data[DOMAIN][ATTR_SETTINGS] ) stream.add_provider(HLS_PROVIDER) if not py_av: py_av = MockPyAv() py_av.container.packets = iter(packets) # Can't be rewound with patch("av.open", new=py_av.open), patch( "homeassistant.components.stream.core.StreamOutput.put", side_effect=py_av.capture_buffer.capture_output_segment, ): try: run_worker(hass, stream, STREAM_SOURCE, stream_settings) except StreamEndedError: # Tests only use a limited number of packets, then the worker exits as expected. In # production, stream ending would be unexpected. pass finally: # Wait for all packets to be flushed even when exceptions are thrown await hass.async_block_till_done() return py_av.capture_buffer
async def test_stream_open_fails(hass): """Test failure on stream open.""" stream = Stream(hass, STREAM_SOURCE, {}) stream.add_provider(HLS_PROVIDER) with patch("av.open") as av_open, pytest.raises(StreamWorkerError): av_open.side_effect = av.error.InvalidDataError(-2, "error") run_worker(hass, stream, STREAM_SOURCE) await hass.async_block_till_done() av_open.assert_called_once()
async def test_stream_open_fails(hass): """Test failure on stream open.""" stream = Stream(hass, STREAM_SOURCE) stream.add_provider(STREAM_OUTPUT_FORMAT) with patch("av.open") as av_open: av_open.side_effect = av.error.InvalidDataError(-2, "error") stream_worker(hass, stream, threading.Event()) await hass.async_block_till_done() av_open.assert_called_once()
async def test_stream_open_fails(hass): """Test failure on stream open.""" stream = Stream(hass, STREAM_SOURCE, {}) stream.add_provider(HLS_PROVIDER) with patch("av.open") as av_open: av_open.side_effect = av.error.InvalidDataError(-2, "error") segment_buffer = SegmentBuffer(hass, stream.outputs) stream_worker(STREAM_SOURCE, {}, segment_buffer, threading.Event()) await hass.async_block_till_done() av_open.assert_called_once()
async def test_worker_log(hass, caplog): """Test that the worker logs the url without username and password.""" stream = Stream(hass, "https://*****:*****@foo.bar", {}) stream.add_provider(HLS_PROVIDER) with patch("av.open") as av_open, pytest.raises(StreamWorkerError) as err: av_open.side_effect = av.error.InvalidDataError(-2, "error") run_worker(hass, stream, "https://*****:*****@foo.bar") await hass.async_block_till_done() assert str(err.value) == "Error opening stream https://****:****@foo.bar" assert "https://*****:*****@foo.bar" not in caplog.text
async def test_worker_log(hass, caplog): """Test that the worker logs the url without username and password.""" stream = Stream(hass, "https://*****:*****@foo.bar", {}) stream.add_provider(HLS_PROVIDER) with patch("av.open") as av_open: av_open.side_effect = av.error.InvalidDataError(-2, "error") segment_buffer = SegmentBuffer(hass, stream.outputs) stream_worker("https://*****:*****@foo.bar", {}, segment_buffer, threading.Event()) await hass.async_block_till_done() assert "https://*****:*****@foo.bar" not in caplog.text assert "https://****:****@foo.bar" in caplog.text
async def async_decode_stream(hass, packets, py_av=None): """Start a stream worker that decodes incoming stream packets into output segments.""" stream = Stream(hass, STREAM_SOURCE) stream.add_provider(STREAM_OUTPUT_FORMAT) if not py_av: py_av = MockPyAv() py_av.container.packets = packets with patch("av.open", new=py_av.open), patch( "homeassistant.components.stream.core.StreamOutput.put", side_effect=py_av.capture_buffer.capture_output_segment, ): stream_worker(hass, stream, threading.Event()) await hass.async_block_till_done() return py_av.capture_buffer
async def test_update_stream_source(hass): """Tests that the worker is re-invoked when the stream source is updated.""" worker_open = threading.Event() worker_wake = threading.Event() stream = Stream(hass, STREAM_SOURCE, {}) stream.add_provider(HLS_PROVIDER) # Note that keepalive is not set here. The stream is "restarted" even though # it is not stopping due to failure. py_av = MockPyAv() py_av.container.packets = PacketSequence(TEST_SEQUENCE_LENGTH) last_stream_source = None def blocking_open(stream_source, *args, **kwargs): nonlocal last_stream_source if not isinstance(stream_source, io.BytesIO): last_stream_source = stream_source # Let test know the thread is running worker_open.set() # Block worker thread until test wakes up worker_wake.wait() return py_av.open(stream_source, args, kwargs) with patch("av.open", new=blocking_open), patch( "homeassistant.components.stream.worker.SegmentBuffer.check_flush_part", side_effect=MockFlushPart.wrapped_check_flush_part, autospec=True, ): stream.start() assert worker_open.wait(TIMEOUT) assert last_stream_source == STREAM_SOURCE # Update the stream source, then the test wakes up the worker and assert # that it re-opens the new stream (the test again waits on thread_started) worker_open.clear() stream.update_source(STREAM_SOURCE + "-updated-source") worker_wake.set() assert worker_open.wait(TIMEOUT) assert last_stream_source == STREAM_SOURCE + "-updated-source" worker_wake.set() # Cleanup stream.stop()
async def async_decode_stream(hass, packets, py_av=None): """Start a stream worker that decodes incoming stream packets into output segments.""" stream = Stream(hass, STREAM_SOURCE, {}) stream.add_provider(HLS_PROVIDER) if not py_av: py_av = MockPyAv() py_av.container.packets = iter(packets) # Can't be rewound with patch("av.open", new=py_av.open), patch( "homeassistant.components.stream.core.StreamOutput.put", side_effect=py_av.capture_buffer.capture_output_segment, ): segment_buffer = SegmentBuffer(hass, stream.outputs) stream_worker(STREAM_SOURCE, {}, segment_buffer, threading.Event()) await hass.async_block_till_done() return py_av.capture_buffer
async def test_update_stream_source(hass): """Tests that the worker is re-invoked when the stream source is updated.""" worker_open = threading.Event() worker_wake = threading.Event() stream = Stream(hass, STREAM_SOURCE, {}) stream.add_provider(HLS_PROVIDER) # Note that retries are disabled by default in tests, however the stream is "restarted" when # the stream source is updated. py_av = MockPyAv() py_av.container.packets = PacketSequence(TEST_SEQUENCE_LENGTH) last_stream_source = None def blocking_open(stream_source, *args, **kwargs): nonlocal last_stream_source if not isinstance(stream_source, io.BytesIO): last_stream_source = stream_source # Let test know the thread is running worker_open.set() # Block worker thread until test wakes up worker_wake.wait() return py_av.open(stream_source, args, kwargs) with patch("av.open", new=blocking_open): stream.start() assert worker_open.wait(TIMEOUT) assert last_stream_source == STREAM_SOURCE assert stream.available # Update the stream source, then the test wakes up the worker and assert # that it re-opens the new stream (the test again waits on thread_started) worker_open.clear() stream.update_source(STREAM_SOURCE + "-updated-source") worker_wake.set() assert worker_open.wait(TIMEOUT) assert last_stream_source == STREAM_SOURCE + "-updated-source" worker_wake.set() assert stream.available # Cleanup stream.stop()
async def test_update_stream_source(hass): """Tests that the worker is re-invoked when the stream source is updated.""" worker_open = threading.Event() worker_wake = threading.Event() stream = Stream(hass, STREAM_SOURCE) stream.add_provider(STREAM_OUTPUT_FORMAT) # Note that keepalive is not set here. The stream is "restarted" even though # it is not stopping due to failure. py_av = MockPyAv() py_av.container.packets = PacketSequence(TEST_SEQUENCE_LENGTH) last_stream_source = None def blocking_open(stream_source, *args, **kwargs): nonlocal last_stream_source if not isinstance(stream_source, io.BytesIO): last_stream_source = stream_source # Let test know the thread is running worker_open.set() # Block worker thread until test wakes up worker_wake.wait() return py_av.open(stream_source, args, kwargs) with patch("av.open", new=blocking_open): stream.start() assert worker_open.wait(TIMEOUT) assert last_stream_source == STREAM_SOURCE # Update the stream source, then the test wakes up the worker and assert # that it re-opens the new stream (the test again waits on thread_started) worker_open.clear() stream.update_source(STREAM_SOURCE + "-updated-source") worker_wake.set() assert worker_open.wait(TIMEOUT) assert last_stream_source == STREAM_SOURCE + "-updated-source" worker_wake.set() # Ccleanup stream.stop()
def preload_stream(hass, stream_source): """Preload a stream for use in tests.""" stream = Stream(hass, stream_source) hass.data[DOMAIN][ATTR_STREAMS][stream_source] = stream return stream
def add_provider(self, fmt, timeout=OUTPUT_IDLE_TIMEOUT): """Add a finished event to Stream.add_provider.""" provider = Stream.add_provider(self, fmt, timeout) provider_ready.set() return provider