github.com/serversong/goreporter@v0.0.0-20200325104552-3cfaf44fd178/linters/simpler/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 "github.com/360EntSecGroup-Skylar/goreporter/linters/simpler/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 "github.com/360EntSecGroup-Skylar/goreporter/linters/simpler/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) 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 pos := lprog.Fset.Position(p.Position) 105 if pos.Line != in.Line || filepath.Base(pos.Filename) != name { 106 continue 107 } 108 if in.Match.MatchString(p.Text) { 109 // remove this problem from ps 110 copy(res[i:], res[i+1:]) 111 res = res[:len(res)-1] 112 113 //t.Logf("/%v/ matched at %s:%d", in.Match, fi.Name(), in.Line) 114 ok = true 115 break 116 } 117 } 118 if !ok { 119 t.Errorf("Lint failed at %s:%d; /%v/ did not match", name, in.Line, in.Match) 120 } 121 } 122 } 123 for _, p := range res { 124 pos := lprog.Fset.Position(p.Position) 125 name := filepath.Base(pos.Filename) 126 for _, fi := range fis { 127 if name == fi.Name() { 128 t.Errorf("Unexpected problem at %s: %v", pos, p.Text) 129 break 130 } 131 } 132 } 133 } 134 } 135 136 type instruction struct { 137 Line int // the line number this applies to 138 Match *regexp.Regexp // what pattern to match 139 Replacement string // what the suggested replacement line should be 140 } 141 142 // parseInstructions parses instructions from the comments in a Go source file. 143 // It returns nil if none were parsed. 144 func parseInstructions(t *testing.T, filename string, src []byte) []instruction { 145 fset := token.NewFileSet() 146 f, err := parser.ParseFile(fset, filename, src, parser.ParseComments) 147 if err != nil { 148 t.Fatalf("Test file %v does not parse: %v", filename, err) 149 } 150 var ins []instruction 151 for _, cg := range f.Comments { 152 ln := fset.Position(cg.Pos()).Line 153 raw := cg.Text() 154 for _, line := range strings.Split(raw, "\n") { 155 if line == "" || strings.HasPrefix(line, "#") { 156 continue 157 } 158 if line == "OK" && ins == nil { 159 // so our return value will be non-nil 160 ins = make([]instruction, 0) 161 continue 162 } 163 if !strings.Contains(line, "MATCH") { 164 continue 165 } 166 rx, err := extractPattern(line) 167 if err != nil { 168 t.Fatalf("At %v:%d: %v", filename, ln, err) 169 } 170 matchLine := ln 171 if i := strings.Index(line, "MATCH:"); i >= 0 { 172 // This is a match for a different line. 173 lns := strings.TrimPrefix(line[i:], "MATCH:") 174 lns = lns[:strings.Index(lns, " ")] 175 matchLine, err = strconv.Atoi(lns) 176 if err != nil { 177 t.Fatalf("Bad match line number %q at %v:%d: %v", lns, filename, ln, err) 178 } 179 } 180 var repl string 181 if r, ok := extractReplacement(line); ok { 182 repl = r 183 } 184 ins = append(ins, instruction{ 185 Line: matchLine, 186 Match: rx, 187 Replacement: repl, 188 }) 189 } 190 } 191 return ins 192 } 193 194 func extractPattern(line string) (*regexp.Regexp, error) { 195 n := strings.Index(line, " ") 196 if n == 01 { 197 return nil, fmt.Errorf("malformed match instruction %q", line) 198 } 199 line = line[n+1:] 200 var pat string 201 switch line[0] { 202 case '/': 203 a, b := strings.Index(line, "/"), strings.LastIndex(line, "/") 204 if a == -1 || a == b { 205 return nil, fmt.Errorf("malformed match instruction %q", line) 206 } 207 pat = line[a+1 : b] 208 case '"': 209 a, b := strings.Index(line, `"`), strings.LastIndex(line, `"`) 210 if a == -1 || a == b { 211 return nil, fmt.Errorf("malformed match instruction %q", line) 212 } 213 pat = regexp.QuoteMeta(line[a+1 : b]) 214 default: 215 return nil, fmt.Errorf("malformed match instruction %q", line) 216 } 217 218 rx, err := regexp.Compile(pat) 219 if err != nil { 220 return nil, fmt.Errorf("bad match pattern %q: %v", pat, err) 221 } 222 return rx, nil 223 } 224 225 func extractReplacement(line string) (string, bool) { 226 // Look for this: / -> ` 227 // (the end of a match and start of a backtick string), 228 // and then the closing backtick. 229 const start = "/ -> `" 230 a, b := strings.Index(line, start), strings.LastIndex(line, "`") 231 if a < 0 || a > b { 232 return "", false 233 } 234 return line[a+len(start) : b], true 235 }