From 25db2cb07520945e11afc9029dbc7139f063f8af Mon Sep 17 00:00:00 2001 From: Deluan Date: Sun, 20 Jun 2021 11:45:59 -0400 Subject: [PATCH] Add concurrency test for singleton --- utils/singleton/singleton_test.go | 62 +++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/utils/singleton/singleton_test.go b/utils/singleton/singleton_test.go index 716b725ab..d81616767 100644 --- a/utils/singleton/singleton_test.go +++ b/utils/singleton/singleton_test.go @@ -1,8 +1,12 @@ package singleton_test import ( + "sync" + "sync/atomic" "testing" + "github.com/google/uuid" + "github.com/navidrome/navidrome/utils/singleton" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -14,40 +18,58 @@ func TestSingleton(t *testing.T) { } var _ = Describe("Get", func() { - type T struct{ val int } - var wasCalled bool - var instance interface{} + type T struct{ id string } + var numInstances int constructor := func() interface{} { - wasCalled = true - return &T{} + numInstances++ + return &T{id: uuid.NewString()} } - BeforeEach(func() { - instance = singleton.Get(T{}, constructor) - }) - It("calls the constructor to create a new instance", func() { - Expect(wasCalled).To(BeTrue()) + instance := singleton.Get(T{}, constructor) + Expect(numInstances).To(Equal(1)) Expect(instance).To(BeAssignableToTypeOf(&T{})) }) It("does not call the constructor the next time", func() { - instance.(*T).val = 10 - wasCalled = false - + instance := singleton.Get(T{}, constructor) newInstance := singleton.Get(T{}, constructor) - Expect(newInstance.(*T).val).To(Equal(10)) - Expect(wasCalled).To(BeFalse()) + Expect(newInstance.(*T).id).To(Equal(instance.(*T).id)) + Expect(numInstances).To(Equal(1)) }) It("does not call the constructor even if a pointer is passed as the object", func() { - instance.(*T).val = 20 - wasCalled = false - + instance := singleton.Get(T{}, constructor) newInstance := singleton.Get(&T{}, constructor) - Expect(newInstance.(*T).val).To(Equal(20)) - Expect(wasCalled).To(BeFalse()) + Expect(newInstance.(*T).id).To(Equal(instance.(*T).id)) + Expect(numInstances).To(Equal(1)) + }) + + It("only calls the constructor once when called concurrently", func() { + const maxCalls = 2000 + var numCalls int32 + start := sync.WaitGroup{} + start.Add(1) + prepare := sync.WaitGroup{} + prepare.Add(maxCalls) + done := sync.WaitGroup{} + done.Add(maxCalls) + for i := 0; i < maxCalls; i++ { + go func() { + start.Wait() + singleton.Get(T{}, constructor) + atomic.AddInt32(&numCalls, 1) + done.Done() + }() + prepare.Done() + } + prepare.Wait() + start.Done() + done.Wait() + + Expect(numCalls).To(Equal(int32(maxCalls))) + Expect(numInstances).To(Equal(1)) }) })