test: bring coverage from 50% to 86%#16
Merged
Merged
Conversation
Adds tests for the engine, CLI subcommand handlers, and the non-protocol
parts of the daemon — all mocked at the right boundary so no real model
load or audio device is required.
- tests/test_engine.py (new): Stackvox.__init__/synthesize/speak/voices/
speak_sequence, the module-level speak/synthesize singletons, the
thread-safety contract on _get_default, the urlretrieve reporthook
progress output, and _ensure_models's skip-existing/download-missing
logic. Mocks Kokoro and sounddevice so nothing runtime-heavy runs.
- tests/test_cli.py (extended): every _cmd_* handler — speak (text/--out/
blank), say (daemon-up/down/fallback-say with-and-without `say` binary),
serve (normal + RuntimeError), stop (running/stopped), status, voices,
welcome, completion (defensive unsupported-shell branch).
- tests/test_daemon.py (new): _pid_alive against this process and an
unused PID, is_running across missing/garbage/alive/dead pid files,
the send/say/stop/ping client wrappers (payload shape + timeout),
_refresh_audio_devices happy-path and exception swallowing, and the
_start_device_watcher non-darwin no-op.
Coverage by file:
cli.py 54% → 99%
engine.py 26% → 93%
daemon.py 55% → 73% (remainder is the macOS CoreAudio ctypes
watcher, hard to test portably)
overall 50% → 86%
69 tests, all green. ruff + mypy clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
57158e7 to
c5fe636
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Mechanical coverage push. No production code changes — three new/extended test files mocking at the right boundaries (`Kokoro`, `sounddevice`, `daemon.send`) so we exercise the engine and CLI handlers without loading the real model or talking to an audio device.
Coverage delta
The remaining 14% on `daemon.py` is mostly the macOS CoreAudio ctypes innards in `_start_device_watcher` — those need a real macOS runloop to exercise meaningfully and aren't worth simulating.
What's tested
`tests/test_engine.py` (new)
`Stackvox.init` (stored attrs, custom + default `cache_dir`), `synthesize` (call shape, per-call overrides, `speed=0` not silently dropped), `speak` (blocking vs not, `stop` propagation), `voices` (sorting), `speak_sequence` (empty input, gap concatenation), module-level `speak`/`synthesize` singleton reuse, `_get_default` thread-safety with a `Barrier`, `_download_with_progress` reporthook (known size + unknown size), `_ensure_models` skip-existing / download-missing.
`tests/test_cli.py` (extended)
Every subcommand handler — `_cmd_speak` (text / `--out` / blank), `_cmd_say` (daemon up / unreachable / fallback with and without `say` on PATH / blank), `_cmd_serve` (normal + `RuntimeError`), `_cmd_stop` (already-stopped / running), `_cmd_status` (running prints pid / stopped returns 1), `_cmd_voices`, `_cmd_welcome`, `_cmd_completion` (defensive unsupported-shell branch).
`tests/test_daemon.py` (new)
`_pid_alive` against `os.getpid()` and an unused PID; `is_running` across missing, garbage, alive, dead pid files; `send`/`say`/`stop`/`ping` client wrappers (payload shape + ping timeout); `_refresh_audio_devices` happy-path and exception-swallowing; `_start_device_watcher` non-darwin no-op.
Test plan