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 }