Skip to content

Commit 9dd8f1e

Browse files
committed
Add some stub fuzzing types/functions to testing
1 parent 9a7f9a4 commit 9dd8f1e

File tree

7 files changed

+286
-12
lines changed

7 files changed

+286
-12
lines changed

main_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ func TestBuild(t *testing.T) {
6666
"stdlib.go",
6767
"string.go",
6868
"structs.go",
69-
"testing.go",
7069
"zeroalloc.go",
7170
}
7271
_, minor, err := goenv.GetGorootVersion(goenv.Get("GOROOT"))
@@ -76,6 +75,11 @@ func TestBuild(t *testing.T) {
7675
if minor >= 17 {
7776
tests = append(tests, "go1.17.go")
7877
}
78+
if minor >= 18 {
79+
tests = append(tests, "testing_go1.18.go")
80+
} else {
81+
tests = append(tests, "testing_other.go")
82+
}
7983

8084
if *testTarget != "" {
8185
// This makes it possible to run one specific test (instead of all),

src/testing/fuzz.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package testing
2+
3+
// FIXME: These are only the types and functions necessary to compile; they are not properly used or implemented.
4+
5+
import (
6+
"errors"
7+
"fmt"
8+
"reflect"
9+
"time"
10+
)
11+
12+
// InternalFuzzTarget is an internal type but exported because it is
13+
// cross-package; it is part of the implementation of the "go test" command.
14+
type InternalFuzzTarget struct {
15+
Name string
16+
Fn func(f *F)
17+
}
18+
19+
// F is a type passed to fuzz tests.
20+
//
21+
// Fuzz tests run generated inputs against a provided fuzz target, which can
22+
// find and report potential bugs in the code being tested.
23+
//
24+
// A fuzz test runs the seed corpus by default, which includes entries provided
25+
// by (*F).Add and entries in the testdata/fuzz/<FuzzTestName> directory. After
26+
// any necessary setup and calls to (*F).Add, the fuzz test must then call
27+
// (*F).Fuzz to provide the fuzz target. See the testing package documentation
28+
// for an example, and see the F.Fuzz and F.Add method documentation for
29+
// details.
30+
//
31+
// *F methods can only be called before (*F).Fuzz. Once the test is
32+
// executing the fuzz target, only (*T) methods can be used. The only *F methods
33+
// that are allowed in the (*F).Fuzz function are (*F).Failed and (*F).Name.
34+
type F struct {
35+
common
36+
fuzzContext *fuzzContext
37+
testContext *testContext
38+
39+
// inFuzzFn is true when the fuzz function is running. Most F methods cannot
40+
// be called when inFuzzFn is true.
41+
inFuzzFn bool
42+
43+
// corpus is a set of seed corpus entries, added with F.Add and loaded
44+
// from testdata.
45+
corpus []corpusEntry
46+
47+
result fuzzResult
48+
fuzzCalled bool
49+
}
50+
51+
// corpusEntry is an alias to the same type as internal/fuzz.CorpusEntry.
52+
// We use a type alias because we don't want to export this type, and we can't
53+
// import internal/fuzz from testing.
54+
type corpusEntry = struct {
55+
Parent string
56+
Path string
57+
Data []byte
58+
Values []interface{}
59+
Generation int
60+
IsSeed bool
61+
}
62+
63+
// Add will add the arguments to the seed corpus for the fuzz test. This will be
64+
// a no-op if called after or within the fuzz target, and args must match the
65+
// arguments for the fuzz target.
66+
func (f *F) Add(args ...interface{}) {
67+
var values []interface{}
68+
for i := range args {
69+
if t := reflect.TypeOf(args[i]); !supportedTypes[t] {
70+
panic(fmt.Sprintf("testing: unsupported type to Add %v", t))
71+
}
72+
values = append(values, args[i])
73+
}
74+
f.corpus = append(f.corpus, corpusEntry{Values: values, IsSeed: true, Path: fmt.Sprintf("seed#%d", len(f.corpus))})
75+
}
76+
77+
// supportedTypes represents all of the supported types which can be fuzzed.
78+
var supportedTypes = map[reflect.Type]bool{
79+
reflect.TypeOf(([]byte)("")): true,
80+
reflect.TypeOf((string)("")): true,
81+
reflect.TypeOf((bool)(false)): true,
82+
reflect.TypeOf((byte)(0)): true,
83+
reflect.TypeOf((rune)(0)): true,
84+
reflect.TypeOf((float32)(0)): true,
85+
reflect.TypeOf((float64)(0)): true,
86+
reflect.TypeOf((int)(0)): true,
87+
reflect.TypeOf((int8)(0)): true,
88+
reflect.TypeOf((int16)(0)): true,
89+
reflect.TypeOf((int32)(0)): true,
90+
reflect.TypeOf((int64)(0)): true,
91+
reflect.TypeOf((uint)(0)): true,
92+
reflect.TypeOf((uint8)(0)): true,
93+
reflect.TypeOf((uint16)(0)): true,
94+
reflect.TypeOf((uint32)(0)): true,
95+
reflect.TypeOf((uint64)(0)): true,
96+
}
97+
98+
// Fuzz runs the fuzz function, ff, for fuzz testing. If ff fails for a set of
99+
// arguments, those arguments will be added to the seed corpus.
100+
//
101+
// ff must be a function with no return value whose first argument is *T and
102+
// whose remaining arguments are the types to be fuzzed.
103+
// For example:
104+
//
105+
// f.Fuzz(func(t *testing.T, b []byte, i int) { ... })
106+
//
107+
// The following types are allowed: []byte, string, bool, byte, rune, float32,
108+
// float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64.
109+
// More types may be supported in the future.
110+
//
111+
// ff must not call any *F methods, e.g. (*F).Log, (*F).Error, (*F).Skip. Use
112+
// the corresponding *T method instead. The only *F methods that are allowed in
113+
// the (*F).Fuzz function are (*F).Failed and (*F).Name.
114+
//
115+
// This function should be fast and deterministic, and its behavior should not
116+
// depend on shared state. No mutatable input arguments, or pointers to them,
117+
// should be retained between executions of the fuzz function, as the memory
118+
// backing them may be mutated during a subsequent invocation. ff must not
119+
// modify the underlying data of the arguments provided by the fuzzing engine.
120+
//
121+
// When fuzzing, F.Fuzz does not return until a problem is found, time runs out
122+
// (set with -fuzztime), or the test process is interrupted by a signal. F.Fuzz
123+
// should be called exactly once, unless F.Skip or F.Fail is called beforehand.
124+
func (f *F) Fuzz(ff interface{}) {
125+
f.failed = true
126+
f.result.N = 0
127+
f.result.T = 0
128+
f.result.Error = errors.New("operation not implemented")
129+
return
130+
}
131+
132+
// fuzzContext holds fields common to all fuzz tests.
133+
type fuzzContext struct {
134+
deps testDeps
135+
mode fuzzMode
136+
}
137+
138+
type fuzzMode uint8
139+
140+
// fuzzResult contains the results of a fuzz run.
141+
type fuzzResult struct {
142+
N int // The number of iterations.
143+
T time.Duration // The total time taken.
144+
Error error // Error is the error from the failing input
145+
}

src/testing/testing.go

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -416,8 +416,9 @@ func newTestContext(m *matcher) *testContext {
416416
// M is a test suite.
417417
type M struct {
418418
// tests is a list of the test names to execute
419-
Tests []InternalTest
420-
Benchmarks []InternalBenchmark
419+
Tests []InternalTest
420+
Benchmarks []InternalBenchmark
421+
fuzzTargets []InternalFuzzTarget
421422

422423
deps testDeps
423424

@@ -430,15 +431,6 @@ type testDeps interface {
430431
MatchString(pat, str string) (bool, error)
431432
}
432433

433-
func MainStart(deps interface{}, tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) *M {
434-
Init()
435-
return &M{
436-
Tests: tests,
437-
Benchmarks: benchmarks,
438-
deps: deps.(testDeps),
439-
}
440-
}
441-
442434
// Run runs the tests. It returns an exit code to pass to os.Exit.
443435
func (m *M) Run() (code int) {
444436
defer func() {

src/testing/testing_go118.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//go:build go1.18
2+
// +build go1.18
3+
4+
package testing
5+
6+
// MainStart is meant for use by tests generated by 'go test'.
7+
// It is not meant to be called directly and is not subject to the Go 1 compatibility document.
8+
// It may change signature from release to release.
9+
func MainStart(deps interface{}, tests []InternalTest, benchmarks []InternalBenchmark, fuzzTargets []InternalFuzzTarget, examples []InternalExample) *M {
10+
Init()
11+
return &M{
12+
Tests: tests,
13+
Benchmarks: benchmarks,
14+
fuzzTargets: fuzzTargets,
15+
deps: deps.(testDeps),
16+
}
17+
}

src/testing/testing_other.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//go:build !go1.18
2+
// +build !go1.18
3+
4+
package testing
5+
6+
// MainStart is meant for use by tests generated by 'go test'.
7+
// It is not meant to be called directly and is not subject to the Go 1 compatibility document.
8+
// It may change signature from release to release.
9+
func MainStart(deps interface{}, tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) *M {
10+
Init()
11+
return &M{
12+
Tests: tests,
13+
Benchmarks: benchmarks,
14+
deps: deps.(testDeps),
15+
}
16+
}

testdata/testing_go1.18.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package main
2+
3+
// TODO: also test the verbose version.
4+
5+
import (
6+
"errors"
7+
"flag"
8+
"io"
9+
"strings"
10+
"testing"
11+
)
12+
13+
func TestFoo(t *testing.T) {
14+
t.Log("log Foo.a")
15+
t.Log("log Foo.b")
16+
}
17+
18+
func TestBar(t *testing.T) {
19+
t.Log("log Bar")
20+
t.Log("log g\nh\ni\n")
21+
t.Run("Bar1", func(t *testing.T) {})
22+
t.Run("Bar2", func(t *testing.T) {
23+
t.Log("log Bar2\na\nb\nc")
24+
t.Error("failed")
25+
t.Log("after failed")
26+
})
27+
t.Run("Bar3", func(t *testing.T) {})
28+
t.Log("log Bar end")
29+
}
30+
31+
func TestAllLowercase(t *testing.T) {
32+
names := []string {
33+
"alpha",
34+
"BETA",
35+
"gamma",
36+
"BELTA",
37+
}
38+
39+
for _, name := range names {
40+
t.Run(name, func(t *testing.T) {
41+
if 'a' <= name[0] && name[0] <= 'a' {
42+
t.Logf("expected lowercase name, and got one, so I'm happy")
43+
} else {
44+
t.Errorf("expected lowercase name, got %s", name)
45+
}
46+
})
47+
}
48+
}
49+
50+
var tests = []testing.InternalTest{
51+
{"TestFoo", TestFoo},
52+
{"TestBar", TestBar},
53+
{"TestAllLowercase", TestAllLowercase},
54+
}
55+
56+
var benchmarks = []testing.InternalBenchmark{}
57+
58+
var fuzzings []testing.InternalFuzzTarget{}
59+
60+
var examples = []testing.InternalExample{}
61+
62+
// A fake regexp matcher.
63+
// Inflexible, but saves 50KB of flash and 50KB of RAM per -size full,
64+
// and lets tests pass on cortex-m.
65+
// Must match the one in src/testing/match.go that is substituted on bare-metal platforms,
66+
// or "make test" will fail there.
67+
func fakeMatchString(pat, str string) (bool, error) {
68+
if pat == ".*" {
69+
return true, nil
70+
}
71+
matched := strings.Contains(str, pat)
72+
return matched, nil
73+
}
74+
75+
func main() {
76+
testing.Init()
77+
flag.Set("test.run", ".*/B")
78+
m := testing.MainStart(matchStringOnly(fakeMatchString /*regexp.MatchString*/), tests, benchmarks, fuzzings, examples)
79+
80+
exitcode := m.Run()
81+
if exitcode != 0 {
82+
println("exitcode:", exitcode)
83+
}
84+
}
85+
86+
var errMain = errors.New("testing: unexpected use of func Main")
87+
88+
// matchStringOnly is part of upstream, and is used below to provide a dummy deps to pass to MainStart
89+
// so it can be run with go (tested with go 1.16) to provide a baseline for the regression test.
90+
// See c56cc9b3b57276. Unfortunately, testdeps is internal, so we can't just use &testdeps.TestDeps{}.
91+
type matchStringOnly func(pat, str string) (bool, error)
92+
93+
func (f matchStringOnly) MatchString(pat, str string) (bool, error) { return f(pat, str) }
94+
func (f matchStringOnly) StartCPUProfile(w io.Writer) error { return errMain }
95+
func (f matchStringOnly) StopCPUProfile() {}
96+
func (f matchStringOnly) WriteProfileTo(string, io.Writer, int) error { return errMain }
97+
func (f matchStringOnly) ImportPath() string { return "" }
98+
func (f matchStringOnly) StartTestLog(io.Writer) {}
99+
func (f matchStringOnly) StopTestLog() error { return errMain }
100+
func (f matchStringOnly) SetPanicOnExit0(bool) {}
File renamed without changes.

0 commit comments

Comments
 (0)