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
7package fuzz
8
9import (
10	"bytes"
11	"context"
12	"errors"
13	"fmt"
14	"reflect"
15	"testing"
16	"time"
17	"unicode"
18	"unicode/utf8"
19)
20
21func 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).
165func 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