Replaces LICENSE (GPLv3 -> AGPLv3) and prepends `SPDX-License-Identifier: AGPL-3.0-or-later` to every source file across decnet/, decnet_web/, tests/, scripts/, and tools/. Rationale: closes the GPLv3 ASP loophole so any party operating a modified DECNET as a network service must offer their modified source. Personal copyright (Samuel Paschuan) + inbound=outbound contributions make a future unilateral relicense infeasible. - LICENSE: full AGPL-3.0 text (gnu.org/licenses/agpl-3.0.txt) - COPYRIGHT: project copyright notice - tools/add_spdx_headers.py: idempotent header injector (shebang- and PEP 263-aware) Touches 1565 source files (.py, .ts, .tsx, .js, .jsx, .css, .sh). No behavior change; comments only.
74 lines
2.3 KiB
Python
74 lines
2.3 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
"""Worker shutdown smoke test for fleet_reconciler_worker.
|
|
|
|
The reconcile logic itself is exercised in test_reconciler.py. This file
|
|
just verifies the worker's lifecycle wrapper (control listener + heartbeat
|
|
+ tick loop) exits cleanly when the bus shutdown signal fires.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
|
|
from decnet.fleet.reconciler_worker import fleet_reconciler_worker
|
|
|
|
|
|
class _FakeRepo:
|
|
async def list_fleet_deckies(self, *, host_uuid=None):
|
|
return []
|
|
async def upsert_fleet_decky(self, data): pass
|
|
async def delete_fleet_decky(self, **kw): pass
|
|
async def update_fleet_decky_state(self, **kw): pass
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_worker_exits_on_shutdown_event(monkeypatch):
|
|
# Patch the bus + control listener so the worker doesn't try to bind
|
|
# to a real socket. The control_task will set `shutdown` once we fire it.
|
|
fake_bus = AsyncMock()
|
|
monkeypatch.setattr(
|
|
"decnet.fleet.reconciler_worker.get_bus",
|
|
lambda **kw: fake_bus,
|
|
)
|
|
|
|
captured: dict = {}
|
|
|
|
async def _capturing_control_listener(bus, name, shutdown_event):
|
|
captured["shutdown_event"] = shutdown_event
|
|
# Hold the event loop briefly so the worker enters its tick wait,
|
|
# then trigger shutdown.
|
|
await asyncio.sleep(0.05)
|
|
shutdown_event.set()
|
|
|
|
async def _noop_heartbeat(bus, name):
|
|
await asyncio.sleep(3600) # never returns naturally
|
|
|
|
monkeypatch.setattr(
|
|
"decnet.fleet.reconciler_worker.run_control_listener",
|
|
_capturing_control_listener,
|
|
)
|
|
monkeypatch.setattr(
|
|
"decnet.fleet.reconciler_worker.run_health_heartbeat",
|
|
_noop_heartbeat,
|
|
)
|
|
# Skip docker observation entirely — we just need the loop to exit.
|
|
monkeypatch.setattr(
|
|
"decnet.fleet.reconciler._real_load_state",
|
|
lambda: None,
|
|
)
|
|
with patch("decnet.fleet.reconciler._collect_container_states",
|
|
return_value=None):
|
|
# interval=10 (long) so we exit via shutdown, not via tick completion
|
|
await asyncio.wait_for(
|
|
fleet_reconciler_worker(_FakeRepo(), interval=10),
|
|
timeout=2.0,
|
|
)
|
|
assert captured["shutdown_event"].is_set()
|
|
|
|
|
|
@pytest.fixture
|
|
def anyio_backend():
|
|
return "asyncio"
|