Files
2026-08-27 09:34:21 +03:00

214 lines
4.7 KiB
Go

package playback
import (
"context"
"errors"
"testing"
)
type fakeVideoFactory struct {
reader VideoReader
err error
calls int
}
func (f *fakeVideoFactory) OpenVideo(
context.Context,
FeedConfig,
) (VideoReader, error) {
f.calls++
return f.reader, f.err
}
type fakeVideoReader struct {
frames []VideoFrame
readErr error
closeErr error
readCalls int
closed bool
read func(context.Context) (VideoFrame, error)
}
func (r *fakeVideoReader) ReadVideo(ctx context.Context) (VideoFrame, error) {
r.readCalls++
if r.read != nil {
return r.read(ctx)
}
if len(r.frames) == 0 {
return VideoFrame{}, r.readErr
}
frame := r.frames[0]
r.frames = r.frames[1:]
return frame, nil
}
func (r *fakeVideoReader) Close() error {
r.closed = true
return r.closeErr
}
type fakeVideoSink struct {
frames []VideoFrame
err error
}
func (s *fakeVideoSink) ConsumeVideo(_ context.Context, frame VideoFrame) error {
s.frames = append(s.frames, frame)
return s.err
}
func TestRunVideoAttemptOpenFailure(t *testing.T) {
openErr := errors.New("open failed")
factory := &fakeVideoFactory{err: openErr}
sink := &fakeVideoSink{}
err := runVideoAttempt(
context.Background(),
factory,
sink,
FeedConfig{},
)
if !errors.Is(err, openErr) {
t.Fatalf("runVideoAttempt() error = %v, want %v", err, openErr)
}
if factory.calls != 1 {
t.Errorf("factory calls = %d, want 1", factory.calls)
}
if len(sink.frames) != 0 {
t.Fatalf("consumed frame count = %d, want 0", len(sink.frames))
}
}
func TestRunVideoAttemptConsumesFrameThenReturnsReadFailure(t *testing.T) {
readErr := errors.New("read failed")
wantFrame := VideoFrame{
Index: 42,
Width: 1920,
Height: 1080,
Stride: 5120,
Size: 5120 * 1080,
Invalid: false,
Payload: []byte{1, 2, 3, 4},
}
reader := &fakeVideoReader{
frames: []VideoFrame{wantFrame},
readErr: readErr,
}
sink := &fakeVideoSink{}
err := runVideoAttempt(
context.Background(),
&fakeVideoFactory{reader: reader},
sink,
FeedConfig{},
)
if !errors.Is(err, readErr) {
t.Fatalf("runVideoAttempt() error = %v, want %v", err, readErr)
}
if !reader.closed {
t.Fatal("reader was not closed")
}
if reader.readCalls != 2 {
t.Errorf("read calls = %d, want 2", reader.readCalls)
}
if len(sink.frames) != 1 {
t.Fatalf("consumed frame count = %d, want 1", len(sink.frames))
}
gotFrame := sink.frames[0]
if gotFrame.Index != wantFrame.Index ||
gotFrame.Width != wantFrame.Width ||
gotFrame.Height != wantFrame.Height ||
gotFrame.Stride != wantFrame.Stride ||
gotFrame.Size != wantFrame.Size ||
gotFrame.Invalid != wantFrame.Invalid {
t.Errorf("consumed frame metadata = %+v, want %+v", gotFrame, wantFrame)
}
if len(gotFrame.Payload) == 0 {
t.Fatal("consumed payload is empty")
}
if &gotFrame.Payload[0] != &wantFrame.Payload[0] {
t.Fatal("video payload was copied")
}
}
func TestRunVideoAttemptSinkFailureStopsReadingAndCloses(t *testing.T) {
sinkErr := errors.New("renderer unavailable")
reader := &fakeVideoReader{
frames: []VideoFrame{
{Index: 1, Payload: []byte{1}},
{Index: 2, Payload: []byte{2}},
},
}
sink := &fakeVideoSink{err: sinkErr}
err := runVideoAttempt(
context.Background(),
&fakeVideoFactory{reader: reader},
sink,
FeedConfig{},
)
if !errors.Is(err, sinkErr) {
t.Fatalf("runVideoAttempt() error = %v, want %v", err, sinkErr)
}
var typedErr *videoSinkError
if !errors.As(err, &typedErr) {
t.Fatalf("runVideoAttempt() error type = %T, want *videoSinkError", err)
}
if reader.readCalls != 1 {
t.Errorf("read calls = %d, want 1", reader.readCalls)
}
if !reader.closed {
t.Fatal("reader was not closed")
}
}
func TestRunVideoAttemptCanceledRead(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
reader := &fakeVideoReader{
read: func(ctx context.Context) (VideoFrame, error) {
cancel()
return VideoFrame{}, ctx.Err()
},
}
err := runVideoAttempt(
ctx,
&fakeVideoFactory{reader: reader},
&fakeVideoSink{},
FeedConfig{},
)
if !errors.Is(err, context.Canceled) {
t.Fatalf("runVideoAttempt() error = %v, want context.Canceled", err)
}
if !reader.closed {
t.Fatal("reader was not closed")
}
}
func TestRunVideoAttemptJoinsReadAndCloseErrors(t *testing.T) {
readErr := errors.New("read failed")
closeErr := errors.New("close failed")
reader := &fakeVideoReader{
readErr: readErr,
closeErr: closeErr,
}
err := runVideoAttempt(
context.Background(),
&fakeVideoFactory{reader: reader},
&fakeVideoSink{},
FeedConfig{},
)
if !errors.Is(err, readErr) {
t.Errorf("runVideoAttempt() error does not contain read error: %v", err)
}
if !errors.Is(err, closeErr) {
t.Errorf("runVideoAttempt() error does not contain close error: %v", err)
}
}