summaryrefslogtreecommitdiff
path: root/src/cmd/go/test.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/go/test.go')
-rw-r--r--src/cmd/go/test.go178
1 files changed, 140 insertions, 38 deletions
diff --git a/src/cmd/go/test.go b/src/cmd/go/test.go
index 5935c98db..c81e40639 100644
--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -6,6 +6,7 @@ package main
import (
"bytes"
+ "errors"
"fmt"
"go/ast"
"go/build"
@@ -48,7 +49,7 @@ It prints a summary of the test results in the format:
followed by detailed output for each failed package.
'Go test' recompiles each package along with any files with names matching
-the file pattern "*_test.go".
+the file pattern "*_test.go".
Files whose names begin with "_" (including "_test.go") or "." are ignored.
These additional files can contain test functions, benchmark functions, and
example functions. See 'go help testfunc' for more.
@@ -65,16 +66,23 @@ non-test installation.
In addition to the build flags, the flags handled by 'go test' itself are:
- -c Compile the test binary to pkg.test but do not run it.
- (Where pkg is the last element of the package's import path.)
+ -c
+ Compile the test binary to pkg.test but do not run it
+ (where pkg is the last element of the package's import path).
+ The file name can be changed with the -o flag.
+
+ -exec xprog
+ Run the test binary using xprog. The behavior is the same as
+ in 'go run'. See 'go help run' for details.
-i
Install packages that are dependencies of the test.
Do not run the test.
- -exec xprog
- Run the test binary using xprog. The behavior is the same as
- in 'go run'. See 'go help run' for details.
+ -o file
+ Compile the test binary to the named file.
+ The test still runs (unless -c or -i is specified).
+
The test binary also accepts flags that control execution of the test; these
flags are also accessible by 'go test'. See 'go help testflag' for details.
@@ -122,6 +130,7 @@ control the execution of any test:
-blockprofile block.out
Write a goroutine blocking profile to the specified file
when all tests are complete.
+ Writes test binary as -c would.
-blockprofilerate n
Control the detail provided in goroutine blocking profiles by
@@ -153,8 +162,7 @@ control the execution of any test:
Sets -cover.
-coverprofile cover.out
- Write a coverage profile to the specified file after all tests
- have passed.
+ Write a coverage profile to the file after all tests have passed.
Sets -cover.
-cpu 1,2,4
@@ -164,10 +172,11 @@ control the execution of any test:
-cpuprofile cpu.out
Write a CPU profile to the specified file before exiting.
+ Writes test binary as -c would.
-memprofile mem.out
- Write a memory profile to the specified file after all tests
- have passed.
+ Write a memory profile to the file after all tests have passed.
+ Writes test binary as -c would.
-memprofilerate n
Enable more precise (and expensive) memory profiles by setting
@@ -274,10 +283,10 @@ var (
testCoverMode string // -covermode flag
testCoverPaths []string // -coverpkg flag
testCoverPkgs []*Package // -coverpkg flag
+ testO string // -o flag
testProfile bool // some profiling flag
testNeedBinary bool // profile needs to keep binary around
testV bool // -v flag
- testFiles []string // -file flag(s) TODO: not respected
testTimeout string // -timeout flag
testArgs []string
testBench bool
@@ -291,6 +300,7 @@ var testMainDeps = map[string]bool{
// Dependencies for testmain.
"testing": true,
"regexp": true,
+ "os": true,
}
func runTest(cmd *Command, args []string) {
@@ -308,6 +318,9 @@ func runTest(cmd *Command, args []string) {
if testC && len(pkgs) != 1 {
fatalf("cannot use -c flag with multiple packages")
}
+ if testO != "" && len(pkgs) != 1 {
+ fatalf("cannot use -o flag with multiple packages")
+ }
if testProfile && len(pkgs) != 1 {
fatalf("cannot use test profile flag with multiple packages")
}
@@ -522,6 +535,13 @@ func contains(x []string, s string) bool {
return false
}
+var windowsBadWords = []string{
+ "install",
+ "patch",
+ "setup",
+ "update",
+}
+
func (b *builder) test(p *Package) (buildAction, runAction, printAction *action, err error) {
if len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 {
build := b.action(modeBuild, modeBuild, p)
@@ -687,7 +707,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
omitDWARF: !testC && !testNeedBinary,
}
- // The generated main also imports testing and regexp.
+ // The generated main also imports testing, regexp, and os.
stk.push("testmain")
for dep := range testMainDeps {
if dep == ptest.ImportPath {
@@ -723,11 +743,13 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
if err != nil {
return nil, nil, nil, err
}
- if t.NeedTest || ptest.coverMode != "" {
+ if len(ptest.GoFiles) > 0 {
pmain.imports = append(pmain.imports, ptest)
+ t.ImportTest = true
}
- if t.NeedXtest {
+ if pxtest != nil {
pmain.imports = append(pmain.imports, pxtest)
+ t.ImportXtest = true
}
if ptest != p && localCover {
@@ -779,17 +801,54 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
a.objdir = testDir + string(filepath.Separator)
a.objpkg = filepath.Join(testDir, "main.a")
a.target = filepath.Join(testDir, testBinary) + exeSuffix
- pmainAction := a
+ if goos == "windows" {
+ // There are many reserved words on Windows that,
+ // if used in the name of an executable, cause Windows
+ // to try to ask for extra permissions.
+ // The word list includes setup, install, update, and patch,
+ // but it does not appear to be defined anywhere.
+ // We have run into this trying to run the
+ // go.codereview/patch tests.
+ // For package names containing those words, use test.test.exe
+ // instead of pkgname.test.exe.
+ // Note that this file name is only used in the Go command's
+ // temporary directory. If the -c or other flags are
+ // given, the code below will still use pkgname.test.exe.
+ // There are two user-visible effects of this change.
+ // First, you can actually run 'go test' in directories that
+ // have names that Windows thinks are installer-like,
+ // without getting a dialog box asking for more permissions.
+ // Second, in the Windows process listing during go test,
+ // the test shows up as test.test.exe, not pkgname.test.exe.
+ // That second one is a drawback, but it seems a small
+ // price to pay for the test running at all.
+ // If maintaining the list of bad words is too onerous,
+ // we could just do this always on Windows.
+ for _, bad := range windowsBadWords {
+ if strings.Contains(testBinary, bad) {
+ a.target = filepath.Join(testDir, "test.test") + exeSuffix
+ break
+ }
+ }
+ }
+ buildAction = a
if testC || testNeedBinary {
// -c or profiling flag: create action to copy binary to ./test.out.
- runAction = &action{
+ target := filepath.Join(cwd, testBinary+exeSuffix)
+ if testO != "" {
+ target = testO
+ if !filepath.IsAbs(target) {
+ target = filepath.Join(cwd, target)
+ }
+ }
+ buildAction = &action{
f: (*builder).install,
- deps: []*action{pmainAction},
+ deps: []*action{buildAction},
p: pmain,
- target: filepath.Join(cwd, testBinary+exeSuffix),
+ target: target,
}
- pmainAction = runAction // in case we are running the test
+ runAction = buildAction // make sure runAction != nil even if not running test
}
if testC {
printAction = &action{p: p, deps: []*action{runAction}} // nop
@@ -797,7 +856,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
// run test
runAction = &action{
f: (*builder).runTest,
- deps: []*action{pmainAction},
+ deps: []*action{buildAction},
p: p,
ignoreFail: true,
}
@@ -813,7 +872,7 @@ func (b *builder) test(p *Package) (buildAction, runAction, printAction *action,
}
}
- return pmainAction, runAction, printAction, nil
+ return buildAction, runAction, printAction, nil
}
func testImportStack(top string, p *Package, target string) []string {
@@ -1057,6 +1116,31 @@ func (b *builder) notest(a *action) error {
return nil
}
+// isTestMain tells whether fn is a TestMain(m *testing.M) function.
+func isTestMain(fn *ast.FuncDecl) bool {
+ if fn.Name.String() != "TestMain" ||
+ fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
+ fn.Type.Params == nil ||
+ len(fn.Type.Params.List) != 1 ||
+ len(fn.Type.Params.List[0].Names) > 1 {
+ return false
+ }
+ ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr)
+ if !ok {
+ return false
+ }
+ // We can't easily check that the type is *testing.M
+ // because we don't know how testing has been imported,
+ // but at least check that it's *M or *something.M.
+ if name, ok := ptr.X.(*ast.Ident); ok && name.Name == "M" {
+ return true
+ }
+ if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == "M" {
+ return true
+ }
+ return false
+}
+
// isTest tells whether name looks like a test (or benchmark, according to prefix).
// It is a Test (say) if there is a character after Test that is not a lower-case letter.
// We don't want TesticularCancer.
@@ -1082,12 +1166,12 @@ func loadTestFuncs(ptest *Package) (*testFuncs, error) {
Package: ptest,
}
for _, file := range ptest.TestGoFiles {
- if err := t.load(filepath.Join(ptest.Dir, file), "_test", &t.NeedTest); err != nil {
+ if err := t.load(filepath.Join(ptest.Dir, file), "_test", &t.ImportTest, &t.NeedTest); err != nil {
return nil, err
}
}
for _, file := range ptest.XTestGoFiles {
- if err := t.load(filepath.Join(ptest.Dir, file), "_xtest", &t.NeedXtest); err != nil {
+ if err := t.load(filepath.Join(ptest.Dir, file), "_xtest", &t.ImportXtest, &t.NeedXtest); err != nil {
return nil, err
}
}
@@ -1110,13 +1194,16 @@ func writeTestmain(out string, t *testFuncs) error {
}
type testFuncs struct {
- Tests []testFunc
- Benchmarks []testFunc
- Examples []testFunc
- Package *Package
- NeedTest bool
- NeedXtest bool
- Cover []coverInfo
+ Tests []testFunc
+ Benchmarks []testFunc
+ Examples []testFunc
+ TestMain *testFunc
+ Package *Package
+ ImportTest bool
+ NeedTest bool
+ ImportXtest bool
+ NeedXtest bool
+ Cover []coverInfo
}
func (t *testFuncs) CoverMode() string {
@@ -1151,7 +1238,7 @@ type testFunc struct {
var testFileSet = token.NewFileSet()
-func (t *testFuncs) load(filename, pkg string, seen *bool) error {
+func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error {
f, err := parser.ParseFile(testFileSet, filename, nil, parser.ParseComments)
if err != nil {
return expandScanner(err)
@@ -1166,17 +1253,24 @@ func (t *testFuncs) load(filename, pkg string, seen *bool) error {
}
name := n.Name.String()
switch {
+ case isTestMain(n):
+ if t.TestMain != nil {
+ return errors.New("multiple definitions of TestMain")
+ }
+ t.TestMain = &testFunc{pkg, name, ""}
+ *doImport, *seen = true, true
case isTest(name, "Test"):
t.Tests = append(t.Tests, testFunc{pkg, name, ""})
- *seen = true
+ *doImport, *seen = true, true
case isTest(name, "Benchmark"):
t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, ""})
- *seen = true
+ *doImport, *seen = true, true
}
}
ex := doc.Examples(f)
sort.Sort(byOrder(ex))
for _, e := range ex {
+ *doImport = true // import test file whether executed or not
if e.Output == "" && !e.EmptyOutput {
// Don't run examples with no output.
continue
@@ -1197,14 +1291,17 @@ var testmainTmpl = template.Must(template.New("main").Parse(`
package main
import (
+{{if not .TestMain}}
+ "os"
+{{end}}
"regexp"
"testing"
-{{if .NeedTest}}
- _test {{.Package.ImportPath | printf "%q"}}
+{{if .ImportTest}}
+ {{if .NeedTest}}_test{{else}}_{{end}} {{.Package.ImportPath | printf "%q"}}
{{end}}
-{{if .NeedXtest}}
- _xtest {{.Package.ImportPath | printf "%s_test" | printf "%q"}}
+{{if .ImportXtest}}
+ {{if .NeedXtest}}_xtest{{else}}_{{end}} {{.Package.ImportPath | printf "%s_test" | printf "%q"}}
{{end}}
{{range $i, $p := .Cover}}
_cover{{$i}} {{$p.Package.ImportPath | printf "%q"}}
@@ -1291,7 +1388,12 @@ func main() {
CoveredPackages: {{printf "%q" .Covered}},
})
{{end}}
- testing.Main(matchString, tests, benchmarks, examples)
+ m := testing.MainStart(matchString, tests, benchmarks, examples)
+{{with .TestMain}}
+ {{.Package}}.{{.Name}}(m)
+{{else}}
+ os.Exit(m.Run())
+{{end}}
}
`))