Source file 
src/cmd/vet/vet_test.go
     1  
     2  
     3  
     4  
     5  package main
     6  
     7  
     8  
     9  
    10  
    11  import (
    12  	"bytes"
    13  	"errors"
    14  	"fmt"
    15  	"internal/testenv"
    16  	"log"
    17  	"os"
    18  	"os/exec"
    19  	"path"
    20  	"path/filepath"
    21  	"regexp"
    22  	"strconv"
    23  	"strings"
    24  	"testing"
    25  )
    26  
    27  
    28  
    29  func TestMain(m *testing.M) {
    30  	if os.Getenv("GO_VETTEST_IS_VET") != "" {
    31  		main()
    32  		os.Exit(0)
    33  	}
    34  
    35  	
    36  	os.Setenv("GO_VETTEST_IS_VET", "1") 
    37  	os.Exit(m.Run())
    38  }
    39  
    40  
    41  func vetPath(t testing.TB) string {
    42  	return testenv.Executable(t)
    43  }
    44  
    45  func vetCmd(t *testing.T, arg, pkg string) *exec.Cmd {
    46  	cmd := testenv.Command(t, testenv.GoToolPath(t), "vet", "-vettool="+vetPath(t), arg, path.Join("cmd/vet/testdata", pkg))
    47  	cmd.Env = os.Environ()
    48  	return cmd
    49  }
    50  
    51  func TestVet(t *testing.T) {
    52  	t.Parallel()
    53  	for _, pkg := range []string{
    54  		"appends",
    55  		"asm",
    56  		"assign",
    57  		"atomic",
    58  		"bool",
    59  		"buildtag",
    60  		"cgo",
    61  		"composite",
    62  		"copylock",
    63  		"deadcode",
    64  		"directive",
    65  		"hostport",
    66  		"httpresponse",
    67  		"lostcancel",
    68  		"method",
    69  		"nilfunc",
    70  		"print",
    71  		"shift",
    72  		"slog",
    73  		"structtag",
    74  		"testingpkg",
    75  		
    76  		"unmarshal",
    77  		"unsafeptr",
    78  		"unused",
    79  		"waitgroup",
    80  	} {
    81  		t.Run(pkg, func(t *testing.T) {
    82  			t.Parallel()
    83  
    84  			
    85  			if pkg == "cgo" && !cgoEnabled(t) {
    86  				return
    87  			}
    88  
    89  			cmd := vetCmd(t, "-printfuncs=Warn,Warnf", pkg)
    90  
    91  			
    92  			if pkg == "asm" {
    93  				cmd.Env = append(cmd.Env, "GOOS=linux", "GOARCH=amd64")
    94  			}
    95  
    96  			dir := filepath.Join("testdata", pkg)
    97  			gos, err := filepath.Glob(filepath.Join(dir, "*.go"))
    98  			if err != nil {
    99  				t.Fatal(err)
   100  			}
   101  			asms, err := filepath.Glob(filepath.Join(dir, "*.s"))
   102  			if err != nil {
   103  				t.Fatal(err)
   104  			}
   105  			var files []string
   106  			files = append(files, gos...)
   107  			files = append(files, asms...)
   108  
   109  			errchk(cmd, files, t)
   110  		})
   111  	}
   112  
   113  	
   114  	
   115  	
   116  	
   117  	
   118  	t.Run("loopclosure", func(t *testing.T) {
   119  		cmd := testenv.Command(t, testenv.GoToolPath(t), "vet", "-vettool="+vetPath(t), ".")
   120  		cmd.Env = append(os.Environ(), "GOWORK=off")
   121  		cmd.Dir = "testdata/rangeloop"
   122  		cmd.Stderr = new(strings.Builder) 
   123  		cmd.Run()                         
   124  		stderr := cmd.Stderr.(fmt.Stringer).String()
   125  
   126  		filename := filepath.FromSlash("testdata/rangeloop/rangeloop.go")
   127  
   128  		
   129  		
   130  		
   131  		
   132  		
   133  		
   134  		
   135  		
   136  		
   137  		
   138  		stderr = strings.ReplaceAll(stderr, filepath.FromSlash("./rangeloop.go"), filename)
   139  
   140  		if err := errorCheck(stderr, false, filename, filepath.Base(filename)); err != nil {
   141  			t.Errorf("error check failed: %s", err)
   142  			t.Logf("vet stderr:\n<<%s>>", cmd.Stderr)
   143  		}
   144  	})
   145  
   146  	
   147  	
   148  	
   149  	t.Run("stdversion", func(t *testing.T) {
   150  		cmd := testenv.Command(t, testenv.GoToolPath(t), "vet", "-vettool="+vetPath(t), ".")
   151  		cmd.Env = append(os.Environ(), "GOWORK=off")
   152  		cmd.Dir = "testdata/stdversion"
   153  		cmd.Stderr = new(strings.Builder) 
   154  		cmd.Run()                         
   155  		stderr := cmd.Stderr.(fmt.Stringer).String()
   156  
   157  		filename := filepath.FromSlash("testdata/stdversion/stdversion.go")
   158  
   159  		
   160  		
   161  		
   162  		
   163  		
   164  		
   165  		
   166  		
   167  		
   168  		
   169  		stderr = strings.ReplaceAll(stderr, filepath.FromSlash("./stdversion.go"), filename)
   170  
   171  		if err := errorCheck(stderr, false, filename, filepath.Base(filename)); err != nil {
   172  			t.Errorf("error check failed: %s", err)
   173  			t.Logf("vet stderr:\n<<%s>>", cmd.Stderr)
   174  		}
   175  	})
   176  }
   177  
   178  func cgoEnabled(t *testing.T) bool {
   179  	
   180  	
   181  	
   182  	
   183  	
   184  	cmd := testenv.Command(t, testenv.GoToolPath(t), "list", "-f", "{{context.CgoEnabled}}")
   185  	out, _ := cmd.CombinedOutput()
   186  	return string(out) == "true\n"
   187  }
   188  
   189  func errchk(c *exec.Cmd, files []string, t *testing.T) {
   190  	output, err := c.CombinedOutput()
   191  	if _, ok := err.(*exec.ExitError); !ok {
   192  		t.Logf("vet output:\n<<%s>>", output)
   193  		t.Fatal(err)
   194  	}
   195  	fullshort := make([]string, 0, len(files)*2)
   196  	for _, f := range files {
   197  		fullshort = append(fullshort, f, filepath.Base(f))
   198  	}
   199  	err = errorCheck(string(output), false, fullshort...)
   200  	if err != nil {
   201  		t.Errorf("error check failed: %s", err)
   202  	}
   203  }
   204  
   205  
   206  func TestTags(t *testing.T) {
   207  	t.Parallel()
   208  	for tag, wantFile := range map[string]int{
   209  		"testtag":     1, 
   210  		"x testtag y": 1,
   211  		"othertag":    2,
   212  	} {
   213  		t.Run(tag, func(t *testing.T) {
   214  			t.Parallel()
   215  			t.Logf("-tags=%s", tag)
   216  			cmd := vetCmd(t, "-tags="+tag, "tagtest")
   217  			output, err := cmd.CombinedOutput()
   218  
   219  			want := fmt.Sprintf("file%d.go", wantFile)
   220  			dontwant := fmt.Sprintf("file%d.go", 3-wantFile)
   221  
   222  			
   223  			if !bytes.Contains(output, []byte(filepath.Join("tagtest", want))) {
   224  				t.Errorf("%s: %s was excluded, should be included", tag, want)
   225  			}
   226  			if bytes.Contains(output, []byte(filepath.Join("tagtest", dontwant))) {
   227  				t.Errorf("%s: %s was included, should be excluded", tag, dontwant)
   228  			}
   229  			if t.Failed() {
   230  				t.Logf("err=%s, output=<<%s>>", err, output)
   231  			}
   232  		})
   233  	}
   234  }
   235  
   236  
   237  
   238  
   239  
   240  
   241  
   242  
   243  
   244  
   245  
   246  
   247  
   248  
   249  func errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) {
   250  	var errs []error
   251  	out := splitOutput(outStr, wantAuto)
   252  	
   253  	for i := range out {
   254  		for j := 0; j < len(fullshort); j += 2 {
   255  			full, short := fullshort[j], fullshort[j+1]
   256  			out[i] = strings.ReplaceAll(out[i], full, short)
   257  		}
   258  	}
   259  
   260  	var want []wantedError
   261  	for j := 0; j < len(fullshort); j += 2 {
   262  		full, short := fullshort[j], fullshort[j+1]
   263  		want = append(want, wantedErrors(full, short)...)
   264  	}
   265  	for _, we := range want {
   266  		var errmsgs []string
   267  		if we.auto {
   268  			errmsgs, out = partitionStrings("<autogenerated>", out)
   269  		} else {
   270  			errmsgs, out = partitionStrings(we.prefix, out)
   271  		}
   272  		if len(errmsgs) == 0 {
   273  			errs = append(errs, fmt.Errorf("%s:%d: missing error %q (prefix: %s)", we.file, we.lineNum, we.reStr, we.prefix))
   274  			continue
   275  		}
   276  		matched := false
   277  		n := len(out)
   278  		for _, errmsg := range errmsgs {
   279  			
   280  			
   281  			text := errmsg
   282  			if _, suffix, ok := strings.Cut(text, " "); ok {
   283  				text = suffix
   284  			}
   285  			if we.re.MatchString(text) {
   286  				matched = true
   287  			} else {
   288  				out = append(out, errmsg)
   289  			}
   290  		}
   291  		if !matched {
   292  			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")))
   293  			continue
   294  		}
   295  	}
   296  
   297  	if len(out) > 0 {
   298  		errs = append(errs, fmt.Errorf("Unmatched Errors:"))
   299  		for _, errLine := range out {
   300  			errs = append(errs, fmt.Errorf("%s", errLine))
   301  		}
   302  	}
   303  
   304  	if len(errs) == 0 {
   305  		return nil
   306  	}
   307  	if len(errs) == 1 {
   308  		return errs[0]
   309  	}
   310  	var buf strings.Builder
   311  	fmt.Fprintf(&buf, "\n")
   312  	for _, err := range errs {
   313  		fmt.Fprintf(&buf, "%s\n", err.Error())
   314  	}
   315  	return errors.New(buf.String())
   316  }
   317  
   318  func splitOutput(out string, wantAuto bool) []string {
   319  	
   320  	
   321  	
   322  	var res []string
   323  	for _, line := range strings.Split(out, "\n") {
   324  		line = strings.TrimSuffix(line, "\r") 
   325  		if strings.HasPrefix(line, "\t") {
   326  			res[len(res)-1] += "\n" + line
   327  		} else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "#") || !wantAuto && strings.HasPrefix(line, "<autogenerated>") {
   328  			continue
   329  		} else if strings.TrimSpace(line) != "" {
   330  			res = append(res, line)
   331  		}
   332  	}
   333  	return res
   334  }
   335  
   336  
   337  
   338  func matchPrefix(s, prefix string) bool {
   339  	i := strings.Index(s, ":")
   340  	if i < 0 {
   341  		return false
   342  	}
   343  	j := strings.LastIndex(s[:i], "/")
   344  	s = s[j+1:]
   345  	if len(s) <= len(prefix) || s[:len(prefix)] != prefix {
   346  		return false
   347  	}
   348  	if s[len(prefix)] == ':' {
   349  		return true
   350  	}
   351  	return false
   352  }
   353  
   354  func partitionStrings(prefix string, strs []string) (matched, unmatched []string) {
   355  	for _, s := range strs {
   356  		if matchPrefix(s, prefix) {
   357  			matched = append(matched, s)
   358  		} else {
   359  			unmatched = append(unmatched, s)
   360  		}
   361  	}
   362  	return
   363  }
   364  
   365  type wantedError struct {
   366  	reStr   string
   367  	re      *regexp.Regexp
   368  	lineNum int
   369  	auto    bool 
   370  	file    string
   371  	prefix  string
   372  }
   373  
   374  var (
   375  	errRx       = regexp.MustCompile(`// (?:GC_)?ERROR(NEXT)? (.*)`)
   376  	errAutoRx   = regexp.MustCompile(`// (?:GC_)?ERRORAUTO(NEXT)? (.*)`)
   377  	errQuotesRx = regexp.MustCompile(`"([^"]*)"`)
   378  	lineRx      = regexp.MustCompile(`LINE(([+-])(\d+))?`)
   379  )
   380  
   381  
   382  func wantedErrors(file, short string) (errs []wantedError) {
   383  	cache := make(map[string]*regexp.Regexp)
   384  
   385  	src, err := os.ReadFile(file)
   386  	if err != nil {
   387  		log.Fatal(err)
   388  	}
   389  	for i, line := range strings.Split(string(src), "\n") {
   390  		lineNum := i + 1
   391  		if strings.Contains(line, "////") {
   392  			
   393  			continue
   394  		}
   395  		var auto bool
   396  		m := errAutoRx.FindStringSubmatch(line)
   397  		if m != nil {
   398  			auto = true
   399  		} else {
   400  			m = errRx.FindStringSubmatch(line)
   401  		}
   402  		if m == nil {
   403  			continue
   404  		}
   405  		if m[1] == "NEXT" {
   406  			lineNum++
   407  		}
   408  		all := m[2]
   409  		mm := errQuotesRx.FindAllStringSubmatch(all, -1)
   410  		if mm == nil {
   411  			log.Fatalf("%s:%d: invalid errchk line: %s", file, lineNum, line)
   412  		}
   413  		for _, m := range mm {
   414  			replacedOnce := false
   415  			rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string {
   416  				if replacedOnce {
   417  					return m
   418  				}
   419  				replacedOnce = true
   420  				n := lineNum
   421  				if strings.HasPrefix(m, "LINE+") {
   422  					delta, _ := strconv.Atoi(m[5:])
   423  					n += delta
   424  				} else if strings.HasPrefix(m, "LINE-") {
   425  					delta, _ := strconv.Atoi(m[5:])
   426  					n -= delta
   427  				}
   428  				return fmt.Sprintf("%s:%d", short, n)
   429  			})
   430  			re := cache[rx]
   431  			if re == nil {
   432  				var err error
   433  				re, err = regexp.Compile(rx)
   434  				if err != nil {
   435  					log.Fatalf("%s:%d: invalid regexp \"%#q\" in ERROR line: %v", file, lineNum, rx, err)
   436  				}
   437  				cache[rx] = re
   438  			}
   439  			prefix := fmt.Sprintf("%s:%d", short, lineNum)
   440  			errs = append(errs, wantedError{
   441  				reStr:   rx,
   442  				re:      re,
   443  				prefix:  prefix,
   444  				auto:    auto,
   445  				lineNum: lineNum,
   446  				file:    short,
   447  			})
   448  		}
   449  	}
   450  
   451  	return
   452  }
   453  
View as plain text