blob: 410b78310be998679e3804aa3a91f19301b864b2 [file] [log] [blame]
// 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)
}
}