github.com/lingyao2333/mo-zero@v1.4.1/core/mr/mapreduce_test.go (about) 1 package mr 2 3 import ( 4 "context" 5 "errors" 6 "io" 7 "log" 8 "runtime" 9 "sync/atomic" 10 "testing" 11 "time" 12 13 "github.com/stretchr/testify/assert" 14 "go.uber.org/goleak" 15 ) 16 17 var errDummy = errors.New("dummy") 18 19 func init() { 20 log.SetOutput(io.Discard) 21 } 22 23 func TestFinish(t *testing.T) { 24 defer goleak.VerifyNone(t) 25 26 var total uint32 27 err := Finish(func() error { 28 atomic.AddUint32(&total, 2) 29 return nil 30 }, func() error { 31 atomic.AddUint32(&total, 3) 32 return nil 33 }, func() error { 34 atomic.AddUint32(&total, 5) 35 return nil 36 }) 37 38 assert.Equal(t, uint32(10), atomic.LoadUint32(&total)) 39 assert.Nil(t, err) 40 } 41 42 func TestFinishNone(t *testing.T) { 43 defer goleak.VerifyNone(t) 44 45 assert.Nil(t, Finish()) 46 } 47 48 func TestFinishVoidNone(t *testing.T) { 49 defer goleak.VerifyNone(t) 50 51 FinishVoid() 52 } 53 54 func TestFinishErr(t *testing.T) { 55 defer goleak.VerifyNone(t) 56 57 var total uint32 58 err := Finish(func() error { 59 atomic.AddUint32(&total, 2) 60 return nil 61 }, func() error { 62 atomic.AddUint32(&total, 3) 63 return errDummy 64 }, func() error { 65 atomic.AddUint32(&total, 5) 66 return nil 67 }) 68 69 assert.Equal(t, errDummy, err) 70 } 71 72 func TestFinishVoid(t *testing.T) { 73 defer goleak.VerifyNone(t) 74 75 var total uint32 76 FinishVoid(func() { 77 atomic.AddUint32(&total, 2) 78 }, func() { 79 atomic.AddUint32(&total, 3) 80 }, func() { 81 atomic.AddUint32(&total, 5) 82 }) 83 84 assert.Equal(t, uint32(10), atomic.LoadUint32(&total)) 85 } 86 87 func TestForEach(t *testing.T) { 88 const tasks = 1000 89 90 t.Run("all", func(t *testing.T) { 91 defer goleak.VerifyNone(t) 92 93 var count uint32 94 ForEach(func(source chan<- interface{}) { 95 for i := 0; i < tasks; i++ { 96 source <- i 97 } 98 }, func(item interface{}) { 99 atomic.AddUint32(&count, 1) 100 }, WithWorkers(-1)) 101 102 assert.Equal(t, tasks, int(count)) 103 }) 104 105 t.Run("odd", func(t *testing.T) { 106 defer goleak.VerifyNone(t) 107 108 var count uint32 109 ForEach(func(source chan<- interface{}) { 110 for i := 0; i < tasks; i++ { 111 source <- i 112 } 113 }, func(item interface{}) { 114 if item.(int)%2 == 0 { 115 atomic.AddUint32(&count, 1) 116 } 117 }) 118 119 assert.Equal(t, tasks/2, int(count)) 120 }) 121 122 t.Run("all", func(t *testing.T) { 123 defer goleak.VerifyNone(t) 124 125 assert.PanicsWithValue(t, "foo", func() { 126 ForEach(func(source chan<- interface{}) { 127 for i := 0; i < tasks; i++ { 128 source <- i 129 } 130 }, func(item interface{}) { 131 panic("foo") 132 }) 133 }) 134 }) 135 } 136 137 func TestGeneratePanic(t *testing.T) { 138 defer goleak.VerifyNone(t) 139 140 t.Run("all", func(t *testing.T) { 141 assert.PanicsWithValue(t, "foo", func() { 142 ForEach(func(source chan<- interface{}) { 143 panic("foo") 144 }, func(item interface{}) { 145 }) 146 }) 147 }) 148 } 149 150 func TestMapperPanic(t *testing.T) { 151 defer goleak.VerifyNone(t) 152 153 const tasks = 1000 154 var run int32 155 t.Run("all", func(t *testing.T) { 156 assert.PanicsWithValue(t, "foo", func() { 157 _, _ = MapReduce(func(source chan<- interface{}) { 158 for i := 0; i < tasks; i++ { 159 source <- i 160 } 161 }, func(item interface{}, writer Writer, cancel func(error)) { 162 atomic.AddInt32(&run, 1) 163 panic("foo") 164 }, func(pipe <-chan interface{}, writer Writer, cancel func(error)) { 165 }) 166 }) 167 assert.True(t, atomic.LoadInt32(&run) < tasks/2) 168 }) 169 } 170 171 func TestMapReduce(t *testing.T) { 172 defer goleak.VerifyNone(t) 173 174 tests := []struct { 175 name string 176 mapper MapperFunc 177 reducer ReducerFunc 178 expectErr error 179 expectValue interface{} 180 }{ 181 { 182 name: "simple", 183 expectErr: nil, 184 expectValue: 30, 185 }, 186 { 187 name: "cancel with error", 188 mapper: func(item interface{}, writer Writer, cancel func(error)) { 189 v := item.(int) 190 if v%3 == 0 { 191 cancel(errDummy) 192 } 193 writer.Write(v * v) 194 }, 195 expectErr: errDummy, 196 }, 197 { 198 name: "cancel with nil", 199 mapper: func(item interface{}, writer Writer, cancel func(error)) { 200 v := item.(int) 201 if v%3 == 0 { 202 cancel(nil) 203 } 204 writer.Write(v * v) 205 }, 206 expectErr: ErrCancelWithNil, 207 expectValue: nil, 208 }, 209 { 210 name: "cancel with more", 211 reducer: func(pipe <-chan interface{}, writer Writer, cancel func(error)) { 212 var result int 213 for item := range pipe { 214 result += item.(int) 215 if result > 10 { 216 cancel(errDummy) 217 } 218 } 219 writer.Write(result) 220 }, 221 expectErr: errDummy, 222 }, 223 } 224 225 t.Run("MapReduce", func(t *testing.T) { 226 for _, test := range tests { 227 t.Run(test.name, func(t *testing.T) { 228 if test.mapper == nil { 229 test.mapper = func(item interface{}, writer Writer, cancel func(error)) { 230 v := item.(int) 231 writer.Write(v * v) 232 } 233 } 234 if test.reducer == nil { 235 test.reducer = func(pipe <-chan interface{}, writer Writer, cancel func(error)) { 236 var result int 237 for item := range pipe { 238 result += item.(int) 239 } 240 writer.Write(result) 241 } 242 } 243 value, err := MapReduce(func(source chan<- interface{}) { 244 for i := 1; i < 5; i++ { 245 source <- i 246 } 247 }, test.mapper, test.reducer, WithWorkers(runtime.NumCPU())) 248 249 assert.Equal(t, test.expectErr, err) 250 assert.Equal(t, test.expectValue, value) 251 }) 252 } 253 }) 254 255 t.Run("MapReduce", func(t *testing.T) { 256 for _, test := range tests { 257 t.Run(test.name, func(t *testing.T) { 258 if test.mapper == nil { 259 test.mapper = func(item interface{}, writer Writer, cancel func(error)) { 260 v := item.(int) 261 writer.Write(v * v) 262 } 263 } 264 if test.reducer == nil { 265 test.reducer = func(pipe <-chan interface{}, writer Writer, cancel func(error)) { 266 var result int 267 for item := range pipe { 268 result += item.(int) 269 } 270 writer.Write(result) 271 } 272 } 273 274 source := make(chan interface{}) 275 go func() { 276 for i := 1; i < 5; i++ { 277 source <- i 278 } 279 close(source) 280 }() 281 282 value, err := MapReduceChan(source, test.mapper, test.reducer, WithWorkers(-1)) 283 assert.Equal(t, test.expectErr, err) 284 assert.Equal(t, test.expectValue, value) 285 }) 286 } 287 }) 288 } 289 290 func TestMapReduceWithReduerWriteMoreThanOnce(t *testing.T) { 291 defer goleak.VerifyNone(t) 292 293 assert.Panics(t, func() { 294 MapReduce(func(source chan<- interface{}) { 295 for i := 0; i < 10; i++ { 296 source <- i 297 } 298 }, func(item interface{}, writer Writer, cancel func(error)) { 299 writer.Write(item) 300 }, func(pipe <-chan interface{}, writer Writer, cancel func(error)) { 301 drain(pipe) 302 writer.Write("one") 303 writer.Write("two") 304 }) 305 }) 306 } 307 308 func TestMapReduceVoid(t *testing.T) { 309 defer goleak.VerifyNone(t) 310 311 var value uint32 312 tests := []struct { 313 name string 314 mapper MapperFunc 315 reducer VoidReducerFunc 316 expectValue uint32 317 expectErr error 318 }{ 319 { 320 name: "simple", 321 expectValue: 30, 322 expectErr: nil, 323 }, 324 { 325 name: "cancel with error", 326 mapper: func(item interface{}, writer Writer, cancel func(error)) { 327 v := item.(int) 328 if v%3 == 0 { 329 cancel(errDummy) 330 } 331 writer.Write(v * v) 332 }, 333 expectErr: errDummy, 334 }, 335 { 336 name: "cancel with nil", 337 mapper: func(item interface{}, writer Writer, cancel func(error)) { 338 v := item.(int) 339 if v%3 == 0 { 340 cancel(nil) 341 } 342 writer.Write(v * v) 343 }, 344 expectErr: ErrCancelWithNil, 345 }, 346 { 347 name: "cancel with more", 348 reducer: func(pipe <-chan interface{}, cancel func(error)) { 349 for item := range pipe { 350 result := atomic.AddUint32(&value, uint32(item.(int))) 351 if result > 10 { 352 cancel(errDummy) 353 } 354 } 355 }, 356 expectErr: errDummy, 357 }, 358 } 359 360 for _, test := range tests { 361 t.Run(test.name, func(t *testing.T) { 362 atomic.StoreUint32(&value, 0) 363 364 if test.mapper == nil { 365 test.mapper = func(item interface{}, writer Writer, cancel func(error)) { 366 v := item.(int) 367 writer.Write(v * v) 368 } 369 } 370 if test.reducer == nil { 371 test.reducer = func(pipe <-chan interface{}, cancel func(error)) { 372 for item := range pipe { 373 atomic.AddUint32(&value, uint32(item.(int))) 374 } 375 } 376 } 377 err := MapReduceVoid(func(source chan<- interface{}) { 378 for i := 1; i < 5; i++ { 379 source <- i 380 } 381 }, test.mapper, test.reducer) 382 383 assert.Equal(t, test.expectErr, err) 384 if err == nil { 385 assert.Equal(t, test.expectValue, atomic.LoadUint32(&value)) 386 } 387 }) 388 } 389 } 390 391 func TestMapReduceVoidWithDelay(t *testing.T) { 392 defer goleak.VerifyNone(t) 393 394 var result []int 395 err := MapReduceVoid(func(source chan<- interface{}) { 396 source <- 0 397 source <- 1 398 }, func(item interface{}, writer Writer, cancel func(error)) { 399 i := item.(int) 400 if i == 0 { 401 time.Sleep(time.Millisecond * 50) 402 } 403 writer.Write(i) 404 }, func(pipe <-chan interface{}, cancel func(error)) { 405 for item := range pipe { 406 i := item.(int) 407 result = append(result, i) 408 } 409 }) 410 assert.Nil(t, err) 411 assert.Equal(t, 2, len(result)) 412 assert.Equal(t, 1, result[0]) 413 assert.Equal(t, 0, result[1]) 414 } 415 416 func TestMapReducePanic(t *testing.T) { 417 defer goleak.VerifyNone(t) 418 419 assert.Panics(t, func() { 420 _, _ = MapReduce(func(source chan<- interface{}) { 421 source <- 0 422 source <- 1 423 }, func(item interface{}, writer Writer, cancel func(error)) { 424 i := item.(int) 425 writer.Write(i) 426 }, func(pipe <-chan interface{}, writer Writer, cancel func(error)) { 427 for range pipe { 428 panic("panic") 429 } 430 }) 431 }) 432 } 433 434 func TestMapReducePanicOnce(t *testing.T) { 435 defer goleak.VerifyNone(t) 436 437 assert.Panics(t, func() { 438 _, _ = MapReduce(func(source chan<- interface{}) { 439 for i := 0; i < 100; i++ { 440 source <- i 441 } 442 }, func(item interface{}, writer Writer, cancel func(error)) { 443 i := item.(int) 444 if i == 0 { 445 panic("foo") 446 } 447 writer.Write(i) 448 }, func(pipe <-chan interface{}, writer Writer, cancel func(error)) { 449 for range pipe { 450 panic("bar") 451 } 452 }) 453 }) 454 } 455 456 func TestMapReducePanicBothMapperAndReducer(t *testing.T) { 457 defer goleak.VerifyNone(t) 458 459 assert.Panics(t, func() { 460 _, _ = MapReduce(func(source chan<- interface{}) { 461 source <- 0 462 source <- 1 463 }, func(item interface{}, writer Writer, cancel func(error)) { 464 panic("foo") 465 }, func(pipe <-chan interface{}, writer Writer, cancel func(error)) { 466 panic("bar") 467 }) 468 }) 469 } 470 471 func TestMapReduceVoidCancel(t *testing.T) { 472 defer goleak.VerifyNone(t) 473 474 var result []int 475 err := MapReduceVoid(func(source chan<- interface{}) { 476 source <- 0 477 source <- 1 478 }, func(item interface{}, writer Writer, cancel func(error)) { 479 i := item.(int) 480 if i == 1 { 481 cancel(errors.New("anything")) 482 } 483 writer.Write(i) 484 }, func(pipe <-chan interface{}, cancel func(error)) { 485 for item := range pipe { 486 i := item.(int) 487 result = append(result, i) 488 } 489 }) 490 assert.NotNil(t, err) 491 assert.Equal(t, "anything", err.Error()) 492 } 493 494 func TestMapReduceVoidCancelWithRemains(t *testing.T) { 495 defer goleak.VerifyNone(t) 496 497 var done int32 498 var result []int 499 err := MapReduceVoid(func(source chan<- interface{}) { 500 for i := 0; i < defaultWorkers*2; i++ { 501 source <- i 502 } 503 atomic.AddInt32(&done, 1) 504 }, func(item interface{}, writer Writer, cancel func(error)) { 505 i := item.(int) 506 if i == defaultWorkers/2 { 507 cancel(errors.New("anything")) 508 } 509 writer.Write(i) 510 }, func(pipe <-chan interface{}, cancel func(error)) { 511 for item := range pipe { 512 i := item.(int) 513 result = append(result, i) 514 } 515 }) 516 assert.NotNil(t, err) 517 assert.Equal(t, "anything", err.Error()) 518 assert.Equal(t, int32(1), done) 519 } 520 521 func TestMapReduceWithoutReducerWrite(t *testing.T) { 522 defer goleak.VerifyNone(t) 523 524 uids := []int{1, 2, 3} 525 res, err := MapReduce(func(source chan<- interface{}) { 526 for _, uid := range uids { 527 source <- uid 528 } 529 }, func(item interface{}, writer Writer, cancel func(error)) { 530 writer.Write(item) 531 }, func(pipe <-chan interface{}, writer Writer, cancel func(error)) { 532 drain(pipe) 533 // not calling writer.Write(...), should not panic 534 }) 535 assert.Equal(t, ErrReduceNoOutput, err) 536 assert.Nil(t, res) 537 } 538 539 func TestMapReduceVoidPanicInReducer(t *testing.T) { 540 defer goleak.VerifyNone(t) 541 542 const message = "foo" 543 assert.Panics(t, func() { 544 var done int32 545 _ = MapReduceVoid(func(source chan<- interface{}) { 546 for i := 0; i < defaultWorkers*2; i++ { 547 source <- i 548 } 549 atomic.AddInt32(&done, 1) 550 }, func(item interface{}, writer Writer, cancel func(error)) { 551 i := item.(int) 552 writer.Write(i) 553 }, func(pipe <-chan interface{}, cancel func(error)) { 554 panic(message) 555 }, WithWorkers(1)) 556 }) 557 } 558 559 func TestForEachWithContext(t *testing.T) { 560 defer goleak.VerifyNone(t) 561 562 var done int32 563 ctx, cancel := context.WithCancel(context.Background()) 564 ForEach(func(source chan<- interface{}) { 565 for i := 0; i < defaultWorkers*2; i++ { 566 source <- i 567 } 568 atomic.AddInt32(&done, 1) 569 }, func(item interface{}) { 570 i := item.(int) 571 if i == defaultWorkers/2 { 572 cancel() 573 } 574 }, WithContext(ctx)) 575 } 576 577 func TestMapReduceWithContext(t *testing.T) { 578 defer goleak.VerifyNone(t) 579 580 var done int32 581 var result []int 582 ctx, cancel := context.WithCancel(context.Background()) 583 err := MapReduceVoid(func(source chan<- interface{}) { 584 for i := 0; i < defaultWorkers*2; i++ { 585 source <- i 586 } 587 atomic.AddInt32(&done, 1) 588 }, func(item interface{}, writer Writer, c func(error)) { 589 i := item.(int) 590 if i == defaultWorkers/2 { 591 cancel() 592 } 593 writer.Write(i) 594 }, func(pipe <-chan interface{}, cancel func(error)) { 595 for item := range pipe { 596 i := item.(int) 597 result = append(result, i) 598 } 599 }, WithContext(ctx)) 600 assert.NotNil(t, err) 601 assert.Equal(t, context.DeadlineExceeded, err) 602 } 603 604 func BenchmarkMapReduce(b *testing.B) { 605 b.ReportAllocs() 606 607 mapper := func(v interface{}, writer Writer, cancel func(error)) { 608 writer.Write(v.(int64) * v.(int64)) 609 } 610 reducer := func(input <-chan interface{}, writer Writer, cancel func(error)) { 611 var result int64 612 for v := range input { 613 result += v.(int64) 614 } 615 writer.Write(result) 616 } 617 618 for i := 0; i < b.N; i++ { 619 MapReduce(func(input chan<- interface{}) { 620 for j := 0; j < 2; j++ { 621 input <- int64(j) 622 } 623 }, mapper, reducer) 624 } 625 }