github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/container/queue/chunkqueue_test.go (about)

     1  // Copyright 2022 PingCAP, Inc.
     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  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package queue
    15  
    16  import (
    17  	"fmt"
    18  	"math/rand"
    19  	"testing"
    20  
    21  	"github.com/edwingeng/deque"
    22  	"github.com/labstack/gommon/random"
    23  	"github.com/stretchr/testify/require"
    24  )
    25  
    26  const (
    27  	testCaseSize = 10007
    28  )
    29  
    30  func TestChunkQueueCommon(t *testing.T) {
    31  	t.Parallel()
    32  
    33  	type ZeroSizeType struct{}
    34  	require.NotNil(t, NewChunkQueue[ZeroSizeType]())
    35  
    36  	q := NewChunkQueue[int]()
    37  
    38  	// simple enqueue dequeue
    39  	x := rand.Int()
    40  	q.Push(x)
    41  	require.Equal(t, q.Len(), 1)
    42  	require.Equal(t, x, q.Peek(0))
    43  	v, ok := q.Pop()
    44  	require.Equal(t, v, x)
    45  	require.True(t, ok)
    46  
    47  	// PushMany & PopMany
    48  	elements := make([]int, 0, testCaseSize)
    49  	require.True(t, q.Empty())
    50  	for i := 0; i < testCaseSize; i++ {
    51  		elements = append(elements, i)
    52  	}
    53  	q.PushMany(elements...)
    54  	require.Equal(t, testCaseSize, q.Len(), q.Len())
    55  	vals, ok := q.PopMany(testCaseSize * 3 / 4)
    56  	require.True(t, ok)
    57  	for i, v := range vals {
    58  		require.Equal(t, elements[i], v)
    59  	}
    60  	require.Equal(t, testCaseSize-testCaseSize*3/4, q.Len())
    61  	// Clear
    62  	q.Clear()
    63  	require.True(t, q.Empty())
    64  
    65  	// Element Access
    66  	q.PushMany(elements...)
    67  	require.Equal(t, testCaseSize, q.Len())
    68  	require.False(t, q.Empty())
    69  	for j := 0; j < testCaseSize; j++ {
    70  		i := rand.Intn(testCaseSize)
    71  		v := q.Peek(i)
    72  		it := q.GetIterator(i)
    73  		itv := it.Value()
    74  		require.Equal(t, it.Index(), itv, v)
    75  
    76  		it.Set(i + 1)
    77  		require.Equal(t, it.Value(), v+1)
    78  		q.Replace(i, i)
    79  		require.Equal(t, it.Value(), v)
    80  	}
    81  	require.Panics(t, func() {
    82  		_ = q.Peek(-1)
    83  	})
    84  	require.Panics(t, func() {
    85  		q.Replace(testCaseSize, 0)
    86  	})
    87  	tail, ok := q.Tail()
    88  	require.Equal(t, tail, testCaseSize-1)
    89  	require.True(t, ok)
    90  
    91  	// Pop one by one
    92  	for i := 0; i < testCaseSize; i++ {
    93  		h, ok := q.Head()
    94  		require.Equal(t, i, h)
    95  		require.True(t, ok)
    96  		v, ok = q.Pop()
    97  		require.True(t, ok)
    98  		require.Equal(t, v, i)
    99  	}
   100  
   101  	// EmptyOperation
   102  	require.True(t, q.Empty())
   103  	require.Equal(t, 0, q.Len())
   104  	_, ok = q.Pop()
   105  	require.False(t, ok)
   106  	_, ok = q.Head()
   107  	require.False(t, ok)
   108  	_, ok = q.Tail()
   109  	require.False(t, ok)
   110  }
   111  
   112  func TestChunkQueueRandom(t *testing.T) {
   113  	t.Parallel()
   114  
   115  	getInt := func() int {
   116  		return rand.Int()
   117  	}
   118  	doRandomTest[int](t, getInt)
   119  	getFloat := func() float64 {
   120  		return rand.Float64() + rand.Float64()
   121  	}
   122  
   123  	doRandomTest[float64](t, getFloat)
   124  
   125  	getBool := func() bool {
   126  		return rand.Int()%2 == 1
   127  	}
   128  	doRandomTest[bool](t, getBool)
   129  
   130  	getString := func() string {
   131  		return random.String(16)
   132  	}
   133  	doRandomTest[string](t, getString)
   134  
   135  	type MyType struct {
   136  		x int
   137  		y string
   138  	}
   139  	getMyType := func() MyType {
   140  		return MyType{1, random.String(64)}
   141  	}
   142  
   143  	doRandomTest[MyType](t, getMyType)
   144  
   145  	getMyTypePtr := func() *MyType {
   146  		return &MyType{1, random.String(64)}
   147  	}
   148  
   149  	doRandomTest[*MyType](t, getMyTypePtr)
   150  }
   151  
   152  func doRandomTest[T comparable](t *testing.T, getVal func() T) {
   153  	const (
   154  		opPushOne = iota
   155  		opPushMany
   156  		opPopOne
   157  		opPopMany
   158  		opPopAll
   159  	)
   160  
   161  	q := NewChunkQueue[T]()
   162  	slice := make([]T, 0, 100)
   163  	var val T
   164  	for i := 0; i < 100; i++ {
   165  		op := rand.Intn(4)
   166  		if i == 99 {
   167  			op = opPopAll
   168  		}
   169  		switch op {
   170  		case opPushOne:
   171  			val = getVal()
   172  			q.Push(val)
   173  			slice = append(slice, val)
   174  		case opPushMany:
   175  			n := rand.Intn(1024) + 1
   176  			vals := make([]T, n)
   177  			for j := 0; j < n; j++ {
   178  				vals = append(vals, getVal())
   179  			}
   180  			q.PushMany(vals...)
   181  			slice = append(slice, vals...)
   182  		case opPopOne:
   183  			if q.Empty() {
   184  				require.Equal(t, 0, q.Len(), len(slice))
   185  				_, ok := q.Pop()
   186  				require.False(t, ok)
   187  			} else {
   188  				v, ok := q.Pop()
   189  				require.True(t, ok)
   190  				require.Equal(t, slice[0], v)
   191  				slice = slice[1:]
   192  			}
   193  		case opPopMany:
   194  			if q.Empty() {
   195  				require.True(t, len(slice) == 0)
   196  			} else {
   197  				n := rand.Intn(q.Len()) + 1
   198  				pops, ok := q.PopMany(n)
   199  				require.True(t, ok)
   200  				require.Equal(t, n, len(pops))
   201  				popSlice := slice[0:n]
   202  				slice = append(make([]T, 0, len(slice[n:])), slice[n:]...)
   203  
   204  				for i := 0; i < len(pops); i++ {
   205  					require.Equal(t, popSlice[i], pops[i])
   206  				}
   207  			}
   208  		case opPopAll:
   209  			pops := q.PopAll()
   210  			require.Equal(t, len(pops), len(slice))
   211  			for i := 0; i < len(pops); i++ {
   212  				require.Equal(t, slice[i], pops[i])
   213  			}
   214  			slice = slice[:0]
   215  			q.Clear()
   216  		}
   217  
   218  		require.Equal(t, q.Len(), len(slice))
   219  		require.Nil(t, q.firstChunk().prev)
   220  		require.Nil(t, q.lastChunk().next)
   221  		freeSpace := q.Cap() - q.Len()
   222  		if q.Empty() {
   223  			require.True(t, freeSpace > 0 && freeSpace <= q.chunkLength)
   224  		} else {
   225  			require.True(t, freeSpace >= 0 && freeSpace < q.chunkLength)
   226  		}
   227  	}
   228  }
   229  
   230  func TestExpand(t *testing.T) {
   231  	t.Parallel()
   232  	q := NewChunkQueue[int]()
   233  
   234  	for i := 0; i < testCaseSize; i++ {
   235  		q.Push(1)
   236  		require.Equal(t, 1, q.Len())
   237  		freeSpace := q.Cap() - q.Len()
   238  		require.True(t, freeSpace >= 0 && freeSpace < q.chunkLength)
   239  		p, ok := q.Pop()
   240  		require.True(t, ok)
   241  		require.Equal(t, 1, p)
   242  		require.True(t, q.Empty())
   243  	}
   244  }
   245  
   246  func TestDequeueMany(t *testing.T) {
   247  	t.Parallel()
   248  
   249  	q := NewChunkQueue[int]()
   250  	x := testCaseSize
   251  	for v := 0; v < x; v++ {
   252  		q.Push(v)
   253  	}
   254  	f := 0
   255  	for !q.Empty() {
   256  		l := rand.Intn(q.Len()/5 + 1)
   257  		if l == 0 {
   258  			l = 1
   259  		}
   260  		vals, ok := q.PopMany(l)
   261  		require.True(t, ok)
   262  		for i := 0; i < l; i++ {
   263  			require.Equal(t, f+i, vals[i])
   264  		}
   265  		f += len(vals)
   266  		require.True(t, len(vals) > 0 && len(vals) <= l)
   267  
   268  		freeSpace := q.Cap() - q.Len()
   269  		if q.Empty() {
   270  			require.True(t, freeSpace > 0 && freeSpace <= q.chunkLength)
   271  		} else {
   272  			require.True(t, freeSpace >= 0 && freeSpace < q.chunkLength)
   273  		}
   274  	}
   275  	require.Equal(t, f, testCaseSize)
   276  	require.True(t, q.Empty())
   277  }
   278  
   279  func TestRange(t *testing.T) {
   280  	t.Parallel()
   281  
   282  	q := NewChunkQueue[int]()
   283  	for i := 0; i < testCaseSize; i++ {
   284  		q.Push(i)
   285  	}
   286  
   287  	var x int
   288  	q.Range(func(v int) bool {
   289  		if v >= 1000 {
   290  			return false
   291  		}
   292  		x++
   293  		return true
   294  	})
   295  	require.Equal(t, 1000, x)
   296  
   297  	q.RangeWithIndex(func(i int, v int) bool {
   298  		require.Equal(t, i, v)
   299  		if i >= 1000 {
   300  			return false
   301  		}
   302  		x++
   303  		return true
   304  	})
   305  	require.Equal(t, 2000, x)
   306  
   307  	q.RangeAndPop(func(v int) bool {
   308  		return v < 1000
   309  	})
   310  
   311  	require.Equal(t, testCaseSize-1000, q.Len())
   312  
   313  	process := 0
   314  	q.RangeAndPop(func(v int) bool {
   315  		process = v
   316  		return true
   317  	})
   318  	require.Equal(t, testCaseSize-1, process)
   319  	require.True(t, q.Empty())
   320  
   321  	require.NotPanics(t, func() {
   322  		q.RangeAndPop(func(v int) bool {
   323  			return true
   324  		})
   325  	})
   326  }
   327  
   328  func TestRangeAndPop(t *testing.T) {
   329  	t.Parallel()
   330  
   331  	q := NewChunkQueue[int]()
   332  	for i := 0; i < testCaseSize; i++ {
   333  		q.Push(i)
   334  	}
   335  
   336  	var target int
   337  	q.Range(func(v int) bool {
   338  		if v >= 1000 {
   339  			target = v
   340  			return false
   341  		}
   342  		return true
   343  	})
   344  	require.Equal(t, 1000, target)
   345  
   346  	q.RangeWithIndex(func(i int, v int) bool {
   347  		require.Equal(t, i, v)
   348  		q.Pop()
   349  		return true
   350  	})
   351  	require.True(t, q.Empty())
   352  }
   353  
   354  func BenchmarkPush(b *testing.B) {
   355  	b.Run("Push-ChunkQueue", func(b *testing.B) {
   356  		q := NewChunkQueueLeastCapacity[int](1024)
   357  		b.ResetTimer()
   358  		for i := 0; i < b.N; i++ {
   359  			q.Push(i)
   360  		}
   361  	})
   362  
   363  	b.Run("Push-Slice", func(b *testing.B) {
   364  		q := make([]int, 0, 1024)
   365  		b.ResetTimer()
   366  		for i := 0; i < b.N; i++ {
   367  			q = append(q, i)
   368  		}
   369  	})
   370  
   371  	b.Run("Push-3rdPartyDeque", func(b *testing.B) {
   372  		q := deque.NewDeque()
   373  		b.ResetTimer()
   374  		for i := 0; i < b.N; i++ {
   375  			q.PushBack(i)
   376  		}
   377  	})
   378  }
   379  
   380  func TestChunkQueuePushMany(t *testing.T) {
   381  	q := NewChunkQueueLeastCapacity[int](16)
   382  	n := testCaseSize
   383  	data := make([]int, 0, n)
   384  	cnt := 0
   385  	for i := 1; i < 10; i++ {
   386  		q.PushMany(data[:n]...)
   387  		cnt += n
   388  		freeSpace := q.Cap() - q.Len()
   389  		require.Equal(t, cnt, q.Len())
   390  		require.True(t, freeSpace >= 0 && freeSpace <= q.chunkLength)
   391  	}
   392  }
   393  
   394  func prepareSlice(n int) []int {
   395  	data := make([]int, 0, n)
   396  	for i := 0; i < n; i++ {
   397  		data = append(data, i)
   398  	}
   399  	return data
   400  }
   401  
   402  func prepareChunkQueue(n int) *ChunkQueue[int] {
   403  	q := NewChunkQueue[int]()
   404  	for i := 0; i < n; i++ {
   405  		q.Push(i)
   406  	}
   407  	return q
   408  }
   409  
   410  func BenchmarkPushMany(b *testing.B) {
   411  	b.Run("PushMany-ChunkDeque", func(b *testing.B) {
   412  		q := NewChunkQueueLeastCapacity[int](16)
   413  		n := b.N
   414  		data := prepareSlice(n)
   415  
   416  		b.ResetTimer()
   417  		for i := 1; i < 10; i++ {
   418  			q.PushMany(data[:n]...)
   419  		}
   420  	})
   421  
   422  	b.Run("PushMany-ChunkDeque-OneByOne", func(b *testing.B) {
   423  		q := NewChunkQueueLeastCapacity[int](16)
   424  		n := b.N
   425  		data := prepareSlice(n)
   426  
   427  		b.ResetTimer()
   428  		for i := 1; i < 10; i++ {
   429  			for _, v := range data {
   430  				q.Push(v)
   431  			}
   432  		}
   433  	})
   434  
   435  	b.Run("PushMany-Slice", func(b *testing.B) {
   436  		q := make([]int, 0, 16)
   437  		n := b.N
   438  		data := prepareSlice(n)
   439  
   440  		b.ResetTimer()
   441  		for i := 1; i < 10; i++ {
   442  			q = append(q, data[:n]...)
   443  		}
   444  	})
   445  
   446  	b.Run("PushMany-3rdPartyDeque", func(b *testing.B) {
   447  		q := deque.NewDeque()
   448  		n := b.N
   449  		data := prepareSlice(n)
   450  
   451  		b.ResetTimer()
   452  		for i := 1; i < 10; i++ {
   453  			for _, v := range data {
   454  				q.PushBack(v)
   455  			}
   456  		}
   457  	})
   458  }
   459  
   460  func BenchmarkPopMany(b *testing.B) {
   461  	b.Run("PopMany-ChunkDeque", func(b *testing.B) {
   462  		x := b.N
   463  		q := prepareChunkQueue(x)
   464  		ls := []int{x / 5, x / 5, x / 5, x / 5, x - x/5*4}
   465  		b.ResetTimer()
   466  		for _, l := range ls {
   467  			vals, ok := q.PopMany(l)
   468  			if !ok || len(vals) != l {
   469  				panic("error")
   470  			}
   471  		}
   472  	})
   473  
   474  	b.Run("PopMany-Slice", func(b *testing.B) {
   475  		x := b.N
   476  		q := prepareSlice(x)
   477  		ls := []int{x / 5, x / 5, x / 5, x / 5, x - x/5*4}
   478  		b.ResetTimer()
   479  		for _, l := range ls {
   480  			vals := q[:l]
   481  			if len(vals) != l {
   482  				panic("error")
   483  			}
   484  			q = append(make([]int, 0, len(q[l:])), q[l:]...)
   485  		}
   486  	})
   487  
   488  	b.Run("PopMany-3rdPartyDeque", func(b *testing.B) {
   489  		x := b.N
   490  		q := deque.NewDeque()
   491  		for i := 0; i < x; i++ {
   492  			q.Enqueue(i)
   493  		}
   494  		ls := []int{x / 5, x / 5, x / 5, x / 5, x - x/5*4}
   495  		b.ResetTimer()
   496  		for _, l := range ls {
   497  			if l > 0 {
   498  				vals := q.DequeueMany(l)
   499  				if len(vals) != l {
   500  					fmt.Println(l, len(vals), vals[0])
   501  					panic("error")
   502  				}
   503  			}
   504  		}
   505  	})
   506  }
   507  
   508  func BenchmarkChunkQueueLoopPop(b *testing.B) {
   509  	b.Run("ChunkQueue-RangeAndPop", func(b *testing.B) {
   510  		x := b.N
   511  		q := prepareChunkQueue(x)
   512  		b.ResetTimer()
   513  
   514  		q.RangeAndPop(func(val int) bool {
   515  			return val >= 0
   516  		})
   517  
   518  		require.True(b, q.Empty())
   519  	})
   520  
   521  	b.Run("ChunkQueue-IterateAndPop", func(b *testing.B) {
   522  		x := b.N
   523  		q := prepareChunkQueue(x)
   524  		b.ResetTimer()
   525  
   526  		for it := q.Begin(); it.Valid(); {
   527  			if it.Value() >= 0 {
   528  				it.Next()
   529  				q.Pop()
   530  			} else {
   531  				break
   532  			}
   533  		}
   534  		require.True(b, q.Empty())
   535  	})
   536  
   537  	b.Run("ChunkQueue-PeekAndPop", func(b *testing.B) {
   538  		x := b.N
   539  		q := prepareChunkQueue(x)
   540  		b.ResetTimer()
   541  
   542  		q.RangeAndPop(func(val int) bool {
   543  			return val < 0
   544  		})
   545  		for i := 0; i < x; i++ {
   546  			v, _ := q.Head()
   547  			if v < 0 {
   548  				break
   549  			}
   550  			q.Pop()
   551  		}
   552  		require.True(b, q.Empty())
   553  	})
   554  }