github.com/pankona/gometalinter@v2.0.11+incompatible/_linters/src/honnef.co/go/tools/lint/testutil/util.go (about) 1 // Copyright (c) 2013 The Go Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file or at 5 // https://developers.google.com/open-source/licenses/bsd. 6 7 package testutil // import "honnef.co/go/tools/lint/testutil" 8 9 import ( 10 "flag" 11 "fmt" 12 "go/parser" 13 "go/token" 14 "io/ioutil" 15 "os" 16 "path" 17 "path/filepath" 18 "regexp" 19 "strconv" 20 "strings" 21 "testing" 22 23 "honnef.co/go/tools/lint" 24 25 "golang.org/x/tools/go/loader" 26 ) 27 28 var lintMatch = flag.String("lint.match", "", "restrict testdata matches to this pattern") 29 30 func TestAll(t *testing.T, c lint.Checker, dir string) { 31 baseDir := filepath.Join("testdata", dir) 32 fis, err := ioutil.ReadDir(baseDir) 33 if err != nil { 34 t.Fatalf("ioutil.ReadDir: %v", err) 35 } 36 if len(fis) == 0 { 37 t.Fatalf("no files in %v", baseDir) 38 } 39 rx, err := regexp.Compile(*lintMatch) 40 if err != nil { 41 t.Fatalf("Bad -lint.match value %q: %v", *lintMatch, err) 42 } 43 44 files := map[int][]os.FileInfo{} 45 for _, fi := range fis { 46 if !rx.MatchString(fi.Name()) { 47 continue 48 } 49 if !strings.HasSuffix(fi.Name(), ".go") { 50 continue 51 } 52 parts := strings.Split(fi.Name(), "_") 53 v := 0 54 if len(parts) > 1 && strings.HasPrefix(parts[len(parts)-1], "go1") { 55 var err error 56 s := parts[len(parts)-1][len("go1"):] 57 s = s[:len(s)-len(".go")] 58 v, err = strconv.Atoi(s) 59 if err != nil { 60 t.Fatalf("cannot process file name %q: %s", fi.Name(), err) 61 } 62 } 63 files[v] = append(files[v], fi) 64 } 65 66 conf := &loader.Config{ 67 ParserMode: parser.ParseComments, 68 } 69 sources := map[string][]byte{} 70 for _, fi := range fis { 71 filename := path.Join(baseDir, fi.Name()) 72 src, err := ioutil.ReadFile(filename) 73 if err != nil { 74 t.Errorf("Failed reading %s: %v", fi.Name(), err) 75 continue 76 } 77 f, err := conf.ParseFile(filename, src) 78 if err != nil { 79 t.Errorf("error parsing %s: %s", filename, err) 80 continue 81 } 82 sources[fi.Name()] = src 83 conf.CreateFromFiles(fi.Name(), f) 84 } 85 86 lprog, err := conf.Load() 87 if err != nil { 88 t.Fatalf("error loading program: %s", err) 89 } 90 91 for version, fis := range files { 92 l := &lint.Linter{Checker: c, GoVersion: version} 93 94 res := l.Lint(lprog, conf) 95 for _, fi := range fis { 96 name := fi.Name() 97 src := sources[name] 98 99 ins := parseInstructions(t, name, src) 100 101 for _, in := range ins { 102 ok := false 103 for i, p := range res { 104 if p.Position.Line != in.Line || filepath.Base(p.Position.Filename) != name { 105 continue 106 } 107 if in.Match.MatchString(p.Text) { 108 // remove this problem from ps 109 copy(res[i:], res[i+1:]) 110 res = res[:len(res)-1] 111 112 //t.Logf("/%v/ matched at %s:%d", in.Match, fi.Name(), in.Line) 113 ok = true 114 break 115 } 116 } 117 if !ok { 118 t.Errorf("Lint failed at %s:%d; /%v/ did not match", name, in.Line, in.Match) 119 } 120 } 121 } 122 for _, p := range res { 123 name := filepath.Base(p.Position.Filename) 124 for _, fi := range fis { 125 if name == fi.Name() { 126 t.Errorf("Unexpected problem at %s: %v", p.Position, p.Text) 127 break 128 } 129 } 130 } 131 } 132 } 133 134 type instruction struct { 135 Line int // the line number this applies to 136 Match *regexp.Regexp // what pattern to match 137 Replacement string // what the suggested replacement line should be 138 } 139 140 // parseInstructions parses instructions from the comments in a Go source file. 141 // It returns nil if none were parsed. 142 func parseInstructions(t *testing.T, filename string, src []byte) []instruction { 143 fset := token.NewFileSet() 144 f, err := parser.ParseFile(fset, filename, src, parser.ParseComments) 145 if err != nil { 146 t.Fatalf("Test file %v does not parse: %v", filename, err) 147 } 148 var ins []instruction 149 for _, cg := range f.Comments { 150 ln := fset.PositionFor(cg.Pos(), false).Line 151 raw := cg.Text() 152 for _, line := range strings.Split(raw, "\n") { 153 if line == "" || strings.HasPrefix(line, "#") { 154 continue 155 } 156 if line == "OK" && ins == nil { 157 // so our return value will be non-nil 158 ins = make([]instruction, 0) 159 continue 160 } 161 if !strings.Contains(line, "MATCH") { 162 continue 163 } 164 rx, err := extractPattern(line) 165 if err != nil { 166 t.Fatalf("At %v:%d: %v", filename, ln, err) 167 } 168 matchLine := ln 169 if i := strings.Index(line, "MATCH:"); i >= 0 { 170 // This is a match for a different line. 171 lns := strings.TrimPrefix(line[i:], "MATCH:") 172 lns = lns[:strings.Index(lns, " ")] 173 matchLine, err = strconv.Atoi(lns) 174 if err != nil { 175 t.Fatalf("Bad match line number %q at %v:%d: %v", lns, filename, ln, err) 176 } 177 } 178 var repl string 179 if r, ok := extractReplacement(line); ok { 180 repl = r 181 } 182 ins = append(ins, instruction{ 183 Line: matchLine, 184 Match: rx, 185 Replacement: repl, 186 }) 187 } 188 } 189 return ins 190 } 191 192 func extractPattern(line string) (*regexp.Regexp, error) { 193 n := strings.Index(line, " ") 194 if n == 01 { 195 return nil, fmt.Errorf("malformed match instruction %q", line) 196 } 197 line = line[n+1:] 198 var pat string 199 switch line[0] { 200 case '/': 201 a, b := strings.Index(line, "/"), strings.LastIndex(line, "/") 202 if a == -1 || a == b { 203 return nil, fmt.Errorf("malformed match instruction %q", line) 204 } 205 pat = line[a+1 : b] 206 case '"': 207 a, b := strings.Index(line, `"`), strings.LastIndex(line, `"`) 208 if a == -1 || a == b { 209 return nil, fmt.Errorf("malformed match instruction %q", line) 210 } 211 pat = regexp.QuoteMeta(line[a+1 : b]) 212 default: 213 return nil, fmt.Errorf("malformed match instruction %q", line) 214 } 215 216 rx, err := regexp.Compile(pat) 217 if err != nil { 218 return nil, fmt.Errorf("bad match pattern %q: %v", pat, err) 219 } 220 return rx, nil 221 } 222 223 func extractReplacement(line string) (string, bool) { 224 // Look for this: / -> ` 225 // (the end of a match and start of a backtick string), 226 // and then the closing backtick. 227 const start = "/ -> `" 228 a, b := strings.Index(line, start), strings.LastIndex(line, "`") 229 if a < 0 || a > b { 230 return "", false 231 } 232 return line[a+len(start) : b], true 233 }