summaryrefslogtreecommitdiff
path: root/libgo/go/cmd/vet/vet_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/cmd/vet/vet_test.go')
-rw-r--r--libgo/go/cmd/vet/vet_test.go299
1 files changed, 249 insertions, 50 deletions
diff --git a/libgo/go/cmd/vet/vet_test.go b/libgo/go/cmd/vet/vet_test.go
index 116b77e971b..3e42525e89b 100644
--- a/libgo/go/cmd/vet/vet_test.go
+++ b/libgo/go/cmd/vet/vet_test.go
@@ -6,12 +6,17 @@ package main_test
import (
"bytes"
+ "errors"
"fmt"
"internal/testenv"
+ "io/ioutil"
+ "log"
"os"
"os/exec"
"path/filepath"
+ "regexp"
"runtime"
+ "strconv"
"strings"
"sync"
"testing"
@@ -19,7 +24,7 @@ import (
const (
dataDir = "testdata"
- binary = "testvet.exe"
+ binary = "./testvet.exe"
)
// We implement TestMain so remove the test binary when all is done.
@@ -29,16 +34,6 @@ func TestMain(m *testing.M) {
os.Exit(result)
}
-func MustHavePerl(t *testing.T) {
- switch runtime.GOOS {
- case "plan9", "windows":
- t.Skipf("skipping test: perl not available on %s", runtime.GOOS)
- }
- if _, err := exec.LookPath("perl"); err != nil {
- t.Skipf("skipping test: perl not found in path")
- }
-}
-
var (
buildMu sync.Mutex // guards following
built = false // We have built the binary.
@@ -55,7 +50,6 @@ func Build(t *testing.T) {
t.Skip("cannot run on this environment")
}
testenv.MustHaveGoBuild(t)
- MustHavePerl(t)
cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", binary)
output, err := cmd.CombinedOutput()
if err != nil {
@@ -67,26 +61,19 @@ func Build(t *testing.T) {
}
func Vet(t *testing.T, files []string) {
- errchk := filepath.Join(runtime.GOROOT(), "test", "errchk")
- if _, err := os.Stat(errchk); err != nil {
- t.Skipf("skipping because no errchk: %v", err)
- }
flags := []string{
- "./" + binary,
"-printfuncs=Warn:1,Warnf:1",
"-all",
"-shadow",
}
- cmd := exec.Command(errchk, append(flags, files...)...)
- if !run(cmd, t) {
- t.Fatal("vet command failed")
- }
+ cmd := exec.Command(binary, append(flags, files...)...)
+ errchk(cmd, files, t)
}
-// Run this shell script, but do it in Go so it can be run by "go test".
-// go build -o testvet
-// $(GOROOT)/test/errchk ./testvet -shadow -printfuncs='Warn:1,Warnf:1' testdata/*.go testdata/*.s
-// rm testvet
+// TestVet is equivalent to running this:
+// go build -o ./testvet
+// errorCheck the output of ./testvet -shadow -printfuncs='Warn:1,Warnf:1' testdata/*.go testdata/*.s
+// rm ./testvet
//
// TestVet tests self-contained files in testdata/*.go.
@@ -98,7 +85,6 @@ func TestVet(t *testing.T) {
Build(t)
t.Parallel()
- // errchk ./testvet
gos, err := filepath.Glob(filepath.Join(dataDir, "*.go"))
if err != nil {
t.Fatal(err)
@@ -130,21 +116,23 @@ func TestVet(t *testing.T) {
}
func TestVetPrint(t *testing.T) {
- Build(t)
- errchk := filepath.Join(runtime.GOROOT(), "test", "errchk")
- if _, err := os.Stat(errchk); err != nil {
- t.Skipf("skipping because no errchk: %v", err)
+ if runtime.Compiler == "gccgo" {
+ // This test currently fails with gccgo because, in
+ // the absence of standard library sources, gccgo can
+ // not deduce that the standard log package formatting
+ // functions are just printf wrappers.
+ t.Skip("skipping for gccgo because there are no standard library sources")
}
+
+ Build(t)
+ file := filepath.Join("testdata", "print.go")
cmd := exec.Command(
- errchk,
- "go", "vet", "-vettool=./"+binary,
+ "go", "vet", "-vettool="+binary,
"-printf",
"-printfuncs=Warn:1,Warnf:1",
- "testdata/print.go",
+ file,
)
- if !run(cmd, t) {
- t.Fatal("vet command failed")
- }
+ errchk(cmd, []string{file}, t)
}
func TestVetAsm(t *testing.T) {
@@ -161,7 +149,6 @@ func TestVetAsm(t *testing.T) {
}
t.Parallel()
- // errchk ./testvet
Vet(t, append(gos, asms...))
}
@@ -187,23 +174,20 @@ func TestVetDirs(t *testing.T) {
}
}
-func run(c *exec.Cmd, t *testing.T) bool {
+func errchk(c *exec.Cmd, files []string, t *testing.T) {
output, err := c.CombinedOutput()
- if err != nil {
+ if _, ok := err.(*exec.ExitError); !ok {
t.Logf("vet output:\n%s", output)
t.Fatal(err)
}
- // Errchk delights by not returning non-zero status if it finds errors, so we look at the output.
- // It prints "BUG" if there is a failure.
- if !c.ProcessState.Success() {
- t.Logf("vet output:\n%s", output)
- return false
+ fullshort := make([]string, 0, len(files)*2)
+ for _, f := range files {
+ fullshort = append(fullshort, f, filepath.Base(f))
}
- ok := !bytes.Contains(output, []byte("BUG"))
- if !ok {
- t.Logf("vet output:\n%s", output)
+ err = errorCheck(string(output), false, fullshort...)
+ if err != nil {
+ t.Errorf("error check failed: %s", err)
}
- return ok
}
// TestTags verifies that the -tags argument controls which files to check.
@@ -220,7 +204,7 @@ func TestTags(t *testing.T) {
"-v", // We're going to look at the files it examines.
"testdata/tagtest",
}
- cmd := exec.Command("./"+binary, args...)
+ cmd := exec.Command(binary, args...)
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatal(err)
@@ -240,10 +224,225 @@ func TestTags(t *testing.T) {
func TestVetVerbose(t *testing.T) {
t.Parallel()
Build(t)
- cmd := exec.Command("./"+binary, "-v", "-all", "testdata/cgo/cgo3.go")
+ cmd := exec.Command(binary, "-v", "-all", "testdata/cgo/cgo3.go")
out, err := cmd.CombinedOutput()
if err != nil {
t.Logf("%s", out)
t.Error(err)
}
}
+
+// All declarations below were adapted from test/run.go.
+
+// errorCheck matches errors in outStr against comments in source files.
+// For each line of the source files which should generate an error,
+// there should be a comment of the form // ERROR "regexp".
+// If outStr has an error for a line which has no such comment,
+// this function will report an error.
+// Likewise if outStr does not have an error for a line which has a comment,
+// or if the error message does not match the <regexp>.
+// The <regexp> syntax is Perl but its best to stick to egrep.
+//
+// Sources files are supplied as fullshort slice.
+// It consists of pairs: full path to source file and it's base name.
+func errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) {
+ var errs []error
+ out := splitOutput(outStr, wantAuto)
+ // Cut directory name.
+ for i := range out {
+ for j := 0; j < len(fullshort); j += 2 {
+ full, short := fullshort[j], fullshort[j+1]
+ out[i] = strings.Replace(out[i], full, short, -1)
+ }
+ }
+
+ var want []wantedError
+ for j := 0; j < len(fullshort); j += 2 {
+ full, short := fullshort[j], fullshort[j+1]
+ want = append(want, wantedErrors(full, short)...)
+ }
+ for _, we := range want {
+ var errmsgs []string
+ if we.auto {
+ errmsgs, out = partitionStrings("<autogenerated>", out)
+ } else {
+ errmsgs, out = partitionStrings(we.prefix, out)
+ }
+ if len(errmsgs) == 0 {
+ errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr))
+ continue
+ }
+ matched := false
+ n := len(out)
+ for _, errmsg := range errmsgs {
+ // Assume errmsg says "file:line: foo".
+ // Cut leading "file:line: " to avoid accidental matching of file name instead of message.
+ text := errmsg
+ if i := strings.Index(text, " "); i >= 0 {
+ text = text[i+1:]
+ }
+ if we.re.MatchString(text) {
+ matched = true
+ } else {
+ out = append(out, errmsg)
+ }
+ }
+ if !matched {
+ errs = append(errs, fmt.Errorf("%s:%d: no match for %#q in:\n\t%s", we.file, we.lineNum, we.reStr, strings.Join(out[n:], "\n\t")))
+ continue
+ }
+ }
+
+ if len(out) > 0 {
+ errs = append(errs, fmt.Errorf("Unmatched Errors:"))
+ for _, errLine := range out {
+ errs = append(errs, fmt.Errorf("%s", errLine))
+ }
+ }
+
+ if len(errs) == 0 {
+ return nil
+ }
+ if len(errs) == 1 {
+ return errs[0]
+ }
+ var buf bytes.Buffer
+ fmt.Fprintf(&buf, "\n")
+ for _, err := range errs {
+ fmt.Fprintf(&buf, "%s\n", err.Error())
+ }
+ return errors.New(buf.String())
+}
+
+func splitOutput(out string, wantAuto bool) []string {
+ // gc error messages continue onto additional lines with leading tabs.
+ // Split the output at the beginning of each line that doesn't begin with a tab.
+ // <autogenerated> lines are impossible to match so those are filtered out.
+ var res []string
+ for _, line := range strings.Split(out, "\n") {
+ line = strings.TrimSuffix(line, "\r") // normalize Windows output
+ if strings.HasPrefix(line, "\t") {
+ res[len(res)-1] += "\n" + line
+ } else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "#") || !wantAuto && strings.HasPrefix(line, "<autogenerated>") {
+ continue
+ } else if strings.TrimSpace(line) != "" {
+ res = append(res, line)
+ }
+ }
+ return res
+}
+
+// matchPrefix reports whether s starts with file name prefix followed by a :,
+// and possibly preceded by a directory name.
+func matchPrefix(s, prefix string) bool {
+ i := strings.Index(s, ":")
+ if i < 0 {
+ return false
+ }
+ j := strings.LastIndex(s[:i], "/")
+ s = s[j+1:]
+ if len(s) <= len(prefix) || s[:len(prefix)] != prefix {
+ return false
+ }
+ if s[len(prefix)] == ':' {
+ return true
+ }
+ return false
+}
+
+func partitionStrings(prefix string, strs []string) (matched, unmatched []string) {
+ for _, s := range strs {
+ if matchPrefix(s, prefix) {
+ matched = append(matched, s)
+ } else {
+ unmatched = append(unmatched, s)
+ }
+ }
+ return
+}
+
+type wantedError struct {
+ reStr string
+ re *regexp.Regexp
+ lineNum int
+ auto bool // match <autogenerated> line
+ file string
+ prefix string
+}
+
+var (
+ errRx = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`)
+ errAutoRx = regexp.MustCompile(`// (?:GC_)?ERRORAUTO (.*)`)
+ errQuotesRx = regexp.MustCompile(`"([^"]*)"`)
+ lineRx = regexp.MustCompile(`LINE(([+-])([0-9]+))?`)
+)
+
+// wantedErrors parses expected errors from comments in a file.
+func wantedErrors(file, short string) (errs []wantedError) {
+ cache := make(map[string]*regexp.Regexp)
+
+ src, err := ioutil.ReadFile(file)
+ if err != nil {
+ log.Fatal(err)
+ }
+ for i, line := range strings.Split(string(src), "\n") {
+ lineNum := i + 1
+ if strings.Contains(line, "////") {
+ // double comment disables ERROR
+ continue
+ }
+ var auto bool
+ m := errAutoRx.FindStringSubmatch(line)
+ if m != nil {
+ auto = true
+ } else {
+ m = errRx.FindStringSubmatch(line)
+ }
+ if m == nil {
+ continue
+ }
+ all := m[1]
+ mm := errQuotesRx.FindAllStringSubmatch(all, -1)
+ if mm == nil {
+ log.Fatalf("%s:%d: invalid errchk line: %s", file, lineNum, line)
+ }
+ for _, m := range mm {
+ replacedOnce := false
+ rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string {
+ if replacedOnce {
+ return m
+ }
+ replacedOnce = true
+ n := lineNum
+ if strings.HasPrefix(m, "LINE+") {
+ delta, _ := strconv.Atoi(m[5:])
+ n += delta
+ } else if strings.HasPrefix(m, "LINE-") {
+ delta, _ := strconv.Atoi(m[5:])
+ n -= delta
+ }
+ return fmt.Sprintf("%s:%d", short, n)
+ })
+ re := cache[rx]
+ if re == nil {
+ var err error
+ re, err = regexp.Compile(rx)
+ if err != nil {
+ log.Fatalf("%s:%d: invalid regexp \"%#q\" in ERROR line: %v", file, lineNum, rx, err)
+ }
+ cache[rx] = re
+ }
+ prefix := fmt.Sprintf("%s:%d", short, lineNum)
+ errs = append(errs, wantedError{
+ reStr: rx,
+ re: re,
+ prefix: prefix,
+ auto: auto,
+ lineNum: lineNum,
+ file: short,
+ })
+ }
+ }
+
+ return
+}