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