From c14353a3991f009fed31fb25acf1053523138705 Mon Sep 17 00:00:00 2001 From: rouggy Date: Fri, 31 Jul 2026 18:45:49 +0200 Subject: [PATCH] test: playing a recording must not consume it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported: the recording plays once and never again, "as if it threw the sound away after playing it". PeekQSO is what the playback reads and it is meant to copy — TakeQSO is the one that clears — so this pins that, and that the copy is independent of the recorder's own buffer. It passes, which is the useful part: the take survives, so the one-shot failure is downstream of it and the search moves to the file and the player. --- internal/audio/peek_test.go | 56 +++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 internal/audio/peek_test.go diff --git a/internal/audio/peek_test.go b/internal/audio/peek_test.go new file mode 100644 index 0000000..370c138 --- /dev/null +++ b/internal/audio/peek_test.go @@ -0,0 +1,56 @@ +package audio + +import ( + "testing" + "time" +) + +// Playing a stopped take must not consume it. +// +// Reported on the air: the recording plays once, then nothing — "as if it threw +// the sound away after playing it". PeekQSO is what the playback reads, and it +// is meant to COPY; TakeQSO is the one that clears. +func TestPeekDoesNotConsumeTheTake(t *testing.T) { + r := &Recorder{running: true, active: true, paused: true, prerollSamples: sampleRate} + r.acc = make([]int16, 4000) + for i := range r.acc { + r.acc[i] = int16(i % 500) + } + + first, err := r.PeekQSO() + if err != nil { + t.Fatalf("first peek: %v", err) + } + second, err := r.PeekQSO() + if err != nil { + t.Fatalf("second peek: %v — the take was consumed by the first", err) + } + if len(first) != len(second) { + t.Errorf("second peek returned %d bytes, first %d", len(second), len(first)) + } + + // And the copy must be independent: the caller holds these bytes while the + // recorder may still be appending to its own slice. + if len(first) > 0 { + first[0] = ^first[0] + again, _ := r.PeekQSO() + if len(again) > 0 && again[0] == first[0] { + t.Error("PeekQSO handed out its internal buffer, not a copy") + } + } + + // Resuming and stopping again keeps everything captured so far. + r.ResumeQSO() + r.mu.Lock() + r.acc = append(r.acc, make([]int16, 1000)...) + r.mu.Unlock() + r.PauseQSO() + third, err := r.PeekQSO() + if err != nil { + t.Fatalf("peek after resume: %v", err) + } + if len(third) <= len(second) { + t.Errorf("after resuming, the take is %d bytes — it should have grown past %d", len(third), len(second)) + } + _ = time.Now +}