github.com/mvdan/interfacer@v0.0.0-20180901003855-c20040233aed/check/interfacer_test.go (about) 1 // Copyright (c) 2015, Daniel Martà <mvdan@mvdan.cc> 2 // See LICENSE for licensing information 3 4 package check 5 6 import ( 7 "fmt" 8 "go/ast" 9 "go/build" 10 "go/parser" 11 "go/token" 12 "io/ioutil" 13 "os" 14 "path/filepath" 15 "reflect" 16 "regexp" 17 "strings" 18 "testing" 19 20 "github.com/kisielk/gotool" 21 ) 22 23 const testdata = "testdata" 24 25 var ( 26 issuesRe = regexp.MustCompile(`^WARN (.*)\n?$`) 27 singleRe = regexp.MustCompile(`([^ ]*) can be ([^ ]*)(,|$)`) 28 ) 29 30 func goFiles(t *testing.T, p string) []string { 31 if strings.HasSuffix(p, ".go") { 32 return []string{p} 33 } 34 dirs := gotool.ImportPaths([]string{p}) 35 var paths []string 36 for _, dir := range dirs { 37 files, err := ioutil.ReadDir(dir) 38 if err != nil { 39 t.Fatal(err) 40 } 41 for _, file := range files { 42 if file.IsDir() { 43 continue 44 } 45 paths = append(paths, filepath.Join(dir, file.Name())) 46 } 47 } 48 return paths 49 } 50 51 type identVisitor struct { 52 fset *token.FileSet 53 idents map[string]token.Pos 54 } 55 56 func identKey(line int, name string) string { 57 return fmt.Sprintf("%d %s", line, name) 58 } 59 60 func (v *identVisitor) Visit(n ast.Node) ast.Visitor { 61 switch x := n.(type) { 62 case *ast.Ident: 63 line := v.fset.Position(x.Pos()).Line 64 v.idents[identKey(line, x.Name)] = x.Pos() 65 } 66 return v 67 } 68 69 func identPositions(fset *token.FileSet, f *ast.File) map[string]token.Pos { 70 v := &identVisitor{ 71 fset: fset, 72 idents: make(map[string]token.Pos), 73 } 74 ast.Walk(v, f) 75 return v.idents 76 } 77 78 func wantedIssues(t *testing.T, p string) []string { 79 fset := token.NewFileSet() 80 lines := make([]string, 0) 81 for _, path := range goFiles(t, p) { 82 src, err := os.Open(path) 83 if err != nil { 84 t.Fatal(err) 85 } 86 f, err := parser.ParseFile(fset, path, src, parser.ParseComments) 87 src.Close() 88 if err != nil { 89 t.Fatal(err) 90 } 91 identPos := identPositions(fset, f) 92 for _, group := range f.Comments { 93 cm := issuesRe.FindStringSubmatch(group.Text()) 94 if cm == nil { 95 continue 96 } 97 for _, m := range singleRe.FindAllStringSubmatch(cm[1], -1) { 98 vname, tname := m[1], m[2] 99 line := fset.Position(group.Pos()).Line 100 pos := fset.Position(identPos[identKey(line, vname)]) 101 lines = append(lines, fmt.Sprintf("%s: %s can be %s", 102 pos, vname, tname)) 103 } 104 } 105 } 106 return lines 107 } 108 109 func doTest(t *testing.T, p string) { 110 t.Run(p, func(t *testing.T) { 111 lines := wantedIssues(t, p) 112 doTestLines(t, p, lines, p) 113 }) 114 } 115 116 func doTestLines(t *testing.T, name string, want []string, args ...string) { 117 got, err := CheckArgs(args) 118 if err != nil { 119 t.Fatalf("Did not want error in %s:\n%v", name, err) 120 } 121 if !reflect.DeepEqual(want, got) { 122 t.Fatalf("Output mismatch in %s:\nwant:\n%s\ngot:\n%s", 123 name, strings.Join(want, "\n"), strings.Join(got, "\n")) 124 } 125 } 126 127 func doTestString(t *testing.T, name, want string, args ...string) { 128 switch len(args) { 129 case 0: 130 args = []string{name} 131 case 1: 132 if args[0] == "" { 133 args = nil 134 } 135 } 136 issues, err := CheckArgs(args) 137 if err != nil { 138 t.Fatalf("Did not want error in %s:\n%v", name, err) 139 } 140 got := strings.Join(issues, "\n") 141 if want != got { 142 t.Fatalf("Output mismatch in %s:\nExpected:\n%s\nGot:\n%s", 143 name, want, got) 144 } 145 } 146 147 func inputPaths(t *testing.T) []string { 148 all, err := filepath.Glob("*") 149 if err != nil { 150 t.Fatal(err) 151 } 152 return all 153 } 154 155 func chdirUndo(t *testing.T, d string) func() { 156 wd, err := os.Getwd() 157 if err != nil { 158 t.Fatal(err) 159 } 160 if err := os.Chdir(d); err != nil { 161 t.Fatal(err) 162 } 163 return func() { 164 if err := os.Chdir(wd); err != nil { 165 t.Fatal(err) 166 } 167 } 168 } 169 170 func runFileTests(t *testing.T, paths ...string) { 171 defer chdirUndo(t, "files")() 172 if len(paths) == 0 { 173 paths = inputPaths(t) 174 } 175 for _, p := range paths { 176 doTest(t, p) 177 } 178 } 179 180 func runLocalTests(t *testing.T, paths ...string) { 181 defer chdirUndo(t, "local")() 182 if len(paths) > 0 { 183 for _, p := range paths { 184 doTest(t, p) 185 } 186 return 187 } 188 for _, p := range inputPaths(t) { 189 paths = append(paths, "./"+p+"/...") 190 } 191 for _, p := range paths { 192 doTest(t, p) 193 } 194 // non-recursive 195 doTest(t, "./single") 196 doTestString(t, "no-args", "", "") 197 } 198 199 func runNonlocalTests(t *testing.T, paths ...string) { 200 // std 201 doTestString(t, "std-pkg", "", "sync/atomic") 202 defer chdirUndo(t, "src")() 203 if len(paths) > 0 { 204 for _, p := range paths { 205 doTest(t, p) 206 } 207 return 208 } 209 for _, p := range inputPaths(t) { 210 doTest(t, p+"/...") 211 } 212 // local recursive 213 doTest(t, "./nested/...") 214 // non-recursive 215 doTest(t, "single") 216 // make sure we don't miss a package's imports 217 doTestString(t, "grab-import", "grab-import/use.go:27:15: s can be grab-import/def/nested.Fooer") 218 defer chdirUndo(t, "nested/pkg")() 219 // relative paths 220 doTestString(t, "rel-path", "simple.go:12:17: rc can be Closer", "./...") 221 } 222 223 func TestMain(m *testing.M) { 224 if err := os.Chdir(testdata); err != nil { 225 panic(err) 226 } 227 wd, err := os.Getwd() 228 if err != nil { 229 panic(err) 230 } 231 build.Default.GOPATH = wd 232 gotool.DefaultContext.BuildContext.GOPATH = wd 233 os.Exit(m.Run()) 234 } 235 236 func TestIssues(t *testing.T) { 237 runFileTests(t) 238 runLocalTests(t) 239 runNonlocalTests(t) 240 } 241 242 func TestExtraArg(t *testing.T) { 243 _, err := CheckArgs([]string{"single", "--", "foo", "bar"}) 244 got := err.Error() 245 want := "unwanted extra args: [foo bar]" 246 if got != want { 247 t.Fatalf("Error mismatch:\nExpected:\n%s\nGot:\n%s", want, got) 248 } 249 }