audio: run TestPlayOnIdleContext on the fake clock of synctest

The test was added after the survey in #3716, and waits on real time in
the same way.

Updates #3716
This commit is contained in:
dualfroz committed 2026-10-03 16:06:04 +02:00
1 parent d22b1a2b09
commit fab8fc22e0
1 file changed
+41 -39
+41 -39
View File
@@ -879,52 +879,54 @@ func TestPlayerErrorDoesNotStopOtherPlayers(t *testing.T) {
}
func TestPlayOnIdleContext(t *testing.T) {
setup()
defer teardown()
synctest.Test(t, func(t *testing.T) {
setup()
defer teardown()
// The first update creates the audio device, so that the players below play for real.
if err := audio.UpdateForTesting(); err != nil {
t.Fatal(err)
}
// waitUntil polls cond until it holds or the deadline passes, and reports whether it held.
// The deadline is generous because time.Sleep has a several-millisecond floor on browsers.
waitUntil := func(cond func() bool) bool {
deadline := time.Now().Add(10 * time.Second)
for time.Now().Before(deadline) {
if cond() {
return true
}
time.Sleep(5 * time.Millisecond)
}
return false
}
// A player's position is advanced only by the context, so a growing position shows that the
// context updates the player. The second round starts a player after the context has dropped
// the finished player of the first round, that is, after it went idle by itself.
for i := range 2 {
// Let the context settle into idling with no player to update.
time.Sleep(50 * time.Millisecond)
// 44100 [Hz] * 4 [bytes/sample] is one second of 16bit stereo audio.
p, err := context.NewPlayer(bytes.NewReader(make([]byte, 44100*4)))
if err != nil {
// The first update creates the audio device, so that the players below play for real.
if err := audio.UpdateForTesting(); err != nil {
t.Fatal(err)
}
p.Play()
if !waitUntil(func() bool { return p.Position() > 0 }) {
t.Errorf("round %d: Position() did not advance after Play on an idle context", i)
return
// waitUntil polls cond until it holds or the deadline passes, and reports whether it held.
// The test runs on the fake clock of synctest, so the deadline does not take real time.
waitUntil := func(cond func() bool) bool {
deadline := time.Now().Add(10 * time.Second)
for time.Now().Before(deadline) {
if cond() {
return true
}
time.Sleep(5 * time.Millisecond)
}
return false
}
// The next round needs the context to be idle again, which is only possible once the
// context has dropped the finished player.
if !waitUntil(func() bool { return audio.PlayersCountForTesting() == 0 }) {
t.Fatalf("round %d: time out: the player did not finish", i)
// A player's position is advanced only by the context, so a growing position shows that the
// context updates the player. The second round starts a player after the context has dropped
// the finished player of the first round, that is, after it went idle by itself.
for i := range 2 {
// Let the context settle into idling with no player to update.
time.Sleep(50 * time.Millisecond)
// 44100 [Hz] * 4 [bytes/sample] is one second of 16bit stereo audio.
p, err := context.NewPlayer(bytes.NewReader(make([]byte, 44100*4)))
if err != nil {
t.Fatal(err)
}
p.Play()
if !waitUntil(func() bool { return p.Position() > 0 }) {
t.Errorf("round %d: Position() did not advance after Play on an idle context", i)
return
}
// The next round needs the context to be idle again, which is only possible once the
// context has dropped the finished player.
if !waitUntil(func() bool { return audio.PlayersCountForTesting() == 0 }) {
t.Fatalf("round %d: time out: the player did not finish", i)
}
}
}
})
}
func callWithTimeout(t *testing.T, name string, f func() error) error {