80 lines
2.3 KiB
Go
80 lines
2.3 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"sync"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.stormux.org/storm/fenrir/internal/command"
|
|
"git.stormux.org/storm/fenrir/internal/input"
|
|
"git.stormux.org/storm/fenrir/internal/screen"
|
|
"git.stormux.org/storm/fenrir/internal/speech"
|
|
)
|
|
|
|
type unavailableSpeech struct {
|
|
entered chan struct{}
|
|
once sync.Once
|
|
fatal bool
|
|
}
|
|
|
|
func (f *unavailableSpeech) Say(string) error { return io.EOF }
|
|
func (f *unavailableSpeech) Configure(speech.Parameters) error { return nil }
|
|
func (f *unavailableSpeech) Set_pitch(int) error { return nil }
|
|
func (f *unavailableSpeech) Cancel() error { return nil }
|
|
func (f *unavailableSpeech) Close() {}
|
|
func (f *unavailableSpeech) Reopen(ctx context.Context) (speech.Transport, error) {
|
|
f.once.Do(func() { close(f.entered) })
|
|
if f.fatal {
|
|
return nil, syscall.EACCES
|
|
}
|
|
<-ctx.Done()
|
|
return nil, ctx.Err()
|
|
}
|
|
|
|
func TestAudioRecoveryKeepsKeyboardCommandsAndReleasesOnFailure(t *testing.T) {
|
|
for _, fatal := range []bool{false, true} {
|
|
broken := &unavailableSpeech{entered: make(chan struct{}), fatal: fatal}
|
|
voice := speech.New(broken)
|
|
keys := &fakeKeys{events: make(chan error, 1)}
|
|
polls := 0
|
|
keys.poll = func(_ bool, handle func(input.Decision)) error {
|
|
polls++
|
|
if polls > 1 {
|
|
handle(input.Decision{Forward: true, Interrupt: true, Keypress: true})
|
|
handle(input.Decision{Command: command.Quit})
|
|
}
|
|
return nil
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
result := make(chan error, 1)
|
|
go func() {
|
|
result <- run(ctx, &fakeSource{snapshot: screen.Snapshot{Terminal: 1, Active: true, Rows: 1, Cols: 5, Cells: []rune("ready")}}, keys, voice, nil)
|
|
}()
|
|
select {
|
|
case <-broken.entered:
|
|
if !fatal {
|
|
keys.events <- nil
|
|
}
|
|
case <-ctx.Done():
|
|
t.Fatal("fixture did not begin speech recovery")
|
|
}
|
|
select {
|
|
case err := <-result:
|
|
if fatal && !errors.Is(err, syscall.EACCES) || !fatal && (err != nil || polls < 2) || !keys.closed {
|
|
t.Fatal("audio failure trapped input", err, polls, keys.closed)
|
|
}
|
|
case <-ctx.Done():
|
|
t.Fatal("reader could not quit or release keyboards during audio recovery")
|
|
}
|
|
cancel()
|
|
voice.Close()
|
|
}
|
|
}
|