"""Behaviour tests for the ticket-19 performance-baseline protocol script. The script (scripts/measure_perf_baseline.py) is the locked measurement protocol for the installed-build baseline: every number it reports comes from an observed run on the target machine class, parsed by the pure helpers tested here. Tests run offline with fixture text only and never touch audio hardware, the GPU, or the network. """ import importlib.util from pathlib import Path import pytest SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "measure_perf_baseline.py" SPEC = importlib.util.spec_from_file_location("measure_perf_baseline", SCRIPT) assert SPEC is not None and SPEC.loader is not None PROTOCOL = importlib.util.module_from_spec(SPEC) SPEC.loader.exec_module(PROTOCOL) def test_parse_vmrss_kb_reads_kilobytes() -> None: """VmRSS lines parse to kilobytes; anything else yields None.""" status = "Name:\tvoculinux\nVmRSS:\t 412336 kB\nVmSwap:\t0 kB\n" assert PROTOCOL.parse_vmrss_kb(status) == 412336 assert PROTOCOL.parse_vmrss_kb("Name:\tvoculinux\n") is None def test_describe_reports_count_mean_min_max() -> None: """Spread statistics come from the samples, never fabricated.""" stats = PROTOCOL.describe([1.0, 2.0, 3.0]) assert stats["n"] == 3 assert stats["mean"] == pytest.approx(2.0) assert stats["min"] == pytest.approx(1.0) assert stats["max"] == pytest.approx(3.0) def test_describe_empty_is_explicit() -> None: """No samples means n == 0, never a zero mean pretending to be data.""" stats = PROTOCOL.describe([]) assert stats["n"] == 0 assert stats["mean"] is None WAKE_LOG = """\ 2026-11-05 22:44:01,100 - vocalinux.ui.action_handler - DEBUG - Handling action: toggle 2026-11-05 22:44:01,350 - vocalinux.audio.capture - DEBUG - Opened capture stream: 1 channel(s) at 16000Hz 2026-11-05 22:44:05,100 - vocalinux.ui.action_handler - DEBUG - Handling action: toggle 2026-11-05 22:44:05,500 - vocalinux.audio.capture - DEBUG - Opened capture stream: 1 channel(s) at 16000Hz """ def test_parse_wake_latencies_pairs_toggle_to_capture() -> None: """Each toggle pairs with the next capture-stream line, in seconds.""" assert PROTOCOL.parse_wake_latencies(WAKE_LOG) == pytest.approx([0.25, 0.4]) def test_parse_wake_latencies_ignores_unpaired_toggle() -> None: """A toggle with no later capture start is dropped, not zero-filled.""" log = ( WAKE_LOG + "2026-11-05 22:44:09,100 - vocalinux.ui.action_handler - DEBUG - Handling action: start\n" ) assert PROTOCOL.parse_wake_latencies(log) == pytest.approx([0.25, 0.4]) def test_realtime_factor_is_audio_over_wall() -> None: """RTF below one means faster than real time; zero audio is refused.""" assert PROTOCOL.realtime_factor(30.0, 15.0) == pytest.approx(0.5) with pytest.raises(ValueError): PROTOCOL.realtime_factor(0.0, 1.0) def test_parse_nvidia_smi_csv_reads_first_gpu() -> None: """The locked nvidia-smi query shape parses to total/free MiB.""" out = ( "name, driver_version, memory.total [MiB], memory.free [MiB]\n" "NVIDIA GeForce RTX 2050, 595.112.07, 4096 MiB, 3711 MiB\n" ) assert PROTOCOL.parse_nvidia_smi_memory(out) == (4096, 3711) assert PROTOCOL.parse_nvidia_smi_memory("no gpu here\n") is None def test_recommend_thresholds_come_from_footprints() -> None: """Ladder thresholds derive from measured footprints plus headroom.""" footprints = {"q8_0": 1000.0, "q5_0": 700.0, "small": 500.0} advice = PROTOCOL.recommend_thresholds(footprints, headroom_mib=512.0) assert advice["prefer_q8_0_above_mib"] == pytest.approx(1512.0) assert advice["prefer_q5_0_above_mib"] == pytest.approx(1212.0) assert advice["floor_small_below_mib"] == pytest.approx(1212.0) def test_render_report_names_protocol_and_host() -> None: """The evidence report carries protocol version and host facts.""" report = PROTOCOL.render_markdown_report( {"os": "Void", "gpu": "NVIDIA GeForce RTX 2050"}, {"idle": {}} ) assert PROTOCOL.PROTOCOL_VERSION in report assert "Void" in report assert "NVIDIA GeForce RTX 2050" in report def test_sample_generator_is_deterministic(tmp_path) -> None: """The fixed sample regenerates byte-identical from its seed.""" first = tmp_path / "a.wav" second = tmp_path / "b.wav" PROTOCOL.make_sample_wav(first, seed=19) PROTOCOL.make_sample_wav(second, seed=19) assert first.read_bytes() == second.read_bytes() def test_sample_shape_is_thirty_seconds_mono_16k(tmp_path) -> None: """The sample is 30 s, mono, 16 kHz, 16-bit for whisper.cpp.""" import wave target = tmp_path / "sample.wav" PROTOCOL.make_sample_wav(target, seed=19) with wave.open(str(target), "rb") as handle: assert handle.getnchannels() == 1 assert handle.getsampwidth() == 2 assert handle.getframerate() == 16000 assert handle.getnframes() / 16000 == pytest.approx(30.0) DBUS_TOGGLE_LOG = """\ 2026-11-05 23:01:11,400 - vocalinux.ui.tray_indicator - DEBUG - Toggle requested in state IDLE 2026-11-05 23:01:11,546 - vocalinux.audio.capture - DEBUG - Opened capture stream: 2 channel(s) at 44100Hz 2026-11-05 23:01:16,400 - vocalinux.ui.tray_indicator - DEBUG - Toggle requested in state LISTENING 2026-11-05 23:01:22,100 - vocalinux.ui.tray_indicator - DEBUG - Toggle requested in state IDLE 2026-11-05 23:01:22,749 - vocalinux.audio.capture - DEBUG - Opened capture stream: 2 channel(s) at 44100Hz """ def test_parse_wake_latencies_pairs_dbus_toggle_starts_only() -> None: """Only IDLE toggles open capture; a stop toggle never borrows the next run.""" assert PROTOCOL.parse_wake_latencies(DBUS_TOGGLE_LOG) == pytest.approx([0.146, 0.649])