Text file src/cmd/go/testdata/script/test_fuzz_mutator.txt

     1  [!fuzz] skip
     2  
     3  # Test basic fuzzing mutator behavior.
     4  #
     5  # fuzz_test.go has two fuzz targets (FuzzA, FuzzB) which both add a seed value.
     6  # Each fuzz function writes the input to a log file. The coordinator and worker
     7  # use separate log files. check_logs.go verifies that the coordinator only
     8  # tests seed values and the worker tests mutated values on the fuzz target.
     9  
    10  [short] skip
    11  env GOCACHE=$WORK/cache
    12  
    13  go test -fuzz=FuzzA -fuzztime=100x -parallel=1 -log=fuzz
    14  go run check_logs.go fuzz fuzz.worker
    15  
    16  # TODO(b/181800488): remove -parallel=1, here and below. For now, when a
    17  # crash is found, all workers keep running, wasting resources and reducing
    18  # the number of executions available to the minimizer, increasing flakiness.
    19  
    20  # Test that the mutator is good enough to find several unique mutations.
    21  ! go test -fuzz=FuzzMutator -parallel=1 -fuzztime=100x mutator_test.go
    22  ! stdout '^ok'
    23  stdout FAIL
    24  stdout 'mutator found enough unique mutations'
    25  
    26  -- go.mod --
    27  module m
    28  
    29  go 1.16
    30  -- fuzz_test.go --
    31  package fuzz_test
    32  
    33  import (
    34  	"flag"
    35  	"fmt"
    36  	"os"
    37  	"testing"
    38  )
    39  
    40  var (
    41  	logPath = flag.String("log", "", "path to log file")
    42  	logFile *os.File
    43  )
    44  
    45  func TestMain(m *testing.M) {
    46  	flag.Parse()
    47  	var err error
    48  	logFile, err = os.OpenFile(*logPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
    49  	if os.IsExist(err) {
    50  		*logPath += ".worker"
    51  		logFile, err = os.OpenFile(*logPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
    52  	}
    53  	if err != nil {
    54  		fmt.Fprintln(os.Stderr, err)
    55  		os.Exit(1)
    56  	}
    57  	os.Exit(m.Run())
    58  }
    59  
    60  func FuzzA(f *testing.F) {
    61  	f.Add([]byte("seed"))
    62  	f.Fuzz(func(t *testing.T, b []byte) {
    63  		fmt.Fprintf(logFile, "FuzzA %q\n", b)
    64  	})
    65  }
    66  
    67  func FuzzB(f *testing.F) {
    68  	f.Add([]byte("seed"))
    69  	f.Fuzz(func(t *testing.T, b []byte) {
    70  		fmt.Fprintf(logFile, "FuzzB %q\n", b)
    71  	})
    72  }
    73  
    74  -- check_logs.go --
    75  // +build ignore
    76  
    77  package main
    78  
    79  import (
    80  	"bufio"
    81  	"bytes"
    82  	"fmt"
    83  	"io"
    84  	"os"
    85  	"strings"
    86  )
    87  
    88  func main() {
    89  	coordPath, workerPath := os.Args[1], os.Args[2]
    90  
    91  	coordLog, err := os.Open(coordPath)
    92  	if err != nil {
    93  		fmt.Fprintln(os.Stderr, err)
    94  		os.Exit(1)
    95  	}
    96  	defer coordLog.Close()
    97  	if err := checkCoordLog(coordLog); err != nil {
    98  		fmt.Fprintln(os.Stderr, err)
    99  		os.Exit(1)
   100  	}
   101  
   102  	workerLog, err := os.Open(workerPath)
   103  	if err != nil {
   104  		fmt.Fprintln(os.Stderr, err)
   105  		os.Exit(1)
   106  	}
   107  	defer workerLog.Close()
   108  	if err := checkWorkerLog(workerLog); err != nil {
   109  		fmt.Fprintln(os.Stderr, err)
   110  		os.Exit(1)
   111  	}
   112  }
   113  
   114  func checkCoordLog(r io.Reader) error {
   115  	b, err := io.ReadAll(r)
   116  	if err != nil {
   117  		return err
   118  	}
   119  	if string(bytes.TrimSpace(b)) != `FuzzB "seed"` {
   120  		return fmt.Errorf("coordinator: did not test FuzzB seed")
   121  	}
   122  	return nil
   123  }
   124  
   125  func checkWorkerLog(r io.Reader) error {
   126  	scan := bufio.NewScanner(r)
   127  	var sawAMutant bool
   128  	for scan.Scan() {
   129  		line := scan.Text()
   130  		if !strings.HasPrefix(line, "FuzzA ") {
   131  			return fmt.Errorf("worker: tested something other than target: %s", line)
   132  		}
   133  		if strings.TrimPrefix(line, "FuzzA ") != `"seed"` {
   134  			sawAMutant = true
   135  		}
   136  	}
   137  	if err := scan.Err(); err != nil && err != bufio.ErrTooLong {
   138  		return err
   139  	}
   140  	if !sawAMutant {
   141  		return fmt.Errorf("worker: did not test any mutants")
   142  	}
   143  	return nil
   144  }
   145  -- mutator_test.go --
   146  package fuzz_test
   147  
   148  import (
   149  	"testing"
   150  )
   151  
   152  // TODO(katiehockman): re-work this test once we have a better fuzzing engine
   153  // (ie. more mutations, and compiler instrumentation)
   154  func FuzzMutator(f *testing.F) {
   155  	// TODO(katiehockman): simplify this once we can dedupe crashes (e.g.
   156  	// replace map with calls to panic, and simply count the number of crashes
   157  	// that were added to testdata)
   158  	crashes := make(map[string]bool)
   159  	// No seed corpus initiated
   160  	f.Fuzz(func(t *testing.T, b []byte) {
   161  		crashes[string(b)] = true
   162  		if len(crashes) >= 10 {
   163  			panic("mutator found enough unique mutations")
   164  		}
   165  	})
   166  }
   167  

View as plain text