1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
// 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.
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"golang.org/x/tools/internal/testenv"
)
func canRace(t *testing.T) bool {
_, err := exec.Command("go", "run", "-race", "./testdata/himom.go").CombinedOutput()
return err == nil
}
// buildRunner builds the fuzz-runner executable, returning its path.
func buildRunner(t *testing.T) string {
bindir := filepath.Join(t.TempDir(), "bin")
err := os.Mkdir(bindir, os.ModePerm)
if err != nil {
t.Fatal(err)
}
binary := filepath.Join(bindir, "runner")
if runtime.GOOS == "windows" {
binary += ".exe"
}
cmd := exec.Command("go", "build", "-o", binary)
if err := cmd.Run(); err != nil {
t.Fatalf("Building fuzz-runner: %v", err)
}
return binary
}
// TestRunner builds the binary, then kicks off a collection of sub-tests that invoke it.
func TestRunner(t *testing.T) {
testenv.NeedsTool(t, "go")
if runtime.GOOS == "android" {
t.Skipf("the dependencies are not available on android")
}
binaryPath := buildRunner(t)
// Sub-tests using the binary built above.
t.Run("Basic", func(t *testing.T) { testBasic(t, binaryPath) })
t.Run("Race", func(t *testing.T) { testRace(t, binaryPath) })
t.Run("Minimization1", func(t *testing.T) { testMinimization1(t, binaryPath) })
t.Run("Minimization2", func(t *testing.T) { testMinimization2(t, binaryPath) })
}
func testBasic(t *testing.T, binaryPath string) {
t.Parallel()
args := []string{"-numit=1", "-numfcns=1", "-numpkgs=1", "-seed=103", "-cleancache=0"}
c := exec.Command(binaryPath, args...)
b, err := c.CombinedOutput()
t.Logf("%s\n", b)
if err != nil {
t.Fatalf("error invoking fuzz-runner: %v", err)
}
}
func testRace(t *testing.T, binaryPath string) {
t.Parallel()
// For this test to work, the current test platform has to support the
// race detector. Check to see if that is the case by running a very
// simple Go program through it.
if !canRace(t) {
t.Skip("current platform does not appear to support the race detector")
}
args := []string{"-v=1", "-numit=1", "-race", "-numfcns=3", "-numpkgs=3", "-seed=987", "-cleancache=0"}
c := exec.Command(binaryPath, args...)
b, err := c.CombinedOutput()
t.Logf("%s\n", b)
if err != nil {
t.Fatalf("error invoking fuzz-runner: %v", err)
}
}
func testMinimization1(t *testing.T, binaryPath string) {
if binaryPath == "" {
t.Skipf("No runner binary")
}
t.Parallel()
// Fire off the runner passing it -emitbad=1, so that the generated code
// contains illegal Go code (which will force the build to fail). Verify that
// it does fail, that the error reflects the nature of the failure, and that
// we can minimize the error down to a single package.
args := []string{"-emitbad=1", "-badfcnidx=2", "-badpkgidx=2",
"-forcetmpclean", "-cleancache=0",
"-numit=1", "-numfcns=3", "-numpkgs=3", "-seed=909"}
invocation := fmt.Sprintf("%s %v", binaryPath, args)
c := exec.Command(binaryPath, args...)
b, err := c.CombinedOutput()
t.Logf("%s\n", b)
if err == nil {
t.Fatalf("unexpected pass of fuzz-runner (invocation %q): %v", invocation, err)
}
result := string(b)
if !strings.Contains(result, "syntax error") {
t.Fatalf("-emitbad=1 did not trigger syntax error (invocation %q): output: %s", invocation, result)
}
if !strings.Contains(result, "package minimization succeeded: found bad pkg 2") {
t.Fatalf("failed to minimize package (invocation %q): output: %s", invocation, result)
}
if !strings.Contains(result, "function minimization succeeded: found bad fcn 2") {
t.Fatalf("failed to minimize package (invocation %q): output: %s", invocation, result)
}
}
func testMinimization2(t *testing.T, binaryPath string) {
if binaryPath == "" {
t.Skipf("No runner binary")
}
t.Parallel()
// Fire off the runner passing it -emitbad=2, so that the
// generated code forces a runtime error. Verify that it does
// fail, and that the error is reflective.
args := []string{"-emitbad=2", "-badfcnidx=1", "-badpkgidx=1",
"-forcetmpclean", "-cleancache=0",
"-numit=1", "-numfcns=3", "-numpkgs=3", "-seed=55909"}
invocation := fmt.Sprintf("%s %v", binaryPath, args)
c := exec.Command(binaryPath, args...)
b, err := c.CombinedOutput()
t.Logf("%s\n", b)
if err == nil {
t.Fatalf("unexpected pass of fuzz-runner (invocation %q): %v", invocation, err)
}
result := string(b)
if !strings.Contains(result, "Error: fail") || !strings.Contains(result, "Checker1.Test1") {
t.Fatalf("-emitbad=2 did not trigger runtime error (invocation %q): output: %s", invocation, result)
}
if !strings.Contains(result, "package minimization succeeded: found bad pkg 1") {
t.Fatalf("failed to minimize package (invocation %q): output: %s", invocation, result)
}
if !strings.Contains(result, "function minimization succeeded: found bad fcn 1") {
t.Fatalf("failed to minimize package (invocation %q): output: %s", invocation, result)
}
}
|