- Fix critical resource leak when cv2.VideoCapture.isOpened() returns False: create capture in local variable, verify it opened, call .release() before raising RuntimeError, only assign to self._capture after confirming success - Fix potential leak on repeated open(): release any existing self._capture before creating a new one - Add regression test: test_open_raises_when_capture_not_opened now verifies .release() is called on failed capture - Add new test: test_open_twice_releases_first_capture verifies first capture's .release() is called before second is assigned All 28 tests passing (5 camera + 23 existing).
77 lines
2.6 KiB
Python
77 lines
2.6 KiB
Python
from unittest.mock import MagicMock, patch
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from bookmark.camera import CameraSource
|
|
|
|
|
|
def test_open_raises_when_capture_not_opened():
|
|
with patch("bookmark.camera.cv2.VideoCapture") as mock_video_capture:
|
|
mock_instance = MagicMock()
|
|
mock_instance.isOpened.return_value = False
|
|
mock_video_capture.return_value = mock_instance
|
|
|
|
source = CameraSource(device_index=0)
|
|
with pytest.raises(RuntimeError, match="Could not open camera"):
|
|
source.open()
|
|
|
|
# Verify that release() was called on the failed capture
|
|
mock_instance.release.assert_called_once()
|
|
|
|
|
|
def test_read_frame_returns_frame_from_capture():
|
|
with patch("bookmark.camera.cv2.VideoCapture") as mock_video_capture:
|
|
mock_instance = MagicMock()
|
|
mock_instance.isOpened.return_value = True
|
|
fake_frame = np.zeros((10, 10, 3), dtype=np.uint8)
|
|
mock_instance.read.return_value = (True, fake_frame)
|
|
mock_video_capture.return_value = mock_instance
|
|
|
|
source = CameraSource(device_index=0)
|
|
source.open()
|
|
frame = source.read_frame()
|
|
|
|
assert frame.shape == (10, 10, 3)
|
|
|
|
|
|
def test_read_frame_raises_when_capture_fails():
|
|
with patch("bookmark.camera.cv2.VideoCapture") as mock_video_capture:
|
|
mock_instance = MagicMock()
|
|
mock_instance.isOpened.return_value = True
|
|
mock_instance.read.return_value = (False, None)
|
|
mock_video_capture.return_value = mock_instance
|
|
|
|
source = CameraSource(device_index=0)
|
|
source.open()
|
|
with pytest.raises(RuntimeError, match="Failed to read frame"):
|
|
source.read_frame()
|
|
|
|
|
|
def test_read_frame_raises_if_not_opened():
|
|
source = CameraSource(device_index=0)
|
|
with pytest.raises(RuntimeError, match="not opened"):
|
|
source.read_frame()
|
|
|
|
|
|
def test_open_twice_releases_first_capture():
|
|
with patch("bookmark.camera.cv2.VideoCapture") as mock_video_capture:
|
|
# Setup two distinct mock instances
|
|
first_mock = MagicMock()
|
|
first_mock.isOpened.return_value = True
|
|
second_mock = MagicMock()
|
|
second_mock.isOpened.return_value = True
|
|
mock_video_capture.side_effect = [first_mock, second_mock]
|
|
|
|
source = CameraSource(device_index=0)
|
|
|
|
# First open
|
|
source.open()
|
|
assert source._capture is first_mock
|
|
|
|
# Second open should release the first capture
|
|
source.open()
|
|
assert source._capture is second_mock
|
|
|
|
# Verify the first mock's release() was called before the second was assigned
|
|
first_mock.release.assert_called_once()
|