"""Client-identity env recognition and the production heartbeat lifecycle (#975). Two defects left behind by #948, covered here against the issue's acceptance criteria: 1. ``GITEA_MCP_CLIENT`` / ``GITEA_MCP_CLIENT_INSTANCE`` / ``GITEA_MCP_CLIENT_SESSION`` were consumed by production while absent from the recognized-key allowlist, so the peer-env scan classified them as unsupported overrides and the capability resolver refused every mutation fleet-wide. 2. ``WorkerRegistry.heartbeat()`` had no production caller, so ``last_heartbeat_at`` never left ``started_at`` and ``heartbeat_ttl_seconds`` became a hard cap on attachment rather than a liveness window. Every TTL assertion here uses an injected clock (AC13). No test waits for a real TTL. The one test that exercises the real thread loop uses a millisecond-scale interval and polls with a bounded timeout, never a TTL. All client, session, and generation identifiers are synthetic. """ from __future__ import annotations import os import tempfile import threading import time import unittest from datetime import datetime, timedelta, timezone import gitea_config import mcp_worker_identity as mwi NOW = datetime(2026, 7, 30, 0, 40, 9, tzinfo=timezone.utc) TTL = 900.0 #: The three keys production reads as client identity. CLIENT_IDENTITY_ENV_KEYS = ( "GITEA_MCP_CLIENT", "GITEA_MCP_CLIENT_INSTANCE", "GITEA_MCP_CLIENT_SESSION", ) def _registry() -> mwi.WorkerRegistry: """A registry on a throwaway path; never the operator's real one.""" handle, path = tempfile.mkstemp(suffix=".sqlite3") os.close(handle) os.unlink(path) return mwi.WorkerRegistry(path) def _attach( registry: mwi.WorkerRegistry, *, client: str = "claude_code", session: str = "sess-0001", generation: str = "gen-0001", profile: str = "prgs-author", role: str = "author", pid: int = 4242, now: datetime = NOW, ttl: float = TTL, ) -> dict: """Register one synthetic worker and return the outcome plus its identity.""" identity = mwi.generate_worker_identity(client, session, now=now) outcome = registry.register( worker_identity=identity, client_name=client, client_instance_id=f"inst-{session}", session_id=session, generation_id=generation, role=role, profile=profile, pid=pid, heartbeat_ttl_seconds=ttl, now=now, ) outcome["identity"] = identity return outcome def _supervisor( registry: mwi.WorkerRegistry, outcome: dict, *, client: str = "claude_code", session: str = "sess-0001", generation: str = "gen-0001", pid: int = 4242, ttl: float = TTL, interval_seconds: float | None = None, clock=None, ) -> mwi.WorkerHeartbeatSupervisor: """A supervisor bound to *outcome*'s registration. Never started here.""" return mwi.WorkerHeartbeatSupervisor( registry, worker_identity=outcome["identity"], fencing_epoch=outcome["fencing_epoch"], session_id=session, generation_id=generation, client_name=client, pid=pid, ttl_seconds=ttl, interval_seconds=interval_seconds, clock=clock, ) class _ManualClock: """Controlled time. Nothing in this suite sleeps to reach a TTL.""" def __init__(self, start: datetime = NOW) -> None: self.now = start def __call__(self) -> datetime: return self.now def advance(self, seconds: float) -> datetime: self.now = self.now + timedelta(seconds=seconds) return self.now # --- AC1-AC3: environment recognition ------------------------------------ class ClientIdentityEnvRecognitionTests(unittest.TestCase): """AC1-AC3: the keys production consumes are the keys the gate accepts.""" def test_each_client_identity_key_is_recognized(self): # AC1: individually asserted, so a partial fix cannot pass. for key in CLIENT_IDENTITY_ENV_KEYS: with self.subTest(key=key): self.assertIn( key, gitea_config.RECOGNIZED_GITEA_ENV_KEYS, f"{key} is read by the server but not recognized by the gate", ) def test_recognized_client_identity_keys_are_not_unconsumed(self): # AC3 at the classification layer the resolver reads: with only these # keys set there is no unsupported override to raise a blocker from. env = { "GITEA_MCP_CLIENT": "claude_code", "GITEA_MCP_CLIENT_INSTANCE": "inst-0001", "GITEA_MCP_CLIENT_SESSION": "sess-0001", } self.assertEqual(gitea_config.get_unconsumed_gitea_env_overrides(env), {}) def test_unknown_gitea_key_is_still_unsupported(self): # AC2: the gate is not broadened. An unrecognised override is refused # exactly as before, and the recognised siblings do not shield it. env = { "GITEA_MCP_CLIENT": "claude_code", "GITEA_TOTALLY_INVENTED_OVERRIDE": "1", } unconsumed = gitea_config.get_unconsumed_gitea_env_overrides(env) self.assertIn("GITEA_TOTALLY_INVENTED_OVERRIDE", unconsumed) self.assertNotIn("GITEA_MCP_CLIENT", unconsumed) def test_no_new_prefix_admits_arbitrary_client_keys(self): # A prefix would have been the lazy fix; it would admit anything # beginning GITEA_MCP_CLIENT, including typos and future unknowns. env = {"GITEA_MCP_CLIENT_SOMETHING_ELSE": "x"} self.assertIn( "GITEA_MCP_CLIENT_SOMETHING_ELSE", gitea_config.get_unconsumed_gitea_env_overrides(env), ) def test_server_consumes_exactly_the_recognized_key_names(self): # Guards the actual defect class: the constants production reads and the # allowlist drifting apart again. import gitea_mcp_server as mcp_server for name in ( mcp_server.CLIENT_NAME_ENV, mcp_server.CLIENT_INSTANCE_ENV, mcp_server.CLIENT_SESSION_ENV, ): with self.subTest(env_key=name): self.assertIn(name, gitea_config.RECOGNIZED_GITEA_ENV_KEYS) class RuntimeDiagnosticReasonPropagationTests(unittest.TestCase): """`unsupported-env:` must not be swallowed where `stale-runtime:` is not.""" def test_both_reason_families_are_hard_prefixes(self): import gitea_mcp_server as mcp_server self.assertIn("stale-runtime:", mcp_server.RUNTIME_DIAGNOSTIC_HARD_PREFIXES) self.assertIn("unsupported-env:", mcp_server.RUNTIME_DIAGNOSTIC_HARD_PREFIXES) def test_unsupported_env_message_is_recognized_as_hard(self): import gitea_mcp_server as mcp_server message = ( "unsupported-env: Unsupported GITEA_* environment variable override(s) " "detected: GITEA_INVENTED=1. Unknown env overrides are unsupported." ) self.assertTrue( any(p in message for p in mcp_server.RUNTIME_DIAGNOSTIC_HARD_PREFIXES) ) # --- Interval policy ------------------------------------------------------ class HeartbeatIntervalPolicyTests(unittest.TestCase): """The interval must always leave room for lost beats inside the TTL.""" def test_default_interval_is_one_third_of_ttl(self): self.assertAlmostEqual(mwi.heartbeat_interval_for(900.0), 300.0) def test_interval_is_capped_at_half_the_ttl(self): # An override may not exceed the ceiling, or it would expire the very # registration it exists to renew. self.assertAlmostEqual(mwi.heartbeat_interval_for(900.0, 5000), 450.0) def test_override_below_the_ceiling_is_honoured(self): self.assertAlmostEqual(mwi.heartbeat_interval_for(900.0, 120), 120.0) def test_unparsable_or_nonpositive_override_falls_back_to_policy(self): for bad in ("", "abc", "0", "-5", None): with self.subTest(override=bad): self.assertAlmostEqual(mwi.heartbeat_interval_for(900.0, bad), 300.0) def test_interval_is_always_strictly_below_the_ttl(self): for ttl in (1.0, 30.0, 900.0, 14400.0): with self.subTest(ttl=ttl): self.assertLess(mwi.heartbeat_interval_for(ttl), ttl) # --- AC4-AC6: a healthy worker stays owned -------------------------------- class HealthyWorkerRemainsOwnedTests(unittest.TestCase): """AC4-AC6: the TTL becomes a liveness window instead of an attachment cap.""" def test_unsupervised_worker_goes_unowned_at_the_ttl(self): # The regression itself, asserted so the fix cannot be mistaken for a # TTL change: without a beater the row still expires exactly as before. registry = _registry() outcome = _attach(registry) record = registry.get(outcome["identity"]) self.assertEqual(record["started_at"], record["last_heartbeat_at"]) after_ttl = NOW + timedelta(seconds=TTL + 1) liveness = registry.is_live(record, now=after_ttl, pid_alive=True) self.assertFalse(liveness["live"]) assessment = mwi.assess_provenance( registry=registry, worker_identity=outcome["identity"], generation_id="gen-0001", env={"GITEA_CLIENT_MANAGED": "1"}, native_transport_bound=True, now=after_ttl, pid_alive_probe=lambda _pid: True, ) self.assertEqual(assessment["session_ownership"], "unowned") self.assertEqual(assessment["blocker_kind"], mwi.BLOCKER_NO_ATTACHMENT) def test_supervised_worker_stays_owned_past_two_full_ttls(self): # AC4: beyond two complete TTL periods of simulated time. registry = _registry() outcome = _attach(registry) clock = _ManualClock() supervisor = _supervisor(registry, outcome, clock=clock) interval = supervisor.interval_seconds self.assertLess(interval, TTL) elapsed = 0.0 target = TTL * 2 + interval while elapsed < target: clock.advance(interval) elapsed += interval result = supervisor.beat_once() self.assertTrue(result["renewed"], result.get("reasons")) assessment = mwi.assess_provenance( registry=registry, worker_identity=outcome["identity"], generation_id="gen-0001", env={"GITEA_CLIENT_MANAGED": "1"}, native_transport_bound=True, now=clock.now, pid_alive_probe=lambda _pid: True, ) self.assertEqual( assessment["session_ownership"], "owned", f"ownership lost after {elapsed:.0f}s of simulated time", ) self.assertGreater(elapsed, TTL * 2) def test_last_heartbeat_advances_and_diverges_from_started_at(self): # AC5. registry = _registry() outcome = _attach(registry) clock = _ManualClock() supervisor = _supervisor(registry, outcome, clock=clock) before = registry.get(outcome["identity"]) clock.advance(300) supervisor.beat_once() after = registry.get(outcome["identity"]) self.assertEqual(before["started_at"], after["started_at"]) self.assertNotEqual(after["started_at"], after["last_heartbeat_at"]) self.assertGreater(after["last_heartbeat_at"], before["last_heartbeat_at"]) def test_repeated_heartbeats_create_no_duplicate_rows(self): # AC6: heartbeating is not a second registration path. registry = _registry() outcome = _attach(registry) clock = _ManualClock() supervisor = _supervisor(registry, outcome, clock=clock) for _ in range(5): clock.advance(300) self.assertTrue(supervisor.beat_once()["renewed"]) workers = registry.list_workers() self.assertEqual(len(workers), 1) self.assertEqual(workers[0]["worker_identity"], outcome["identity"]) self.assertEqual(supervisor.status()["beats_renewed"], 5) # --- AC7, AC8, AC12: only the owner may renew ----------------------------- class HeartbeatOwnershipFencingTests(unittest.TestCase): """AC7/AC8/AC12: one worker can never refresh another worker's row.""" def test_two_client_names_stay_distinct_rows_and_identities(self): # AC7. registry = _registry() first = _attach(registry, client="claude_code", session="sess-a") second = _attach(registry, client="gemini", session="sess-b") self.assertNotEqual(first["identity"], second["identity"]) rows = {w["worker_identity"]: w for w in registry.list_workers()} self.assertEqual(len(rows), 2) self.assertEqual(rows[first["identity"]]["client_name"], "claude_code") self.assertEqual(rows[second["identity"]]["client_name"], "gemini") def test_wrong_session_cannot_refresh_the_row(self): # AC8, session dimension. registry = _registry() outcome = _attach(registry, session="sess-real") result = registry.heartbeat( worker_identity=outcome["identity"], fencing_epoch=outcome["fencing_epoch"], expected_session_id="sess-impostor", ) self.assertFalse(result["renewed"]) self.assertEqual(result["blocker_kind"], mwi.BLOCKER_FENCED) self.assertIn( "session_id", [field for field, _r, _p in result["expectation_drift"]] ) def test_wrong_generation_cannot_refresh_the_row(self): # AC8, generation dimension. registry = _registry() outcome = _attach(registry, generation="gen-real") result = registry.heartbeat( worker_identity=outcome["identity"], fencing_epoch=outcome["fencing_epoch"], expected_generation_id="gen-other", ) self.assertFalse(result["renewed"]) self.assertEqual(result["blocker_kind"], mwi.BLOCKER_FENCED) def test_wrong_client_name_cannot_refresh_the_row(self): registry = _registry() outcome = _attach(registry, client="claude_code") result = registry.heartbeat( worker_identity=outcome["identity"], fencing_epoch=outcome["fencing_epoch"], expected_client_name="gemini", ) self.assertFalse(result["renewed"]) self.assertEqual(result["blocker_kind"], mwi.BLOCKER_FENCED) def test_wrong_pid_cannot_refresh_the_row(self): registry = _registry() outcome = _attach(registry, pid=4242) result = registry.heartbeat( worker_identity=outcome["identity"], fencing_epoch=outcome["fencing_epoch"], expected_pid=9999, ) self.assertFalse(result["renewed"]) self.assertEqual(result["blocker_kind"], mwi.BLOCKER_FENCED) def test_namespaces_of_one_application_share_a_client_name(self): # Several namespaces of one application must stay one client, while # separate applications stay distinct. Aliases normalise, and the # expectation check compares normalised values, so an author and a # reviewer namespace of the same product are not treated as impostors. registry = _registry() outcome = _attach(registry, client="claude") self.assertEqual( registry.get(outcome["identity"])["client_name"], "claude_code" ) result = registry.heartbeat( worker_identity=outcome["identity"], fencing_epoch=outcome["fencing_epoch"], expected_client_name="claude_desktop", ) self.assertTrue(result["renewed"], result.get("reasons")) def test_matching_expectations_renew_normally(self): registry = _registry() outcome = _attach(registry) result = registry.heartbeat( worker_identity=outcome["identity"], fencing_epoch=outcome["fencing_epoch"], expected_session_id="sess-0001", expected_generation_id="gen-0001", expected_client_name="claude_code", expected_pid=4242, ) self.assertTrue(result["renewed"], result.get("reasons")) def test_omitted_expectations_preserve_pre_fix_behaviour(self): # AC12: the #948 contract is unchanged for callers presenting nothing. registry = _registry() outcome = _attach(registry) self.assertTrue( registry.heartbeat( worker_identity=outcome["identity"], fencing_epoch=outcome["fencing_epoch"], )["renewed"] ) def test_stale_fencing_epoch_is_still_refused(self): # AC12: the pre-existing epoch fence is untouched. registry = _registry() outcome = _attach(registry) result = registry.heartbeat( worker_identity=outcome["identity"], fencing_epoch=int(outcome["fencing_epoch"]) + 1, ) self.assertFalse(result["renewed"]) self.assertEqual(result["blocker_kind"], mwi.BLOCKER_FENCED) def test_fenced_supervisor_stops_permanently(self): # A fenced session must never beat its way back into ownership. registry = _registry() outcome = _attach(registry) supervisor = mwi.WorkerHeartbeatSupervisor( registry, worker_identity=outcome["identity"], fencing_epoch=int(outcome["fencing_epoch"]) + 1, session_id="sess-0001", generation_id="gen-0001", client_name="claude_code", pid=4242, ttl_seconds=TTL, clock=_ManualClock(), ) first = supervisor.beat_once() self.assertFalse(first["renewed"]) self.assertIsNotNone(supervisor.stopped_reason) # A second attempt does not even reach the registry. second = supervisor.beat_once() self.assertFalse(second["renewed"]) self.assertFalse(second["beat_attempted"]) self.assertEqual(supervisor.status()["beats_attempted"], 1) def test_missing_registration_stops_the_supervisor(self): registry = _registry() supervisor = mwi.WorkerHeartbeatSupervisor( registry, worker_identity=mwi.generate_worker_identity("grok", "sess-gone", now=NOW), fencing_epoch=1, ttl_seconds=TTL, clock=_ManualClock(), ) result = supervisor.beat_once() self.assertEqual(result["blocker_kind"], mwi.BLOCKER_NO_ATTACHMENT) self.assertIsNotNone(supervisor.stopped_reason) # --- AC9, AC10, AC11: stopping and expiry --------------------------------- class ShutdownAndExpiryTests(unittest.TestCase): """AC9-AC11: orderly shutdown stops beating; dead workers still expire.""" def test_stop_ends_the_heartbeat(self): # AC9. registry = _registry() outcome = _attach(registry) clock = _ManualClock() supervisor = _supervisor(registry, outcome, clock=clock) clock.advance(300) self.assertTrue(supervisor.beat_once()["renewed"]) supervisor.stop(reason="orderly shutdown") self.assertFalse(supervisor.status()["running"]) self.assertEqual(supervisor.stopped_reason, "orderly shutdown") clock.advance(300) after_stop = supervisor.beat_once() self.assertFalse(after_stop["renewed"]) self.assertFalse(after_stop["beat_attempted"]) def test_stop_is_idempotent_and_keeps_the_first_reason(self): registry = _registry() outcome = _attach(registry) supervisor = _supervisor(registry, outcome, clock=_ManualClock()) supervisor.stop(reason="orderly shutdown") supervisor.stop(reason="second call") self.assertEqual(supervisor.stopped_reason, "orderly shutdown") def test_stopped_worker_becomes_unowned_once_the_ttl_elapses(self): # AC10: stopping beating restores normal expiration; the fix does not # make a worker immortal. registry = _registry() outcome = _attach(registry) clock = _ManualClock() supervisor = _supervisor(registry, outcome, clock=clock) clock.advance(300) supervisor.beat_once() supervisor.stop(reason="orderly shutdown") last_beat = clock.now clock.advance(TTL + 1) record = registry.get(outcome["identity"]) liveness = registry.is_live(record, now=clock.now, pid_alive=True) self.assertFalse(liveness["live"]) self.assertGreater(liveness["heartbeat_age_seconds"], TTL) assessment = mwi.assess_provenance( registry=registry, worker_identity=outcome["identity"], generation_id="gen-0001", env={"GITEA_CLIENT_MANAGED": "1"}, native_transport_bound=True, now=clock.now, pid_alive_probe=lambda _pid: True, ) self.assertEqual(assessment["session_ownership"], "unowned") self.assertLess(last_beat, clock.now) def test_dead_worker_expires_even_with_a_live_pid(self): # A recycled or long-lived pid must not keep a silent worker owned. registry = _registry() outcome = _attach(registry) record = registry.get(outcome["identity"]) liveness = registry.is_live( record, now=NOW + timedelta(seconds=TTL + 1), pid_alive=True ) self.assertFalse(liveness["live"]) self.assertFalse(liveness["heartbeat_fresh"]) def test_one_worker_expiring_does_not_stale_healthy_workers(self): # AC11. registry = _registry() healthy = _attach(registry, client="claude_code", session="sess-live") abandoned = _attach(registry, client="gemini", session="sess-dead") clock = _ManualClock() supervisor = _supervisor( registry, healthy, client="claude_code", session="sess-live", clock=clock, ) elapsed = 0.0 while elapsed < TTL + 300: clock.advance(supervisor.interval_seconds) elapsed += supervisor.interval_seconds self.assertTrue(supervisor.beat_once()["renewed"]) healthy_live = registry.is_live( registry.get(healthy["identity"]), now=clock.now, pid_alive=True ) abandoned_live = registry.is_live( registry.get(abandoned["identity"]), now=clock.now, pid_alive=True ) self.assertTrue(healthy_live["live"]) self.assertFalse(abandoned_live["live"]) # --- Failure handling and observability ----------------------------------- class HeartbeatFailureHandlingTests(unittest.TestCase): """A heartbeat failure never grants ownership and never crashes a caller.""" def test_transient_exception_is_counted_and_beating_continues(self): registry = _registry() outcome = _attach(registry) clock = _ManualClock() supervisor = _supervisor(registry, outcome, clock=clock) calls = {"n": 0} real = registry.heartbeat def flaky(**kwargs): calls["n"] += 1 if calls["n"] == 1: raise RuntimeError("database is locked") return real(**kwargs) registry.heartbeat = flaky # type: ignore[method-assign] try: clock.advance(300) first = supervisor.beat_once() self.assertFalse(first["renewed"]) self.assertTrue(first["transient"]) self.assertIsNone(supervisor.stopped_reason) clock.advance(300) second = supervisor.beat_once() self.assertTrue(second["renewed"], second.get("reasons")) finally: registry.heartbeat = real # type: ignore[method-assign] status = supervisor.status() self.assertEqual(status["transient_failures"], 1) self.assertEqual(status["beats_renewed"], 1) self.assertTrue(status["supervised"]) def test_status_reports_an_unstarted_supervisor_honestly(self): registry = _registry() outcome = _attach(registry) status = _supervisor(registry, outcome, clock=_ManualClock()).status() self.assertTrue(status["supervised"]) self.assertFalse(status["started"]) self.assertFalse(status["running"]) self.assertEqual(status["beats_renewed"], 0) self.assertIsNone(status["stopped_reason"]) def test_runtime_context_reports_unsupervised_when_none_attached(self): # The observability surface must distinguish "no supervisor" from # "supervisor with no beats yet", or the regression is invisible again. import gitea_mcp_server as mcp_server saved = mcp_server._WORKER_HEARTBEAT_SUPERVISOR mcp_server._WORKER_HEARTBEAT_SUPERVISOR = None try: status = mcp_server._worker_heartbeat_status() finally: mcp_server._WORKER_HEARTBEAT_SUPERVISOR = saved self.assertFalse(status["supervised"]) self.assertFalse(status["running"]) self.assertTrue(status["reasons"]) def test_runtime_context_surfaces_an_attached_supervisor(self): import gitea_mcp_server as mcp_server registry = _registry() outcome = _attach(registry) supervisor = _supervisor(registry, outcome, clock=_ManualClock()) saved = mcp_server._WORKER_HEARTBEAT_SUPERVISOR mcp_server._WORKER_HEARTBEAT_SUPERVISOR = supervisor try: status = mcp_server._worker_heartbeat_status() finally: mcp_server._WORKER_HEARTBEAT_SUPERVISOR = saved self.assertTrue(status["supervised"]) self.assertEqual(status["worker_identity"], outcome["identity"]) self.assertEqual(status["heartbeat_ttl_seconds"], TTL) self.assertLess(status["heartbeat_interval_seconds"], TTL) def test_status_failure_degrades_instead_of_raising(self): import gitea_mcp_server as mcp_server class Exploding: def status(self): raise RuntimeError("boom") saved = mcp_server._WORKER_HEARTBEAT_SUPERVISOR mcp_server._WORKER_HEARTBEAT_SUPERVISOR = Exploding() try: status = mcp_server._worker_heartbeat_status() finally: mcp_server._WORKER_HEARTBEAT_SUPERVISOR = saved self.assertTrue(status["supervised"]) self.assertFalse(status["running"]) self.assertTrue(status["reasons"]) class SupervisorThreadLoopTests(unittest.TestCase): """The real thread, on a millisecond interval. Never waits for a TTL.""" def test_thread_beats_repeatedly_then_stops_on_request(self): registry = _registry() outcome = _attach(registry) beats = threading.Event() seen = {"n": 0} real = registry.heartbeat def counting(**kwargs): result = real(**kwargs) seen["n"] += 1 if seen["n"] >= 3: beats.set() return result registry.heartbeat = counting # type: ignore[method-assign] supervisor = _supervisor(registry, outcome, ttl=1.0, interval_seconds=0.01) try: started = supervisor.start() self.assertTrue(started["started"]) self.assertTrue( beats.wait(timeout=10.0), "heartbeat thread produced no beats" ) self.assertTrue(supervisor.status()["running"]) finally: supervisor.stop(reason="test teardown") registry.heartbeat = real # type: ignore[method-assign] self.assertGreaterEqual(supervisor.status()["beats_renewed"], 3) self.assertFalse(supervisor.status()["running"]) # Stopping is prompt: the loop waits on an Event, not a sleep. deadline = time.monotonic() + 5.0 while supervisor._thread is not None and supervisor._thread.is_alive(): if time.monotonic() > deadline: self.fail("heartbeat thread did not exit after stop()") time.sleep(0.01) def test_start_is_idempotent(self): registry = _registry() outcome = _attach(registry) supervisor = _supervisor(registry, outcome, ttl=1.0, interval_seconds=0.05) try: self.assertFalse(supervisor.start()["already_running"]) self.assertTrue(supervisor.start()["already_running"]) finally: supervisor.stop(reason="test teardown") def test_start_refuses_after_a_terminal_stop(self): registry = _registry() outcome = _attach(registry) supervisor = _supervisor(registry, outcome, clock=_ManualClock()) supervisor.stop(reason="orderly shutdown") self.assertFalse(supervisor.start()["started"]) if __name__ == "__main__": unittest.main()