Text file
src/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt
1 [short] skip
2 [!fuzz-instrumented] skip
3
4 # Test that when an interesting value is discovered (one that expands coverage),
5 # the fuzzing engine minimizes it before writing it to the cache.
6 #
7 # The program below starts with a seed value of length 100, but more coverage
8 # will be found for any value other than the seed. We should end with a value
9 # in the cache of length 1 (the minimizer currently does not produce empty
10 # strings). check_cache.go confirms that.
11 #
12 # We would like to verify that ALL values in the cache were minimized to a
13 # length of 1, but this isn't always possible when new coverage is found in
14 # functions called by testing or internal/fuzz in the background.
15
16 go test -c -fuzz=. # Build using shared build cache for speed.
17 env GOCACHE=$WORK/gocache
18 exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=FuzzMinCache -test.fuzztime=1000x
19 go run check_cache/check_cache.go $GOCACHE/fuzz/FuzzMinCache
20
21 go test -c -fuzz=. # Build using shared build cache for speed.
22 env GOCACHE=$WORK/gocache
23
24 # Test that minimization occurs for a crash that appears while minimizing a
25 # newly found interesting input. There must be only one worker for this test to
26 # be flaky like we want.
27 ! exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=FuzzMinimizerCrashInMinimization -test.run=FuzzMinimizerCrashInMinimization -test.fuzztime=10000x -test.parallel=1
28 ! stdout '^ok'
29 stdout -count=1 'got the minimum size!'
30 stdout -count=1 'flaky failure'
31 stdout FAIL
32 # Check that the input written to testdata will reproduce the error, and is the
33 # smallest possible.
34 go run check_testdata/check_testdata.go FuzzMinimizerCrashInMinimization 50
35
36 # Test that a nonrecoverable error that occurs while minimizing an interesting
37 # input is reported correctly.
38 ! exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=FuzzMinimizerNonrecoverableCrashInMinimization -test.run=FuzzMinimizerNonrecoverableCrashInMinimization -test.fuzztime=10000x -test.parallel=1
39 ! stdout '^ok'
40 stdout -count=1 'fuzzing process hung or terminated unexpectedly while minimizing'
41 stdout -count=1 'EOF'
42 stdout FAIL
43 # Check that the input written to testdata will reproduce the error.
44 go run check_testdata/check_testdata.go FuzzMinimizerNonrecoverableCrashInMinimization 100
45
46 -- go.mod --
47 module fuzz
48
49 go 1.17
50 -- y.go --
51 package fuzz
52
53 import (
54 "bytes"
55 "io"
56 )
57
58 func Y(w io.Writer, s string) {
59 if !bytes.Equal([]byte(s), []byte("y")) {
60 w.Write([]byte("not equal"))
61 }
62 }
63 -- fuzz_test.go --
64 package fuzz
65
66 import (
67 "bytes"
68 "io"
69 "os"
70 "strings"
71 "testing"
72 "unicode/utf8"
73 )
74
75 func FuzzMinimizerCrashInMinimization(f *testing.F) {
76 seed := strings.Repeat("A", 1000)
77 f.Add(seed)
78 i := 3
79 f.Fuzz(func(t *testing.T, s string) {
80 if len(s) < 50 || len(s) > 1100 {
81 // Make sure that b is large enough that it can be minimized
82 return
83 }
84 if s != seed {
85 // This should hit a new edge, and the interesting input
86 // should attempt minimization
87 Y(io.Discard, s)
88 }
89 if i > 0 {
90 // Don't let it fail right away.
91 i--
92 } else if utf8.RuneCountInString(s) == len(s) && len(s) <= 100 {
93 // Make sure this only fails if the number of bytes in the
94 // marshaled string is the same as the unmarshaled string,
95 // so that we can check the length of the testdata file.
96 t.Error("flaky failure")
97 if len(s) == 50 {
98 t.Error("got the minimum size!")
99 }
100 }
101 })
102 }
103
104 func FuzzMinimizerNonrecoverableCrashInMinimization(f *testing.F) {
105 seed := strings.Repeat("A", 1000)
106 f.Add(seed)
107 i := 3
108 f.Fuzz(func(t *testing.T, s string) {
109 if len(s) < 50 || len(s) > 1100 {
110 return
111 }
112 if s != seed {
113 Y(io.Discard, s)
114 }
115 if i > 0 {
116 i--
117 } else if utf8.RuneCountInString(s) == len(s) && len(s) <= 100 {
118 os.Exit(19)
119 }
120 })
121 }
122
123 func FuzzMinCache(f *testing.F) {
124 seed := bytes.Repeat([]byte("a"), 20)
125 f.Add(seed)
126 f.Fuzz(func(t *testing.T, buf []byte) {
127 if bytes.Equal(buf, seed) {
128 return
129 }
130 })
131 }
132 -- check_testdata/check_testdata.go --
133 //go:build ignore
134 // +build ignore
135
136 // check_testdata.go checks that the string written
137 // is not longer than the provided length.
138 package main
139
140 import (
141 "fmt"
142 "io/ioutil"
143 "os"
144 "path/filepath"
145 "strconv"
146 )
147
148 func main() {
149 wantLen, err := strconv.Atoi(os.Args[2])
150 if err != nil {
151 fmt.Fprintln(os.Stderr, err)
152 os.Exit(1)
153 }
154 testName := os.Args[1]
155 dir := filepath.Join("testdata/fuzz", testName)
156
157 files, err := ioutil.ReadDir(dir)
158 if err != nil {
159 fmt.Fprintln(os.Stderr, err)
160 os.Exit(1)
161 }
162
163 if len(files) == 0 {
164 fmt.Fprintf(os.Stderr, "expect at least one failure to be written to testdata\n")
165 os.Exit(1)
166 }
167
168 fname := files[0].Name()
169 contents, err := ioutil.ReadFile(filepath.Join(dir, fname))
170 if err != nil {
171 fmt.Fprintln(os.Stderr, err)
172 os.Exit(1)
173 }
174 contentsLen := len(contents) - len(`go test fuzz v1
175 string("")
176 `)
177 if got, want := contentsLen, wantLen; got > want {
178 fmt.Fprintf(os.Stderr, "expect length <= %d, got %d\n", want, got)
179 os.Exit(1)
180 }
181 fmt.Fprintf(os.Stderr, "%s\n", contents)
182 }
183
184 -- check_cache/check_cache.go --
185 //go:build ignore
186 // +build ignore
187
188 // check_cache.go checks that each file in the cached corpus has a []byte
189 // of length at most 1. This verifies that at least one cached input is minimized.
190 package main
191
192 import (
193 "bytes"
194 "fmt"
195 "os"
196 "path/filepath"
197 "regexp"
198 "strconv"
199 )
200
201 func main() {
202 dir := os.Args[1]
203 ents, err := os.ReadDir(dir)
204 if err != nil {
205 fmt.Fprintln(os.Stderr, err)
206 os.Exit(1)
207 }
208 for _, ent := range ents {
209 name := filepath.Join(dir, ent.Name())
210 if good, err := checkCacheFile(name); err != nil {
211 fmt.Fprintln(os.Stderr, err)
212 os.Exit(1)
213 } else if good {
214 os.Exit(0)
215 }
216 }
217 fmt.Fprintln(os.Stderr, "no cached inputs were minimized")
218 os.Exit(1)
219 }
220
221 func checkCacheFile(name string) (good bool, err error) {
222 data, err := os.ReadFile(name)
223 if err != nil {
224 return false, err
225 }
226 for _, line := range bytes.Split(data, []byte("\n")) {
227 m := valRe.FindSubmatch(line)
228 if m == nil {
229 continue
230 }
231 if s, err := strconv.Unquote(string(m[1])); err != nil {
232 return false, err
233 } else if len(s) <= 1 {
234 return true, nil
235 }
236 }
237 return false, nil
238 }
239
240 var valRe = regexp.MustCompile(`^\[\]byte\(([^)]+)\)$`)
241
View as plain text