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  }