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 7package lint 8 9import ( 10 "bytes" 11 "flag" 12 "go/parser" 13 "go/printer" 14 "go/token" 15 "io/ioutil" 16 "path" 17 "regexp" 18 "strings" 19 "testing" 20) 21 22var lintMatch = flag.String("lint.match", "", "restrict testdata matches to this pattern") 23 24func TestAll(t *testing.T) { 25 l := new(Linter) 26 rx, err := regexp.Compile(*lintMatch) 27 if err != nil { 28 t.Fatalf("Bad -lint.match value %q: %v", *lintMatch, err) 29 } 30 31 baseDir := "testdata" 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 for _, fi := range fis { 40 if !rx.MatchString(fi.Name()) { 41 continue 42 } 43 //t.Logf("Testing %s", fi.Name()) 44 src, err := ioutil.ReadFile(path.Join(baseDir, fi.Name())) 45 if err != nil { 46 t.Fatalf("Failed reading %s: %v", fi.Name(), err) 47 } 48 49 ins := parseInstructions(t, fi.Name(), src) 50 if ins == nil { 51 t.Errorf("Test file %v does not have instructions", fi.Name()) 52 continue 53 } 54 55 ps, err := l.Lint(fi.Name(), src) 56 if err != nil { 57 t.Errorf("Linting %s: %v", fi.Name(), err) 58 continue 59 } 60 61 for _, in := range ins { 62 ok := false 63 for i, p := range ps { 64 if p.Position.Line != in.Line { 65 continue 66 } 67 if in.Match.MatchString(p.Text) { 68 // remove this problem from ps 69 copy(ps[i:], ps[i+1:]) 70 ps = ps[:len(ps)-1] 71 72 //t.Logf("/%v/ matched at %s:%d", in.Match, fi.Name(), in.Line) 73 ok = true 74 break 75 } 76 } 77 if !ok { 78 t.Errorf("Lint failed at %s:%d; /%v/ did not match", fi.Name(), in.Line, in.Match) 79 } 80 } 81 for _, p := range ps { 82 t.Errorf("Unexpected problem at %s:%d: %v", fi.Name(), p.Position.Line, p.Text) 83 } 84 } 85} 86 87type instruction struct { 88 Line int // the line number this applies to 89 Match *regexp.Regexp // what pattern to match 90} 91 92// parseInstructions parses instructions from the comments in a Go source file. 93// It returns nil if none were parsed. 94func parseInstructions(t *testing.T, filename string, src []byte) []instruction { 95 fset := token.NewFileSet() 96 f, err := parser.ParseFile(fset, filename, src, parser.ParseComments) 97 if err != nil { 98 t.Fatalf("Test file %v does not parse: %v", filename, err) 99 } 100 var ins []instruction 101 for _, cg := range f.Comments { 102 ln := fset.Position(cg.Pos()).Line 103 raw := cg.Text() 104 for _, line := range strings.Split(raw, "\n") { 105 if line == "" || strings.HasPrefix(line, "#") { 106 continue 107 } 108 if line == "OK" && ins == nil { 109 // so our return value will be non-nil 110 ins = make([]instruction, 0) 111 continue 112 } 113 if strings.Contains(line, "MATCH") { 114 a, b := strings.Index(line, "/"), strings.LastIndex(line, "/") 115 if a == -1 || a == b { 116 t.Fatalf("Malformed match instruction %q at %v:%d", line, filename, ln) 117 } 118 pat := line[a+1 : b] 119 rx, err := regexp.Compile(pat) 120 if err != nil { 121 t.Fatalf("Bad match pattern %q at %v:%d: %v", pat, filename, ln, err) 122 } 123 ins = append(ins, instruction{ 124 Line: ln, 125 Match: rx, 126 }) 127 } 128 } 129 } 130 return ins 131} 132 133func render(fset *token.FileSet, x interface{}) string { 134 var buf bytes.Buffer 135 if err := printer.Fprint(&buf, fset, x); err != nil { 136 panic(err) 137 } 138 return buf.String() 139} 140 141func TestLine(t *testing.T) { 142 tests := []struct { 143 src string 144 offset int 145 want string 146 }{ 147 {"single line file", 5, "single line file"}, 148 {"single line file with newline\n", 5, "single line file with newline\n"}, 149 {"first\nsecond\nthird\n", 2, "first\n"}, 150 {"first\nsecond\nthird\n", 9, "second\n"}, 151 {"first\nsecond\nthird\n", 14, "third\n"}, 152 {"first\nsecond\nthird with no newline", 16, "third with no newline"}, 153 {"first byte\n", 0, "first byte\n"}, 154 } 155 for _, test := range tests { 156 got := srcLine([]byte(test.src), token.Position{Offset: test.offset}) 157 if got != test.want { 158 t.Errorf("srcLine(%q, offset=%d) = %q, want %q", test.src, test.offset, got, test.want) 159 } 160 } 161} 162 163func TestLintName(t *testing.T) { 164 tests := []struct { 165 name, want string 166 }{ 167 {"foo_bar", "fooBar"}, 168 {"foo_bar_baz", "fooBarBaz"}, 169 {"Foo_bar", "FooBar"}, 170 {"foo_WiFi", "fooWiFi"}, 171 {"id", "id"}, 172 {"Id", "ID"}, 173 {"foo_id", "fooID"}, 174 {"fooId", "fooID"}, 175 {"fooUid", "fooUID"}, 176 {"idFoo", "idFoo"}, 177 {"uidFoo", "uidFoo"}, 178 {"midIdDle", "midIDDle"}, 179 {"APIProxy", "APIProxy"}, 180 {"ApiProxy", "APIProxy"}, 181 {"apiProxy", "apiProxy"}, 182 {"_Leading", "_Leading"}, 183 {"___Leading", "_Leading"}, 184 {"trailing_", "trailing"}, 185 {"trailing___", "trailing"}, 186 {"a_b", "aB"}, 187 {"a__b", "aB"}, 188 {"a___b", "aB"}, 189 {"Rpc1150", "RPC1150"}, 190 } 191 for _, test := range tests { 192 got := lintName(test.name) 193 if got != test.want { 194 t.Errorf("lintName(%q) = %q, want %q", test.name, got, test.want) 195 } 196 } 197} 198