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  }