Files
galahad2lcd/internal/stream/stream_test.go
T
Maksim Totmin 4c34f0bedc Initial commit: Go driver for the Lian Li Galahad II LCD
Streams H.264 to the Galahad II LCD via USB (gousb) with:
- ffmpeg CLI transcoding (no fragile FFmpeg C-ABI bindings)
- pure-Go Annex-B access-unit splitting
- framerate pacing with graceful shutdown and USB reconnect
- TOML config, cobra CLI, structured slog logging, unit tests
2026-08-19 12:03:54 +07:00

144 lines
3.2 KiB
Go

package stream
import (
"context"
"errors"
"sync"
"testing"
"time"
)
type fakeDevice struct {
mu sync.Mutex
wrote [][]byte
failAt int // fail after this many writes; <0 = never fail
failErr error
// onWrite is invoked after each successful write with the running count.
onWrite func(n int)
}
func (f *fakeDevice) WriteFrame(_ context.Context, frame []byte) error {
f.mu.Lock()
defer f.mu.Unlock()
if f.failAt >= 0 && f.failAt == len(f.wrote) {
if f.failErr == nil {
f.failErr = errors.New("boom")
}
return f.failErr
}
cp := make([]byte, len(frame))
copy(cp, frame)
f.wrote = append(f.wrote, cp)
if f.onWrite != nil {
f.onWrite(len(f.wrote))
}
return nil
}
func (f *fakeDevice) Close() error { return nil }
func (f *fakeDevice) count() int {
f.mu.Lock()
defer f.mu.Unlock()
return len(f.wrote)
}
func TestStreamerPreservesOrder(t *testing.T) {
frames := [][]byte{{1, 2, 3}, {4, 5}, {6}}
ctx, cancel := context.WithCancel(context.Background())
f := &fakeDevice{
failAt: -1,
onWrite: func(n int) {
if n == len(frames) {
cancel()
}
},
}
s := New(f, frames, 1000, 1)
if err := s.Run(ctx); err != nil {
t.Fatalf("Run error = %v", err)
}
if f.count() != 3 {
t.Fatalf("wrote %d frames, want 3", f.count())
}
f.mu.Lock()
defer f.mu.Unlock()
for i, want := range frames {
if len(f.wrote[i]) != len(want) {
t.Errorf("frame %d length = %d, want %d", i, len(f.wrote[i]), len(want))
}
}
}
func TestStreamerStopsOnCancel(t *testing.T) {
f := &fakeDevice{failAt: -1}
s := New(f, [][]byte{{1}}, 100, 1)
ctx, cancel := context.WithCancel(context.Background())
cancel()
if err := s.Run(ctx); err != nil {
t.Fatalf("Run error = %v, want nil on clean shutdown", err)
}
}
func TestStreamerPropagatesDeviceError(t *testing.T) {
f := &fakeDevice{failAt: 1}
s := New(f, [][]byte{{1}, {2}}, 1000, 1)
ctx := context.Background()
if err := s.Run(ctx); err == nil {
t.Fatal("Run returned nil, want device error")
}
if f.count() != 1 {
t.Errorf("wrote %d frames before error, want 1", f.count())
}
}
func TestStreamerNoFrames(t *testing.T) {
f := &fakeDevice{failAt: -1}
s := New(f, nil, 30, 1)
if err := s.Run(context.Background()); err == nil {
t.Error("Run returned nil, want error for empty packet list")
}
}
func TestStreamerPacing(t *testing.T) {
// 10 fps => 100ms per frame. Two frames should take ~200ms.
f := &fakeDevice{failAt: -1}
s := New(f, [][]byte{{1}, {2}}, 10, 1)
ctx, cancel := context.WithCancel(context.Background())
start := time.Now()
go func() {
time.Sleep(400 * time.Millisecond)
cancel()
}()
_ = s.Run(ctx)
elapsed := time.Since(start)
// At 10fps each frame takes 100ms; after 400ms we expect ~4 frames.
if f.count() < 2 {
t.Errorf("too few frames streamed: %d", f.count())
}
if elapsed < 150*time.Millisecond {
t.Errorf("elapsed = %v, pacing too fast", elapsed)
}
}
func TestLoadPacketsMissingFile(t *testing.T) {
if _, err := LoadPackets("/nonexistent/cache.h264"); err == nil {
t.Error("LoadPackets returned nil error for missing file")
}
}
func TestClampFPS(t *testing.T) {
if got := clampFPS(0); got != defaultFPS {
t.Errorf("clampFPS(0) = %v, want %v", got, defaultFPS)
}
if got := clampFPS(300); got != 120 {
t.Errorf("clampFPS(300) = %v, want 120", got)
}
}