Home | History | Annotate | Download | only in vet
      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 	"fmt"
     10 	"internal/testenv"
     11 	"os"
     12 	"os/exec"
     13 	"path/filepath"
     14 	"runtime"
     15 	"strings"
     16 	"sync"
     17 	"testing"
     18 )
     19 
     20 const (
     21 	dataDir = "testdata"
     22 	binary  = "testvet.exe"
     23 )
     24 
     25 // We implement TestMain so remove the test binary when all is done.
     26 func TestMain(m *testing.M) {
     27 	result := m.Run()
     28 	os.Remove(binary)
     29 	os.Exit(result)
     30 }
     31 
     32 func MustHavePerl(t *testing.T) {
     33 	switch runtime.GOOS {
     34 	case "plan9", "windows":
     35 		t.Skipf("skipping test: perl not available on %s", runtime.GOOS)
     36 	}
     37 	if _, err := exec.LookPath("perl"); err != nil {
     38 		t.Skipf("skipping test: perl not found in path")
     39 	}
     40 }
     41 
     42 var (
     43 	buildMu sync.Mutex // guards following
     44 	built   = false    // We have built the binary.
     45 	failed  = false    // We have failed to build the binary, don't try again.
     46 )
     47 
     48 func Build(t *testing.T) {
     49 	buildMu.Lock()
     50 	defer buildMu.Unlock()
     51 	if built {
     52 		return
     53 	}
     54 	if failed {
     55 		t.Skip("cannot run on this environment")
     56 	}
     57 	testenv.MustHaveGoBuild(t)
     58 	MustHavePerl(t)
     59 	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", binary)
     60 	output, err := cmd.CombinedOutput()
     61 	if err != nil {
     62 		failed = true
     63 		fmt.Fprintf(os.Stderr, "%s\n", output)
     64 		t.Fatal(err)
     65 	}
     66 	built = true
     67 }
     68 
     69 func Vet(t *testing.T, files []string) {
     70 	errchk := filepath.Join(runtime.GOROOT(), "test", "errchk")
     71 	flags := []string{
     72 		"./" + binary,
     73 		"-printfuncs=Warn:1,Warnf:1",
     74 		"-all",
     75 		"-shadow",
     76 	}
     77 	cmd := exec.Command(errchk, append(flags, files...)...)
     78 	if !run(cmd, t) {
     79 		t.Fatal("vet command failed")
     80 	}
     81 }
     82 
     83 // Run this shell script, but do it in Go so it can be run by "go test".
     84 // 	go build -o testvet
     85 // 	$(GOROOT)/test/errchk ./testvet -shadow -printfuncs='Warn:1,Warnf:1' testdata/*.go testdata/*.s
     86 // 	rm testvet
     87 //
     88 
     89 // TestVet tests self-contained files in testdata/*.go.
     90 //
     91 // If a file contains assembly or has inter-dependencies, it should be
     92 // in its own test, like TestVetAsm, TestDivergentPackagesExamples,
     93 // etc below.
     94 func TestVet(t *testing.T) {
     95 	Build(t)
     96 	t.Parallel()
     97 
     98 	// errchk ./testvet
     99 	gos, err := filepath.Glob(filepath.Join(dataDir, "*.go"))
    100 	if err != nil {
    101 		t.Fatal(err)
    102 	}
    103 	wide := runtime.GOMAXPROCS(0)
    104 	if wide > len(gos) {
    105 		wide = len(gos)
    106 	}
    107 	batch := make([][]string, wide)
    108 	for i, file := range gos {
    109 		// TODO: Remove print.go exception once we require type checking for everything,
    110 		// and then delete TestVetPrint.
    111 		if strings.HasSuffix(file, "print.go") {
    112 			continue
    113 		}
    114 		batch[i%wide] = append(batch[i%wide], file)
    115 	}
    116 	for i, files := range batch {
    117 		if len(files) == 0 {
    118 			continue
    119 		}
    120 		files := files
    121 		t.Run(fmt.Sprint(i), func(t *testing.T) {
    122 			t.Parallel()
    123 			t.Logf("files: %q", files)
    124 			Vet(t, files)
    125 		})
    126 	}
    127 }
    128 
    129 func TestVetPrint(t *testing.T) {
    130 	Build(t)
    131 	errchk := filepath.Join(runtime.GOROOT(), "test", "errchk")
    132 	cmd := exec.Command(
    133 		errchk,
    134 		"go", "vet", "-vettool=./"+binary,
    135 		"-printf",
    136 		"-printfuncs=Warn:1,Warnf:1",
    137 		"testdata/print.go",
    138 	)
    139 	if !run(cmd, t) {
    140 		t.Fatal("vet command failed")
    141 	}
    142 }
    143 
    144 func TestVetAsm(t *testing.T) {
    145 	Build(t)
    146 
    147 	asmDir := filepath.Join(dataDir, "asm")
    148 	gos, err := filepath.Glob(filepath.Join(asmDir, "*.go"))
    149 	if err != nil {
    150 		t.Fatal(err)
    151 	}
    152 	asms, err := filepath.Glob(filepath.Join(asmDir, "*.s"))
    153 	if err != nil {
    154 		t.Fatal(err)
    155 	}
    156 
    157 	t.Parallel()
    158 	// errchk ./testvet
    159 	Vet(t, append(gos, asms...))
    160 }
    161 
    162 func TestVetDirs(t *testing.T) {
    163 	t.Parallel()
    164 	Build(t)
    165 	for _, dir := range []string{
    166 		"testingpkg",
    167 		"divergent",
    168 		"buildtag",
    169 		"incomplete", // incomplete examples
    170 		"cgo",
    171 	} {
    172 		dir := dir
    173 		t.Run(dir, func(t *testing.T) {
    174 			t.Parallel()
    175 			gos, err := filepath.Glob(filepath.Join("testdata", dir, "*.go"))
    176 			if err != nil {
    177 				t.Fatal(err)
    178 			}
    179 			Vet(t, gos)
    180 		})
    181 	}
    182 }
    183 
    184 func run(c *exec.Cmd, t *testing.T) bool {
    185 	output, err := c.CombinedOutput()
    186 	if err != nil {
    187 		t.Logf("vet output:\n%s", output)
    188 		t.Fatal(err)
    189 	}
    190 	// Errchk delights by not returning non-zero status if it finds errors, so we look at the output.
    191 	// It prints "BUG" if there is a failure.
    192 	if !c.ProcessState.Success() {
    193 		t.Logf("vet output:\n%s", output)
    194 		return false
    195 	}
    196 	ok := !bytes.Contains(output, []byte("BUG"))
    197 	if !ok {
    198 		t.Logf("vet output:\n%s", output)
    199 	}
    200 	return ok
    201 }
    202 
    203 // TestTags verifies that the -tags argument controls which files to check.
    204 func TestTags(t *testing.T) {
    205 	t.Parallel()
    206 	Build(t)
    207 	for _, tag := range []string{"testtag", "x testtag y", "x,testtag,y"} {
    208 		tag := tag
    209 		t.Run(tag, func(t *testing.T) {
    210 			t.Parallel()
    211 			t.Logf("-tags=%s", tag)
    212 			args := []string{
    213 				"-tags=" + tag,
    214 				"-v", // We're going to look at the files it examines.
    215 				"testdata/tagtest",
    216 			}
    217 			cmd := exec.Command("./"+binary, args...)
    218 			output, err := cmd.CombinedOutput()
    219 			if err != nil {
    220 				t.Fatal(err)
    221 			}
    222 			// file1 has testtag and file2 has !testtag.
    223 			if !bytes.Contains(output, []byte(filepath.Join("tagtest", "file1.go"))) {
    224 				t.Error("file1 was excluded, should be included")
    225 			}
    226 			if bytes.Contains(output, []byte(filepath.Join("tagtest", "file2.go"))) {
    227 				t.Error("file2 was included, should be excluded")
    228 			}
    229 		})
    230 	}
    231 }
    232 
    233 // Issue #21188.
    234 func TestVetVerbose(t *testing.T) {
    235 	t.Parallel()
    236 	Build(t)
    237 	cmd := exec.Command("./"+binary, "-v", "-all", "testdata/cgo/cgo3.go")
    238 	out, err := cmd.CombinedOutput()
    239 	if err != nil {
    240 		t.Logf("%s", out)
    241 		t.Error(err)
    242 	}
    243 }
    244