Files
2026-09-01 20:05:00 +03:00

222 lines
6.2 KiB
Go

package playback
import (
"errors"
"sync"
"testing"
)
func TestStatusStoreSnapshotUnknownUnit(t *testing.T) {
store := NewStatusStore()
status, ok := store.Snapshot(UnitVideo)
if ok {
t.Fatalf("Snapshot() = %#v, true; want false", status)
}
}
func TestStatusStoreKeepsUnitsIndependent(t *testing.T) {
store := NewStatusStore()
wantVideo := Status{
Unit: UnitVideo,
State: StateReconnecting,
Attempt: 3,
FailedAttempts: 2,
Err: errors.New("video unavailable"),
}
wantAudio := Status{
Unit: UnitAudio,
State: StatePlaying,
Attempt: 1,
}
store.Observe(wantVideo)
store.Observe(wantAudio)
if got, ok := store.Snapshot(UnitVideo); !ok || got != wantVideo {
t.Fatalf("video Snapshot() = %#v, %t; want %#v, true", got, ok, wantVideo)
}
if got, ok := store.Snapshot(UnitAudio); !ok || got != wantAudio {
t.Fatalf("audio Snapshot() = %#v, %t; want %#v, true", got, ok, wantAudio)
}
}
func TestStatusStoreObserveReplacesLatestStatus(t *testing.T) {
store := NewStatusStore()
store.Observe(Status{Unit: UnitVideo, State: StateConnecting, Attempt: 1})
want := Status{Unit: UnitVideo, State: StatePlaying, Attempt: 2}
store.Observe(want)
got, ok := store.Snapshot(UnitVideo)
if !ok || got != want {
t.Fatalf("Snapshot() = %#v, %t; want %#v, true", got, ok, want)
}
}
func TestStatusStoreClearOnlySelectedUnit(t *testing.T) {
store := NewStatusStore()
wantAudio := Status{Unit: UnitAudio, State: StatePlaying}
store.Observe(Status{Unit: UnitVideo, State: StatePlaying})
store.Observe(wantAudio)
store.Clear(UnitVideo)
if status, ok := store.Snapshot(UnitVideo); ok {
t.Fatalf("video Snapshot() = %#v, true after Clear", status)
}
if got, ok := store.Snapshot(UnitAudio); !ok || got != wantAudio {
t.Fatalf("audio Snapshot() = %#v, %t; want %#v, true", got, ok, wantAudio)
}
}
func TestStatusStoreConcurrentAccess(t *testing.T) {
store := NewStatusStore()
const iterations = 1000
var writers sync.WaitGroup
for _, unit := range []Unit{UnitVideo, UnitAudio, UnitSync} {
unit := unit
writers.Add(1)
go func() {
defer writers.Done()
for attempt := 1; attempt <= iterations; attempt++ {
store.Observe(Status{
Unit: unit,
State: StatePlaying,
Attempt: attempt,
})
store.Snapshot(unit)
}
}()
}
writers.Wait()
for _, unit := range []Unit{UnitVideo, UnitAudio, UnitSync} {
status, ok := store.Snapshot(unit)
if !ok {
t.Fatalf("Snapshot(%v) not found", unit)
}
if status.Attempt != iterations {
t.Fatalf(
"Snapshot(%v) attempt = %d, want %d",
unit,
status.Attempt,
iterations,
)
}
}
}
func TestStatusStoreNewGenerationClearsPreviousUnits(t *testing.T) {
store := NewStatusStore()
store.Observe(Status{Unit: UnitVideo, State: StatePlaying, Generation: 1})
store.Observe(Status{Unit: UnitAudio, State: StatePlaying, Generation: 1})
want := Status{Unit: UnitSync, State: StateConnecting, Generation: 2}
store.Observe(want)
if _, ok := store.Snapshot(UnitVideo); ok {
t.Fatal("video status survived generation change")
}
if _, ok := store.Snapshot(UnitAudio); ok {
t.Fatal("audio status survived generation change")
}
if got, ok := store.Snapshot(UnitSync); !ok || got != want {
t.Fatalf("sync Snapshot() = %#v, %t; want %#v, true", got, ok, want)
}
}
func TestStatusStoreIgnoresOlderGeneration(t *testing.T) {
store := NewStatusStore()
want := Status{Unit: UnitSync, State: StatePlaying, Generation: 3}
store.Observe(want)
store.Observe(Status{Unit: UnitVideo, State: StateIdle, Generation: 2})
if _, ok := store.Snapshot(UnitVideo); ok {
t.Fatal("older video status was stored")
}
if got, ok := store.Snapshot(UnitSync); !ok || got != want {
t.Fatalf("sync Snapshot() = %#v, %t; want %#v, true", got, ok, want)
}
}
func TestStatusStoreKeepsEqualGenerationUnitsIndependent(t *testing.T) {
store := NewStatusStore()
wantVideo := Status{Unit: UnitVideo, State: StatePlaying, Generation: 4}
wantAudio := Status{Unit: UnitAudio, State: StateReconnecting, Generation: 4}
store.Observe(wantVideo)
store.Observe(wantAudio)
if got, ok := store.Snapshot(UnitVideo); !ok || got != wantVideo {
t.Fatalf("video Snapshot() = %#v, %t", got, ok)
}
if got, ok := store.Snapshot(UnitAudio); !ok || got != wantAudio {
t.Fatalf("audio Snapshot() = %#v, %t", got, ok)
}
}
func TestStatusStoreSnapshotAll(t *testing.T) {
store := NewStatusStore()
wantVideo := Status{Unit: UnitVideo, State: StatePlaying, Generation: 5}
wantAudio := Status{Unit: UnitAudio, State: StateReconnecting, Generation: 5}
store.Observe(wantVideo)
store.Observe(wantAudio)
got := store.SnapshotAll()
if got.Generation != 5 {
t.Fatalf("SnapshotAll() generation = %d, want 5", got.Generation)
}
if !got.HasVideo || got.Video != wantVideo {
t.Fatalf("SnapshotAll() video = %#v, %v; want %#v, true", got.Video, got.HasVideo, wantVideo)
}
if !got.HasAudio || got.Audio != wantAudio {
t.Fatalf("SnapshotAll() audio = %#v, %v; want %#v, true", got.Audio, got.HasAudio, wantAudio)
}
if got.HasSync {
t.Fatalf("SnapshotAll() HasSync = true, want false")
}
}
func TestStatusStoreSnapshotAllClearsOldGenerationUnits(t *testing.T) {
store := NewStatusStore()
store.Observe(Status{Unit: UnitVideo, State: StatePlaying, Generation: 2})
store.Observe(Status{Unit: UnitAudio, State: StatePlaying, Generation: 2})
wantSync := Status{Unit: UnitSync, State: StateConnecting, Generation: 3}
store.Observe(wantSync)
got := store.SnapshotAll()
if got.Generation != 3 {
t.Fatalf("SnapshotAll() generation = %d, want 3", got.Generation)
}
if got.HasVideo || got.HasAudio {
t.Fatalf("SnapshotAll() retained old units: %#v", got)
}
if !got.HasSync || got.Sync != wantSync {
t.Fatalf("SnapshotAll() sync = %#v, %v; want %#v, true", got.Sync, got.HasSync, wantSync)
}
}
func TestStatusStoreSnapshotAllConcurrentObserve(t *testing.T) {
store := NewStatusStore()
done := make(chan struct{})
go func() {
defer close(done)
for generation := uint64(1); generation <= 1000; generation++ {
store.Observe(Status{
Unit: Unit(generation % 3),
State: StatePlaying,
Generation: generation,
})
}
}()
for {
select {
case <-done:
_ = store.SnapshotAll()
return
default:
_ = store.SnapshotAll()
}
}
}