|
| 1 | +"""Regression test: monitors report is_running before the startup card snapshots them. |
| 2 | +
|
| 3 | +The startup card is built immediately after `_start_background_monitors` returns, |
| 4 | +with no further yield. Each monitor sets `_running=True` synchronously at the top |
| 5 | +of its `start()` coroutine, but `create_task` only schedules that coroutine — it |
| 6 | +doesn't run it. So `_start_background_monitors` must yield once before returning, |
| 7 | +or freshly-created monitors render red on the card despite running fine. |
| 8 | +""" |
| 9 | + |
| 10 | +import asyncio |
| 11 | +from unittest.mock import MagicMock |
| 12 | + |
| 13 | +import src.startup as startup_mod |
| 14 | +from src.background import _BackgroundTasks |
| 15 | + |
| 16 | + |
| 17 | +class _FakeMonitor: |
| 18 | + """Mimics a real monitor: flips _running before its first await, then loops.""" |
| 19 | + |
| 20 | + def __init__(self) -> None: |
| 21 | + self._running = False |
| 22 | + |
| 23 | + @property |
| 24 | + def is_running(self) -> bool: |
| 25 | + return self._running |
| 26 | + |
| 27 | + async def start(self) -> None: |
| 28 | + self._running = True |
| 29 | + while self._running: |
| 30 | + await asyncio.sleep(3600) |
| 31 | + |
| 32 | + |
| 33 | +class _FakeClient: |
| 34 | + async def connect(self) -> None: |
| 35 | + return None |
| 36 | + |
| 37 | + |
| 38 | +class _FakeUnraid: |
| 39 | + def __init__(self, client, system_monitor, array_monitor) -> None: |
| 40 | + self.client = client |
| 41 | + self.system_monitor = system_monitor |
| 42 | + self.array_monitor = array_monitor |
| 43 | + |
| 44 | + |
| 45 | +async def _cancel(bg: _BackgroundTasks) -> None: |
| 46 | + for task in bg._tasks: |
| 47 | + task.cancel() |
| 48 | + await asyncio.gather(*bg._tasks, return_exceptions=True) |
| 49 | + |
| 50 | + |
| 51 | +async def test_unraid_monitors_running_when_card_is_built(): |
| 52 | + """The reported bug: Unraid system/array created after connect() showed red.""" |
| 53 | + bg = _BackgroundTasks() |
| 54 | + bg.monitor = _FakeMonitor() |
| 55 | + bg.log_watcher = _FakeMonitor() |
| 56 | + uc = _FakeUnraid(_FakeClient(), _FakeMonitor(), _FakeMonitor()) |
| 57 | + |
| 58 | + await startup_mod._start_background_monitors(bg, None, uc, MagicMock(), MagicMock()) |
| 59 | + try: |
| 60 | + assert bg.monitor.is_running |
| 61 | + assert bg.log_watcher.is_running |
| 62 | + assert uc.system_monitor.is_running |
| 63 | + assert uc.array_monitor.is_running |
| 64 | + finally: |
| 65 | + await _cancel(bg) |
| 66 | + |
| 67 | + |
| 68 | +async def test_core_monitors_running_when_unraid_not_configured(): |
| 69 | + """The latent case: with no Unraid client there's no connect() await at all.""" |
| 70 | + bg = _BackgroundTasks() |
| 71 | + bg.monitor = _FakeMonitor() |
| 72 | + bg.log_watcher = _FakeMonitor() |
| 73 | + uc = _FakeUnraid(None, None, None) |
| 74 | + |
| 75 | + await startup_mod._start_background_monitors(bg, None, uc, MagicMock(), MagicMock()) |
| 76 | + try: |
| 77 | + assert bg.monitor.is_running |
| 78 | + assert bg.log_watcher.is_running |
| 79 | + finally: |
| 80 | + await _cancel(bg) |
0 commit comments