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  }