Skip to content

Commit 466006b

Browse files
committed
go1.12: migrate from perl GOROOT/test/errcheck
1 parent c55a62a commit 466006b

File tree

4 files changed

+246
-13
lines changed

4 files changed

+246
-13
lines changed

test/errchk.go

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
package test
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"fmt"
7+
"io/ioutil"
8+
"log"
9+
"regexp"
10+
"strconv"
11+
"strings"
12+
)
13+
14+
// errorCheck matches errors in outStr against comments in source files.
15+
// For each line of the source files which should generate an error,
16+
// there should be a comment of the form // ERROR "regexp".
17+
// If outStr has an error for a line which has no such comment,
18+
// this function will report an error.
19+
// Likewise if outStr does not have an error for a line which has a comment,
20+
// or if the error message does not match the <regexp>.
21+
// The <regexp> syntax is Perl but it's best to stick to egrep.
22+
//
23+
// Sources files are supplied as fullshort slice.
24+
// It consists of pairs: full path to source file and its base name.
25+
//nolint:gocyclo
26+
func errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) {
27+
var errs []error
28+
out := splitOutput(outStr, wantAuto)
29+
// Cut directory name.
30+
for i := range out {
31+
for j := 0; j < len(fullshort); j += 2 {
32+
full, short := fullshort[j], fullshort[j+1]
33+
out[i] = strings.ReplaceAll(out[i], full, short)
34+
}
35+
}
36+
37+
var want []wantedError
38+
for j := 0; j < len(fullshort); j += 2 {
39+
full, short := fullshort[j], fullshort[j+1]
40+
want = append(want, wantedErrors(full, short)...)
41+
}
42+
for _, we := range want {
43+
var errmsgs []string
44+
if we.auto {
45+
errmsgs, out = partitionStrings("<autogenerated>", out)
46+
} else {
47+
errmsgs, out = partitionStrings(we.prefix, out)
48+
}
49+
if len(errmsgs) == 0 {
50+
errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr))
51+
continue
52+
}
53+
matched := false
54+
n := len(out)
55+
for _, errmsg := range errmsgs {
56+
// Assume errmsg says "file:line: foo".
57+
// Cut leading "file:line: " to avoid accidental matching of file name instead of message.
58+
text := errmsg
59+
if i := strings.Index(text, " "); i >= 0 {
60+
text = text[i+1:]
61+
}
62+
if we.re.MatchString(text) {
63+
matched = true
64+
} else {
65+
out = append(out, errmsg)
66+
}
67+
}
68+
if !matched {
69+
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")))
70+
continue
71+
}
72+
}
73+
74+
if len(out) > 0 {
75+
errs = append(errs, fmt.Errorf("unmatched errors"))
76+
for _, errLine := range out {
77+
errs = append(errs, fmt.Errorf("%s", errLine))
78+
}
79+
}
80+
81+
if len(errs) == 0 {
82+
return nil
83+
}
84+
if len(errs) == 1 {
85+
return errs[0]
86+
}
87+
var buf bytes.Buffer
88+
fmt.Fprintf(&buf, "\n")
89+
for _, err := range errs {
90+
fmt.Fprintf(&buf, "%s\n", err.Error())
91+
}
92+
return errors.New(buf.String())
93+
}
94+
95+
func splitOutput(out string, wantAuto bool) []string {
96+
// gc error messages continue onto additional lines with leading tabs.
97+
// Split the output at the beginning of each line that doesn't begin with a tab.
98+
// <autogenerated> lines are impossible to match so those are filtered out.
99+
var res []string
100+
for _, line := range strings.Split(out, "\n") {
101+
line = strings.TrimSuffix(line, "\r") // normalize Windows output
102+
if strings.HasPrefix(line, "\t") { //nolint:gocritic
103+
res[len(res)-1] += "\n" + line
104+
} else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "#") || !wantAuto && strings.HasPrefix(line, "<autogenerated>") {
105+
continue
106+
} else if strings.TrimSpace(line) != "" {
107+
res = append(res, line)
108+
}
109+
}
110+
return res
111+
}
112+
113+
// matchPrefix reports whether s starts with file name prefix followed by a :,
114+
// and possibly preceded by a directory name.
115+
func matchPrefix(s, prefix string) bool {
116+
i := strings.Index(s, ":")
117+
if i < 0 {
118+
return false
119+
}
120+
j := strings.LastIndex(s[:i], "/")
121+
s = s[j+1:]
122+
if len(s) <= len(prefix) || s[:len(prefix)] != prefix {
123+
return false
124+
}
125+
if s[len(prefix)] == ':' {
126+
return true
127+
}
128+
return false
129+
}
130+
131+
func partitionStrings(prefix string, strs []string) (matched, unmatched []string) {
132+
for _, s := range strs {
133+
if matchPrefix(s, prefix) {
134+
matched = append(matched, s)
135+
} else {
136+
unmatched = append(unmatched, s)
137+
}
138+
}
139+
return
140+
}
141+
142+
type wantedError struct {
143+
reStr string
144+
re *regexp.Regexp
145+
lineNum int
146+
auto bool // match <autogenerated> line
147+
file string
148+
prefix string
149+
}
150+
151+
var (
152+
errRx = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`)
153+
errAutoRx = regexp.MustCompile(`// (?:GC_)?ERRORAUTO (.*)`)
154+
errQuotesRx = regexp.MustCompile(`"([^"]*)"`)
155+
lineRx = regexp.MustCompile(`LINE(([+-])([0-9]+))?`)
156+
)
157+
158+
// wantedErrors parses expected errors from comments in a file.
159+
//nolint:nakedret,gocyclo
160+
func wantedErrors(file, short string) (errs []wantedError) {
161+
cache := make(map[string]*regexp.Regexp)
162+
163+
src, err := ioutil.ReadFile(file)
164+
if err != nil {
165+
log.Fatal(err)
166+
}
167+
for i, line := range strings.Split(string(src), "\n") {
168+
lineNum := i + 1
169+
if strings.Contains(line, "////") {
170+
// double comment disables ERROR
171+
continue
172+
}
173+
var auto bool
174+
m := errAutoRx.FindStringSubmatch(line)
175+
if m != nil {
176+
auto = true
177+
} else {
178+
m = errRx.FindStringSubmatch(line)
179+
}
180+
if m == nil {
181+
continue
182+
}
183+
all := m[1]
184+
mm := errQuotesRx.FindAllStringSubmatch(all, -1)
185+
if mm == nil {
186+
log.Fatalf("%s:%d: invalid errchk line: %s", file, lineNum, line)
187+
}
188+
for _, m := range mm {
189+
replacedOnce := false
190+
rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string {
191+
if replacedOnce {
192+
return m
193+
}
194+
replacedOnce = true
195+
n := lineNum
196+
if strings.HasPrefix(m, "LINE+") {
197+
delta, _ := strconv.Atoi(m[5:])
198+
n += delta
199+
} else if strings.HasPrefix(m, "LINE-") {
200+
delta, _ := strconv.Atoi(m[5:])
201+
n -= delta
202+
}
203+
return fmt.Sprintf("%s:%d", short, n)
204+
})
205+
re := cache[rx]
206+
if re == nil {
207+
var err error
208+
re, err = regexp.Compile(rx)
209+
if err != nil {
210+
log.Fatalf("%s:%d: invalid regexp \"%#q\" in ERROR line: %v", file, lineNum, rx, err)
211+
}
212+
cache[rx] = re
213+
}
214+
prefix := fmt.Sprintf("%s:%d", short, lineNum)
215+
errs = append(errs, wantedError{
216+
reStr: rx,
217+
re: re,
218+
prefix: prefix,
219+
auto: auto,
220+
lineNum: lineNum,
221+
file: short,
222+
})
223+
}
224+
}
225+
226+
return
227+
}

