|
@@ -0,0 +1,476 @@
|
|
|
|
|
+"""Tests for the Supervisor class (daemon, assistant, TUI orchestration)."""
|
|
|
|
|
+
|
|
|
|
|
+import signal
|
|
|
|
|
+import sys
|
|
|
|
|
+import types
|
|
|
|
|
+
|
|
|
|
|
+from stt_daemon.config import Config
|
|
|
|
|
+from stt_daemon.supervisor import Supervisor
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class FakePopen:
|
|
|
|
|
+ """Fake subprocess.Popen for testing without spawning real processes."""
|
|
|
|
|
+
|
|
|
|
|
+ _pid_counter = 10000
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(self, argv, redirect_output=False):
|
|
|
|
|
+ self.argv = argv
|
|
|
|
|
+ self.redirect_output = redirect_output
|
|
|
|
|
+ self.pid = FakePopen._pid_counter
|
|
|
|
|
+ FakePopen._pid_counter += 1
|
|
|
|
|
+ self.returncode = None
|
|
|
|
|
+ self._terminated = False
|
|
|
|
|
+ self._killed = False
|
|
|
|
|
+
|
|
|
|
|
+ def poll(self):
|
|
|
|
|
+ """Return None (running) or a returncode."""
|
|
|
|
|
+ return self.returncode
|
|
|
|
|
+
|
|
|
|
|
+ def terminate(self):
|
|
|
|
|
+ """Simulate termination."""
|
|
|
|
|
+ self._terminated = True
|
|
|
|
|
+ if self.returncode is None:
|
|
|
|
|
+ self.returncode = -15
|
|
|
|
|
+
|
|
|
|
|
+ def kill(self):
|
|
|
|
|
+ """Simulate kill."""
|
|
|
|
|
+ self._killed = True
|
|
|
|
|
+ if self.returncode is None:
|
|
|
|
|
+ self.returncode = -9
|
|
|
|
|
+
|
|
|
|
|
+ def wait(self, timeout=None):
|
|
|
|
|
+ """Wait for process to exit."""
|
|
|
|
|
+ if self.returncode is None:
|
|
|
|
|
+ self.returncode = 0
|
|
|
|
|
+ return self.returncode
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _cfg():
|
|
|
|
|
+ """Create a test Config object."""
|
|
|
|
|
+ return Config()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class FakeTermios:
|
|
|
|
|
+ """Records tcgetattr/tcsetattr calls instead of touching a real terminal."""
|
|
|
|
|
+
|
|
|
|
|
+ TCSADRAIN = 1
|
|
|
|
|
+ ISIG = 1
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(self, log):
|
|
|
|
|
+ self._log = log
|
|
|
|
|
+
|
|
|
|
|
+ def tcgetattr(self, fd):
|
|
|
|
|
+ self._log.append(("get", fd))
|
|
|
|
|
+ return [0, 0, 0, 0, 0, 0, []]
|
|
|
|
|
+
|
|
|
|
|
+ def tcsetattr(self, fd, when, attrs):
|
|
|
|
|
+ self._log.append(("set", fd, when, attrs))
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class FakeStdin:
|
|
|
|
|
+ """Stand-in for sys.stdin so the helpers never touch pytest's captured stdin."""
|
|
|
|
|
+
|
|
|
|
|
+ def fileno(self):
|
|
|
|
|
+ return 7
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_supervisor_run_spawns_daemon_then_assistant_no_tui():
|
|
|
|
|
+ """run() spawns daemon then assistant, not TUI when with_tui=False."""
|
|
|
|
|
+ cfg = _cfg()
|
|
|
|
|
+ spawned = []
|
|
|
|
|
+
|
|
|
|
|
+ def fake_spawn(argv, *, redirect_output=False):
|
|
|
|
|
+ spawned.append((argv, redirect_output))
|
|
|
|
|
+ proc = FakePopen(argv, redirect_output=redirect_output)
|
|
|
|
|
+ if len(spawned) == 1:
|
|
|
|
|
+ proc.returncode = 0
|
|
|
|
|
+ return proc
|
|
|
|
|
+
|
|
|
|
|
+ supervisor = Supervisor(cfg, with_tui=False, spawn=fake_spawn, poll_s=0.001)
|
|
|
|
|
+ result = supervisor.run()
|
|
|
|
|
+
|
|
|
|
|
+ assert len(spawned) == 2
|
|
|
|
|
+ assert "stt_daemon" in " ".join(spawned[0][0]) and "run" in spawned[0][0]
|
|
|
|
|
+ assert "stt_daemon.assistant" in " ".join(spawned[1][0])
|
|
|
|
|
+ assert "stt_daemon.tui" not in " ".join(str(x) for x in spawned)
|
|
|
|
|
+ assert result == 0
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_supervisor_run_fails_when_tui_socket_timeout():
|
|
|
|
|
+ """run() with_tui=True fails and terminates all if control socket timeout."""
|
|
|
|
|
+ cfg = _cfg()
|
|
|
|
|
+ cfg.assistant.control_socket = "/nonexistent/socket.sock"
|
|
|
|
|
+
|
|
|
|
|
+ spawned = []
|
|
|
|
|
+
|
|
|
|
|
+ def fake_spawn(argv, *, redirect_output=False):
|
|
|
|
|
+ proc = FakePopen(argv, redirect_output=redirect_output)
|
|
|
|
|
+ spawned.append(proc)
|
|
|
|
|
+ return proc
|
|
|
|
|
+
|
|
|
|
|
+ clock_val = [0.0]
|
|
|
|
|
+
|
|
|
|
|
+ def fake_clock():
|
|
|
|
|
+ return clock_val[0]
|
|
|
|
|
+
|
|
|
|
|
+ def fake_sleep(s):
|
|
|
|
|
+ clock_val[0] += 0.1
|
|
|
|
|
+
|
|
|
|
|
+ supervisor = Supervisor(
|
|
|
|
|
+ cfg,
|
|
|
|
|
+ with_tui=True,
|
|
|
|
|
+ spawn=fake_spawn,
|
|
|
|
|
+ sleep=fake_sleep,
|
|
|
|
|
+ clock=fake_clock,
|
|
|
|
|
+ poll_s=0.001,
|
|
|
|
|
+ wait_timeout_s=0.05,
|
|
|
|
|
+ )
|
|
|
|
|
+ result = supervisor.run()
|
|
|
|
|
+
|
|
|
|
|
+ assert result == 1
|
|
|
|
|
+ assert len(spawned) == 2
|
|
|
|
|
+ for proc in spawned:
|
|
|
|
|
+ assert proc._terminated or proc._killed
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_supervisor_run_returns_child_exit_code():
|
|
|
|
|
+ """run() returns first child's exit code when it exits."""
|
|
|
|
|
+ cfg = _cfg()
|
|
|
|
|
+ spawned = []
|
|
|
|
|
+
|
|
|
|
|
+ def fake_spawn(argv, *, redirect_output=False):
|
|
|
|
|
+ proc = FakePopen(argv, redirect_output=redirect_output)
|
|
|
|
|
+ spawned.append(proc)
|
|
|
|
|
+ if len(spawned) == 1:
|
|
|
|
|
+ proc.returncode = 42
|
|
|
|
|
+ return proc
|
|
|
|
|
+
|
|
|
|
|
+ supervisor = Supervisor(cfg, with_tui=False, spawn=fake_spawn, poll_s=0.001)
|
|
|
|
|
+ result = supervisor.run()
|
|
|
|
|
+
|
|
|
|
|
+ assert result == 42
|
|
|
|
|
+ assert spawned[0].returncode == 42
|
|
|
|
|
+ assert spawned[1]._terminated or spawned[1]._killed
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_supervisor_forwards_overrides_to_daemon():
|
|
|
|
|
+ """Daemon receives device, backend, model, language overrides."""
|
|
|
|
|
+ cfg = _cfg()
|
|
|
|
|
+ overrides = {
|
|
|
|
|
+ "device": "microphone.usb",
|
|
|
|
|
+ "backend": "sounddevice",
|
|
|
|
|
+ "model": "base",
|
|
|
|
|
+ "language": "es",
|
|
|
|
|
+ "config": "/path/to/config.toml",
|
|
|
|
|
+ }
|
|
|
|
|
+ spawned = []
|
|
|
|
|
+
|
|
|
|
|
+ def fake_spawn(argv, *, redirect_output=False):
|
|
|
|
|
+ spawned.append(argv)
|
|
|
|
|
+ proc = FakePopen(argv, redirect_output=redirect_output)
|
|
|
|
|
+ if len(spawned) == 1:
|
|
|
|
|
+ proc.returncode = 0
|
|
|
|
|
+ return proc
|
|
|
|
|
+
|
|
|
|
|
+ supervisor = Supervisor(
|
|
|
|
|
+ cfg, with_tui=False, overrides=overrides, spawn=fake_spawn, poll_s=0.001
|
|
|
|
|
+ )
|
|
|
|
|
+ supervisor.run()
|
|
|
|
|
+
|
|
|
|
|
+ daemon_argv = spawned[0]
|
|
|
|
|
+ assert "--device" in daemon_argv and "microphone.usb" in daemon_argv
|
|
|
|
|
+ assert "--backend" in daemon_argv and "sounddevice" in daemon_argv
|
|
|
|
|
+ assert "--model" in daemon_argv and "base" in daemon_argv
|
|
|
|
|
+ assert "--language" in daemon_argv and "es" in daemon_argv
|
|
|
|
|
+ assert "--config" in daemon_argv and "/path/to/config.toml" in daemon_argv
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_supervisor_forwards_verbose():
|
|
|
|
|
+ """Verbose flag forwarded to daemon and assistant."""
|
|
|
|
|
+ cfg = _cfg()
|
|
|
|
|
+ spawned = []
|
|
|
|
|
+
|
|
|
|
|
+ def fake_spawn(argv, *, redirect_output=False):
|
|
|
|
|
+ spawned.append(argv)
|
|
|
|
|
+ proc = FakePopen(argv, redirect_output=redirect_output)
|
|
|
|
|
+ if len(spawned) == 1:
|
|
|
|
|
+ proc.returncode = 0
|
|
|
|
|
+ return proc
|
|
|
|
|
+
|
|
|
|
|
+ supervisor = Supervisor(cfg, with_tui=False, verbose=True, spawn=fake_spawn, poll_s=0.001)
|
|
|
|
|
+ supervisor.run()
|
|
|
|
|
+
|
|
|
|
|
+ assert "-v" in spawned[0]
|
|
|
|
|
+ assert "-v" in spawned[1]
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_supervisor_skips_config_when_not_given():
|
|
|
|
|
+ """--config flag omitted when not in overrides."""
|
|
|
|
|
+ cfg = _cfg()
|
|
|
|
|
+ overrides = {"device": "test"}
|
|
|
|
|
+ spawned = []
|
|
|
|
|
+
|
|
|
|
|
+ def fake_spawn(argv, *, redirect_output=False):
|
|
|
|
|
+ spawned.append(argv)
|
|
|
|
|
+ proc = FakePopen(argv, redirect_output=redirect_output)
|
|
|
|
|
+ if len(spawned) == 1:
|
|
|
|
|
+ proc.returncode = 0
|
|
|
|
|
+ return proc
|
|
|
|
|
+
|
|
|
|
|
+ supervisor = Supervisor(
|
|
|
|
|
+ cfg, with_tui=False, overrides=overrides, spawn=fake_spawn, poll_s=0.001
|
|
|
|
|
+ )
|
|
|
|
|
+ supervisor.run()
|
|
|
|
|
+
|
|
|
|
|
+ assert "--config" not in spawned[0]
|
|
|
|
|
+ assert "--device" in spawned[0]
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_supervisor_assistant_receives_only_allowed_flags():
|
|
|
|
|
+ """Assistant gets config and verbose, but not device/backend/model."""
|
|
|
|
|
+ cfg = _cfg()
|
|
|
|
|
+ overrides = {
|
|
|
|
|
+ "config": "/config.toml",
|
|
|
|
|
+ "device": "ignore",
|
|
|
|
|
+ "backend": "ignore",
|
|
|
|
|
+ "model": "ignore",
|
|
|
|
|
+ }
|
|
|
|
|
+ spawned = []
|
|
|
|
|
+
|
|
|
|
|
+ def fake_spawn(argv, *, redirect_output=False):
|
|
|
|
|
+ spawned.append(argv)
|
|
|
|
|
+ proc = FakePopen(argv, redirect_output=redirect_output)
|
|
|
|
|
+ if len(spawned) == 1:
|
|
|
|
|
+ proc.returncode = 0
|
|
|
|
|
+ return proc
|
|
|
|
|
+
|
|
|
|
|
+ supervisor = Supervisor(
|
|
|
|
|
+ cfg, with_tui=False, overrides=overrides, verbose=True, spawn=fake_spawn, poll_s=0.001
|
|
|
|
|
+ )
|
|
|
|
|
+ supervisor.run()
|
|
|
|
|
+
|
|
|
|
|
+ assistant_argv = spawned[1]
|
|
|
|
|
+ assert "--config" in assistant_argv
|
|
|
|
|
+ assert "/config.toml" in assistant_argv
|
|
|
|
|
+ assert "-v" in assistant_argv
|
|
|
|
|
+ assert "--device" not in assistant_argv
|
|
|
|
|
+ assert "--backend" not in assistant_argv
|
|
|
|
|
+ assert "--model" not in assistant_argv
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_supervisor_spawn_redirect_output_false_without_tui():
|
|
|
|
|
+ """spawn() is called with redirect_output=False when with_tui=False."""
|
|
|
|
|
+ cfg = _cfg()
|
|
|
|
|
+ spawned = []
|
|
|
|
|
+
|
|
|
|
|
+ def fake_spawn(argv, *, redirect_output=False):
|
|
|
|
|
+ spawned.append((argv, redirect_output))
|
|
|
|
|
+ proc = FakePopen(argv, redirect_output=redirect_output)
|
|
|
|
|
+ if len(spawned) == 1:
|
|
|
|
|
+ proc.returncode = 0
|
|
|
|
|
+ return proc
|
|
|
|
|
+
|
|
|
|
|
+ supervisor = Supervisor(cfg, with_tui=False, spawn=fake_spawn, poll_s=0.001)
|
|
|
|
|
+ supervisor.run()
|
|
|
|
|
+
|
|
|
|
|
+ for argv, redirect_output in spawned:
|
|
|
|
|
+ assert redirect_output is False
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_supervisor_spawn_redirect_output_true_with_tui():
|
|
|
|
|
+ """spawn() is called with redirect_output=True when with_tui=True."""
|
|
|
|
|
+ cfg = _cfg()
|
|
|
|
|
+ spawned = []
|
|
|
|
|
+
|
|
|
|
|
+ def fake_spawn(argv, *, redirect_output=False):
|
|
|
|
|
+ spawned.append((argv, redirect_output))
|
|
|
|
|
+ proc = FakePopen(argv, redirect_output=redirect_output)
|
|
|
|
|
+ if len(spawned) == 1:
|
|
|
|
|
+ proc.returncode = 0
|
|
|
|
|
+ return proc
|
|
|
|
|
+
|
|
|
|
|
+ supervisor = Supervisor(cfg, with_tui=True, spawn=fake_spawn, wait_timeout_s=0.01, poll_s=0.001)
|
|
|
|
|
+ supervisor.run()
|
|
|
|
|
+
|
|
|
|
|
+ for argv, redirect_output in spawned:
|
|
|
|
|
+ assert redirect_output is True
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_supervisor_run_spawns_tui_after_control_socket(tmp_path):
|
|
|
|
|
+ """With_tui=True spawns the TUI once the control socket exists; TUI exit stops all."""
|
|
|
|
|
+ cfg = _cfg()
|
|
|
|
|
+ cfg.assistant.control_socket = str(tmp_path / "assistant.sock")
|
|
|
|
|
+ (tmp_path / "assistant.sock").touch()
|
|
|
|
|
+
|
|
|
|
|
+ spawned = []
|
|
|
|
|
+
|
|
|
|
|
+ def fake_spawn(argv, *, redirect_output=False):
|
|
|
|
|
+ proc = FakePopen(argv, redirect_output=redirect_output)
|
|
|
|
|
+ spawned.append((argv, proc))
|
|
|
|
|
+ if len(spawned) == 3:
|
|
|
|
|
+ proc.returncode = 0
|
|
|
|
|
+ return proc
|
|
|
|
|
+
|
|
|
|
|
+ clock_val = [0.0]
|
|
|
|
|
+
|
|
|
|
|
+ def fake_clock():
|
|
|
|
|
+ return clock_val[0]
|
|
|
|
|
+
|
|
|
|
|
+ def fake_sleep(_s):
|
|
|
|
|
+ clock_val[0] += 0.1
|
|
|
|
|
+
|
|
|
|
|
+ supervisor = Supervisor(
|
|
|
|
|
+ cfg,
|
|
|
|
|
+ with_tui=True,
|
|
|
|
|
+ spawn=fake_spawn,
|
|
|
|
|
+ sleep=fake_sleep,
|
|
|
|
|
+ clock=fake_clock,
|
|
|
|
|
+ poll_s=0.001,
|
|
|
|
|
+ )
|
|
|
|
|
+ result = supervisor.run()
|
|
|
|
|
+
|
|
|
|
|
+ assert len(spawned) == 3
|
|
|
|
|
+ assert "stt_daemon.tui" in " ".join(spawned[2][0])
|
|
|
|
|
+ assert spawned[0][1].redirect_output is True
|
|
|
|
|
+ assert spawned[1][1].redirect_output is True
|
|
|
|
|
+ assert spawned[2][1].redirect_output is False
|
|
|
|
|
+ assert result == 0
|
|
|
|
|
+ for _argv, proc in spawned[:2]:
|
|
|
|
|
+ assert proc._terminated or proc._killed
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_supervisor_force_kills_stubborn_children():
|
|
|
|
|
+ """A child that ignores SIGTERM is force-killed after the grace period."""
|
|
|
|
|
+ cfg = _cfg()
|
|
|
|
|
+
|
|
|
|
|
+ class StubbornPopen(FakePopen):
|
|
|
|
|
+ def terminate(self):
|
|
|
|
|
+ self._terminated = True
|
|
|
|
|
+
|
|
|
|
|
+ daemon = StubbornPopen(["daemon"])
|
|
|
|
|
+
|
|
|
|
|
+ clock_val = [0.0]
|
|
|
|
|
+
|
|
|
|
|
+ def fake_clock():
|
|
|
|
|
+ return clock_val[0]
|
|
|
|
|
+
|
|
|
|
|
+ def fake_sleep(_s):
|
|
|
|
|
+ clock_val[0] += 0.1
|
|
|
|
|
+
|
|
|
|
|
+ supervisor = Supervisor(
|
|
|
|
|
+ cfg, with_tui=False, grace_s=0.05, clock=fake_clock, sleep=fake_sleep
|
|
|
|
|
+ )
|
|
|
|
|
+ supervisor._terminate_all([daemon])
|
|
|
|
|
+
|
|
|
|
|
+ assert daemon._killed
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_supervisor_run_signal_shutdown_returns_zero(monkeypatch):
|
|
|
|
|
+ """SIGINT stops the loop, terminates every child, and returns 0."""
|
|
|
|
|
+ cfg = _cfg()
|
|
|
|
|
+ handlers = {}
|
|
|
|
|
+ monkeypatch.setattr(
|
|
|
|
|
+ "stt_daemon.supervisor.signal.signal",
|
|
|
|
|
+ lambda signum, handler: handlers.__setitem__(signum, handler),
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ spawned = []
|
|
|
|
|
+
|
|
|
|
|
+ def fake_spawn(argv, *, redirect_output=False):
|
|
|
|
|
+ proc = FakePopen(argv, redirect_output=redirect_output)
|
|
|
|
|
+ spawned.append(proc)
|
|
|
|
|
+ return proc
|
|
|
|
|
+
|
|
|
|
|
+ clock_val = [0.0]
|
|
|
|
|
+
|
|
|
|
|
+ def fake_clock():
|
|
|
|
|
+ return clock_val[0]
|
|
|
|
|
+
|
|
|
|
|
+ def fake_sleep(_s):
|
|
|
|
|
+ clock_val[0] += 0.1
|
|
|
|
|
+ if signal.SIGINT in handlers:
|
|
|
|
|
+ handlers[signal.SIGINT](signal.SIGINT, None)
|
|
|
|
|
+
|
|
|
|
|
+ supervisor = Supervisor(
|
|
|
|
|
+ cfg,
|
|
|
|
|
+ with_tui=False,
|
|
|
|
|
+ spawn=fake_spawn,
|
|
|
|
|
+ sleep=fake_sleep,
|
|
|
|
|
+ clock=fake_clock,
|
|
|
|
|
+ poll_s=0.001,
|
|
|
|
|
+ )
|
|
|
|
|
+ result = supervisor.run()
|
|
|
|
|
+
|
|
|
|
|
+ assert len(spawned) == 2
|
|
|
|
|
+ assert result == 0
|
|
|
|
|
+ for proc in spawned:
|
|
|
|
|
+ assert proc._terminated or proc._killed
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_supervisor_restores_terminal_after_tui(monkeypatch, tmp_path):
|
|
|
|
|
+ """A killed TUI must not leave the terminal in raw mode."""
|
|
|
|
|
+ cfg = _cfg()
|
|
|
|
|
+ cfg.assistant.control_socket = str(tmp_path / "assistant.sock")
|
|
|
|
|
+ (tmp_path / "assistant.sock").touch()
|
|
|
|
|
+
|
|
|
|
|
+ log = []
|
|
|
|
|
+ monkeypatch.setattr("stt_daemon.supervisor.termios", FakeTermios(log))
|
|
|
|
|
+ monkeypatch.setattr(
|
|
|
|
|
+ "stt_daemon.supervisor.sys",
|
|
|
|
|
+ types.SimpleNamespace(stdin=FakeStdin(), executable=sys.executable),
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ spawned = []
|
|
|
|
|
+
|
|
|
|
|
+ def fake_spawn(argv, *, redirect_output=False):
|
|
|
|
|
+ proc = FakePopen(argv, redirect_output=redirect_output)
|
|
|
|
|
+ spawned.append(proc)
|
|
|
|
|
+ if len(spawned) == 3:
|
|
|
|
|
+ proc.returncode = 0
|
|
|
|
|
+ return proc
|
|
|
|
|
+
|
|
|
|
|
+ clock_val = [0.0]
|
|
|
|
|
+
|
|
|
|
|
+ def fake_clock():
|
|
|
|
|
+ return clock_val[0]
|
|
|
|
|
+
|
|
|
|
|
+ def fake_sleep(_s):
|
|
|
|
|
+ clock_val[0] += 0.1
|
|
|
|
|
+
|
|
|
|
|
+ supervisor = Supervisor(
|
|
|
|
|
+ cfg,
|
|
|
|
|
+ with_tui=True,
|
|
|
|
|
+ spawn=fake_spawn,
|
|
|
|
|
+ sleep=fake_sleep,
|
|
|
|
|
+ clock=fake_clock,
|
|
|
|
|
+ poll_s=0.001,
|
|
|
|
|
+ )
|
|
|
|
|
+ supervisor.run()
|
|
|
|
|
+
|
|
|
|
|
+ assert ("get", 7) in log
|
|
|
|
|
+ assert ("set", 7, FakeTermios.TCSADRAIN, [0, 0, 0, 1, 0, 0, []]) in log
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_supervisor_leaves_terminal_alone_without_tui(monkeypatch):
|
|
|
|
|
+ """Without --with-tui the supervisor never touches terminal attributes."""
|
|
|
|
|
+ cfg = _cfg()
|
|
|
|
|
+ log = []
|
|
|
|
|
+ monkeypatch.setattr("stt_daemon.supervisor.termios", FakeTermios(log))
|
|
|
|
|
+ monkeypatch.setattr(
|
|
|
|
|
+ "stt_daemon.supervisor.sys",
|
|
|
|
|
+ types.SimpleNamespace(stdin=FakeStdin(), executable=sys.executable),
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ spawned = []
|
|
|
|
|
+
|
|
|
|
|
+ def fake_spawn(argv, *, redirect_output=False):
|
|
|
|
|
+ proc = FakePopen(argv, redirect_output=redirect_output)
|
|
|
|
|
+ spawned.append(proc)
|
|
|
|
|
+ if len(spawned) == 1:
|
|
|
|
|
+ proc.returncode = 0
|
|
|
|
|
+ return proc
|
|
|
|
|
+
|
|
|
|
|
+ supervisor = Supervisor(cfg, with_tui=False, spawn=fake_spawn, poll_s=0.001)
|
|
|
|
|
+ supervisor.run()
|
|
|
|
|
+
|
|
|
|
|
+ assert log == []
|