| // Copyright 2021 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| //go:build darwin || freebsd || linux || openbsd || windows | |
| package fuzz | |
| import ( | |
| "bytes" | |
| "context" | |
| "errors" | |
| "fmt" | |
| "reflect" | |
| "testing" | |
| "time" | |
| "unicode" | |
| "unicode/utf8" | |
| ) | |
| func TestMinimizeInput(t *testing.T) { | |
| type testcase struct { | |
| name string | |
| fn func(CorpusEntry) error | |
| input []any | |
| expected []any | |
| } | |
| cases := []testcase{ | |
| { | |
| name: "ones_byte", | |
| fn: func(e CorpusEntry) error { | |
| b := e.Values[0].([]byte) | |
| ones := 0 | |
| for _, v := range b { | |
| if v == 1 { | |
| ones++ | |
| } | |
| } | |
| if ones == 3 { | |
| return fmt.Errorf("bad %v", e.Values[0]) | |
| } | |
| return nil | |
| }, | |
| input: []any{[]byte{0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, | |
| expected: []any{[]byte{1, 1, 1}}, | |
| }, | |
| { | |
| name: "single_bytes", | |
| fn: func(e CorpusEntry) error { | |
| b := e.Values[0].([]byte) | |
| if len(b) < 2 { | |
| return nil | |
| } | |
| if len(b) == 2 && b[0] == 1 && b[1] == 2 { | |
| return nil | |
| } | |
| return fmt.Errorf("bad %v", e.Values[0]) | |
| }, | |
| input: []any{[]byte{1, 2, 3, 4, 5}}, | |
| expected: []any{[]byte("00")}, | |
| }, | |
| { | |
| name: "set_of_bytes", | |
| fn: func(e CorpusEntry) error { | |
| b := e.Values[0].([]byte) | |
| if len(b) < 3 { | |
| return nil | |
| } | |
| if bytes.Equal(b, []byte{0, 1, 2, 3, 4, 5}) || bytes.Equal(b, []byte{0, 4, 5}) { | |
| return fmt.Errorf("bad %v", e.Values[0]) | |
| } | |
| return nil | |
| }, | |
| input: []any{[]byte{0, 1, 2, 3, 4, 5}}, | |
| expected: []any{[]byte{0, 4, 5}}, | |
| }, | |
| { | |
| name: "non_ascii_bytes", | |
| fn: func(e CorpusEntry) error { | |
| b := e.Values[0].([]byte) | |
| if len(b) == 3 { | |
| return fmt.Errorf("bad %v", e.Values[0]) | |
| } | |
| return nil | |
| }, | |
| input: []any{[]byte("ท")}, // ท is 3 bytes | |
| expected: []any{[]byte("000")}, | |
| }, | |
| { | |
| name: "ones_string", | |
| fn: func(e CorpusEntry) error { | |
| b := e.Values[0].(string) | |
| ones := 0 | |
| for _, v := range b { | |
| if v == '1' { | |
| ones++ | |
| } | |
| } | |
| if ones == 3 { | |
| return fmt.Errorf("bad %v", e.Values[0]) | |
| } | |
| return nil | |
| }, | |
| input: []any{"001010001000000000000000000"}, | |
| expected: []any{"111"}, | |
| }, | |
| { | |
| name: "string_length", | |
| fn: func(e CorpusEntry) error { | |
| b := e.Values[0].(string) | |
| if len(b) == 5 { | |
| return fmt.Errorf("bad %v", e.Values[0]) | |
| } | |
| return nil | |
| }, | |
| input: []any{"zzzzz"}, | |
| expected: []any{"00000"}, | |
| }, | |
| { | |
| name: "string_with_letter", | |
| fn: func(e CorpusEntry) error { | |
| b := e.Values[0].(string) | |
| r, _ := utf8.DecodeRune([]byte(b)) | |
| if unicode.IsLetter(r) { | |
| return fmt.Errorf("bad %v", e.Values[0]) | |
| } | |
| return nil | |
| }, | |
| input: []any{"ZZZZZ"}, | |
| expected: []any{"A"}, | |
| }, | |
| } | |
| for _, tc := range cases { | |
| t.Run(tc.name, func(t *testing.T) { | |
| t.Parallel() | |
| ws := &workerServer{ | |
| fuzzFn: func(e CorpusEntry) (time.Duration, error) { | |
| return time.Second, tc.fn(e) | |
| }, | |
| } | |
| mem := &sharedMem{region: make([]byte, 100)} // big enough to hold value and header | |
| vals := tc.input | |
| success, err := ws.minimizeInput(context.Background(), vals, mem, minimizeArgs{}) | |
| if !success { | |
| t.Errorf("minimizeInput did not succeed") | |
| } | |
| if err == nil { | |
| t.Fatal("minimizeInput didn't provide an error") | |
| } | |
| if expected := fmt.Sprintf("bad %v", tc.expected[0]); err.Error() != expected { | |
| t.Errorf("unexpected error: got %q, want %q", err, expected) | |
| } | |
| if !reflect.DeepEqual(vals, tc.expected) { | |
| t.Errorf("unexpected results: got %v, want %v", vals, tc.expected) | |
| } | |
| }) | |
| } | |
| } | |
| // TestMinimizeFlaky checks that if we're minimizing an interesting | |
| // input and a flaky failure occurs, that minimization was not indicated | |
| // to be successful, and the error isn't returned (since it's flaky). | |
| func TestMinimizeFlaky(t *testing.T) { | |
| ws := &workerServer{fuzzFn: func(e CorpusEntry) (time.Duration, error) { | |
| return time.Second, errors.New("ohno") | |
| }} | |
| mem := &sharedMem{region: make([]byte, 100)} // big enough to hold value and header | |
| vals := []any{[]byte(nil)} | |
| args := minimizeArgs{KeepCoverage: make([]byte, len(coverageSnapshot))} | |
| success, err := ws.minimizeInput(context.Background(), vals, mem, args) | |
| if success { | |
| t.Error("unexpected success") | |
| } | |
| if err != nil { | |
| t.Errorf("unexpected error: %v", err) | |
| } | |
| if count := mem.header().count; count != 1 { | |
| t.Errorf("count: got %d, want 1", count) | |
| } | |
| } |