mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
test(artwork): convert non-synctest timing tests to Ginkgo specs
TestArtworkBackoffSchedule and TestArtworkWorkerRunNoLeak needed no real *testing.T (no synctest), so move them into worker_test.go as Ginkgo specs. TestArtworkBreakerHalfOpen stays plain since testing/synctest requires a real *testing.T, matching core/scrobbler's precedent.
This commit is contained in:
parent
55608b2d20
commit
ba2290af6d
@ -14,6 +14,7 @@ import (
|
||||
"github.com/navidrome/navidrome/tests"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
// reenqueueOnDequeue simulates a concurrent scan Enqueue between DequeueBatch and the
|
||||
@ -304,5 +305,58 @@ var _ = Describe("Worker", func() {
|
||||
cancel()
|
||||
Eventually(done, time.Second).Should(Receive(BeNil()))
|
||||
})
|
||||
|
||||
It("does not leak goroutines after Run exits", func() {
|
||||
DeferCleanup(configtest.SetupConfig())
|
||||
|
||||
ignore := goleak.IgnoreCurrent()
|
||||
DeferCleanup(func() { goleak.VerifyNone(GinkgoT(), ignore) })
|
||||
|
||||
localDS := &tests.MockDataStore{MockedArtworkQueue: tests.CreateMockArtworkQueueRepo()}
|
||||
lw := NewWorker(localDS, NewImageStore(GinkgoT().TempDir()), &fakeExternalProvider{}, tests.NewMockFFmpeg(""))
|
||||
|
||||
runCtx, cancel := context.WithCancel(ctx)
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- lw.Run(runCtx) }()
|
||||
|
||||
time.Sleep(20 * time.Millisecond) // let the loop settle on the idle select
|
||||
cancel()
|
||||
Eventually(done, 2*time.Second).Should(Receive(BeNil()))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("backoff", func() {
|
||||
It("returns the expected schedule with no jitter", func() {
|
||||
for _, c := range []struct {
|
||||
attempts int
|
||||
want time.Duration
|
||||
}{
|
||||
{0, 5 * time.Minute},
|
||||
{1, 20 * time.Minute},
|
||||
{2, 80 * time.Minute},
|
||||
{3, 320 * time.Minute},
|
||||
{4, 1280 * time.Minute},
|
||||
{5, 48 * time.Hour},
|
||||
{6, 48 * time.Hour},
|
||||
} {
|
||||
Expect(backoffFor(c.attempts, 0)).To(Equal(c.want), "attempt %d", c.attempts)
|
||||
}
|
||||
})
|
||||
|
||||
It("applies jitter proportionally", func() {
|
||||
base := backoffFor(2, 0)
|
||||
Expect(backoffFor(2, 0.2)).To(Equal(time.Duration(float64(base) * 1.2)))
|
||||
Expect(backoffFor(2, -0.2)).To(Equal(time.Duration(float64(base) * 0.8)))
|
||||
})
|
||||
|
||||
It("keeps random jitter within +/-20%", func() {
|
||||
lo := time.Duration(float64(320*time.Minute) * 0.8)
|
||||
hi := time.Duration(float64(320*time.Minute) * 1.2)
|
||||
for range 200 {
|
||||
d := backoff(3)
|
||||
Expect(d).To(BeNumerically(">=", lo))
|
||||
Expect(d).To(BeNumerically("<=", hi))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,48 +1,16 @@
|
||||
package artwork
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
|
||||
"github.com/navidrome/navidrome/conf/configtest"
|
||||
"github.com/navidrome/navidrome/tests"
|
||||
. "github.com/onsi/gomega"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
func TestArtworkBackoffSchedule(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
for _, c := range []struct {
|
||||
attempts int
|
||||
want time.Duration
|
||||
}{
|
||||
{0, 5 * time.Minute},
|
||||
{1, 20 * time.Minute},
|
||||
{2, 80 * time.Minute},
|
||||
{3, 320 * time.Minute},
|
||||
{4, 1280 * time.Minute},
|
||||
{5, 48 * time.Hour},
|
||||
{6, 48 * time.Hour},
|
||||
} {
|
||||
g.Expect(backoffFor(c.attempts, 0)).To(Equal(c.want), "attempt %d", c.attempts)
|
||||
}
|
||||
|
||||
base := backoffFor(2, 0)
|
||||
g.Expect(backoffFor(2, 0.2)).To(Equal(time.Duration(float64(base) * 1.2)))
|
||||
g.Expect(backoffFor(2, -0.2)).To(Equal(time.Duration(float64(base) * 0.8)))
|
||||
|
||||
lo := time.Duration(float64(320*time.Minute) * 0.8)
|
||||
hi := time.Duration(float64(320*time.Minute) * 1.2)
|
||||
for range 200 {
|
||||
d := backoff(3)
|
||||
g.Expect(d).To(BeNumerically(">=", lo))
|
||||
g.Expect(d).To(BeNumerically("<=", hi))
|
||||
}
|
||||
}
|
||||
|
||||
// Drives the real breaker state machine with the fake clock. Plain test: testing/synctest
|
||||
// needs a *testing.T, which Ginkgo doesn't give.
|
||||
func TestArtworkBreakerHalfOpen(t *testing.T) {
|
||||
synctest.Test(t, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
@ -69,27 +37,3 @@ func TestArtworkBreakerHalfOpen(t *testing.T) {
|
||||
g.Expect(b.allow()).To(BeTrue())
|
||||
})
|
||||
}
|
||||
|
||||
func TestArtworkWorkerRunNoLeak(t *testing.T) {
|
||||
ignore := goleak.IgnoreCurrent()
|
||||
defer goleak.VerifyNone(t, ignore)
|
||||
|
||||
t.Cleanup(configtest.SetupConfig())
|
||||
ds := &tests.MockDataStore{MockedArtworkQueue: tests.CreateMockArtworkQueueRepo()}
|
||||
w := NewWorker(ds, NewImageStore(t.TempDir()), &fakeExternalProvider{}, tests.NewMockFFmpeg(""))
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- w.Run(ctx) }()
|
||||
|
||||
time.Sleep(20 * time.Millisecond) // let the loop settle on the idle select
|
||||
cancel()
|
||||
select {
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
t.Fatalf("Run returned error on cancel: %v", err)
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("Run did not exit after context cancel")
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user