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