go1.12: migrate from perl GOROOT/test/errcheck
This commit is contained in:
parent
c55a62a8de
commit
466006b463
227
test/errchk.go
Normal file
227
test/errchk.go
Normal file
@ -0,0 +1,227 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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 it's best to stick to egrep.
|
||||
//
|
||||
// Sources files are supplied as fullshort slice.
|
||||
// It consists of pairs: full path to source file and its base name.
|
||||
//nolint:gocyclo
|
||||
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.ReplaceAll(out[i], full, short)
|
||||
}
|
||||
}
|
||||
|
||||
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") { //nolint:gocritic
|
||||
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.
|
||||
//nolint:nakedret,gocyclo
|
||||
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
|
||||
}
|
@ -2,15 +2,15 @@ package test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/exitcodes"
|
||||
|
||||
"github.com/golangci/golangci-lint/test/testshared"
|
||||
|
||||
assert "github.com/stretchr/testify/require"
|
||||
@ -18,12 +18,19 @@ import (
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func runGoErrchk(c *exec.Cmd, t *testing.T) {
|
||||
func runGoErrchk(c *exec.Cmd, files []string, t *testing.T) {
|
||||
output, err := c.CombinedOutput()
|
||||
assert.NoError(t, err, "Output:\n%s", output)
|
||||
exitErr, ok := err.(*exec.ExitError)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, exitcodes.IssuesFound, exitErr.ExitCode())
|
||||
|
||||
// Can't check exit code: tool only prints to output
|
||||
assert.False(t, bytes.Contains(output, []byte("BUG")), "Output:\n%s", output)
|
||||
fullshort := make([]string, 0, len(files)*2)
|
||||
for _, f := range files {
|
||||
fullshort = append(fullshort, f, filepath.Base(f))
|
||||
}
|
||||
|
||||
err = errorCheck(string(output), false, fullshort...)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func testSourcesFromDir(t *testing.T, dir string) {
|
||||
@ -92,9 +99,8 @@ func saveConfig(t *testing.T, cfg map[string]interface{}) (cfgPath string, finis
|
||||
}
|
||||
|
||||
func testOneSource(t *testing.T, sourcePath string) {
|
||||
goErrchkBin := filepath.Join(runtime.GOROOT(), "test", "errchk")
|
||||
args := []string{
|
||||
binName, "run",
|
||||
"run",
|
||||
"--disable-all",
|
||||
"--print-issued-lines=false",
|
||||
"--print-linter-name=false",
|
||||
@ -126,9 +132,9 @@ func testOneSource(t *testing.T, sourcePath string) {
|
||||
|
||||
caseArgs = append(caseArgs, sourcePath)
|
||||
|
||||
cmd := exec.Command(goErrchkBin, caseArgs...)
|
||||
cmd := exec.Command(binName, caseArgs...)
|
||||
t.Log(caseArgs)
|
||||
runGoErrchk(cmd, t)
|
||||
runGoErrchk(cmd, []string{sourcePath}, t)
|
||||
}
|
||||
}
|
||||
|
||||
|
4
test/testdata/dupl.go
vendored
4
test/testdata/dupl.go
vendored
@ -11,7 +11,7 @@ func (DuplLogger) level() int {
|
||||
func (DuplLogger) Debug(args ...interface{}) {}
|
||||
func (DuplLogger) Info(args ...interface{}) {}
|
||||
|
||||
func (logger *DuplLogger) First(args ...interface{}) { // ERROR "14-23 lines are duplicate of `testdata/dupl.go:25-34`"
|
||||
func (logger *DuplLogger) First(args ...interface{}) { // ERROR "14-23 lines are duplicate of `.*dupl.go:25-34`"
|
||||
if logger.level() >= 0 {
|
||||
logger.Debug(args...)
|
||||
logger.Debug(args...)
|
||||
@ -22,7 +22,7 @@ func (logger *DuplLogger) First(args ...interface{}) { // ERROR "14-23 lines are
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *DuplLogger) Second(args ...interface{}) { // ERROR "25-34 lines are duplicate of `testdata/dupl.go:14-23`"
|
||||
func (logger *DuplLogger) Second(args ...interface{}) { // ERROR "25-34 lines are duplicate of `.*dupl.go:14-23`"
|
||||
if logger.level() >= 1 {
|
||||
logger.Info(args...)
|
||||
logger.Info(args...)
|
||||
|
2
test/testdata/govet.go
vendored
2
test/testdata/govet.go
vendored
@ -13,7 +13,7 @@ func Govet() error {
|
||||
|
||||
func GovetShadow(f io.Reader, buf []byte) (err error) {
|
||||
if f != nil {
|
||||
_, err := f.Read(buf) // ERROR "declaration of .err. shadows declaration at testdata/govet.go:\d+"
|
||||
_, err := f.Read(buf) // ERROR "declaration of .err. shadows declaration at .*govet.go:\d+"
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user