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) } }