github.com/maypok86/otter@v1.2.1/internal/queue/growable_test.go (about) 1 // Copyright (c) 2024 Alexey Mayshev. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package queue 16 17 import ( 18 "fmt" 19 "runtime" 20 "sync" 21 "sync/atomic" 22 "testing" 23 "time" 24 ) 25 26 const minCapacity = 4 27 28 func TestGrowable_PushPop(t *testing.T) { 29 const capacity = 10 30 g := NewGrowable[int](minCapacity, capacity) 31 for i := 0; i < capacity; i++ { 32 g.Push(i) 33 } 34 for i := 0; i < capacity; i++ { 35 if got := g.Pop(); got != i { 36 t.Fatalf("got %v, want %d", got, i) 37 } 38 } 39 } 40 41 func TestGrowable_ClearAndPopBlocksOnEmpty(t *testing.T) { 42 const capacity = 10 43 g := NewGrowable[int](minCapacity, capacity) 44 for i := 0; i < capacity; i++ { 45 g.Push(i) 46 } 47 48 g.Clear() 49 50 cdone := make(chan bool) 51 flag := int32(0) 52 go func() { 53 g.Pop() 54 if atomic.LoadInt32(&flag) == 0 { 55 t.Error("pop on empty queue didn't wait for pop") 56 } 57 cdone <- true 58 }() 59 time.Sleep(50 * time.Millisecond) 60 atomic.StoreInt32(&flag, 1) 61 g.Push(-1) 62 <-cdone 63 } 64 65 func TestGrowable_PushBlocksOnFull(t *testing.T) { 66 g := NewGrowable[string](1, 1) 67 g.Push("foo") 68 69 done := make(chan struct{}) 70 flag := int32(0) 71 go func() { 72 g.Push("bar") 73 if atomic.LoadInt32(&flag) == 0 { 74 t.Error("push on full queue didn't wait for pop") 75 } 76 done <- struct{}{} 77 }() 78 79 time.Sleep(50 * time.Millisecond) 80 atomic.StoreInt32(&flag, 1) 81 if got := g.Pop(); got != "foo" { 82 t.Fatalf("got %v, want foo", got) 83 } 84 <-done 85 } 86 87 func TestGrowable_PopBlocksOnEmpty(t *testing.T) { 88 g := NewGrowable[string](2, 2) 89 90 done := make(chan struct{}) 91 flag := int32(0) 92 go func() { 93 g.Pop() 94 if atomic.LoadInt32(&flag) == 0 { 95 t.Error("pop on empty queue didn't wait for push") 96 } 97 done <- struct{}{} 98 }() 99 100 time.Sleep(50 * time.Millisecond) 101 atomic.StoreInt32(&flag, 1) 102 g.Push("foobar") 103 <-done 104 } 105 106 func testGrowableConcurrent(t *testing.T, parallelism, ops, goroutines int) { 107 t.Helper() 108 runtime.GOMAXPROCS(parallelism) 109 110 g := NewGrowable[int](minCapacity, uint32(goroutines)) 111 var wg sync.WaitGroup 112 wg.Add(1) 113 csum := make(chan int, goroutines) 114 115 // run producers. 116 for i := 0; i < goroutines; i++ { 117 go func(n int) { 118 wg.Wait() 119 for j := n; j < ops; j += goroutines { 120 g.Push(j) 121 } 122 }(i) 123 } 124 125 // run consumers. 126 for i := 0; i < goroutines; i++ { 127 go func(n int) { 128 wg.Wait() 129 sum := 0 130 for j := n; j < ops; j += goroutines { 131 item := g.Pop() 132 sum += item 133 } 134 csum <- sum 135 }(i) 136 } 137 wg.Done() 138 // Wait for all the sums from producers. 139 sum := 0 140 for i := 0; i < goroutines; i++ { 141 s := <-csum 142 sum += s 143 } 144 145 expectedSum := ops * (ops - 1) / 2 146 if sum != expectedSum { 147 t.Errorf("calculated sum is wrong. got %d, want %d", sum, expectedSum) 148 } 149 } 150 151 func TestGrowable_Concurrent(t *testing.T) { 152 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1)) 153 154 n := 100 155 if testing.Short() { 156 n = 10 157 } 158 159 tests := []struct { 160 parallelism int 161 ops int 162 goroutines int 163 }{ 164 { 165 parallelism: 1, 166 ops: 100 * n, 167 goroutines: n, 168 }, 169 { 170 parallelism: 1, 171 ops: 1000 * n, 172 goroutines: 10 * n, 173 }, 174 { 175 parallelism: 4, 176 ops: 100 * n, 177 goroutines: n, 178 }, 179 { 180 parallelism: 4, 181 ops: 1000 * n, 182 goroutines: 10 * n, 183 }, 184 { 185 parallelism: 8, 186 ops: 100 * n, 187 goroutines: n, 188 }, 189 { 190 parallelism: 8, 191 ops: 1000 * n, 192 goroutines: 10 * n, 193 }, 194 } 195 196 for _, tt := range tests { 197 t.Run(fmt.Sprintf("testConcurrent-%d-%d", tt.parallelism, tt.ops), func(t *testing.T) { 198 testGrowableConcurrent(t, tt.parallelism, tt.ops, tt.goroutines) 199 }) 200 } 201 }