github.com/guyezi/gofrontend@v0.0.0-20200228202240-7a62a49e62c0/libgo/go/cmd/vet/vet_test.go (about)

     1  // Copyright 2013 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main_test
     6  
     7  import (
     8  	"bytes"
     9  	"errors"
    10  	"fmt"
    11  	"internal/testenv"
    12  	"io/ioutil"
    13  	"log"
    14  	"os"
    15  	"os/exec"
    16  	"path"
    17  	"path/filepath"
    18  	"regexp"
    19  	"runtime"
    20  	"strconv"
    21  	"strings"
    22  	"sync"
    23  	"testing"
    24  )
    25  
    26  const dataDir = "testdata"
    27  
    28  var binary string
    29  
    30  // We implement TestMain so remove the test binary when all is done.
    31  func TestMain(m *testing.M) {
    32  	os.Exit(testMain(m))
    33  }
    34  
    35  func testMain(m *testing.M) int {
    36  	dir, err := ioutil.TempDir("", "vet_test")
    37  	if err != nil {
    38  		fmt.Fprintln(os.Stderr, err)
    39  		return 1
    40  	}
    41  	defer os.RemoveAll(dir)
    42  	binary = filepath.Join(dir, "testvet.exe")
    43  
    44  	return m.Run()
    45  }
    46  
    47  var (
    48  	buildMu sync.Mutex // guards following
    49  	built   = false    // We have built the binary.
    50  	failed  = false    // We have failed to build the binary, don't try again.
    51  )
    52  
    53  func Build(t *testing.T) {
    54  	buildMu.Lock()
    55  	defer buildMu.Unlock()
    56  	if built {
    57  		return
    58  	}
    59  	if failed {
    60  		t.Skip("cannot run on this environment")
    61  	}
    62  	testenv.MustHaveGoBuild(t)
    63  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", binary)
    64  	output, err := cmd.CombinedOutput()
    65  	if err != nil {
    66  		failed = true
    67  		fmt.Fprintf(os.Stderr, "%s\n", output)
    68  		t.Fatal(err)
    69  	}
    70  	built = true
    71  }
    72  
    73  func vetCmd(t *testing.T, arg, pkg string) *exec.Cmd {
    74  	cmd := exec.Command(testenv.GoToolPath(t), "vet", "-vettool="+binary, arg, path.Join("cmd/vet/testdata", pkg))
    75  	cmd.Env = os.Environ()
    76  	return cmd
    77  }
    78  
    79  func TestVet(t *testing.T) {
    80  	t.Parallel()
    81  	Build(t)
    82  	for _, pkg := range []string{
    83  		"asm",
    84  		"assign",
    85  		"atomic",
    86  		"bool",
    87  		"buildtag",
    88  		"cgo",
    89  		"composite",
    90  		"copylock",
    91  		"deadcode",
    92  		"httpresponse",
    93  		"lostcancel",
    94  		"method",
    95  		"nilfunc",
    96  		"print",
    97  		"rangeloop",
    98  		"shift",
    99  		"structtag",
   100  		"testingpkg",
   101  		// "testtag" has its own test
   102  		"unmarshal",
   103  		"unsafeptr",
   104  		"unused",
   105  	} {
   106  		pkg := pkg
   107  		t.Run(pkg, func(t *testing.T) {
   108  			t.Parallel()
   109  
   110  			// Skip cgo test on platforms without cgo.
   111  			if pkg == "cgo" && !cgoEnabled(t) {
   112  				return
   113  			}
   114  
   115  			cmd := vetCmd(t, "-printfuncs=Warn,Warnf", pkg)
   116  
   117  			// The asm test assumes amd64.
   118  			if pkg == "asm" {
   119  				if runtime.Compiler == "gccgo" {
   120  					t.Skip("asm test assumes gc")
   121  				}
   122  				cmd.Env = append(cmd.Env, "GOOS=linux", "GOARCH=amd64")
   123  			}
   124  
   125  			dir := filepath.Join("testdata", pkg)
   126  			gos, err := filepath.Glob(filepath.Join(dir, "*.go"))
   127  			if err != nil {
   128  				t.Fatal(err)
   129  			}
   130  			asms, err := filepath.Glob(filepath.Join(dir, "*.s"))
   131  			if err != nil {
   132  				t.Fatal(err)
   133  			}
   134  			var files []string
   135  			files = append(files, gos...)
   136  			files = append(files, asms...)
   137  
   138  			errchk(cmd, files, t)
   139  		})
   140  	}
   141  }
   142  
   143  func cgoEnabled(t *testing.T) bool {
   144  	// Don't trust build.Default.CgoEnabled as it is false for
   145  	// cross-builds unless CGO_ENABLED is explicitly specified.
   146  	// That's fine for the builders, but causes commands like
   147  	// 'GOARCH=386 go test .' to fail.
   148  	// Instead, we ask the go command.
   149  	cmd := exec.Command(testenv.GoToolPath(t), "list", "-f", "{{context.CgoEnabled}}")
   150  	out, _ := cmd.CombinedOutput()
   151  	return string(out) == "true\n"
   152  }
   153  
   154  func errchk(c *exec.Cmd, files []string, t *testing.T) {
   155  	output, err := c.CombinedOutput()
   156  	if _, ok := err.(*exec.ExitError); !ok {
   157  		t.Logf("vet output:\n%s", output)
   158  		t.Fatal(err)
   159  	}
   160  	fullshort := make([]string, 0, len(files)*2)
   161  	for _, f := range files {
   162  		fullshort = append(fullshort, f, filepath.Base(f))
   163  	}
   164  	err = errorCheck(string(output), false, fullshort...)
   165  	if err != nil {
   166  		t.Errorf("error check failed: %s", err)
   167  	}
   168  }
   169  
   170  // TestTags verifies that the -tags argument controls which files to check.
   171  func TestTags(t *testing.T) {
   172  	t.Parallel()
   173  	Build(t)
   174  	for tag, wantFile := range map[string]int{
   175  		"testtag":     1, // file1
   176  		"x testtag y": 1,
   177  		"othertag":    2,
   178  	} {
   179  		tag, wantFile := tag, wantFile
   180  		t.Run(tag, func(t *testing.T) {
   181  			t.Parallel()
   182  			t.Logf("-tags=%s", tag)
   183  			cmd := vetCmd(t, "-tags="+tag, "tagtest")
   184  			output, err := cmd.CombinedOutput()
   185  
   186  			want := fmt.Sprintf("file%d.go", wantFile)
   187  			dontwant := fmt.Sprintf("file%d.go", 3-wantFile)
   188  
   189  			// file1 has testtag and file2 has !testtag.
   190  			if !bytes.Contains(output, []byte(filepath.Join("tagtest", want))) {
   191  				t.Errorf("%s: %s was excluded, should be included", tag, want)
   192  			}
   193  			if bytes.Contains(output, []byte(filepath.Join("tagtest", dontwant))) {
   194  				t.Errorf("%s: %s was included, should be excluded", tag, dontwant)
   195  			}
   196  			if t.Failed() {
   197  				t.Logf("err=%s, output=<<%s>>", err, output)
   198  			}
   199  		})
   200  	}
   201  }
   202  
   203  // All declarations below were adapted from test/run.go.
   204  
   205  // errorCheck matches errors in outStr against comments in source files.
   206  // For each line of the source files which should generate an error,
   207  // there should be a comment of the form // ERROR "regexp".
   208  // If outStr has an error for a line which has no such comment,
   209  // this function will report an error.
   210  // Likewise if outStr does not have an error for a line which has a comment,
   211  // or if the error message does not match the <regexp>.
   212  // The <regexp> syntax is Perl but it's best to stick to egrep.
   213  //
   214  // Sources files are supplied as fullshort slice.
   215  // It consists of pairs: full path to source file and its base name.
   216  func errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) {
   217  	var errs []error
   218  	out := splitOutput(outStr, wantAuto)
   219  	// Cut directory name.
   220  	for i := range out {
   221  		for j := 0; j < len(fullshort); j += 2 {
   222  			full, short := fullshort[j], fullshort[j+1]
   223  			out[i] = strings.ReplaceAll(out[i], full, short)
   224  		}
   225  	}
   226  
   227  	var want []wantedError
   228  	for j := 0; j < len(fullshort); j += 2 {
   229  		full, short := fullshort[j], fullshort[j+1]
   230  		want = append(want, wantedErrors(full, short)...)
   231  	}
   232  	for _, we := range want {
   233  		var errmsgs []string
   234  		if we.auto {
   235  			errmsgs, out = partitionStrings("<autogenerated>", out)
   236  		} else {
   237  			errmsgs, out = partitionStrings(we.prefix, out)
   238  		}
   239  		if len(errmsgs) == 0 {
   240  			errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr))
   241  			continue
   242  		}
   243  		matched := false
   244  		n := len(out)
   245  		for _, errmsg := range errmsgs {
   246  			// Assume errmsg says "file:line: foo".
   247  			// Cut leading "file:line: " to avoid accidental matching of file name instead of message.
   248  			text := errmsg
   249  			if i := strings.Index(text, " "); i >= 0 {
   250  				text = text[i+1:]
   251  			}
   252  			if we.re.MatchString(text) {
   253  				matched = true
   254  			} else {
   255  				out = append(out, errmsg)
   256  			}
   257  		}
   258  		if !matched {
   259  			errs = append(errs, fmt.Errorf("%s:%d: no match for %#q in:\n\t%s", we.file, we.lineNum, we.reStr, strings.Join(out[n:], "\n\t")))
   260  			continue
   261  		}
   262  	}
   263  
   264  	if len(out) > 0 {
   265  		errs = append(errs, fmt.Errorf("Unmatched Errors:"))
   266  		for _, errLine := range out {
   267  			errs = append(errs, fmt.Errorf("%s", errLine))
   268  		}
   269  	}
   270  
   271  	if len(errs) == 0 {
   272  		return nil
   273  	}
   274  	if len(errs) == 1 {
   275  		return errs[0]
   276  	}
   277  	var buf bytes.Buffer
   278  	fmt.Fprintf(&buf, "\n")
   279  	for _, err := range errs {
   280  		fmt.Fprintf(&buf, "%s\n", err.Error())
   281  	}
   282  	return errors.New(buf.String())
   283  }
   284  
   285  func splitOutput(out string, wantAuto bool) []string {
   286  	// gc error messages continue onto additional lines with leading tabs.
   287  	// Split the output at the beginning of each line that doesn't begin with a tab.
   288  	// <autogenerated> lines are impossible to match so those are filtered out.
   289  	var res []string
   290  	for _, line := range strings.Split(out, "\n") {
   291  		line = strings.TrimSuffix(line, "\r") // normalize Windows output
   292  		if strings.HasPrefix(line, "\t") {
   293  			res[len(res)-1] += "\n" + line
   294  		} else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "#") || !wantAuto && strings.HasPrefix(line, "<autogenerated>") {
   295  			continue
   296  		} else if strings.TrimSpace(line) != "" {
   297  			res = append(res, line)
   298  		}
   299  	}
   300  	return res
   301  }
   302  
   303  // matchPrefix reports whether s starts with file name prefix followed by a :,
   304  // and possibly preceded by a directory name.
   305  func matchPrefix(s, prefix string) bool {
   306  	i := strings.Index(s, ":")
   307  	if i < 0 {
   308  		return false
   309  	}
   310  	j := strings.LastIndex(s[:i], "/")
   311  	s = s[j+1:]
   312  	if len(s) <= len(prefix) || s[:len(prefix)] != prefix {
   313  		return false
   314  	}
   315  	if s[len(prefix)] == ':' {
   316  		return true
   317  	}
   318  	return false
   319  }
   320  
   321  func partitionStrings(prefix string, strs []string) (matched, unmatched []string) {
   322  	for _, s := range strs {
   323  		if matchPrefix(s, prefix) {
   324  			matched = append(matched, s)
   325  		} else {
   326  			unmatched = append(unmatched, s)
   327  		}
   328  	}
   329  	return
   330  }
   331  
   332  type wantedError struct {
   333  	reStr   string
   334  	re      *regexp.Regexp
   335  	lineNum int
   336  	auto    bool // match <autogenerated> line
   337  	file    string
   338  	prefix  string
   339  }
   340  
   341  var (
   342  	errRx       = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`)
   343  	errAutoRx   = regexp.MustCompile(`// (?:GC_)?ERRORAUTO (.*)`)
   344  	errQuotesRx = regexp.MustCompile(`"([^"]*)"`)
   345  	lineRx      = regexp.MustCompile(`LINE(([+-])([0-9]+))?`)
   346  )
   347  
   348  // wantedErrors parses expected errors from comments in a file.
   349  func wantedErrors(file, short string) (errs []wantedError) {
   350  	cache := make(map[string]*regexp.Regexp)
   351  
   352  	src, err := ioutil.ReadFile(file)
   353  	if err != nil {
   354  		log.Fatal(err)
   355  	}
   356  	for i, line := range strings.Split(string(src), "\n") {
   357  		lineNum := i + 1
   358  		if strings.Contains(line, "////") {
   359  			// double comment disables ERROR
   360  			continue
   361  		}
   362  		var auto bool
   363  		m := errAutoRx.FindStringSubmatch(line)
   364  		if m != nil {
   365  			auto = true
   366  		} else {
   367  			m = errRx.FindStringSubmatch(line)
   368  		}
   369  		if m == nil {
   370  			continue
   371  		}
   372  		all := m[1]
   373  		mm := errQuotesRx.FindAllStringSubmatch(all, -1)
   374  		if mm == nil {
   375  			log.Fatalf("%s:%d: invalid errchk line: %s", file, lineNum, line)
   376  		}
   377  		for _, m := range mm {
   378  			replacedOnce := false
   379  			rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string {
   380  				if replacedOnce {
   381  					return m
   382  				}
   383  				replacedOnce = true
   384  				n := lineNum
   385  				if strings.HasPrefix(m, "LINE+") {
   386  					delta, _ := strconv.Atoi(m[5:])
   387  					n += delta
   388  				} else if strings.HasPrefix(m, "LINE-") {
   389  					delta, _ := strconv.Atoi(m[5:])
   390  					n -= delta
   391  				}
   392  				return fmt.Sprintf("%s:%d", short, n)
   393  			})
   394  			re := cache[rx]
   395  			if re == nil {
   396  				var err error
   397  				re, err = regexp.Compile(rx)
   398  				if err != nil {
   399  					log.Fatalf("%s:%d: invalid regexp \"%#q\" in ERROR line: %v", file, lineNum, rx, err)
   400  				}
   401  				cache[rx] = re
   402  			}
   403  			prefix := fmt.Sprintf("%s:%d", short, lineNum)
   404  			errs = append(errs, wantedError{
   405  				reStr:   rx,
   406  				re:      re,
   407  				prefix:  prefix,
   408  				auto:    auto,
   409  				lineNum: lineNum,
   410  				file:    short,
   411  			})
   412  		}
   413  	}
   414  
   415  	return
   416  }