aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/go/internal
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/go/internal')
-rw-r--r--src/cmd/go/internal/cache/cache.go10
-rw-r--r--src/cmd/go/internal/cfg/cfg.go4
-rw-r--r--src/cmd/go/internal/clean/clean.go18
-rw-r--r--src/cmd/go/internal/load/flag.go5
-rw-r--r--src/cmd/go/internal/load/pkg.go18
-rw-r--r--src/cmd/go/internal/load/test.go16
-rw-r--r--src/cmd/go/internal/test/flagdefs.go3
-rw-r--r--src/cmd/go/internal/test/flagdefs_test.go2
-rw-r--r--src/cmd/go/internal/test/genflags.go2
-rw-r--r--src/cmd/go/internal/test/test.go131
-rw-r--r--src/cmd/go/internal/test/testflag.go3
-rw-r--r--src/cmd/go/internal/work/init.go8
12 files changed, 191 insertions, 29 deletions
diff --git a/src/cmd/go/internal/cache/cache.go b/src/cmd/go/internal/cache/cache.go
index d592d70497..596f22e8fc 100644
--- a/src/cmd/go/internal/cache/cache.go
+++ b/src/cmd/go/internal/cache/cache.go
@@ -533,3 +533,13 @@ func (c *Cache) copyFile(file io.ReadSeeker, out OutputID, size int64) error {
return nil
}
+
+// FuzzDir returns a subdirectory within the cache for storing fuzzing data.
+// The subdirectory may not exist.
+//
+// This directory is managed by the internal/fuzz package. Files in this
+// directory aren't removed by the 'go clean -cache' command or by Trim.
+// They may be removed with 'go clean -fuzzcache'.
+func (c *Cache) FuzzDir() string {
+ return filepath.Join(c.dir, "fuzz")
+}
diff --git a/src/cmd/go/internal/cfg/cfg.go b/src/cmd/go/internal/cfg/cfg.go
index 5f4465e06b..dd0e8cbbd6 100644
--- a/src/cmd/go/internal/cfg/cfg.go
+++ b/src/cmd/go/internal/cfg/cfg.go
@@ -60,6 +60,10 @@ var (
func defaultContext() build.Context {
ctxt := build.Default
+
+ // TODO(#47037): remove this tag before merging to master.
+ ctxt.BuildTags = []string{"gofuzzbeta"}
+
ctxt.JoinPath = filepath.Join // back door to say "do not use go command"
ctxt.GOROOT = findGOROOT()
diff --git a/src/cmd/go/internal/clean/clean.go b/src/cmd/go/internal/clean/clean.go
index 1089211f0c..518473c914 100644
--- a/src/cmd/go/internal/clean/clean.go
+++ b/src/cmd/go/internal/clean/clean.go
@@ -75,6 +75,8 @@ The -modcache flag causes clean to remove the entire module
download cache, including unpacked source code of versioned
dependencies.
+The -fuzzcache flag causes clean to remove values used for fuzz testing.
+
For more about build flags, see 'go help build'.
For more about specifying packages, see 'go help packages'.
@@ -85,6 +87,7 @@ var (
cleanI bool // clean -i flag
cleanR bool // clean -r flag
cleanCache bool // clean -cache flag
+ cleanFuzzcache bool // clean -fuzzcache flag
cleanModcache bool // clean -modcache flag
cleanTestcache bool // clean -testcache flag
)
@@ -96,6 +99,7 @@ func init() {
CmdClean.Flag.BoolVar(&cleanI, "i", false, "")
CmdClean.Flag.BoolVar(&cleanR, "r", false, "")
CmdClean.Flag.BoolVar(&cleanCache, "cache", false, "")
+ CmdClean.Flag.BoolVar(&cleanFuzzcache, "fuzzcache", false, "")
CmdClean.Flag.BoolVar(&cleanModcache, "modcache", false, "")
CmdClean.Flag.BoolVar(&cleanTestcache, "testcache", false, "")
@@ -112,7 +116,7 @@ func runClean(ctx context.Context, cmd *base.Command, args []string) {
// or no other target (such as a cache) was requested to be cleaned.
cleanPkg := len(args) > 0 || cleanI || cleanR
if (!modload.Enabled() || modload.HasModRoot()) &&
- !cleanCache && !cleanModcache && !cleanTestcache {
+ !cleanCache && !cleanModcache && !cleanTestcache && !cleanFuzzcache {
cleanPkg = true
}
@@ -206,6 +210,18 @@ func runClean(ctx context.Context, cmd *base.Command, args []string) {
}
}
}
+
+ if cleanFuzzcache {
+ fuzzDir := cache.Default().FuzzDir()
+ if cfg.BuildN || cfg.BuildX {
+ b.Showcmd("", "rm -rf %s", fuzzDir)
+ }
+ if !cfg.BuildN {
+ if err := os.RemoveAll(fuzzDir); err != nil {
+ base.Errorf("go clean -fuzzcache: %v", err)
+ }
+ }
+ }
}
var cleaned = map[*load.Package]bool{}
diff --git a/src/cmd/go/internal/load/flag.go b/src/cmd/go/internal/load/flag.go
index 4e0cb5bc19..24670524fc 100644
--- a/src/cmd/go/internal/load/flag.go
+++ b/src/cmd/go/internal/load/flag.go
@@ -22,8 +22,9 @@ var (
// that allows specifying different effective flags for different packages.
// See 'go help build' for more details about per-package flags.
type PerPackageFlag struct {
- present bool
- values []ppfValue
+ present bool
+ values []ppfValue
+ seenPackages map[*Package]bool // the packages for which the flags have already been set
}
// A ppfValue is a single <pattern>=<flags> per-package flag value.
diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go
index 4013330bc4..317053d918 100644
--- a/src/cmd/go/internal/load/pkg.go
+++ b/src/cmd/go/internal/load/pkg.go
@@ -2630,10 +2630,20 @@ func (e *mainPackageError) ImportPath() string {
func setToolFlags(pkgs ...*Package) {
for _, p := range PackageList(pkgs) {
- p.Internal.Asmflags = BuildAsmflags.For(p)
- p.Internal.Gcflags = BuildGcflags.For(p)
- p.Internal.Ldflags = BuildLdflags.For(p)
- p.Internal.Gccgoflags = BuildGccgoflags.For(p)
+ appendFlags(p, &p.Internal.Asmflags, &BuildAsmflags)
+ appendFlags(p, &p.Internal.Gcflags, &BuildGcflags)
+ appendFlags(p, &p.Internal.Ldflags, &BuildLdflags)
+ appendFlags(p, &p.Internal.Gccgoflags, &BuildGccgoflags)
+ }
+}
+
+func appendFlags(p *Package, flags *[]string, packageFlag *PerPackageFlag) {
+ if !packageFlag.seenPackages[p] {
+ if packageFlag.seenPackages == nil {
+ packageFlag.seenPackages = make(map[*Package]bool)
+ }
+ packageFlag.seenPackages[p] = true
+ *flags = append(*flags, packageFlag.For(p)...)
}
}
diff --git a/src/cmd/go/internal/load/test.go b/src/cmd/go/internal/load/test.go
index 42eefe37ba..da6d1cb21d 100644
--- a/src/cmd/go/internal/load/test.go
+++ b/src/cmd/go/internal/load/test.go
@@ -555,6 +555,7 @@ func formatTestmain(t *testFuncs) ([]byte, error) {
type testFuncs struct {
Tests []testFunc
Benchmarks []testFunc
+ FuzzTargets []testFunc
Examples []testFunc
TestMain *testFunc
Package *Package
@@ -653,6 +654,13 @@ func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error {
}
t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, "", false})
*doImport, *seen = true, true
+ case isTest(name, "Fuzz"):
+ err := checkTestFunc(n, "F")
+ if err != nil {
+ return err
+ }
+ t.FuzzTargets = append(t.FuzzTargets, testFunc{pkg, name, "", false})
+ *doImport, *seen = true, true
}
}
ex := doc.Examples(f)
@@ -716,6 +724,12 @@ var benchmarks = []testing.InternalBenchmark{
{{end}}
}
+var fuzzTargets = []testing.InternalFuzzTarget{
+{{range .FuzzTargets}}
+ {"{{.Name}}", {{.Package}}.{{.Name}}},
+{{end}}
+}
+
var examples = []testing.InternalExample{
{{range .Examples}}
{"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}, {{.Unordered}}},
@@ -774,7 +788,7 @@ func main() {
CoveredPackages: {{printf "%q" .Covered}},
})
{{end}}
- m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, examples)
+ m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, fuzzTargets, examples)
{{with .TestMain}}
{{.Package}}.{{.Name}}(m)
os.Exit(int(reflect.ValueOf(m).Elem().FieldByName("exitCode").Int()))
diff --git a/src/cmd/go/internal/test/flagdefs.go b/src/cmd/go/internal/test/flagdefs.go
index 37ac81c267..3148074d57 100644
--- a/src/cmd/go/internal/test/flagdefs.go
+++ b/src/cmd/go/internal/test/flagdefs.go
@@ -19,6 +19,9 @@ var passFlagToTest = map[string]bool{
"cpu": true,
"cpuprofile": true,
"failfast": true,
+ "fuzz": true,
+ "fuzzminimizetime": true,
+ "fuzztime": true,
"list": true,
"memprofile": true,
"memprofilerate": true,
diff --git a/src/cmd/go/internal/test/flagdefs_test.go b/src/cmd/go/internal/test/flagdefs_test.go
index ab5440b380..f238fc7d33 100644
--- a/src/cmd/go/internal/test/flagdefs_test.go
+++ b/src/cmd/go/internal/test/flagdefs_test.go
@@ -17,7 +17,7 @@ func TestPassFlagToTestIncludesAllTestFlags(t *testing.T) {
}
name := strings.TrimPrefix(f.Name, "test.")
switch name {
- case "testlogfile", "paniconexit0":
+ case "testlogfile", "paniconexit0", "fuzzcachedir", "fuzzworker":
// These are internal flags.
default:
if !passFlagToTest[name] {
diff --git a/src/cmd/go/internal/test/genflags.go b/src/cmd/go/internal/test/genflags.go
index 9277de7fee..645aae68b1 100644
--- a/src/cmd/go/internal/test/genflags.go
+++ b/src/cmd/go/internal/test/genflags.go
@@ -64,7 +64,7 @@ func testFlags() []string {
name := strings.TrimPrefix(f.Name, "test.")
switch name {
- case "testlogfile", "paniconexit0":
+ case "testlogfile", "paniconexit0", "fuzzcachedir", "fuzzworker":
// These flags are only for use by cmd/go.
default:
names = append(names, name)
diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go
index 198afbf4c3..8f5d57eff1 100644
--- a/src/cmd/go/internal/test/test.go
+++ b/src/cmd/go/internal/test/test.go
@@ -61,8 +61,8 @@ followed by detailed output for each failed package.
'Go test' recompiles each package along with any files with names matching
the file pattern "*_test.go".
-These additional files can contain test functions, benchmark functions, and
-example functions. See 'go help testfunc' for more.
+These additional files can contain test functions, benchmark functions, fuzz
+targets and example functions. See 'go help testfunc' for more.
Each listed package causes the execution of a separate test binary.
Files whose names begin with "_" (including "_test.go") or "." are ignored.
@@ -132,6 +132,8 @@ variables are unchanged. A cached test result is treated as executing
in no time at all,so a successful package test result will be cached and
reused regardless of -timeout setting.
+Run 'go help fuzz' for details around how the go command handles fuzz targets.
+
In addition to the build flags, the flags handled by 'go test' itself are:
-args
@@ -208,7 +210,8 @@ control the execution of any test:
(for example, -benchtime 100x).
-count n
- Run each test and benchmark n times (default 1).
+ Run each test, benchmark, and fuzz targets' seed corpora n times
+ (default 1).
If -cpu is set, run n times for each GOMAXPROCS value.
Examples are always run once.
@@ -237,36 +240,55 @@ control the execution of any test:
Sets -cover.
-cpu 1,2,4
- Specify a list of GOMAXPROCS values for which the tests or
- benchmarks should be executed. The default is the current value
+ Specify a list of GOMAXPROCS values for which the tests, benchmarks or
+ fuzz targets should be executed. The default is the current value
of GOMAXPROCS.
-failfast
Do not start new tests after the first test failure.
+ -fuzz name
+ Run the fuzz target with the given regexp. Must match exactly one fuzz
+ target. This is an experimental feature.
+
+ -fuzztime t
+ Run enough iterations of the fuzz test to take t, specified as a
+ time.Duration (for example, -fuzztime 1h30s). The default is to run
+ forever.
+ The special syntax Nx means to run the fuzz test N times
+ (for example, -fuzztime 100x).
+
-json
Log verbose output and test results in JSON. This presents the
same information as the -v flag in a machine-readable format.
+ -keepfuzzing
+ Keep running the fuzz target if a crasher is found.
+
-list regexp
- List tests, benchmarks, or examples matching the regular expression.
- No tests, benchmarks or examples will be run. This will only
- list top-level tests. No subtest or subbenchmarks will be shown.
+ List tests, benchmarks, fuzz targets, or examples matching the regular
+ expression. No tests, benchmarks, fuzz targets, or examples will be run.
+ This will only list top-level tests. No subtest or subbenchmarks will be
+ shown.
-parallel n
- Allow parallel execution of test functions that call t.Parallel.
+ Allow parallel execution of test functions that call t.Parallel, and
+ f.Fuzz functions that call t.Parallel when running the seed corpus.
The value of this flag is the maximum number of tests to run
- simultaneously; by default, it is set to the value of GOMAXPROCS.
+ simultaneously. While fuzzing, the value of this flag is the
+ maximum number of workers to run the fuzz function simultaneously,
+ regardless of whether t.Parallel has been called; by default, it is set
+ to the value of GOMAXPROCS.
Note that -parallel only applies within a single test binary.
The 'go test' command may run tests for different packages
in parallel as well, according to the setting of the -p flag
(see 'go help build').
-run regexp
- Run only those tests and examples matching the regular expression.
- For tests, the regular expression is split by unbracketed slash (/)
- characters into a sequence of regular expressions, and each part
- of a test's identifier must match the corresponding element in
+ Run only those tests, examples, and fuzz targets matching the regular
+ expression. For tests, the regular expression is split by unbracketed
+ slash (/) characters into a sequence of regular expressions, and each
+ part of a test's identifier must match the corresponding element in
the sequence, if any. Note that possible parents of matches are
run too, so that -run=X/Y matches and runs and reports the result
of all tests matching X, even those without sub-tests matching Y,
@@ -436,6 +458,10 @@ A benchmark function is one named BenchmarkXxx and should have the signature,
func BenchmarkXxx(b *testing.B) { ... }
+A fuzz target is one named FuzzXxx and should have the signature,
+
+ func FuzzXxx(f *testing.F) { ... }
+
An example function is similar to a test function but, instead of using
*testing.T to report success or failure, prints output to os.Stdout.
If the last comment in the function starts with "Output:" then the output
@@ -475,12 +501,34 @@ Here is another example where the ordering of the output is ignored:
The entire test file is presented as the example when it contains a single
example function, at least one other function, type, variable, or constant
-declaration, and no test or benchmark functions.
+declaration, and no fuzz targets or test or benchmark functions.
See the documentation of the testing package for more information.
`,
}
+var HelpFuzz = &base.Command{
+ UsageLine: "fuzz",
+ Short: "fuzzing",
+ Long: `
+By default, go test will build and run the fuzz targets using the target's seed
+corpus only. Any generated corpora in $GOCACHE that were previously written by
+the fuzzing engine will not be run by default.
+
+When -fuzz is set, the binary will be instrumented for coverage. After all
+tests, examples, benchmark functions, and the seed corpora for all fuzz targets
+have been run, go test will begin to fuzz the specified fuzz target.
+Note that this feature is experimental.
+
+-run can be used for testing a single seed corpus entry for a fuzz target. The
+regular expression value of -run can be in the form $target/$name, where $target
+is the name of the fuzz target, and $name is the name of the file (ignoring file
+extensions) to run. For example, -run=FuzzFoo/497b6f87.
+
+See https://golang.org/s/draft-fuzzing-design for more details.
+`,
+}
+
var (
testBench string // -bench flag
testC bool // -c flag
@@ -489,6 +537,7 @@ var (
testCoverPaths []string // -coverpkg flag
testCoverPkgs []*load.Package // -coverpkg flag
testCoverProfile string // -coverprofile flag
+ testFuzz string // -fuzz flag
testJSON bool // -json flag
testList string // -list flag
testO string // -o flag
@@ -622,6 +671,9 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) {
if testO != "" && len(pkgs) != 1 {
base.Fatalf("cannot use -o flag with multiple packages")
}
+ if testFuzz != "" && len(pkgs) != 1 {
+ base.Fatalf("cannot use -fuzz flag with multiple packages")
+ }
if testProfile() != "" && len(pkgs) != 1 {
base.Fatalf("cannot use %s flag with multiple packages", testProfile())
}
@@ -632,7 +684,9 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) {
// to that timeout plus one minute. This is a backup alarm in case
// the test wedges with a goroutine spinning and its background
// timer does not get a chance to fire.
- if testTimeout > 0 {
+ // Don't set this if fuzzing, since it should be able to run
+ // indefinitely.
+ if testTimeout > 0 && testFuzz == "" {
testKillTimeout = testTimeout + 1*time.Minute
}
@@ -782,6 +836,32 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) {
}
}
+ // Inform the compiler that it should instrument the binary at
+ // build-time when fuzzing is enabled.
+ fuzzFlags := work.FuzzInstrumentFlags()
+ if testFuzz != "" && fuzzFlags != nil {
+ // Don't instrument packages which may affect coverage guidance but are
+ // unlikely to be useful. Most of these are used by the testing or
+ // internal/fuzz concurrently with fuzzing.
+ var fuzzNoInstrument = map[string]bool{
+ "context": true,
+ "internal/fuzz": true,
+ "reflect": true,
+ "runtime": true,
+ "sync": true,
+ "sync/atomic": true,
+ "syscall": true,
+ "testing": true,
+ "time": true,
+ }
+ for _, p := range load.TestPackageList(ctx, pkgOpts, pkgs) {
+ if fuzzNoInstrument[p.ImportPath] {
+ continue
+ }
+ p.Internal.Gcflags = append(p.Internal.Gcflags, fuzzFlags...)
+ }
+ }
+
// Prepare build + run + print actions for all packages being tested.
for _, p := range pkgs {
// sync/atomic import is inserted by the cover tool. See #18486
@@ -1087,6 +1167,8 @@ func declareCoverVars(p *load.Package, files ...string) map[string]*load.CoverVa
}
var noTestsToRun = []byte("\ntesting: warning: no tests to run\n")
+var noTargetsToFuzz = []byte("\ntesting: warning: no targets to fuzz\n")
+var tooManyTargetsToFuzz = []byte("\ntesting: warning: -fuzz matches more than one target, won't fuzz\n")
type runCache struct {
disableCache bool // cache should be disabled for this run
@@ -1134,10 +1216,10 @@ func (c *runCache) builderRunTest(b *work.Builder, ctx context.Context, a *work.
}
var buf bytes.Buffer
- if len(pkgArgs) == 0 || (testBench != "") {
+ if len(pkgArgs) == 0 || testBench != "" || testFuzz != "" {
// Stream test output (no buffering) when no package has
// been given on the command line (implicit current directory)
- // or when benchmarking.
+ // or when benchmarking or fuzzing.
// No change to stdout.
} else {
// If we're only running a single package under test or if parallelism is
@@ -1190,7 +1272,12 @@ func (c *runCache) builderRunTest(b *work.Builder, ctx context.Context, a *work.
testlogArg = []string{"-test.testlogfile=" + a.Objdir + "testlog.txt"}
}
panicArg := "-test.paniconexit0"
- args := str.StringList(execCmd, a.Deps[0].BuiltTarget(), testlogArg, panicArg, testArgs)
+ fuzzArg := []string{}
+ if testFuzz != "" {
+ fuzzCacheDir := filepath.Join(cache.Default().FuzzDir(), a.Package.ImportPath)
+ fuzzArg = []string{"-test.fuzzcachedir=" + fuzzCacheDir}
+ }
+ args := str.StringList(execCmd, a.Deps[0].BuiltTarget(), testlogArg, panicArg, fuzzArg, testArgs)
if testCoverProfile != "" {
// Write coverage to temporary profile, for merging later.
@@ -1283,6 +1370,12 @@ func (c *runCache) builderRunTest(b *work.Builder, ctx context.Context, a *work.
if bytes.HasPrefix(out, noTestsToRun[1:]) || bytes.Contains(out, noTestsToRun) {
norun = " [no tests to run]"
}
+ if bytes.HasPrefix(out, noTargetsToFuzz[1:]) || bytes.Contains(out, noTargetsToFuzz) {
+ norun = " [no targets to fuzz]"
+ }
+ if bytes.HasPrefix(out, tooManyTargetsToFuzz[1:]) || bytes.Contains(out, tooManyTargetsToFuzz) {
+ norun = " [will not fuzz, -fuzz matches more than one target]"
+ }
fmt.Fprintf(cmd.Stdout, "ok \t%s\t%s%s%s\n", a.Package.ImportPath, t, coveragePercentage(out), norun)
c.saveOutput(a)
} else {
diff --git a/src/cmd/go/internal/test/testflag.go b/src/cmd/go/internal/test/testflag.go
index e0a3e010fa..cb3543884a 100644
--- a/src/cmd/go/internal/test/testflag.go
+++ b/src/cmd/go/internal/test/testflag.go
@@ -57,6 +57,7 @@ func init() {
cf.String("cpu", "", "")
cf.StringVar(&testCPUProfile, "cpuprofile", "", "")
cf.Bool("failfast", false, "")
+ cf.StringVar(&testFuzz, "fuzz", "", "")
cf.StringVar(&testList, "list", "", "")
cf.StringVar(&testMemProfile, "memprofile", "", "")
cf.String("memprofilerate", "", "")
@@ -67,6 +68,8 @@ func init() {
cf.String("run", "", "")
cf.Bool("short", false, "")
cf.DurationVar(&testTimeout, "timeout", 10*time.Minute, "")
+ cf.String("fuzztime", "", "")
+ cf.String("fuzzminimizetime", "", "")
cf.StringVar(&testTrace, "trace", "", "")
cf.BoolVar(&testV, "v", false, "")
cf.Var(&testShuffle, "shuffle", "")
diff --git a/src/cmd/go/internal/work/init.go b/src/cmd/go/internal/work/init.go
index 7aa8dfe55f..2a605e73ee 100644
--- a/src/cmd/go/internal/work/init.go
+++ b/src/cmd/go/internal/work/init.go
@@ -60,6 +60,14 @@ func BuildInit() {
}
}
+func FuzzInstrumentFlags() []string {
+ if cfg.Goarch != "amd64" && cfg.Goarch != "arm64" {
+ // Instrumentation is only supported on 64-bit architectures.
+ return nil
+ }
+ return []string{"-d=libfuzzer"}
+}
+
func instrumentInit() {
if !cfg.BuildRace && !cfg.BuildMSan {
return