github.com/serversong/goreporter@v0.0.0-20200325104552-3cfaf44fd178/linters/golint/lint_test.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 golint 8 9 import ( 10 "bytes" 11 "flag" 12 "fmt" 13 "go/ast" 14 "go/parser" 15 "go/printer" 16 "go/token" 17 "go/types" 18 "io/ioutil" 19 "path" 20 "regexp" 21 "strconv" 22 "strings" 23 "testing" 24 ) 25 26 var lintMatch = flag.String("lint.match", "", "restrict testdata matches to this pattern") 27 28 func TestAll(t *testing.T) { 29 l := new(Linter) 30 rx, err := regexp.Compile(*lintMatch) 31 if err != nil { 32 t.Fatalf("Bad -lint.match value %q: %v", *lintMatch, err) 33 } 34 35 baseDir := "testdata" 36 fis, err := ioutil.ReadDir(baseDir) 37 if err != nil { 38 t.Fatalf("ioutil.ReadDir: %v", err) 39 } 40 if len(fis) == 0 { 41 t.Fatalf("no files in %v", baseDir) 42 } 43 for _, fi := range fis { 44 if !rx.MatchString(fi.Name()) { 45 continue 46 } 47 //t.Logf("Testing %s", fi.Name()) 48 src, err := ioutil.ReadFile(path.Join(baseDir, fi.Name())) 49 if err != nil { 50 t.Fatalf("Failed reading %s: %v", fi.Name(), err) 51 } 52 53 ins := parseInstructions(t, fi.Name(), src) 54 if ins == nil { 55 t.Errorf("Test file %v does not have instructions", fi.Name()) 56 continue 57 } 58 59 ps, err := l.Lint(fi.Name(), src) 60 if err != nil { 61 t.Errorf("Linting %s: %v", fi.Name(), err) 62 continue 63 } 64 65 for _, in := range ins { 66 ok := false 67 for i, p := range ps { 68 if p.Position.Line != in.Line { 69 continue 70 } 71 if in.Match.MatchString(p.Text) { 72 // check replacement if we are expecting one 73 if in.Replacement != "" { 74 // ignore any inline comments, since that would be recursive 75 r := p.ReplacementLine 76 if i := strings.Index(r, " //"); i >= 0 { 77 r = r[:i] 78 } 79 if r != in.Replacement { 80 t.Errorf("Lint failed at %s:%d; got replacement %q, want %q", fi.Name(), in.Line, r, in.Replacement) 81 } 82 } 83 84 // remove this problem from ps 85 copy(ps[i:], ps[i+1:]) 86 ps = ps[:len(ps)-1] 87 88 //t.Logf("/%v/ matched at %s:%d", in.Match, fi.Name(), in.Line) 89 ok = true 90 break 91 } 92 } 93 if !ok { 94 t.Errorf("Lint failed at %s:%d; /%v/ did not match", fi.Name(), in.Line, in.Match) 95 } 96 } 97 for _, p := range ps { 98 t.Errorf("Unexpected problem at %s:%d: %v", fi.Name(), p.Position.Line, p.Text) 99 } 100 } 101 } 102 103 type instruction struct { 104 Line int // the line number this applies to 105 Match *regexp.Regexp // what pattern to match 106 Replacement string // what the suggested replacement line should be 107 } 108 109 // parseInstructions parses instructions from the comments in a Go source file. 110 // It returns nil if none were parsed. 111 func parseInstructions(t *testing.T, filename string, src []byte) []instruction { 112 fset := token.NewFileSet() 113 f, err := parser.ParseFile(fset, filename, src, parser.ParseComments) 114 if err != nil { 115 t.Fatalf("Test file %v does not parse: %v", filename, err) 116 } 117 var ins []instruction 118 for _, cg := range f.Comments { 119 ln := fset.Position(cg.Pos()).Line 120 raw := cg.Text() 121 for _, line := range strings.Split(raw, "\n") { 122 if line == "" || strings.HasPrefix(line, "#") { 123 continue 124 } 125 if line == "OK" && ins == nil { 126 // so our return value will be non-nil 127 ins = make([]instruction, 0) 128 continue 129 } 130 if strings.Contains(line, "MATCH") { 131 rx, err := extractPattern(line) 132 if err != nil { 133 t.Fatalf("At %v:%d: %v", filename, ln, err) 134 } 135 matchLine := ln 136 if i := strings.Index(line, "MATCH:"); i >= 0 { 137 // This is a match for a different line. 138 lns := strings.TrimPrefix(line[i:], "MATCH:") 139 lns = lns[:strings.Index(lns, " ")] 140 matchLine, err = strconv.Atoi(lns) 141 if err != nil { 142 t.Fatalf("Bad match line number %q at %v:%d: %v", lns, filename, ln, err) 143 } 144 } 145 var repl string 146 if r, ok := extractReplacement(line); ok { 147 repl = r 148 } 149 ins = append(ins, instruction{ 150 Line: matchLine, 151 Match: rx, 152 Replacement: repl, 153 }) 154 } 155 } 156 } 157 return ins 158 } 159 160 func extractPattern(line string) (*regexp.Regexp, error) { 161 a, b := strings.Index(line, "/"), strings.LastIndex(line, "/") 162 if a == -1 || a == b { 163 return nil, fmt.Errorf("malformed match instruction %q", line) 164 } 165 pat := line[a+1 : b] 166 rx, err := regexp.Compile(pat) 167 if err != nil { 168 return nil, fmt.Errorf("bad match pattern %q: %v", pat, err) 169 } 170 return rx, nil 171 } 172 173 func extractReplacement(line string) (string, bool) { 174 // Look for this: / -> ` 175 // (the end of a match and start of a backtick string), 176 // and then the closing backtick. 177 const start = "/ -> `" 178 a, b := strings.Index(line, start), strings.LastIndex(line, "`") 179 if a < 0 || a > b { 180 return "", false 181 } 182 return line[a+len(start) : b], true 183 } 184 185 func render(fset *token.FileSet, x interface{}) string { 186 var buf bytes.Buffer 187 if err := printer.Fprint(&buf, fset, x); err != nil { 188 panic(err) 189 } 190 return buf.String() 191 } 192 193 func TestLine(t *testing.T) { 194 tests := []struct { 195 src string 196 offset int 197 want string 198 }{ 199 {"single line file", 5, "single line file"}, 200 {"single line file with newline\n", 5, "single line file with newline\n"}, 201 {"first\nsecond\nthird\n", 2, "first\n"}, 202 {"first\nsecond\nthird\n", 9, "second\n"}, 203 {"first\nsecond\nthird\n", 14, "third\n"}, 204 {"first\nsecond\nthird with no newline", 16, "third with no newline"}, 205 {"first byte\n", 0, "first byte\n"}, 206 } 207 for _, test := range tests { 208 got := srcLine([]byte(test.src), token.Position{Offset: test.offset}) 209 if got != test.want { 210 t.Errorf("srcLine(%q, offset=%d) = %q, want %q", test.src, test.offset, got, test.want) 211 } 212 } 213 } 214 215 func TestLintName(t *testing.T) { 216 tests := []struct { 217 name, want string 218 }{ 219 {"foo_bar", "fooBar"}, 220 {"foo_bar_baz", "fooBarBaz"}, 221 {"Foo_bar", "FooBar"}, 222 {"foo_WiFi", "fooWiFi"}, 223 {"id", "id"}, 224 {"Id", "ID"}, 225 {"foo_id", "fooID"}, 226 {"fooId", "fooID"}, 227 {"fooUid", "fooUID"}, 228 {"idFoo", "idFoo"}, 229 {"uidFoo", "uidFoo"}, 230 {"midIdDle", "midIDDle"}, 231 {"APIProxy", "APIProxy"}, 232 {"ApiProxy", "APIProxy"}, 233 {"apiProxy", "apiProxy"}, 234 {"_Leading", "_Leading"}, 235 {"___Leading", "_Leading"}, 236 {"trailing_", "trailing"}, 237 {"trailing___", "trailing"}, 238 {"a_b", "aB"}, 239 {"a__b", "aB"}, 240 {"a___b", "aB"}, 241 {"Rpc1150", "RPC1150"}, 242 {"case3_1", "case3_1"}, 243 {"case3__1", "case3_1"}, 244 {"IEEE802_16bit", "IEEE802_16bit"}, 245 {"IEEE802_16Bit", "IEEE802_16Bit"}, 246 } 247 for _, test := range tests { 248 got := lintName(test.name) 249 if got != test.want { 250 t.Errorf("lintName(%q) = %q, want %q", test.name, got, test.want) 251 } 252 } 253 } 254 255 func TestExportedType(t *testing.T) { 256 tests := []struct { 257 typString string 258 exp bool 259 }{ 260 {"int", true}, 261 {"string", false}, // references the shadowed builtin "string" 262 {"T", true}, 263 {"t", false}, 264 {"*T", true}, 265 {"*t", false}, 266 {"map[int]complex128", true}, 267 } 268 for _, test := range tests { 269 src := `package foo; type T int; type t int; type string struct{}` 270 fset := token.NewFileSet() 271 file, err := parser.ParseFile(fset, "foo.go", src, 0) 272 if err != nil { 273 t.Fatalf("Parsing %q: %v", src, err) 274 } 275 // use the package name as package path 276 config := &types.Config{} 277 pkg, err := config.Check(file.Name.Name, fset, []*ast.File{file}, nil) 278 if err != nil { 279 t.Fatalf("Type checking %q: %v", src, err) 280 } 281 tv, err := types.Eval(fset, pkg, token.NoPos, test.typString) 282 if err != nil { 283 t.Errorf("types.Eval(%q): %v", test.typString, err) 284 continue 285 } 286 if got := exportedType(tv.Type); got != test.exp { 287 t.Errorf("exportedType(%v) = %t, want %t", tv.Type, got, test.exp) 288 } 289 } 290 } 291 292 func TestIsGenerated(t *testing.T) { 293 tests := []struct { 294 source string 295 generated bool 296 }{ 297 {"// Code Generated by some tool. DO NOT EDIT.", false}, 298 {"// Code generated by some tool. DO NOT EDIT.", true}, 299 {"// Code generated by some tool. DO NOT EDIT", false}, 300 {"// Code generated DO NOT EDIT.", true}, 301 {"// Code generated DO NOT EDIT.", false}, 302 {"\t\t// Code generated by some tool. DO NOT EDIT.\npackage foo\n", false}, 303 {"// Code generated by some tool. DO NOT EDIT.\npackage foo\n", true}, 304 {"package foo\n// Code generated by some tool. DO NOT EDIT.\ntype foo int\n", true}, 305 {"package foo\n // Code generated by some tool. DO NOT EDIT.\ntype foo int\n", false}, 306 {"package foo\n// Code generated by some tool. DO NOT EDIT. \ntype foo int\n", false}, 307 {"package foo\ntype foo int\n// Code generated by some tool. DO NOT EDIT.\n", true}, 308 {"package foo\ntype foo int\n// Code generated by some tool. DO NOT EDIT.", true}, 309 } 310 311 for i, test := range tests { 312 got := isGenerated([]byte(test.source)) 313 if got != test.generated { 314 t.Errorf("test %d, isGenerated() = %v, want %v", i, got, test.generated) 315 } 316 } 317 }