github.com/serversong/goreporter@v0.0.0-20200325104552-3cfaf44fd178/linters/simplecode/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/simplecode/lint/testutil" 8 9 import ( 10 "flag" 11 "fmt" 12 "go/parser" 13 "go/token" 14 "io/ioutil" 15 "path" 16 "path/filepath" 17 "regexp" 18 "strconv" 19 "strings" 20 "testing" 21 22 "github.com/360EntSecGroup-Skylar/goreporter/linters/simplecode/lint" 23 ) 24 25 var lintMatch = flag.String("lint.match", "", "restrict testdata matches to this pattern") 26 27 func TestAll(t *testing.T, funcs []lint.Func, dir string) { 28 l := &lint.Linter{Funcs: funcs} 29 rx, err := regexp.Compile(*lintMatch) 30 if err != nil { 31 t.Fatalf("Bad -lint.match value %q: %v", *lintMatch, err) 32 } 33 34 baseDir := filepath.Join("testdata", dir) 35 fis, err := ioutil.ReadDir(baseDir) 36 if err != nil { 37 t.Fatalf("ioutil.ReadDir: %v", err) 38 } 39 if len(fis) == 0 { 40 t.Fatalf("no files in %v", baseDir) 41 } 42 for _, fi := range fis { 43 if !rx.MatchString(fi.Name()) { 44 continue 45 } 46 if !strings.HasSuffix(fi.Name(), ".go") { 47 continue 48 } 49 //t.Logf("Testing %s", fi.Name()) 50 src, err := ioutil.ReadFile(path.Join(baseDir, fi.Name())) 51 if err != nil { 52 t.Fatalf("Failed reading %s: %v", fi.Name(), err) 53 } 54 55 ins := parseInstructions(t, fi.Name(), src) 56 57 ps, err := l.Lint(fi.Name(), src) 58 if err != nil { 59 t.Errorf("Linting %s: %v", fi.Name(), err) 60 continue 61 } 62 63 for _, in := range ins { 64 ok := false 65 for i, p := range ps { 66 if p.Position.Line != in.Line { 67 continue 68 } 69 if in.Match.MatchString(p.Text) { 70 // check replacement if we are expecting one 71 if in.Replacement != "" { 72 // ignore any inline comments, since that would be recursive 73 r := p.ReplacementLine 74 if i := strings.Index(r, " //"); i >= 0 { 75 r = r[:i] 76 } 77 if r != in.Replacement { 78 t.Errorf("Lint failed at %s:%d; got replacement %q, want %q", fi.Name(), in.Line, r, in.Replacement) 79 } 80 } 81 82 // remove this problem from ps 83 copy(ps[i:], ps[i+1:]) 84 ps = ps[:len(ps)-1] 85 86 //t.Logf("/%v/ matched at %s:%d", in.Match, fi.Name(), in.Line) 87 ok = true 88 break 89 } 90 } 91 if !ok { 92 t.Errorf("Lint failed at %s:%d; /%v/ did not match", fi.Name(), in.Line, in.Match) 93 } 94 } 95 for _, p := range ps { 96 t.Errorf("Unexpected problem at %s:%d: %v", fi.Name(), p.Position.Line, p.Text) 97 } 98 } 99 } 100 101 type instruction struct { 102 Line int // the line number this applies to 103 Match *regexp.Regexp // what pattern to match 104 Replacement string // what the suggested replacement line should be 105 } 106 107 // parseInstructions parses instructions from the comments in a Go source file. 108 // It returns nil if none were parsed. 109 func parseInstructions(t *testing.T, filename string, src []byte) []instruction { 110 fset := token.NewFileSet() 111 f, err := parser.ParseFile(fset, filename, src, parser.ParseComments) 112 if err != nil { 113 t.Fatalf("Test file %v does not parse: %v", filename, err) 114 } 115 var ins []instruction 116 for _, cg := range f.Comments { 117 ln := fset.Position(cg.Pos()).Line 118 raw := cg.Text() 119 for _, line := range strings.Split(raw, "\n") { 120 if line == "" || strings.HasPrefix(line, "#") { 121 continue 122 } 123 if line == "OK" && ins == nil { 124 // so our return value will be non-nil 125 ins = make([]instruction, 0) 126 continue 127 } 128 if strings.Contains(line, "MATCH") { 129 rx, err := extractPattern(line) 130 if err != nil { 131 t.Fatalf("At %v:%d: %v", filename, ln, err) 132 } 133 matchLine := ln 134 if i := strings.Index(line, "MATCH:"); i >= 0 { 135 // This is a match for a different line. 136 lns := strings.TrimPrefix(line[i:], "MATCH:") 137 lns = lns[:strings.Index(lns, " ")] 138 matchLine, err = strconv.Atoi(lns) 139 if err != nil { 140 t.Fatalf("Bad match line number %q at %v:%d: %v", lns, filename, ln, err) 141 } 142 } 143 var repl string 144 if r, ok := extractReplacement(line); ok { 145 repl = r 146 } 147 ins = append(ins, instruction{ 148 Line: matchLine, 149 Match: rx, 150 Replacement: repl, 151 }) 152 } 153 } 154 } 155 return ins 156 } 157 158 func extractPattern(line string) (*regexp.Regexp, error) { 159 a, b := strings.Index(line, "/"), strings.LastIndex(line, "/") 160 if a == -1 || a == b { 161 return nil, fmt.Errorf("malformed match instruction %q", line) 162 } 163 pat := line[a+1 : b] 164 rx, err := regexp.Compile(pat) 165 if err != nil { 166 return nil, fmt.Errorf("bad match pattern %q: %v", pat, err) 167 } 168 return rx, nil 169 } 170 171 func extractReplacement(line string) (string, bool) { 172 // Look for this: / -> ` 173 // (the end of a match and start of a backtick string), 174 // and then the closing backtick. 175 const start = "/ -> `" 176 a, b := strings.Index(line, start), strings.LastIndex(line, "`") 177 if a < 0 || a > b { 178 return "", false 179 } 180 return line[a+len(start) : b], true 181 }