"""Marlin-Dienst starten, ohne ihn zu beenden. Kein echtes USB-Gerät.""" import sys import tempfile import threading import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) from grotti import marlin_supervisor as sup class SupervisorTests(unittest.TestCase): def tearDown(self): sup.disable() def test_running_service_is_not_started_again(self): starts = [] service = sup.MarlinSupervisor( Path("."), healthy=lambda: True, start=lambda folder: starts.append(folder), wait_s=0.1, ) result = service.ensure() self.assertTrue(result["running"]) self.assertFalse(result["started"]) self.assertEqual(starts, []) def test_starts_once_when_down(self): state = {"up": False} starts = [] def healthy(): return state["up"] def start(folder): starts.append(folder) state["up"] = True service = sup.MarlinSupervisor(Path("."), healthy=healthy, start=start, wait_s=0.2) first = service.ensure() second = service.ensure() self.assertTrue(first["started"]) self.assertEqual(len(starts), 1) self.assertFalse(second["started"]) self.assertTrue(second["running"]) def test_failed_start_does_not_repeat_immediately(self): starts = [] service = sup.MarlinSupervisor( Path("."), healthy=lambda: False, start=lambda folder: starts.append(1), wait_s=0.05, ) service.ensure() service.ensure() self.assertEqual(len(starts), 1) def test_parallel_ensure_starts_once(self): gate = threading.Event() starts = [] def start(folder): starts.append(1) gate.wait(1) service = sup.MarlinSupervisor( Path("."), healthy=lambda: False, start=start, wait_s=0.05, ) threads = [threading.Thread(target=service.ensure) for _ in range(4)] for thread in threads: thread.start() gate.set() for thread in threads: thread.join(2) self.assertEqual(len(starts), 1) def test_spawn_is_detached_and_runs_the_module(self): seen = {} class Proc: pid = 42 def popen(**kwargs): seen.update(kwargs) if kwargs.get("creationflags", 0) & sup._CREATE_BREAKAWAY: raise OSError("breakaway") return Proc() with tempfile.TemporaryDirectory() as folder: pid = sup.spawn_detached(folder, popen=popen) self.assertEqual(pid, 42) self.assertEqual(seen["args"][1:], ["-m", "grotti.marlin_daemon"]) self.assertIs(seen["stdin"], sup.subprocess.DEVNULL) if sup.os.name == "nt": flags = seen["creationflags"] self.assertTrue(flags & sup._DETACHED) self.assertFalse(flags & sup._CREATE_BREAKAWAY) else: self.assertTrue(seen["start_new_session"]) def test_enable_reuses_a_live_daemon(self): sup.enable(Path(".")) sup._supervisor.healthy = lambda: True sup._supervisor.start = lambda folder: (_ for _ in ()).throw(AssertionError("gestartet")) result = sup.ensure_if_enabled() self.assertTrue(result["running"]) self.assertFalse(result["started"])