test/linters_test.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,35 @@ package test
22

33
import (
44
"bufio"
5-
"bytes"
65
"io/ioutil"
76
"os"
87
"os/exec"
98
"path/filepath"
10-
"runtime"
119
"strings"
1210
"testing"
1311

12+
"github.com/golangci/golangci-lint/pkg/exitcodes"
13+
1414
"github.com/golangci/golangci-lint/test/testshared"
1515

1616
assert "github.com/stretchr/testify/require"
1717

1818
yaml "gopkg.in/yaml.v2"
1919
)
2020

21-
func runGoErrchk(c *exec.Cmd, t *testing.T) {
21+
func runGoErrchk(c *exec.Cmd, files []string, t *testing.T) {
2222
output, err := c.CombinedOutput()
23-
assert.NoError(t, err, "Output:\n%s", output)
23+
exitErr, ok := err.(*exec.ExitError)
24+
assert.True(t, ok)
25+
assert.Equal(t, exitcodes.IssuesFound, exitErr.ExitCode())
26+
27+
fullshort := make([]string, 0, len(files)*2)
28+
for _, f := range files {
29+
fullshort = append(fullshort, f, filepath.Base(f))
30+
}
2431

25-
// Can't check exit code: tool only prints to output
26-
assert.False(t, bytes.Contains(output, []byte("BUG")), "Output:\n%s", output)
32+
err = errorCheck(string(output), false, fullshort...)
33+
assert.NoError(t, err)
2734
}
2835

2936
func testSourcesFromDir(t *testing.T, dir string) {
@@ -92,9 +99,8 @@ func saveConfig(t *testing.T, cfg map[string]interface{}) (cfgPath string, finis
9299
}
93100

94101
func testOneSource(t *testing.T, sourcePath string) {
95-
goErrchkBin := filepath.Join(runtime.GOROOT(), "test", "errchk")
96102
args := []string{
97-
binName, "run",
103+
"run",
98104
"--disable-all",
99105
"--print-issued-lines=false",
100106
"--print-linter-name=false",
@@ -126,9 +132,9 @@ func testOneSource(t *testing.T, sourcePath string) {
126132

127133
caseArgs = append(caseArgs, sourcePath)
128134

129-
cmd := exec.Command(goErrchkBin, caseArgs...)
135+
cmd := exec.Command(binName, caseArgs...)
130136
t.Log(caseArgs)
131-
runGoErrchk(cmd, t)
137+
runGoErrchk(cmd, []string{sourcePath}, t)
132138
}
133139
}
134140

test/testdata/dupl.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ func (DuplLogger) level() int {
1111
func (DuplLogger) Debug(args ...interface{}) {}
1212
func (DuplLogger) Info(args ...interface{}) {}
1313

14-
func (logger *DuplLogger) First(args ...interface{}) { // ERROR "14-23 lines are duplicate of `testdata/dupl.go:25-34`"
14+
func (logger *DuplLogger) First(args ...interface{}) { // ERROR "14-23 lines are duplicate of `.*dupl.go:25-34`"
1515
if logger.level() >= 0 {
1616
logger.Debug(args...)
1717
logger.Debug(args...)
@@ -22,7 +22,7 @@ func (logger *DuplLogger) First(args ...interface{}) { // ERROR "14-23 lines are
2222
}
2323
}
2424

25-
func (logger *DuplLogger) Second(args ...interface{}) { // ERROR "25-34 lines are duplicate of `testdata/dupl.go:14-23`"
25+
func (logger *DuplLogger) Second(args ...interface{}) { // ERROR "25-34 lines are duplicate of `.*dupl.go:14-23`"
2626
if logger.level() >= 1 {
2727
logger.Info(args...)
2828
logger.Info(args...)

test/testdata/govet.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ func Govet() error {
1313

1414
func GovetShadow(f io.Reader, buf []byte) (err error) {
1515
if f != nil {
16-
_, err := f.Read(buf) // ERROR "declaration of .err. shadows declaration at testdata/govet.go:\d+"
16+
_, err := f.Read(buf) // ERROR "declaration of .err. shadows declaration at .*govet.go:\d+"
1717
if err != nil {
1818
return err
1919
}

0 commit comments

Comments
 (0)