diff --git a/bookmark/camera.py b/bookmark/camera.py index fcc0415..6704eed 100644 --- a/bookmark/camera.py +++ b/bookmark/camera.py @@ -16,9 +16,16 @@ class CameraSource: self._capture: cv2.VideoCapture | None = None def open(self) -> None: - self._capture = cv2.VideoCapture(self.device_index) - if not self._capture.isOpened(): + # Release any existing capture before creating a new one + if self._capture is not None: + self._capture.release() + + # Create capture in local variable and verify it opened before assignment + capture = cv2.VideoCapture(self.device_index) + if not capture.isOpened(): + capture.release() raise RuntimeError(f"Could not open camera at index {self.device_index}") + self._capture = capture def read_frame(self) -> np.ndarray: if self._capture is None: diff --git a/tests/test_camera.py b/tests/test_camera.py index 13f2d06..858c9ef 100644 --- a/tests/test_camera.py +++ b/tests/test_camera.py @@ -16,6 +16,9 @@ def test_open_raises_when_capture_not_opened(): 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: @@ -49,3 +52,26 @@ 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()