github.com/serversong/goreporter@v0.0.0-20200325104552-3cfaf44fd178/linters/simplecode/lint/testutil/util.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 testutil // import "github.com/360EntSecGroup-Skylar/goreporter/linters/simplecode/lint/testutil"
     8  
     9  import (
    10  	"flag"
    11  	"fmt"
    12  	"go/parser"
    13  	"go/token"
    14  	"io/ioutil"
    15  	"path"
    16  	"path/filepath"
    17  	"regexp"
    18  	"strconv"
    19  	"strings"
    20  	"testing"
    21  
    22  	"github.com/360EntSecGroup-Skylar/goreporter/linters/simplecode/lint"
    23  )
    24  
    25  var lintMatch = flag.String("lint.match", "", "restrict testdata matches to this pattern")
    26  
    27  func TestAll(t *testing.T, funcs []lint.Func, dir string) {
    28  	l := &lint.Linter{Funcs: funcs}
    29  	rx, err := regexp.Compile(*lintMatch)
    30  	if err != nil {
    31  		t.Fatalf("Bad -lint.match value %q: %v", *lintMatch, err)
    32  	}
    33  
    34  	baseDir := filepath.Join("testdata", dir)
    35  	fis, err := ioutil.ReadDir(baseDir)
    36  	if err != nil {
    37  		t.Fatalf("ioutil.ReadDir: %v", err)
    38  	}
    39  	if len(fis) == 0 {
    40  		t.Fatalf("no files in %v", baseDir)
    41  	}
    42  	for _, fi := range fis {
    43  		if !rx.MatchString(fi.Name()) {
    44  			continue
    45  		}
    46  		if !strings.HasSuffix(fi.Name(), ".go") {
    47  			continue
    48  		}
    49  		//t.Logf("Testing %s", fi.Name())
    50  		src, err := ioutil.ReadFile(path.Join(baseDir, fi.Name()))
    51  		if err != nil {
    52  			t.Fatalf("Failed reading %s: %v", fi.Name(), err)
    53  		}
    54  
    55  		ins := parseInstructions(t, fi.Name(), src)
    56  
    57  		ps, err := l.Lint(fi.Name(), src)
    58  		if err != nil {
    59  			t.Errorf("Linting %s: %v", fi.Name(), err)
    60  			continue
    61  		}
    62  
    63  		for _, in := range ins {
    64  			ok := false
    65  			for i, p := range ps {
    66  				if p.Position.Line != in.Line {
    67  					continue
    68  				}
    69  				if in.Match.MatchString(p.Text) {
    70  					// check replacement if we are expecting one
    71  					if in.Replacement != "" {
    72  						// ignore any inline comments, since that would be recursive
    73  						r := p.ReplacementLine
    74  						if i := strings.Index(r, " //"); i >= 0 {
    75  							r = r[:i]
    76  						}
    77  						if r != in.Replacement {
    78  							t.Errorf("Lint failed at %s:%d; got replacement %q, want %q", fi.Name(), in.Line, r, in.Replacement)
    79  						}
    80  					}
    81  
    82  					// remove this problem from ps
    83  					copy(ps[i:], ps[i+1:])
    84  					ps = ps[:len(ps)-1]
    85  
    86  					//t.Logf("/%v/ matched at %s:%d", in.Match, fi.Name(), in.Line)
    87  					ok = true
    88  					break
    89  				}
    90  			}
    91  			if !ok {
    92  				t.Errorf("Lint failed at %s:%d; /%v/ did not match", fi.Name(), in.Line, in.Match)
    93  			}
    94  		}
    95  		for _, p := range ps {
    96  			t.Errorf("Unexpected problem at %s:%d: %v", fi.Name(), p.Position.Line, p.Text)
    97  		}
    98  	}
    99  }
   100  
   101  type instruction struct {
   102  	Line        int            // the line number this applies to
   103  	Match       *regexp.Regexp // what pattern to match
   104  	Replacement string         // what the suggested replacement line should be
   105  }
   106  
   107  // parseInstructions parses instructions from the comments in a Go source file.
   108  // It returns nil if none were parsed.
   109  func parseInstructions(t *testing.T, filename string, src []byte) []instruction {
   110  	fset := token.NewFileSet()
   111  	f, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
   112  	if err != nil {
   113  		t.Fatalf("Test file %v does not parse: %v", filename, err)
   114  	}
   115  	var ins []instruction
   116  	for _, cg := range f.Comments {
   117  		ln := fset.Position(cg.Pos()).Line
   118  		raw := cg.Text()
   119  		for _, line := range strings.Split(raw, "\n") {
   120  			if line == "" || strings.HasPrefix(line, "#") {
   121  				continue
   122  			}
   123  			if line == "OK" && ins == nil {
   124  				// so our return value will be non-nil
   125  				ins = make([]instruction, 0)
   126  				continue
   127  			}
   128  			if strings.Contains(line, "MATCH") {
   129  				rx, err := extractPattern(line)
   130  				if err != nil {
   131  					t.Fatalf("At %v:%d: %v", filename, ln, err)
   132  				}
   133  				matchLine := ln
   134  				if i := strings.Index(line, "MATCH:"); i >= 0 {
   135  					// This is a match for a different line.
   136  					lns := strings.TrimPrefix(line[i:], "MATCH:")
   137  					lns = lns[:strings.Index(lns, " ")]
   138  					matchLine, err = strconv.Atoi(lns)
   139  					if err != nil {
   140  						t.Fatalf("Bad match line number %q at %v:%d: %v", lns, filename, ln, err)
   141  					}
   142  				}
   143  				var repl string
   144  				if r, ok := extractReplacement(line); ok {
   145  					repl = r
   146  				}
   147  				ins = append(ins, instruction{
   148  					Line:        matchLine,
   149  					Match:       rx,
   150  					Replacement: repl,
   151  				})
   152  			}
   153  		}
   154  	}
   155  	return ins
   156  }
   157  
   158  func extractPattern(line string) (*regexp.Regexp, error) {
   159  	a, b := strings.Index(line, "/"), strings.LastIndex(line, "/")
   160  	if a == -1 || a == b {
   161  		return nil, fmt.Errorf("malformed match instruction %q", line)
   162  	}
   163  	pat := line[a+1 : b]
   164  	rx, err := regexp.Compile(pat)
   165  	if err != nil {
   166  		return nil, fmt.Errorf("bad match pattern %q: %v", pat, err)
   167  	}
   168  	return rx, nil
   169  }
   170  
   171  func extractReplacement(line string) (string, bool) {
   172  	// Look for this:  / -> `
   173  	// (the end of a match and start of a backtick string),
   174  	// and then the closing backtick.
   175  	const start = "/ -> `"
   176  	a, b := strings.Index(line, start), strings.LastIndex(line, "`")
   177  	if a < 0 || a > b {
   178  		return "", false
   179  	}
   180  	return line[a+len(start) : b], true
   181  }