|  | // 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 || 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 { | 
|  | tc := tc | 
|  | 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) | 
|  | } | 
|  | } |