gitee.com/wgliang/goreporter@v0.0.0-20180902115603-df1b20f7c5d0/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  }