// 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) } }