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  }