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