personal memory agent
0

Configure Feed

Select the types of activity you want to include in your feed.

solstone / tests / test_supervisor_parakeet.py
24 kB 702 lines
1# SPDX-License-Identifier: AGPL-3.0-only 2# Copyright (c) 2026 sol pbc 3 4from __future__ import annotations 5 6import logging 7from pathlib import Path 8from types import SimpleNamespace 9from typing import Any 10 11import pytest 12 13from solstone.think import supervisor 14from solstone.think.providers import ( 15 local_cuda, 16 local_vulkan, 17 parakeet_install, 18 parakeet_server, 19) 20from solstone.think.providers.parakeet_placement import ( 21 PARAKEET_ATT_CONTEXT_ENV, 22 PARAKEET_ATT_CONTEXT_FRAMES, 23) 24 25_LaunchRecord = dict[str, Any] 26 27 28def _assert_att_context(launch: _LaunchRecord) -> None: 29 assert launch["env"][PARAKEET_ATT_CONTEXT_ENV] == str(PARAKEET_ATT_CONTEXT_FRAMES) 30 31 32def _launch_log(backend: str) -> str: 33 return ( 34 "parakeet-server launch " 35 f"backend={backend} attention=local " 36 f"att_context_frames={PARAKEET_ATT_CONTEXT_FRAMES}" 37 ) 38 39 40@pytest.fixture(autouse=True) 41def _reset_vulkan_detect_cache(): 42 local_vulkan.reset_detect_cache() 43 yield 44 local_vulkan.reset_detect_cache() 45 46 47class _FakeProcess: 48 def __init__(self, poll_value: int | None, returncode: int | None = None): 49 self.pid = 12345 50 self.returncode = returncode 51 self._poll_value = poll_value 52 53 def poll(self) -> int | None: 54 return self._poll_value 55 56 57class _FakeManaged: 58 def __init__(self, poll_value: int | None, returncode: int | None = None): 59 self.name = supervisor.PARAKEET_SERVER_PROCESS_NAME 60 self.process = _FakeProcess(poll_value, returncode) 61 self.cleanup_called = False 62 63 def cleanup(self) -> None: 64 self.cleanup_called = True 65 66 67def _nvidia_probe( 68 *, 69 vram_mib: int, 70 memory_source: str = local_cuda.MEMORY_SOURCE_NVIDIA_VRAM, 71) -> local_cuda.NvidiaProbe: 72 return local_cuda.NvidiaProbe( 73 index=0, 74 compute_cap="sm_75", 75 driver_cuda_version=13, 76 vram_mib=vram_mib, 77 tiering_memory_mib=vram_mib, 78 memory_source=memory_source, 79 detected=True, 80 ) 81 82 83def _patch_ready_parakeet_launch( 84 monkeypatch, 85 launches: list[_LaunchRecord], 86 *, 87 poll_sequence: list[tuple[int | None, int | None]] | None = None, 88) -> list[tuple[str, int]]: 89 def fake_ensure(backend: str): 90 return Path(f"/tmp/{backend}/parakeet-server"), Path("/tmp/model.gguf") 91 92 monkeypatch.setattr(parakeet_install, "ensure_artifacts_installed", fake_ensure) 93 monkeypatch.setattr(supervisor, "find_available_port", lambda: 45123) 94 ports: list[tuple[str, int]] = [] 95 monkeypatch.setattr( 96 supervisor, 97 "write_service_port", 98 lambda service, port: ports.append((service, port)), 99 ) 100 monkeypatch.setattr(supervisor, "parakeet_physical_thread_count", lambda: 6) 101 monkeypatch.setattr( 102 supervisor, "_parakeet_runtime_library_dirs", lambda: [Path("/parakeet/lib")] 103 ) 104 monkeypatch.setattr( 105 parakeet_server, "probe_state", lambda: (parakeet_server.STATE_READY, None) 106 ) 107 108 sequence = poll_sequence or [(None, None)] 109 110 def fake_launch_process( 111 name, cmd, *, restart=False, shutdown_timeout=15, ref=None, env=None 112 ): 113 index = min(len(launches), len(sequence) - 1) 114 poll_value, returncode = sequence[index] 115 managed = _FakeManaged(poll_value, returncode) 116 launches.append( 117 { 118 "name": name, 119 "cmd": cmd, 120 "restart": restart, 121 "env": env, 122 "managed": managed, 123 } 124 ) 125 return managed 126 127 monkeypatch.setattr(supervisor, "_launch_process", fake_launch_process) 128 return ports 129 130 131def test_parakeet_server_is_sweepable_orphan_name() -> None: 132 assert ( 133 supervisor.PARAKEET_SERVER_PROCESS_NAME in supervisor._LOCAL_SERVER_PROCTITLES 134 ) 135 assert supervisor._is_sweepable_orphan_name("parakeet-server") is True 136 137 138def test_resolve_launch_plan_cpu_ignores_gpu() -> None: 139 gpu = SimpleNamespace(index=2) 140 141 plan = supervisor.resolve_parakeet_server_launch_plan("cpu", gpu) 142 143 assert plan == supervisor.ParakeetServerLaunchPlan("cpu", {}, None) 144 145 146def test_resolve_launch_plan_auto_uses_selected_gpu() -> None: 147 gpu = SimpleNamespace(index=2) 148 149 plan = supervisor.resolve_parakeet_server_launch_plan("auto", gpu) 150 151 assert plan == supervisor.ParakeetServerLaunchPlan( 152 "vulkan", {"GGML_VK_VISIBLE_DEVICES": "2"}, 2 153 ) 154 155 156def test_resolve_launch_plan_auto_without_gpu_uses_cpu() -> None: 157 plan = supervisor.resolve_parakeet_server_launch_plan("auto", None) 158 159 assert plan == supervisor.ParakeetServerLaunchPlan("cpu", {}, None) 160 161 162def test_resolve_launch_plan_rejects_invalid_device() -> None: 163 with pytest.raises(ValueError, match="auto"): 164 supervisor.resolve_parakeet_server_launch_plan("bogus", None) 165 166 167def test_parakeet_physical_thread_count_uses_physical(monkeypatch) -> None: 168 monkeypatch.setattr(supervisor.psutil, "cpu_count", lambda logical=False: 6) 169 monkeypatch.setattr( 170 supervisor.os, 171 "cpu_count", 172 lambda: pytest.fail("logical count should not be primary"), 173 ) 174 175 assert supervisor.parakeet_physical_thread_count() == 6 176 177 178@pytest.mark.parametrize( 179 ("logical_count", "expected"), 180 [ 181 (8, 4), 182 (None, 1), 183 ], 184) 185def test_parakeet_physical_thread_count_fallback_halves_logical( 186 monkeypatch, logical_count, expected 187) -> None: 188 monkeypatch.setattr(supervisor.psutil, "cpu_count", lambda logical=False: None) 189 monkeypatch.setattr(supervisor.os, "cpu_count", lambda: logical_count) 190 191 assert supervisor.parakeet_physical_thread_count() == expected 192 193 194def test_build_parakeet_cmd_load_bearing_invariants() -> None: 195 cmd = supervisor._build_parakeet_cmd( 196 Path("/tmp/parakeet-server"), 197 Path("/tmp/model.gguf"), 198 45123, 199 6, 200 ) 201 202 assert "127.0.0.1" in cmd 203 assert "0.0.0.0" not in cmd 204 assert cmd[cmd.index("--model") + 1] == "/tmp/model.gguf" 205 assert cmd[cmd.index("--threads") + 1] == "6" 206 207 208def test_parakeet_runtime_library_dirs_aliases_bundled_libgomp( 209 tmp_path, monkeypatch 210) -> None: 211 site_dir = tmp_path / "site-packages" 212 libs_dir = site_dir / "scikit_learn.libs" 213 libs_dir.mkdir(parents=True) 214 bundled = libs_dir / "libgomp-e985bcbb.so.1.0.0" 215 bundled.write_text("runtime") 216 journal = tmp_path / "journal" 217 218 monkeypatch.setattr(supervisor, "_site_package_search_dirs", lambda: [site_dir]) 219 monkeypatch.setattr(supervisor, "get_journal", lambda: str(journal)) 220 221 result = supervisor._parakeet_runtime_library_dirs() 222 223 assert result == [journal / "cache" / "providers" / "parakeet" / "lib"] 224 alias = result[0] / "libgomp.so.1" 225 assert alias.is_symlink() 226 assert alias.resolve() == bundled.resolve() 227 228 229def test_with_library_path_prepends_dirs() -> None: 230 env = {"LD_LIBRARY_PATH": "/existing"} 231 232 result = supervisor._with_library_path(env, [Path("/parakeet/lib")]) 233 234 assert result["LD_LIBRARY_PATH"] == "/parakeet/lib:/existing" 235 assert env["LD_LIBRARY_PATH"] == "/existing" 236 237 238def test_start_parakeet_server_vulkan_crash_falls_back_to_cpu( 239 monkeypatch, 240 tmp_path, 241 caplog, 242) -> None: 243 monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path / "journal")) 244 monkeypatch.delenv("GGML_VK_VISIBLE_DEVICES", raising=False) 245 monkeypatch.delenv("LD_LIBRARY_PATH", raising=False) 246 monkeypatch.setattr(supervisor.sys, "platform", "linux") 247 monkeypatch.setattr(supervisor, "linux_stt_uses_parakeet_cpp", lambda: True) 248 monkeypatch.setattr(supervisor, "_configured_parakeet_device", lambda: "auto") 249 monkeypatch.setattr(supervisor, "is_local_provider_needed", lambda: True) 250 monkeypatch.setattr( 251 "solstone.think.providers.local_endpoint.resolve_local_endpoint", 252 lambda: SimpleNamespace(is_bundled=True), 253 ) 254 gpu = local_vulkan.VulkanDevice( 255 2, 256 "NVIDIA Test GPU", 257 local_vulkan.VK_TYPE_DISCRETE, 258 12288, 259 ) 260 monkeypatch.setattr(local_vulkan, "detect_gpus", lambda: [gpu]) 261 monkeypatch.setattr(local_vulkan, "select_device", lambda devices: devices[0]) 262 monkeypatch.setattr(local_vulkan, "classify", lambda _device: "discrete") 263 monkeypatch.setattr( 264 local_cuda, "probe_nvidia_gpu", lambda: _nvidia_probe(vram_mib=12288) 265 ) 266 267 def fake_ensure(backend: str): 268 return Path(f"/tmp/{backend}/parakeet-server"), Path("/tmp/model.gguf") 269 270 monkeypatch.setattr(parakeet_install, "ensure_artifacts_installed", fake_ensure) 271 monkeypatch.setattr(supervisor, "find_available_port", lambda: 45123) 272 ports: list[tuple[str, int]] = [] 273 monkeypatch.setattr( 274 supervisor, 275 "write_service_port", 276 lambda service, port: ports.append((service, port)), 277 ) 278 monkeypatch.setattr(supervisor, "parakeet_physical_thread_count", lambda: 6) 279 monkeypatch.setattr( 280 supervisor, "_parakeet_runtime_library_dirs", lambda: [Path("/parakeet/lib")] 281 ) 282 monkeypatch.setattr( 283 parakeet_server, "probe_state", lambda: (parakeet_server.STATE_READY, None) 284 ) 285 terminated = [] 286 monkeypatch.setattr( 287 supervisor, 288 "_terminate_managed", 289 lambda managed, timeout, *, reason: terminated.append( 290 (managed, timeout, reason) 291 ), 292 ) 293 294 launches: list[_LaunchRecord] = [] 295 296 def fake_launch_process( 297 name, cmd, *, restart=False, shutdown_timeout=15, ref=None, env=None 298 ): 299 managed = _FakeManaged(9, 9) if not launches else _FakeManaged(None, None) 300 launches.append( 301 { 302 "name": name, 303 "cmd": cmd, 304 "restart": restart, 305 "env": env, 306 "managed": managed, 307 } 308 ) 309 return managed 310 311 monkeypatch.setattr(supervisor, "_launch_process", fake_launch_process) 312 313 caplog.set_level(logging.INFO) 314 result = supervisor.start_parakeet_server() 315 316 assert result is launches[1]["managed"] 317 assert len(launches) == 2 318 assert launches[0]["name"] == supervisor.PARAKEET_SERVER_PROCESS_NAME 319 assert launches[0]["restart"] is True 320 assert launches[0]["cmd"][0] == "/tmp/vulkan/parakeet-server" 321 assert launches[0]["env"]["GGML_VK_VISIBLE_DEVICES"] == "2" 322 assert launches[0]["env"]["LD_LIBRARY_PATH"] == "/parakeet/lib" 323 _assert_att_context(launches[0]) 324 assert launches[1]["cmd"][0] == "/tmp/cpu/parakeet-server" 325 assert "GGML_VK_VISIBLE_DEVICES" not in launches[1]["env"] 326 assert launches[1]["env"]["LD_LIBRARY_PATH"] == "/parakeet/lib" 327 _assert_att_context(launches[1]) 328 assert launches[0]["managed"].cleanup_called is True 329 assert terminated[0][0] is launches[0]["managed"] 330 assert ports == [("parakeet-cpp", 45123)] 331 assert parakeet_server.read_parakeet_placement() == "cpu" 332 assert _launch_log("vulkan") in caplog.text 333 assert _launch_log("cpu") in caplog.text 334 335 336def test_start_parakeet_server_forces_cpu_on_small_single_discrete_bundled_brain( 337 monkeypatch, 338 tmp_path, 339 caplog, 340) -> None: 341 monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path / "journal")) 342 monkeypatch.delenv("GGML_VK_VISIBLE_DEVICES", raising=False) 343 monkeypatch.setattr(supervisor.sys, "platform", "linux") 344 monkeypatch.setattr(supervisor, "linux_stt_uses_parakeet_cpp", lambda: True) 345 monkeypatch.setattr(supervisor, "_configured_parakeet_device", lambda: "auto") 346 monkeypatch.setattr(supervisor, "is_local_provider_needed", lambda: True) 347 monkeypatch.setattr( 348 "solstone.think.providers.local_endpoint.resolve_local_endpoint", 349 lambda: SimpleNamespace(is_bundled=True), 350 ) 351 gpu = local_vulkan.VulkanDevice( 352 2, 353 "NVIDIA Test GPU", 354 local_vulkan.VK_TYPE_DISCRETE, 355 6144, 356 ) 357 monkeypatch.setattr(local_vulkan, "detect_gpus", lambda: [gpu]) 358 monkeypatch.setattr(local_vulkan, "select_device", lambda devices: devices[0]) 359 monkeypatch.setattr(local_vulkan, "classify", lambda _device: "discrete") 360 monkeypatch.setattr( 361 local_cuda, "probe_nvidia_gpu", lambda: _nvidia_probe(vram_mib=6144) 362 ) 363 launches: list[_LaunchRecord] = [] 364 ports = _patch_ready_parakeet_launch(monkeypatch, launches) 365 366 caplog.set_level(logging.INFO) 367 result = supervisor.start_parakeet_server() 368 369 assert result is launches[0]["managed"] 370 assert len(launches) == 1 371 assert launches[0]["cmd"][0] == "/tmp/cpu/parakeet-server" 372 assert "GGML_VK_VISIBLE_DEVICES" not in launches[0]["env"] 373 _assert_att_context(launches[0]) 374 assert ports == [("parakeet-cpp", 45123)] 375 assert parakeet_server.read_parakeet_placement() == "cpu" 376 assert ( 377 "parakeet-server auto placement resolved to CPU: tier=floor " 378 "tier_resident_mib=4137 parakeet_worst_case_mib=2947 margin_mib=1024 " 379 "required_mib=8108 gpu_vram_mib=6144 placement=cpu" 380 ) in caplog.text 381 assert _launch_log("cpu") in caplog.text 382 383 384def test_start_parakeet_server_brain_lane_inactive_keeps_auto_vulkan( 385 monkeypatch, 386 tmp_path, 387) -> None: 388 monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path / "journal")) 389 monkeypatch.setattr(supervisor.sys, "platform", "linux") 390 monkeypatch.setattr(supervisor, "linux_stt_uses_parakeet_cpp", lambda: True) 391 monkeypatch.setattr(supervisor, "_configured_parakeet_device", lambda: "auto") 392 monkeypatch.setattr(supervisor, "is_local_provider_needed", lambda: False) 393 gpu = local_vulkan.VulkanDevice( 394 2, 395 "NVIDIA Test GPU", 396 local_vulkan.VK_TYPE_DISCRETE, 397 6144, 398 ) 399 monkeypatch.setattr(local_vulkan, "detect_gpus", lambda: [gpu]) 400 monkeypatch.setattr(local_vulkan, "select_device", lambda devices: devices[0]) 401 monkeypatch.setattr(local_vulkan, "classify", lambda _device: "discrete") 402 monkeypatch.setattr( 403 local_cuda, "probe_nvidia_gpu", lambda: _nvidia_probe(vram_mib=6144) 404 ) 405 launches: list[_LaunchRecord] = [] 406 _patch_ready_parakeet_launch(monkeypatch, launches) 407 408 result = supervisor.start_parakeet_server() 409 410 assert result is launches[0]["managed"] 411 assert len(launches) == 1 412 assert launches[0]["cmd"][0] == "/tmp/vulkan/parakeet-server" 413 assert launches[0]["env"]["GGML_VK_VISIBLE_DEVICES"] == "2" 414 _assert_att_context(launches[0]) 415 assert parakeet_server.read_parakeet_placement() == "gpu" 416 417 418def test_start_parakeet_server_explicit_cpu_skips_auto_placement( 419 monkeypatch, 420 tmp_path, 421) -> None: 422 monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path / "journal")) 423 monkeypatch.setattr(supervisor.sys, "platform", "linux") 424 monkeypatch.setattr(supervisor, "linux_stt_uses_parakeet_cpp", lambda: True) 425 monkeypatch.setattr(supervisor, "_configured_parakeet_device", lambda: "cpu") 426 monkeypatch.setattr( 427 local_vulkan, 428 "detect_gpus", 429 lambda: pytest.fail("explicit CPU should not enumerate Vulkan devices"), 430 ) 431 monkeypatch.setattr( 432 local_cuda, 433 "probe_nvidia_gpu", 434 lambda: pytest.fail("explicit CPU should not probe NVIDIA"), 435 ) 436 launches: list[_LaunchRecord] = [] 437 _patch_ready_parakeet_launch(monkeypatch, launches) 438 439 result = supervisor.start_parakeet_server() 440 441 assert result is launches[0]["managed"] 442 assert launches[0]["cmd"][0] == "/tmp/cpu/parakeet-server" 443 _assert_att_context(launches[0]) 444 assert parakeet_server.read_parakeet_placement() == "cpu" 445 446 447def test_parakeet_attention_context_overrides_ambient_env( 448 monkeypatch, 449 tmp_path, 450) -> None: 451 monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path / "journal")) 452 monkeypatch.setenv(PARAKEET_ATT_CONTEXT_ENV, "0") 453 monkeypatch.setattr(supervisor.sys, "platform", "linux") 454 monkeypatch.setattr(supervisor, "linux_stt_uses_parakeet_cpp", lambda: True) 455 monkeypatch.setattr(supervisor, "_configured_parakeet_device", lambda: "cpu") 456 monkeypatch.setattr( 457 local_vulkan, 458 "detect_gpus", 459 lambda: pytest.fail("explicit CPU should not enumerate Vulkan devices"), 460 ) 461 monkeypatch.setattr( 462 local_cuda, 463 "probe_nvidia_gpu", 464 lambda: pytest.fail("explicit CPU should not probe NVIDIA"), 465 ) 466 launches: list[_LaunchRecord] = [] 467 _patch_ready_parakeet_launch(monkeypatch, launches) 468 469 result = supervisor.start_parakeet_server() 470 471 assert result is launches[0]["managed"] 472 assert launches[0]["cmd"][0] == "/tmp/cpu/parakeet-server" 473 _assert_att_context(launches[0]) 474 assert parakeet_server.read_parakeet_placement() == "cpu" 475 476 477@pytest.mark.parametrize( 478 ( 479 "sys_platform", 480 "machine", 481 "backend", 482 "available_bytes", 483 "confidential", 484 "confidential_audio", 485 "local_backend", 486 "expected", 487 ), 488 [ 489 ("linux", "x86_64", None, 5 * 1024**3, False, True, "parakeet", True), 490 ("linux", "x86_64", None, 3 * 1024**3, False, True, "parakeet", False), 491 ( 492 "linux", 493 "x86_64", 494 "parakeet", 495 3 * 1024**3, 496 False, 497 True, 498 "parakeet", 499 True, 500 ), 501 ( 502 "linux", 503 "x86_64", 504 "parakeet-cpp", 505 3 * 1024**3, 506 False, 507 True, 508 "parakeet", 509 True, 510 ), 511 ("linux", "aarch64", None, 5 * 1024**3, False, True, "parakeet", True), 512 ( 513 "linux", 514 "aarch64", 515 "parakeet", 516 3 * 1024**3, 517 False, 518 True, 519 "parakeet", 520 True, 521 ), 522 ("darwin", "arm64", None, 5 * 1024**3, False, True, "parakeet", False), 523 ( 524 "linux", 525 "x86_64", 526 "parakeet", 527 3 * 1024**3, 528 True, 529 True, 530 "parakeet", 531 True, 532 ), 533 ("linux", "x86_64", None, 3 * 1024**3, True, True, "parakeet", False), 534 ("linux", "x86_64", None, 3 * 1024**3, True, False, "parakeet", True), 535 ("linux", "x86_64", None, 3 * 1024**3, True, True, None, False), 536 ], 537) 538def test_linux_stt_uses_parakeet_cpp_truth_table( 539 monkeypatch, 540 sys_platform: str, 541 machine: str, 542 backend: str | None, 543 available_bytes: int, 544 confidential: bool, 545 confidential_audio: bool, 546 local_backend: str | None, 547 expected: bool, 548): 549 monkeypatch.setattr(supervisor.sys, "platform", sys_platform) 550 monkeypatch.setattr(supervisor.platform, "machine", lambda: machine) 551 transcribe_config = {} 552 if backend is not None: 553 transcribe_config["backend"] = backend 554 if not confidential_audio: 555 transcribe_config["confidential_audio"] = False 556 config = {"transcribe": transcribe_config} if transcribe_config else {} 557 monkeypatch.setattr(supervisor, "read_journal_config", lambda: config) 558 monkeypatch.setattr(supervisor, "read_available_bytes", lambda: available_bytes) 559 monkeypatch.setattr(supervisor, "stt_local_floor_bytes", lambda: 4 * 1024**3) 560 monkeypatch.setattr(supervisor, "local_stt_backend", lambda: local_backend) 561 monkeypatch.setattr( 562 "solstone.think.services.spp.confidential_provenance", 563 lambda: {"enabled_at": "2026-05-24T00:00:00Z"} if confidential else None, 564 ) 565 monkeypatch.delenv("GOOGLE_API_KEY", raising=False) 566 567 assert supervisor.linux_stt_uses_parakeet_cpp() is expected 568 569 570def test_start_parakeet_server_early_returns_for_non_linux( 571 monkeypatch, tmp_path 572) -> None: 573 monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path / "journal")) 574 parakeet_server.write_parakeet_placement("gpu") 575 monkeypatch.setattr(supervisor.sys, "platform", "darwin") 576 monkeypatch.setattr(supervisor.platform, "machine", lambda: "arm64") 577 monkeypatch.setattr( 578 supervisor, 579 "read_journal_config", 580 lambda: pytest.fail("config should not be read off-linux"), 581 ) 582 583 assert supervisor.start_parakeet_server() is None 584 assert parakeet_server.read_parakeet_placement() is None 585 586 587def test_start_parakeet_server_early_returns_for_other_backend( 588 monkeypatch, tmp_path 589) -> None: 590 monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path / "journal")) 591 parakeet_server.write_parakeet_placement("gpu") 592 monkeypatch.setattr(supervisor.sys, "platform", "linux") 593 monkeypatch.setattr(supervisor.platform, "machine", lambda: "x86_64") 594 monkeypatch.setattr( 595 supervisor, 596 "read_journal_config", 597 lambda: {"transcribe": {"backend": "confidential"}}, 598 ) 599 monkeypatch.setattr( 600 "solstone.think.services.spp.confidential_provenance", 601 lambda: {"enabled_at": "2026-05-24T00:00:00Z"}, 602 ) 603 604 assert supervisor.start_parakeet_server() is None 605 assert parakeet_server.read_parakeet_placement() is None 606 607 608def test_start_parakeet_server_starts_background_install_when_missing( 609 monkeypatch, 610 tmp_path, 611) -> None: 612 monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path / "journal")) 613 parakeet_server.write_parakeet_placement("gpu") 614 monkeypatch.setattr(supervisor, "linux_stt_uses_parakeet_cpp", lambda: True) 615 monkeypatch.setattr(supervisor, "_configured_parakeet_device", lambda: "cpu") 616 started: list[str] = [] 617 618 class _Thread: 619 def __init__(self, *, target, name, daemon): 620 self.target = target 621 self.name = name 622 self.daemon = daemon 623 624 def is_alive(self): 625 return False 626 627 def start(self): 628 started.append(self.name) 629 630 monkeypatch.setattr(supervisor.threading, "Thread", _Thread) 631 monkeypatch.setattr( 632 parakeet_install, 633 "ensure_artifacts_installed", 634 lambda _backend: (_ for _ in ()).throw( 635 parakeet_install.ParakeetProviderError( 636 "model_missing", "Parakeet model is not installed." 637 ) 638 ), 639 ) 640 monkeypatch.setattr( 641 parakeet_install, 642 "inspect_readiness", 643 lambda: { 644 "install_state": "idle", 645 "binary_installed": False, 646 "model_installed": False, 647 }, 648 ) 649 monkeypatch.setattr(supervisor, "_parakeet_bootstrap_thread", None) 650 651 assert supervisor.start_parakeet_server() is None 652 assert started == ["parakeet-cpp-provider-bootstrap"] 653 assert parakeet_server.read_parakeet_placement() is None 654 655 656def test_parakeet_bootstrap_worker_requests_start_after_install(monkeypatch) -> None: 657 calls: list[str] = [] 658 requests: list[str] = [] 659 660 monkeypatch.setattr( 661 parakeet_install, "install_parakeet", lambda: calls.append("install") 662 ) 663 monkeypatch.setattr( 664 supervisor, "_request_parakeet_server_start", lambda: requests.append("start") 665 ) 666 monkeypatch.setattr( 667 supervisor, 668 "_parakeet_bootstrap_thread", 669 supervisor.threading.current_thread(), 670 ) 671 672 supervisor._run_parakeet_bootstrap_worker() 673 674 assert calls == ["install"] 675 assert requests == ["start"] 676 assert supervisor._parakeet_bootstrap_thread is None 677 678 679def test_parakeet_bootstrap_worker_uses_captured_journal_path( 680 monkeypatch, tmp_path 681) -> None: 682 calls: list[Path] = [] 683 requests: list[str] = [] 684 journal_path = tmp_path / "original" 685 686 def fake_install_parakeet(*, journal_path=None): 687 calls.append(journal_path) 688 689 monkeypatch.setattr(parakeet_install, "install_parakeet", fake_install_parakeet) 690 monkeypatch.setattr( 691 supervisor, "_request_parakeet_server_start", lambda: requests.append("start") 692 ) 693 monkeypatch.setattr( 694 supervisor, 695 "_parakeet_bootstrap_thread", 696 supervisor.threading.current_thread(), 697 ) 698 699 supervisor._run_parakeet_bootstrap_worker(journal_path) 700 701 assert calls == [journal_path] 702 assert requests == ["start"]