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