test_camera_janitor_spares_reconnect.py 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. """The cleanup tick leaves a reconnecting stream's entry alone (RC-C13).
  2. An X1C ends every RTSP session after about a minute. Between its ffmpeg
  3. exiting and the next one starting, the stream's entry still holds the dead
  4. process while the generator tears it down. A cleanup tick in that window
  5. removed the entry; the generator then read "removed" as "stopped", logged
  6. "stopping reconnect" and ended the live view, and one-shot captures
  7. meanwhile saw no stream and dialled the camera themselves.
  8. """
  9. from __future__ import annotations
  10. import asyncio
  11. from types import SimpleNamespace
  12. from unittest.mock import patch
  13. import pytest
  14. from backend.app.api.routes import camera
  15. pytestmark = pytest.mark.asyncio
  16. SID = "7-fanout-deadbeef"
  17. @pytest.fixture
  18. def dead_stream():
  19. camera._active_streams[SID] = SimpleNamespace(pid=999_999_999, returncode=0)
  20. yield
  21. camera._active_streams.pop(SID, None)
  22. camera._disconnect_events.pop(SID, None)
  23. async def _cleanup():
  24. with patch.object(camera, "_scan_bambu_ffmpeg_pids", return_value=[]):
  25. await asyncio.wait_for(camera.cleanup_orphaned_streams(), timeout=2.0)
  26. async def test_an_entry_whose_generator_will_redial_is_kept(dead_stream):
  27. camera._disconnect_events[SID] = asyncio.Event()
  28. await _cleanup()
  29. assert SID in camera._active_streams
  30. assert camera.is_stream_active(7)
  31. async def test_a_stopped_stream_s_entry_is_removed(dead_stream):
  32. stopped = asyncio.Event()
  33. stopped.set()
  34. camera._disconnect_events[SID] = stopped
  35. await _cleanup()
  36. assert SID not in camera._active_streams
  37. async def test_an_entry_nothing_owns_is_removed(dead_stream):
  38. await _cleanup()
  39. assert SID not in camera._active_streams