Source file src/internal/fuzz/minimize_test.go

     1  // Copyright 2021 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build darwin || freebsd || linux || windows
     6  
     7  package fuzz
     8  
     9  import (
    10  	"bytes"
    11  	"context"
    12  	"errors"
    13  	"fmt"
    14  	"reflect"
    15  	"testing"
    16  	"time"
    17  	"unicode"
    18  	"unicode/utf8"
    19  )
    20  
    21  func TestMinimizeInput(t *testing.T) {
    22  	type testcase struct {
    23  		name     string
    24  		fn       func(CorpusEntry) error
    25  		input    []any
    26  		expected []any
    27  	}
    28  	cases := []testcase{
    29  		{
    30  			name: "ones_byte",
    31  			fn: func(e CorpusEntry) error {
    32  				b := e.Values[0].([]byte)
    33  				ones := 0
    34  				for _, v := range b {
    35  					if v == 1 {
    36  						ones++
    37  					}
    38  				}
    39  				if ones == 3 {
    40  					return fmt.Errorf("bad %v", e.Values[0])
    41  				}
    42  				return nil
    43  			},
    44  			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}},
    45  			expected: []any{[]byte{1, 1, 1}},
    46  		},
    47  		{
    48  			name: "single_bytes",
    49  			fn: func(e CorpusEntry) error {
    50  				b := e.Values[0].([]byte)
    51  				if len(b) < 2 {
    52  					return nil
    53  				}
    54  				if len(b) == 2 && b[0] == 1 && b[1] == 2 {
    55  					return nil
    56  				}
    57  				return fmt.Errorf("bad %v", e.Values[0])
    58  			},
    59  			input:    []any{[]byte{1, 2, 3, 4, 5}},
    60  			expected: []any{[]byte("00")},
    61  		},
    62  		{
    63  			name: "set_of_bytes",
    64  			fn: func(e CorpusEntry) error {
    65  				b := e.Values[0].([]byte)
    66  				if len(b) < 3 {
    67  					return nil
    68  				}
    69  				if bytes.Equal(b, []byte{0, 1, 2, 3, 4, 5}) || bytes.Equal(b, []byte{0, 4, 5}) {
    70  					return fmt.Errorf("bad %v", e.Values[0])
    71  				}
    72  				return nil
    73  			},
    74  			input:    []any{[]byte{0, 1, 2, 3, 4, 5}},
    75  			expected: []any{[]byte{0, 4, 5}},
    76  		},
    77  		{
    78  			name: "non_ascii_bytes",
    79  			fn: func(e CorpusEntry) error {
    80  				b := e.Values[0].([]byte)
    81  				if len(b) == 3 {
    82  					return fmt.Errorf("bad %v", e.Values[0])
    83  				}
    84  				return nil
    85  			},
    86  			input:    []any{[]byte("ท")}, // ท is 3 bytes
    87  			expected: []any{[]byte("000")},
    88  		},
    89  		{
    90  			name: "ones_string",
    91  			fn: func(e CorpusEntry) error {
    92  				b := e.Values[0].(string)
    93  				ones := 0
    94  				for _, v := range b {
    95  					if v == '1' {
    96  						ones++
    97  					}
    98  				}
    99  				if ones == 3 {
   100  					return fmt.Errorf("bad %v", e.Values[0])
   101  				}
   102  				return nil
   103  			},
   104  			input:    []any{"001010001000000000000000000"},
   105  			expected: []any{"111"},
   106  		},
   107  		{
   108  			name: "string_length",
   109  			fn: func(e CorpusEntry) error {
   110  				b := e.Values[0].(string)
   111  				if len(b) == 5 {
   112  					return fmt.Errorf("bad %v", e.Values[0])
   113  				}
   114  				return nil
   115  			},
   116  			input:    []any{"zzzzz"},
   117  			expected: []any{"00000"},
   118  		},
   119  		{
   120  			name: "string_with_letter",
   121  			fn: func(e CorpusEntry) error {
   122  				b := e.Values[0].(string)
   123  				r, _ := utf8.DecodeRune([]byte(b))
   124  				if unicode.IsLetter(r) {
   125  					return fmt.Errorf("bad %v", e.Values[0])
   126  				}
   127  				return nil
   128  			},
   129  			input:    []any{"ZZZZZ"},
   130  			expected: []any{"A"},
   131  		},
   132  	}
   133  
   134  	for _, tc := range cases {
   135  		tc := tc
   136  		t.Run(tc.name, func(t *testing.T) {
   137  			t.Parallel()
   138  			ws := &workerServer{
   139  				fuzzFn: func(e CorpusEntry) (time.Duration, error) {
   140  					return time.Second, tc.fn(e)
   141  				},
   142  			}
   143  			mem := &sharedMem{region: make([]byte, 100)} // big enough to hold value and header
   144  			vals := tc.input
   145  			success, err := ws.minimizeInput(context.Background(), vals, mem, minimizeArgs{})
   146  			if !success {
   147  				t.Errorf("minimizeInput did not succeed")
   148  			}
   149  			if err == nil {
   150  				t.Fatal("minimizeInput didn't provide an error")
   151  			}
   152  			if expected := fmt.Sprintf("bad %v", tc.expected[0]); err.Error() != expected {
   153  				t.Errorf("unexpected error: got %q, want %q", err, expected)
   154  			}
   155  			if !reflect.DeepEqual(vals, tc.expected) {
   156  				t.Errorf("unexpected results: got %v, want %v", vals, tc.expected)
   157  			}
   158  		})
   159  	}
   160  }
   161  
   162  // TestMinimizeFlaky checks that if we're minimizing an interesting
   163  // input and a flaky failure occurs, that minimization was not indicated
   164  // to be successful, and the error isn't returned (since it's flaky).
   165  func TestMinimizeFlaky(t *testing.T) {
   166  	ws := &workerServer{fuzzFn: func(e CorpusEntry) (time.Duration, error) {
   167  		return time.Second, errors.New("ohno")
   168  	}}
   169  	mem := &sharedMem{region: make([]byte, 100)} // big enough to hold value and header
   170  	vals := []any{[]byte(nil)}
   171  	args := minimizeArgs{KeepCoverage: make([]byte, len(coverageSnapshot))}
   172  	success, err := ws.minimizeInput(context.Background(), vals, mem, args)
   173  	if success {
   174  		t.Error("unexpected success")
   175  	}
   176  	if err != nil {
   177  		t.Errorf("unexpected error: %v", err)
   178  	}
   179  	if count := mem.header().count; count != 1 {
   180  		t.Errorf("count: got %d, want 1", count)
   181  	}
   182  }
   183  

View as plain text