The pytest plugin
Two ways a turn-taking regression should show up in a test run: as one assertion inside a test you already wrote, or as a whole-session gate you didn't have to write at all. Install hotato and the plugin registers both, no extra config.
pip install hotato # the plugin auto-registers (pytest11 entry point)
The hotato_score fixture
Same inputs as the CLI, returns the same envelope. Assert on whichever measurement your test cares about.
def test_call_yields(hotato_score): env = hotato_score(stereo="call.wav", expect="yield") assert env["exit_code"] == 0 assert all(e["verdict"]["passed"] for e in env["events"]) def test_split_channels(hotato_score): env = hotato_score(caller="caller.wav", agent="agent.wav", expect="yield") assert env["events"][0]["verdict"]["passed"] def test_selftest_battery(hotato_score): env = hotato_score(suite="barge-in") assert env["exit_code"] == 0
hotato_score(**kwargs) takes the same keyword arguments as the CLI: stack names the voice stack for fix wording, max_talk_over_sec and max_time_to_yield_sec tighten a bound for one test without touching the CLI default.
The --hotato-suite session gate
Opt in on the command line and the battery runs after your tests, printing a summary and failing the session (exit 1) on a regression.
$ pytest --hotato-suite -q . [100%] ================== hotato suite: barge-in ================== 8 of 8 events pass (failed=0) 1 passed in 0.54s
On a regression the gate is explicit:
FAIL svd-st-missed: fix[config] expected the agent to yield but it kept talking
hotato: regression detected; failing the session.Gate on your own labelled sets
The bundled battery is a self-test of the harness. Point the same flag at your own scenario and audio directories, including the tiered suites:
pytest --hotato-suite \
--hotato-suite-scenarios corpus/suites/gold/scenarios \
--hotato-suite-audio corpus/suites/gold/audioAn empty or wrong --hotato-suite-audio path leaves every scenario not-scorable: no caller onset detected, or the agent already silent where a should-yield case needs it talking. The gate still fails the session (scoring nothing is a setup failure), but the summary says so directly: hotato: no scorable events, never regression detected. A missing file is an input problem, not a measured miss.
If CI already runs pytest, this flag is the whole integration. Prefer a PR comment with the results table? The GitHub workflow posts one and gates on the same envelope. Use either, or both.
A regression here means a frozen battery scenario that should pass didn't: a config change, a prompt edit, or an upstream dependency bump to go look at, not a diagnosis of what broke.