gitee.com/mirrors/goreporter@v0.0.0-20180902115603-df1b20f7c5d0/linters/interfacer/interfacer_test.go (about)

     1  // Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
     2  // See LICENSE for licensing information
     3  
     4  package interfacer
     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, glob string) []string {
   148  	all, err := filepath.Glob(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  	paths = inputPaths(t, "*")
   210  	for _, p := range paths {
   211  		doTest(t, p+"/...")
   212  	}
   213  	// local recursive
   214  	doTest(t, "./nested/...")
   215  	// non-recursive
   216  	doTest(t, "single")
   217  	// make sure we don't miss a package's imports
   218  	doTestString(t, "grab-import", "grab-import/use.go:27:15: s can be grab-import/def/nested.Fooer")
   219  	defer chdirUndo(t, "nested/pkg")()
   220  	// relative paths
   221  	doTestString(t, "rel-path", "simple.go:12:17: rc can be Closer", "./...")
   222  }
   223  
   224  func TestMain(m *testing.M) {
   225  	if err := os.Chdir(testdata); err != nil {
   226  		panic(err)
   227  	}
   228  	wd, err := os.Getwd()
   229  	if err != nil {
   230  		panic(err)
   231  	}
   232  	build.Default.GOPATH = wd
   233  	gotool.DefaultContext.BuildContext.GOPATH = wd
   234  	os.Exit(m.Run())
   235  }
   236  
   237  func TestIssues(t *testing.T) {
   238  	runFileTests(t)
   239  	runLocalTests(t)
   240  	runNonlocalTests(t)
   241  }
   242  
   243  func TestExtraArg(t *testing.T) {
   244  	_, err := CheckArgs([]string{"single", "--", "foo", "bar"})
   245  	got := err.Error()
   246  	want := "unwanted extra args: [foo bar]"
   247  	if got != want {
   248  		t.Fatalf("Error mismatch:\nExpected:\n%s\nGot:\n%s", want, got)
   249  	}
   250  }