diff options
Diffstat (limited to 'src/internal/fuzz/minimize_test.go')
-rw-r--r-- | src/internal/fuzz/minimize_test.go | 286 |
1 files changed, 286 insertions, 0 deletions
diff --git a/src/internal/fuzz/minimize_test.go b/src/internal/fuzz/minimize_test.go new file mode 100644 index 0000000000..410b78310b --- /dev/null +++ b/src/internal/fuzz/minimize_test.go @@ -0,0 +1,286 @@ +// 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 || linux || windows +// +build darwin linux windows + +package fuzz + +import ( + "bytes" + "context" + "errors" + "fmt" + "reflect" + "testing" +) + +func TestMinimizeInput(t *testing.T) { + type testcase struct { + name string + fn func(CorpusEntry) error + input []interface{} + expected []interface{} + } + 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: []interface{}{[]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: []interface{}{[]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: []interface{}{[]byte{1, 2, 3, 4, 5}}, + expected: []interface{}{[]byte{2, 3}}, + }, + { + 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: []interface{}{[]byte{0, 1, 2, 3, 4, 5}}, + expected: []interface{}{[]byte{0, 4, 5}}, + }, + { + 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: []interface{}{"001010001000000000000000000"}, + expected: []interface{}{"111"}, + }, + { + name: "int", + fn: func(e CorpusEntry) error { + i := e.Values[0].(int) + if i > 100 { + return fmt.Errorf("bad %v", e.Values[0]) + } + return nil + }, + input: []interface{}{123456}, + expected: []interface{}{123}, + }, + { + name: "int8", + fn: func(e CorpusEntry) error { + i := e.Values[0].(int8) + if i > 10 { + return fmt.Errorf("bad %v", e.Values[0]) + } + return nil + }, + input: []interface{}{int8(1<<7 - 1)}, + expected: []interface{}{int8(12)}, + }, + { + name: "int16", + fn: func(e CorpusEntry) error { + i := e.Values[0].(int16) + if i > 10 { + return fmt.Errorf("bad %v", e.Values[0]) + } + return nil + }, + input: []interface{}{int16(1<<15 - 1)}, + expected: []interface{}{int16(32)}, + }, + { + fn: func(e CorpusEntry) error { + i := e.Values[0].(int32) + if i > 10 { + return fmt.Errorf("bad %v", e.Values[0]) + } + return nil + }, + input: []interface{}{int32(1<<31 - 1)}, + expected: []interface{}{int32(21)}, + }, + { + name: "int32", + fn: func(e CorpusEntry) error { + i := e.Values[0].(uint) + if i > 10 { + return fmt.Errorf("bad %v", e.Values[0]) + } + return nil + }, + input: []interface{}{uint(123456)}, + expected: []interface{}{uint(12)}, + }, + { + name: "uint8", + fn: func(e CorpusEntry) error { + i := e.Values[0].(uint8) + if i > 10 { + return fmt.Errorf("bad %v", e.Values[0]) + } + return nil + }, + input: []interface{}{uint8(1<<8 - 1)}, + expected: []interface{}{uint8(25)}, + }, + { + name: "uint16", + fn: func(e CorpusEntry) error { + i := e.Values[0].(uint16) + if i > 10 { + return fmt.Errorf("bad %v", e.Values[0]) + } + return nil + }, + input: []interface{}{uint16(1<<16 - 1)}, + expected: []interface{}{uint16(65)}, + }, + { + name: "uint32", + fn: func(e CorpusEntry) error { + i := e.Values[0].(uint32) + if i > 10 { + return fmt.Errorf("bad %v", e.Values[0]) + } + return nil + }, + input: []interface{}{uint32(1<<32 - 1)}, + expected: []interface{}{uint32(42)}, + }, + { + name: "float32", + fn: func(e CorpusEntry) error { + if i := e.Values[0].(float32); i == 1.23 { + return nil + } + return fmt.Errorf("bad %v", e.Values[0]) + }, + input: []interface{}{float32(1.23456789)}, + expected: []interface{}{float32(1.2)}, + }, + { + name: "float64", + fn: func(e CorpusEntry) error { + if i := e.Values[0].(float64); i == 1.23 { + return nil + } + return fmt.Errorf("bad %v", e.Values[0]) + }, + input: []interface{}{float64(1.23456789)}, + expected: []interface{}{float64(1.2)}, + }, + } + + // If we are on a 64 bit platform add int64 and uint64 tests + if v := int64(1<<63 - 1); int64(int(v)) == v { + cases = append(cases, testcase{ + name: "int64", + fn: func(e CorpusEntry) error { + i := e.Values[0].(int64) + if i > 10 { + return fmt.Errorf("bad %v", e.Values[0]) + } + return nil + }, + input: []interface{}{int64(1<<63 - 1)}, + expected: []interface{}{int64(92)}, + }, testcase{ + name: "uint64", + fn: func(e CorpusEntry) error { + i := e.Values[0].(uint64) + if i > 10 { + return fmt.Errorf("bad %v", e.Values[0]) + } + return nil + }, + input: []interface{}{uint64(1<<64 - 1)}, + expected: []interface{}{uint64(18)}, + }) + } + + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + ws := &workerServer{ + fuzzFn: tc.fn, + } + count := int64(0) + vals := tc.input + success, err := ws.minimizeInput(context.Background(), vals, &count, 0, nil) + 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) + } + }) + } +} + +// TestMinimizeInputCoverageError checks that if we're minimizing an interesting +// input (one that we don't expect to cause an error), and the fuzz function +// returns an error, minimizing fails, and we return the error quickly. +func TestMinimizeInputCoverageError(t *testing.T) { + errOhNo := errors.New("ohno") + ws := &workerServer{fuzzFn: func(e CorpusEntry) error { + return errOhNo + }} + keepCoverage := make([]byte, len(coverageSnapshot)) + count := int64(0) + vals := []interface{}{[]byte(nil)} + success, err := ws.minimizeInput(context.Background(), vals, &count, 0, keepCoverage) + if success { + t.Error("unexpected success") + } + if err != errOhNo { + t.Errorf("unexpected error: %v", err) + } + if count != 1 { + t.Errorf("count: got %d, want 1", count) + } +} |