105 lines
4.1 KiB
Go
105 lines
4.1 KiB
Go
package transcribe_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"git.d-ma.be/mathias/tapir/internal/adapters/transcribe"
|
|
"git.d-ma.be/mathias/tapir/internal/domain"
|
|
"git.d-ma.be/mathias/tapir/internal/errkind"
|
|
"git.d-ma.be/mathias/tapir/internal/reconcile"
|
|
)
|
|
|
|
// A wedged engine (iguana after a Metal OOM, infra#273) does not answer at all. With only
|
|
// the job's deadline, the primary used up the whole job before any fallback could run, and
|
|
// the chain then refused the fallback because the job context had expired. Each engine call
|
|
// gets its own deadline (adversarial review F4).
|
|
|
|
type hangingBackend struct{ name string }
|
|
|
|
func (h hangingBackend) Name() string { return h.name }
|
|
func (hangingBackend) Transcribe(ctx context.Context, _ string) (domain.Transcription, error) {
|
|
<-ctx.Done()
|
|
return domain.Transcription{}, ctx.Err()
|
|
}
|
|
|
|
func TestWithTimeout_AHungBackendFailsAtItsOwnDeadlineWhileTheJobIsStillAlive(t *testing.T) {
|
|
job, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
b := transcribe.WithTimeout(hangingBackend{name: "iguana/kb-whisper"}, 40*time.Millisecond)
|
|
|
|
start := time.Now()
|
|
_, err := b.Transcribe(job, "/a.wav")
|
|
|
|
require.Error(t, err)
|
|
assert.Equal(t, "timeout", errkind.Of(err))
|
|
assert.Less(t, time.Since(start), 2*time.Second, "it must give up at its own deadline, not wait for the job's")
|
|
assert.NoError(t, job.Err(), "the job context is untouched, which is what lets a fallback run")
|
|
assert.Equal(t, "iguana/kb-whisper", b.Name(), "the wrapper is invisible in the recorded endpoint")
|
|
}
|
|
|
|
func TestWithTimeout_ASuccessPassesStraightThrough(t *testing.T) {
|
|
calls := &[]string{}
|
|
inner := &orderedBackend{name: "iguana/kb-whisper", text: "hej", calls: calls}
|
|
|
|
got, err := transcribe.WithTimeout(inner, time.Minute).Transcribe(context.Background(), "/a.wav")
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "hej", got.Text)
|
|
}
|
|
|
|
func TestWithTimeout_ZeroMeansNoDeadlineOfItsOwn(t *testing.T) {
|
|
inner := &orderedBackend{name: "x", text: "hej", calls: &[]string{}}
|
|
|
|
assert.Same(t, inner, transcribe.WithTimeout(inner, 0), "a zero timeout must not wrap or change anything")
|
|
}
|
|
|
|
func TestChain_AHungPrimaryReachesTheFallbackWhenItHasItsOwnDeadline(t *testing.T) {
|
|
calls := &[]string{}
|
|
fb := &orderedBackend{name: "berget/kb-whisper", text: "fallback text", calls: calls}
|
|
c := transcribe.NewChain(transcribe.WithTimeout(hangingBackend{name: "iguana/kb-whisper"}, 40*time.Millisecond), fb)
|
|
job, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
got, err := c.Transcribe(job, "/a.wav")
|
|
|
|
require.NoError(t, err, "a wedged primary is exactly what the fallback exists for")
|
|
assert.Equal(t, "berget/kb-whisper", got.Endpoint)
|
|
}
|
|
|
|
func TestChain_AJobThatEndedWhileThePrimaryHungStillNeverReachesTheFallback(t *testing.T) {
|
|
calls := &[]string{}
|
|
fb := &orderedBackend{name: "berget/kb-whisper", text: "fallback text", calls: calls}
|
|
c := transcribe.NewChain(transcribe.WithTimeout(hangingBackend{name: "iguana/kb-whisper"}, time.Minute), fb)
|
|
job, cancel := context.WithTimeout(context.Background(), 40*time.Millisecond)
|
|
defer cancel()
|
|
|
|
_, err := c.Transcribe(job, "/a.wav")
|
|
|
|
require.Error(t, err)
|
|
assert.Empty(t, *calls, "the job's own deadline or a cancel must still stop the audio from reaching a cloud endpoint")
|
|
}
|
|
|
|
func TestEnsemble_AHungEngineIsASoftFailureAndTheOtherEnginesTextIsUsed(t *testing.T) {
|
|
calls := &[]string{}
|
|
alt := &orderedBackend{name: "iguana/kb-whisper", text: "text from the other engine", calls: calls}
|
|
e := transcribe.NewEnsemble(
|
|
transcribe.WithTimeout(hangingBackend{name: "iguana/pianissimo"}, 40*time.Millisecond), alt, nil,
|
|
&verdictAdj{v: reconcile.VerdictAlt}, ensembleOpts)
|
|
job, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
got, err := e.Transcribe(job, "/a.wav")
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "text from the other engine", got.Text)
|
|
assert.Equal(t, domain.ReconcileSingle, got.Reconciliation.Status)
|
|
assert.Contains(t, got.Reconciliation.Reason, "timeout")
|
|
assert.False(t, errors.Is(err, context.DeadlineExceeded))
|
|
}
|