package pool_test import ( "code.squareroundforest.org/arpio/pool" "code.squareroundforest.org/arpio/syncbus" "code.squareroundforest.org/arpio/times" "errors" "testing" "time" ) var errTest = errors.New("test error") func TestPool(t *testing.T) { t.Run("initial stats", func(t *testing.T) { alloc := func() ([]byte, error) { return make([]byte, 1<<9), nil } p := pool.Make(alloc, nil, pool.Options{Algo: pool.NoShrink()}) s := p.Stats() e := pool.Stats{} if s != e { t.Fatal(s) } }) t.Run("get when empty", func(t *testing.T) { alloc := func() ([]byte, error) { return make([]byte, 1<<9), nil } p := pool.Make(alloc, nil, pool.Options{Algo: pool.NoShrink()}) b, err := p.Get() if err != nil { t.Fatal(err) } if len(b) != 1<<9 { t.Fatal(len(b)) } s := p.Stats() e := pool.Stats{Alloc: 1, Get: 1, Active: 1} if s != e { t.Fatal(s) } }) t.Run("get pooled own", func(t *testing.T) { alloc := func() ([]byte, error) { return make([]byte, 1<<9), nil } p := pool.Make(alloc, nil, pool.Options{Algo: pool.NoShrink()}) b, err := p.Get() if err != nil { t.Fatal(err) } if len(b) != 1<<9 { t.Fatal(len(b)) } p.Put(b) b, err = p.Get() if err != nil { t.Fatal(err) } if len(b) != 1<<9 { t.Fatal(len(b)) } s := p.Stats() e := pool.Stats{Alloc: 1, Get: 2, Put: 1, Active: 1} if s != e { t.Fatal(s) } }) t.Run("get pooled foreign", func(t *testing.T) { alloc := func() ([]byte, error) { return make([]byte, 1<<9), nil } p := pool.Make(alloc, nil, pool.Options{Algo: pool.NoShrink()}) p.Put(make([]byte, 1<<6)) b, err := p.Get() if err != nil { t.Fatal(err) } if len(b) != 1<<6 { t.Fatal(len(b)) } s := p.Stats() e := pool.Stats{Get: 1, Put: 1, Active: 1} if s != e { t.Fatal(s) } }) t.Run("get own alloc not available", func(t *testing.T) { p := pool.Make[[]byte](nil, nil, pool.Options{Algo: pool.NoShrink()}) _, err := p.Get() if !errors.Is(err, pool.ErrEmpty) { t.Fatal(err) } s := p.Stats() e := pool.Stats{Alloc: 1, Get: 1} if s != e { t.Fatal(s) } }) t.Run("get foreign no alloc", func(t *testing.T) { p := pool.Make[[]byte](nil, nil, pool.Options{Algo: pool.NoShrink()}) p.Put(make([]byte, 1<<6)) b, err := p.Get() if err != nil { t.Fatal(err) } if len(b) != 1<<6 { t.Fatal(len(b)) } s := p.Stats() e := pool.Stats{Get: 1, Put: 1, Active: 1} if s != e { t.Fatal(s) } }) t.Run("allocation error", func(t *testing.T) { alloc := func() ([]byte, error) { return nil, errTest } p := pool.Make(alloc, nil, pool.Options{Algo: pool.NoShrink()}) _, err := p.Get() if !errors.Is(err, errTest) { t.Fatal(err) } }) t.Run("put foreign not empty", func(t *testing.T) { p := pool.Make[[]byte](nil, nil, pool.Options{Algo: pool.NoShrink()}) p.Put(make([]byte, 1<<6)) p.Put(make([]byte, 1<<6)) b, err := p.Get() if err != nil { t.Fatal(err) } if len(b) != 1<<6 { t.Fatal(len(b)) } s := p.Stats() e := pool.Stats{Get: 1, Put: 2, Active: 1, Idle: 1} if s != e { t.Fatal(s) } }) t.Run("load items", func(t *testing.T) { p := pool.Make[[]byte](nil, nil, pool.Options{Algo: pool.NoShrink()}) l := make([][]byte, 9) for i := 0; i < len(l); i++ { l[i] = make([]byte, 1<<9) } p.Load(l) s := p.Stats() if s.Idle != 9 { t.Fatal(s) } }) t.Run("release on put no free", func(t *testing.T) { p := pool.Make[[]byte](nil, nil, pool.Options{Algo: pool.Max(2)}) p.Put(make([]byte, 1<<9)) p.Put(make([]byte, 1<<9)) p.Put(make([]byte, 1<<9)) s := p.Stats() e := pool.Stats{Put: 3, Idle: 2, Free: 1} if s != e { t.Fatal(s) } }) t.Run("release on put with free", func(t *testing.T) { var freeCount int f := func([]byte) { freeCount++ } p := pool.Make(nil, f, pool.Options{Algo: pool.Max(2)}) p.Put(make([]byte, 1<<9)) p.Put(make([]byte, 1<<9)) p.Put(make([]byte, 1<<9)) if freeCount != 1 { t.Fatal(freeCount) } s := p.Stats() e := pool.Stats{Put: 3, Idle: 2, Free: 1} if s != e { t.Fatal(s) } }) t.Run("release all no free", func(t *testing.T) { p := pool.Make[[]byte](nil, nil, pool.Options{Algo: pool.NoShrink()}) p.Put(make([]byte, 1<<9)) p.Put(make([]byte, 1<<9)) p.Put(make([]byte, 1<<9)) p.Free() s := p.Stats() e := pool.Stats{Put: 3, Idle: 0, Free: 3} if s != e { t.Fatal(s) } }) t.Run("release all with free", func(t *testing.T) { var freeCount int f := func([]byte) { freeCount++ } p := pool.Make[[]byte](nil, f, pool.Options{Algo: pool.NoShrink()}) p.Put(make([]byte, 1<<9)) p.Put(make([]byte, 1<<9)) p.Put(make([]byte, 1<<9)) p.Free() if freeCount != 3 { t.Fatal(freeCount) } s := p.Stats() e := pool.Stats{Put: 3, Idle: 0, Free: 3} if s != e { t.Fatal(s) } }) t.Run("release all when empty", func(t *testing.T) { p := pool.Make[[]byte](nil, nil, pool.Options{Algo: pool.NoShrink()}) p.Free() s := p.Stats() var e pool.Stats if s != e { t.Fatal(s) } }) t.Run("release on timeout no free", func(t *testing.T) { c := times.Test() b := syncbus.New(time.Second) o := pool.Options{ Algo: pool.Timeout(3 * time.Millisecond), Clock: c, TestBus: b, } p := pool.Make[[]byte](nil, nil, o) p.Put(make([]byte, 1<<9)) if err := b.Wait("background-job-waiting"); err != nil { t.Fatal(err) } c.Pass(2 * time.Millisecond) p.Put(make([]byte, 1<<9)) c.Pass(2 * time.Millisecond) if err := b.Wait("free-idle-done"); err != nil { t.Fatal(err) } s := p.Stats() e := pool.Stats{Put: 2, Idle: 1, Free: 1} if s != e { t.Fatal(s) } }) t.Run("release on timeout with free", func(t *testing.T) { var freeCount int f := func([]byte) { freeCount++ } c := times.Test() b := syncbus.New(time.Second) o := pool.Options{ Algo: pool.Timeout(3 * time.Millisecond), Clock: c, TestBus: b, } p := pool.Make[[]byte](nil, f, o) p.Put(make([]byte, 1<<9)) if err := b.Wait("background-job-waiting"); err != nil { t.Fatal(err) } c.Pass(2 * time.Millisecond) p.Put(make([]byte, 1<<9)) c.Pass(2 * time.Millisecond) if err := b.Wait("free-idle-done"); err != nil { t.Fatal(err) } s := p.Stats() e := pool.Stats{Put: 2, Idle: 1, Free: 1} if s != e || freeCount != 1 { t.Fatal(s, freeCount) } }) t.Run("use default algo", func(t *testing.T) { alloc := func() ([]byte, error) { return make([]byte, 1<<9), nil } p := pool.Make(alloc, nil, pool.Options{}) var bs [][]byte for i := 0; i < 9; i++ { b, err := p.Get() if err != nil { t.Fatal(err) } bs = append(bs, b) } for _, b := range bs[:2*len(bs)/3] { p.Put(b) } s := p.Stats() e := pool.Stats{Alloc: 9, Get: 9, Put: 6, Active: 3, Idle: 6, Free: 0} if s != e { t.Fatal(s) } }) }