Skip to content

Commit 0d18875

Browse files
committed
cmd/go: run vet automatically during go test
This CL adds an automatic, limited "go vet" to "go test". If the building of a test package fails, vet is not run. If vet fails, the test is not run. The goal is that users don't notice vet as part of the "go test" process at all, until vet speaks up and says something important. This should help users find real problems in their code faster (vet can just point to them instead of needing to debug a test failure) and expands the scope of what kinds of things vet can help with. The "go vet" runs in parallel with the linking of the test binary, so for incremental builds it typically does not slow the overall "go test" at all: there's spare machine capacity during the link. all.bash has less spare machine capacity. This CL increases the time for all.bash on my laptop from 4m41s to 4m48s (+2.5%) To opt out for a given run, use "go test -vet=off". The vet checks used during "go test" are a subset of the full set, restricted to ones that are 100% correct and therefore acceptable to make mandatory. In this CL, that set is atomic, bool, buildtags, nilfunc, and printf. Including printf is debatable, but I want to include it for now and find out what needs to be scaled back. (It already found one real problem in package os's tests that previous go vet os had not turned up.) Now that we can rely on type information it may be that printf should make its function-name-based heuristic less aggressive and have a whitelist of known print/printf functions. Determining the exact set for Go 1.10 is #18085. Running vet also means that programs now have to type-check with both cmd/compile and go/types in order to pass "go test". We don't start vet until cmd/compile has built the test package, so normally the added go/types check doesn't find anything. However, there is at least one instance where go/types is more precise than cmd/compile: declared and not used errors involving variables captured into closures. This CL includes a printf fix to os/os_test.go and many declared and not used fixes in the race detector tests. Fixes #18084. Change-Id: I353e00b9d1f9fec540c7557db5653e7501f5e1c9 Reviewed-on: https://go-review.googlesource.com/74356 Run-TryBot: Russ Cox <[email protected]> Reviewed-by: Rob Pike <[email protected]> Reviewed-by: David Crawshaw <[email protected]>
1 parent bd95f88 commit 0d18875

20 files changed

+270
-20
lines changed

misc/cgo/testshared/shared_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ func testABIHashNote(t *testing.T, f *elf.File, note *note) {
504504
t.Errorf("%s has incorrect binding %v, want STB_LOCAL", hashbytes.Name, elf.ST_BIND(hashbytes.Info))
505505
}
506506
if f.Sections[hashbytes.Section] != note.section {
507-
t.Errorf("%s has incorrect section %v, want %s", hashbytes.Name, f.Sections[hashbytes.Section].Name, note.section)
507+
t.Errorf("%s has incorrect section %v, want %s", hashbytes.Name, f.Sections[hashbytes.Section].Name, note.section.Name)
508508
}
509509
if hashbytes.Value-note.section.Addr != 16 {
510510
t.Errorf("%s has incorrect offset into section %d, want 16", hashbytes.Name, hashbytes.Value-note.section.Addr)

src/cmd/go/alldocs.go

+11
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,13 @@
735735
// The go tool will ignore a directory named "testdata", making it available
736736
// to hold ancillary data needed by the tests.
737737
//
738+
// As part of building a test binary, go test runs go vet on the package
739+
// and its test source files to identify significant problems. If go vet
740+
// finds any problems, go test reports those and does not run the test binary.
741+
// Only a high-confidence subset of the default go vet checks are used.
742+
// To disable the running of go vet, use the -vet=off flag.
743+
//
744+
//
738745
// Go test runs in two different modes: local directory mode when invoked with
739746
// no package arguments (for example, 'go test'), and package list mode when
740747
// invoked with package arguments (for example 'go test math', 'go test ./...',
@@ -1547,6 +1554,10 @@
15471554
// Verbose output: log all tests as they are run. Also print all
15481555
// text from Log and Logf calls even if the test succeeds.
15491556
//
1557+
// -vet mode
1558+
// Configure the invocation of "go vet" during "go test".
1559+
// The default is to run "go vet". If mode is "off", vet is disabled.
1560+
//
15501561
// The following flags are also recognized by 'go test' and can be used to
15511562
// profile the tests during execution:
15521563
//

src/cmd/go/go_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -4748,6 +4748,7 @@ func TestTestCache(t *testing.T) {
47484748
}
47494749
tg := testgo(t)
47504750
defer tg.cleanup()
4751+
tg.parallel()
47514752
tg.makeTempdir()
47524753
tg.setenv("GOPATH", tg.tempdir)
47534754
tg.setenv("GOCACHE", filepath.Join(tg.tempdir, "cache"))
@@ -4834,3 +4835,37 @@ func TestTestCache(t *testing.T) {
48344835
tg.grepStderr(`([\\/]link|gccgo).*t4\.test`, "did not relink t4_test")
48354836
tg.grepStdout(`ok \tt/t4\t\(cached\)`, "did not cache t/t4")
48364837
}
4838+
4839+
func TestTestVet(t *testing.T) {
4840+
tg := testgo(t)
4841+
defer tg.cleanup()
4842+
tg.parallel()
4843+
4844+
tg.tempFile("p1_test.go", `
4845+
package p
4846+
import "testing"
4847+
func Test(t *testing.T) {
4848+
t.Logf("%d") // oops
4849+
}
4850+
`)
4851+
4852+
tg.runFail("test", filepath.Join(tg.tempdir, "p1_test.go"))
4853+
tg.grepStderr(`Logf format %d`, "did not diagnose bad Logf")
4854+
tg.run("test", "-vet=off", filepath.Join(tg.tempdir, "p1_test.go"))
4855+
tg.grepStdout(`^ok`, "did not print test summary")
4856+
4857+
tg.tempFile("p1.go", `
4858+
package p
4859+
import "fmt"
4860+
func F() {
4861+
fmt.Printf("%d") // oops
4862+
}
4863+
`)
4864+
tg.runFail("test", filepath.Join(tg.tempdir, "p1.go"))
4865+
tg.grepStderr(`Printf format %d`, "did not diagnose bad Printf")
4866+
tg.run("test", "-x", "-vet=shift", filepath.Join(tg.tempdir, "p1.go"))
4867+
tg.grepStderr(`[\\/]vet.*-shift`, "did not run vet with -shift")
4868+
tg.grepStdout(`\[no test files\]`, "did not print test summary")
4869+
tg.run("test", "-vet=off", filepath.Join(tg.tempdir, "p1.go"))
4870+
tg.grepStdout(`\[no test files\]`, "did not print test summary")
4871+
}

src/cmd/go/internal/test/test.go

+88-6
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ separate package, and then linked and run with the main test binary.
6969
The go tool will ignore a directory named "testdata", making it available
7070
to hold ancillary data needed by the tests.
7171
72+
As part of building a test binary, go test runs go vet on the package
73+
and its test source files to identify significant problems. If go vet
74+
finds any problems, go test reports those and does not run the test binary.
75+
Only a high-confidence subset of the default go vet checks are used.
76+
To disable the running of go vet, use the -vet=off flag.
77+
7278
Go test runs in two different modes: local directory mode when invoked with
7379
no package arguments (for example, 'go test'), and package list mode when
7480
invoked with package arguments (for example 'go test math', 'go test ./...',
@@ -258,6 +264,13 @@ const testFlag2 = `
258264
Verbose output: log all tests as they are run. Also print all
259265
text from Log and Logf calls even if the test succeeds.
260266
267+
-vet list
268+
Configure the invocation of "go vet" during "go test"
269+
to use the comma-separated list of vet checks.
270+
If list is empty, "go test" runs "go vet" with a curated list of
271+
checks believed to be always worth addressing.
272+
If list is "off", "go test" does not run "go vet" at all.
273+
261274
The following flags are also recognized by 'go test' and can be used to
262275
profile the tests during execution:
263276
@@ -446,7 +459,8 @@ var (
446459
testArgs []string
447460
testBench bool
448461
testList bool
449-
testShowPass bool // show passing output
462+
testShowPass bool // show passing output
463+
testVetList string // -vet flag
450464
pkgArgs []string
451465
pkgs []*load.Package
452466

@@ -460,13 +474,41 @@ var testMainDeps = []string{
460474
"testing/internal/testdeps",
461475
}
462476

477+
// testVetFlags is the list of flags to pass to vet when invoked automatically during go test.
478+
var testVetFlags = []string{
479+
// TODO(rsc): Decide which tests are enabled by default.
480+
// See golang.org/issue/18085.
481+
// "-asmdecl",
482+
// "-assign",
483+
"-atomic",
484+
"-bool",
485+
"-buildtags",
486+
// "-cgocall",
487+
// "-composites",
488+
// "-copylocks",
489+
// "-httpresponse",
490+
// "-lostcancel",
491+
// "-methods",
492+
"-nilfunc",
493+
"-printf",
494+
// "-rangeloops",
495+
// "-shift",
496+
// "-structtags",
497+
// "-tests",
498+
// "-unreachable",
499+
// "-unsafeptr",
500+
// "-unusedresult",
501+
}
502+
463503
func runTest(cmd *base.Command, args []string) {
464504
pkgArgs, testArgs = testFlags(args)
465505

466506
work.FindExecCmd() // initialize cached result
467507

468508
work.InstrumentInit()
469509
work.BuildModeInit()
510+
work.VetFlags = testVetFlags
511+
470512
pkgs = load.PackagesForBuild(pkgArgs)
471513
if len(pkgs) == 0 {
472514
base.Fatalf("no packages to test")
@@ -668,6 +710,7 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin
668710
if len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 {
669711
build := b.CompileAction(work.ModeBuild, work.ModeBuild, p)
670712
run := &work.Action{Mode: "test run", Package: p, Deps: []*work.Action{build}}
713+
addTestVet(b, p, run, nil)
671714
print := &work.Action{Mode: "test print", Func: builderNoTest, Package: p, Deps: []*work.Action{run}}
672715
return build, run, print, nil
673716
}
@@ -681,6 +724,7 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin
681724
var imports, ximports []*load.Package
682725
var stk load.ImportStack
683726
stk.Push(p.ImportPath + " (test)")
727+
rawTestImports := str.StringList(p.TestImports)
684728
for i, path := range p.TestImports {
685729
p1 := load.LoadImport(path, p.Dir, p, &stk, p.Internal.Build.TestImportPos[path], load.UseVendor)
686730
if p1.Error != nil {
@@ -708,6 +752,7 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin
708752
stk.Pop()
709753
stk.Push(p.ImportPath + "_test")
710754
pxtestNeedsPtest := false
755+
rawXTestImports := str.StringList(p.XTestImports)
711756
for i, path := range p.XTestImports {
712757
p1 := load.LoadImport(path, p.Dir, p, &stk, p.Internal.Build.XTestImportPos[path], load.UseVendor)
713758
if p1.Error != nil {
@@ -753,8 +798,20 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin
753798
ptest.GoFiles = append(ptest.GoFiles, p.GoFiles...)
754799
ptest.GoFiles = append(ptest.GoFiles, p.TestGoFiles...)
755800
ptest.Target = ""
756-
ptest.Imports = str.StringList(p.Imports, p.TestImports)
757-
ptest.Internal.Imports = append(append([]*load.Package{}, p.Internal.Imports...), imports...)
801+
// Note: The preparation of the vet config requires that common
802+
// indexes in ptest.Imports, ptest.Internal.Imports, and ptest.Internal.RawImports
803+
// all line up (but RawImports can be shorter than the others).
804+
// That is, for 0 ≤ i < len(RawImports),
805+
// RawImports[i] is the import string in the program text,
806+
// Imports[i] is the expanded import string (vendoring applied or relative path expanded away),
807+
// and Internal.Imports[i] is the corresponding *Package.
808+
// Any implicitly added imports appear in Imports and Internal.Imports
809+
// but not RawImports (because they were not in the source code).
810+
// We insert TestImports, imports, and rawTestImports at the start of
811+
// these lists to preserve the alignment.
812+
ptest.Imports = str.StringList(p.TestImports, p.Imports)
813+
ptest.Internal.Imports = append(imports, p.Internal.Imports...)
814+
ptest.Internal.RawImports = str.StringList(rawTestImports, p.Internal.RawImports)
758815
ptest.Internal.ForceLibrary = true
759816
ptest.Internal.Build = new(build.Package)
760817
*ptest.Internal.Build = *p.Internal.Build
@@ -794,7 +851,8 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin
794851
Build: &build.Package{
795852
ImportPos: p.Internal.Build.XTestImportPos,
796853
},
797-
Imports: ximports,
854+
Imports: ximports,
855+
RawImports: rawXTestImports,
798856
},
799857
}
800858
if pxtestNeedsPtest {
@@ -942,6 +1000,7 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin
9421000
}
9431001
}
9441002
buildAction = a
1003+
var installAction *work.Action
9451004

9461005
if testC || testNeedBinary {
9471006
// -c or profiling flag: create action to copy binary to ./test.out.
@@ -953,14 +1012,15 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin
9531012
}
9541013
}
9551014
pmain.Target = target
956-
buildAction = &work.Action{
1015+
installAction = &work.Action{
9571016
Mode: "test build",
9581017
Func: work.BuildInstallFunc,
9591018
Deps: []*work.Action{buildAction},
9601019
Package: pmain,
9611020
Target: target,
9621021
}
963-
runAction = buildAction // make sure runAction != nil even if not running test
1022+
buildAction = installAction
1023+
runAction = installAction // make sure runAction != nil even if not running test
9641024
}
9651025
if testC {
9661026
printAction = &work.Action{Mode: "test print (nop)", Package: p, Deps: []*work.Action{runAction}} // nop
@@ -975,6 +1035,12 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin
9751035
IgnoreFail: true,
9761036
TryCache: c.tryCache,
9771037
}
1038+
if len(ptest.GoFiles)+len(ptest.CgoFiles) > 0 {
1039+
addTestVet(b, ptest, runAction, installAction)
1040+
}
1041+
if pxtest != nil {
1042+
addTestVet(b, pxtest, runAction, installAction)
1043+
}
9781044
cleanAction := &work.Action{
9791045
Mode: "test clean",
9801046
Func: builderCleanTest,
@@ -993,6 +1059,22 @@ func builderTest(b *work.Builder, p *load.Package) (buildAction, runAction, prin
9931059
return buildAction, runAction, printAction, nil
9941060
}
9951061

1062+
func addTestVet(b *work.Builder, p *load.Package, runAction, installAction *work.Action) {
1063+
if testVetList == "off" {
1064+
return
1065+
}
1066+
1067+
vet := b.VetAction(work.ModeBuild, work.ModeBuild, p)
1068+
runAction.Deps = append(runAction.Deps, vet)
1069+
// Install will clean the build directory.
1070+
// Make sure vet runs first.
1071+
// The install ordering in b.VetAction does not apply here
1072+
// because we are using a custom installAction (created above).
1073+
if installAction != nil {
1074+
installAction.Deps = append(installAction.Deps, vet)
1075+
}
1076+
}
1077+
9961078
func testImportStack(top string, p *load.Package, target string) []string {
9971079
stk := []string{top, p.ImportPath}
9981080
Search:

src/cmd/go/internal/test/testflag.go

+17
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ var testFlagDefn = []*cmdflag.Defn{
3333
{Name: "covermode"},
3434
{Name: "coverpkg"},
3535
{Name: "exec"},
36+
{Name: "vet"},
3637

3738
// Passed to 6.out, adding a "test." prefix to the name if necessary: -v becomes -test.v.
3839
{Name: "bench", PassToTest: true},
@@ -175,6 +176,8 @@ func testFlags(args []string) (packageNames, passToTest []string) {
175176
testCover = true
176177
case "outputdir":
177178
outputDir = value
179+
case "vet":
180+
testVetList = value
178181
}
179182
}
180183
if extraWord {
@@ -193,6 +196,20 @@ func testFlags(args []string) (packageNames, passToTest []string) {
193196
}
194197
}
195198

199+
if testVetList != "" && testVetList != "off" {
200+
if strings.Contains(testVetList, "=") {
201+
base.Fatalf("-vet argument cannot contain equal signs")
202+
}
203+
if strings.Contains(testVetList, " ") {
204+
base.Fatalf("-vet argument is comma-separated list, cannot contain spaces")
205+
}
206+
list := strings.Split(testVetList, ",")
207+
for i, arg := range list {
208+
list[i] = "-" + arg
209+
}
210+
testVetFlags = list
211+
}
212+
196213
if cfg.BuildRace && testCoverMode != "atomic" {
197214
base.Fatalf(`-covermode must be "atomic", not %q, when -race is enabled`, testCoverMode)
198215
}

src/cmd/go/testdata/src/testrace/race_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ func TestRace(t *testing.T) {
1212
}()
1313
x = 3
1414
<-c
15+
_ = x
1516
}
1617
}
1718

@@ -25,5 +26,6 @@ func BenchmarkRace(b *testing.B) {
2526
}()
2627
x = 3
2728
<-c
29+
_ = x
2830
}
2931
}

src/cmd/vet/main.go

+13-6
Original file line numberDiff line numberDiff line change
@@ -425,12 +425,19 @@ func doPackage(names []string, basePkg *Package) *Package {
425425
pkg.path = astFiles[0].Name.Name
426426
pkg.files = files
427427
// Type check the package.
428-
err := pkg.check(fs, astFiles)
429-
if err != nil {
430-
// Note that we only report this error when *verbose.
431-
Println(err)
432-
if mustTypecheck {
433-
errorf("%v", err)
428+
errs := pkg.check(fs, astFiles)
429+
if errs != nil {
430+
if *verbose || mustTypecheck {
431+
for _, err := range errs {
432+
fmt.Fprintf(os.Stderr, "%v\n", err)
433+
}
434+
if mustTypecheck {
435+
// This message could be silenced, and we could just exit,
436+
// but it might be helpful at least at first to make clear that the
437+
// above errors are coming from vet and not the compiler
438+
// (they often look like compiler errors, such as "declared but not used").
439+
errorf("typecheck failures")
440+
}
434441
}
435442
}
436443

0 commit comments

Comments
 (0)