|
@@ -0,0 +1,107 @@
|
|
|
+//go:build fuzz
|
|
|
+// +build fuzz
|
|
|
+
|
|
|
+package mr
|
|
|
+
|
|
|
+import (
|
|
|
+ "fmt"
|
|
|
+ "math/rand"
|
|
|
+ "runtime"
|
|
|
+ "strconv"
|
|
|
+ "strings"
|
|
|
+ "sync"
|
|
|
+ "testing"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ "github.com/stretchr/testify/assert"
|
|
|
+ "github.com/zeromicro/go-zero/core/threading"
|
|
|
+ "gopkg.in/cheggaaa/pb.v1"
|
|
|
+)
|
|
|
+
|
|
|
+// If Fuzz stuck, we don't know why, because it only returns hung or unexpected,
|
|
|
+// so we need to simulate the fuzz test in test mode.
|
|
|
+func TestMapReduceRandom(t *testing.T) {
|
|
|
+ rand.Seed(time.Now().UnixNano())
|
|
|
+
|
|
|
+ const (
|
|
|
+ times = 10000
|
|
|
+ nRange = 500
|
|
|
+ mega = 1024 * 1024
|
|
|
+ )
|
|
|
+
|
|
|
+ bar := pb.New(times).Start()
|
|
|
+ runner := threading.NewTaskRunner(runtime.NumCPU())
|
|
|
+ var wg sync.WaitGroup
|
|
|
+ wg.Add(times)
|
|
|
+ for i := 0; i < times; i++ {
|
|
|
+ runner.Schedule(func() {
|
|
|
+ start := time.Now()
|
|
|
+ defer func() {
|
|
|
+ if time.Since(start) > time.Minute {
|
|
|
+ t.Fatal("timeout")
|
|
|
+ }
|
|
|
+ wg.Done()
|
|
|
+ }()
|
|
|
+
|
|
|
+ t.Run(strconv.Itoa(i), func(t *testing.T) {
|
|
|
+ n := rand.Int63n(nRange)%nRange + nRange
|
|
|
+ workers := rand.Int()%50 + runtime.NumCPU()/2
|
|
|
+ genPanic := rand.Intn(100) == 0
|
|
|
+ mapperPanic := rand.Intn(100) == 0
|
|
|
+ reducerPanic := rand.Intn(100) == 0
|
|
|
+ genIdx := rand.Int63n(n)
|
|
|
+ mapperIdx := rand.Int63n(n)
|
|
|
+ reducerIdx := rand.Int63n(n)
|
|
|
+ squareSum := (n - 1) * n * (2*n - 1) / 6
|
|
|
+
|
|
|
+ fn := func() (interface{}, error) {
|
|
|
+ return MapReduce(func(source chan<- interface{}) {
|
|
|
+ for i := int64(0); i < n; i++ {
|
|
|
+ source <- i
|
|
|
+ if genPanic && i == genIdx {
|
|
|
+ panic("foo")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, func(item interface{}, writer Writer, cancel func(error)) {
|
|
|
+ v := item.(int64)
|
|
|
+ if mapperPanic && v == mapperIdx {
|
|
|
+ panic("bar")
|
|
|
+ }
|
|
|
+ writer.Write(v * v)
|
|
|
+ }, func(pipe <-chan interface{}, writer Writer, cancel func(error)) {
|
|
|
+ var idx int64
|
|
|
+ var total int64
|
|
|
+ for v := range pipe {
|
|
|
+ if reducerPanic && idx == reducerIdx {
|
|
|
+ panic("baz")
|
|
|
+ }
|
|
|
+ total += v.(int64)
|
|
|
+ idx++
|
|
|
+ }
|
|
|
+ writer.Write(total)
|
|
|
+ }, WithWorkers(int(workers)%50+runtime.NumCPU()/2))
|
|
|
+ }
|
|
|
+
|
|
|
+ if genPanic || mapperPanic || reducerPanic {
|
|
|
+ var buf strings.Builder
|
|
|
+ buf.WriteString(fmt.Sprintf("n: %d", n))
|
|
|
+ buf.WriteString(fmt.Sprintf(", genPanic: %t", genPanic))
|
|
|
+ buf.WriteString(fmt.Sprintf(", mapperPanic: %t", mapperPanic))
|
|
|
+ buf.WriteString(fmt.Sprintf(", reducerPanic: %t", reducerPanic))
|
|
|
+ buf.WriteString(fmt.Sprintf(", genIdx: %d", genIdx))
|
|
|
+ buf.WriteString(fmt.Sprintf(", mapperIdx: %d", mapperIdx))
|
|
|
+ buf.WriteString(fmt.Sprintf(", reducerIdx: %d", reducerIdx))
|
|
|
+ assert.Panicsf(t, func() { fn() }, buf.String())
|
|
|
+ } else {
|
|
|
+ val, err := fn()
|
|
|
+ assert.Nil(t, err)
|
|
|
+ assert.Equal(t, squareSum, val.(int64))
|
|
|
+ }
|
|
|
+ bar.Increment()
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ wg.Wait()
|
|
|
+ bar.Finish()
|
|
|
+}
|