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