Commit 28a1c36d authored by Rob Pike's avatar Rob Pike

testing: add -outputdir flag so "go test" controls where the files are written

Obscure misfeature now fixed: When run from "go test", profiles were always
written in the package's source directory. This change puts them in the directory
where "go test" is run.
Also fix a couple of problems causing errors in testing.after to go unreported
unless -v was set.

R=rsc, minux.ma, iant, alex.brainman
CC=golang-dev
https://golang.org/cl/10234044
parent 0627248a
...@@ -770,6 +770,10 @@ control the execution of any test: ...@@ -770,6 +770,10 @@ control the execution of any test:
garbage collector, provided the test can run in the available garbage collector, provided the test can run in the available
memory without garbage collection. memory without garbage collection.
-outputdir directory
Place output files from profiling in the specified directory,
by default the directory in which "go test" is running.
-parallel n -parallel n
Allow parallel execution of test functions that call t.Parallel. Allow parallel execution of test functions that call t.Parallel.
The value of this flag is the maximum number of tests to run The value of this flag is the maximum number of tests to run
......
...@@ -156,6 +156,10 @@ control the execution of any test: ...@@ -156,6 +156,10 @@ control the execution of any test:
garbage collector, provided the test can run in the available garbage collector, provided the test can run in the available
memory without garbage collection. memory without garbage collection.
-outputdir directory
Place output files from profiling in the specified directory,
by default the directory in which "go test" is running.
-parallel n -parallel n
Allow parallel execution of test functions that call t.Parallel. Allow parallel execution of test functions that call t.Parallel.
The value of this flag is the maximum number of tests to run The value of this flag is the maximum number of tests to run
......
...@@ -33,6 +33,7 @@ var usageMessage = `Usage of go test: ...@@ -33,6 +33,7 @@ var usageMessage = `Usage of go test:
-memprofilerate=0: passes -test.memprofilerate to test -memprofilerate=0: passes -test.memprofilerate to test
-blockprofile="": pases -test.blockprofile to test -blockprofile="": pases -test.blockprofile to test
-blockprofilerate=0: passes -test.blockprofilerate to test -blockprofilerate=0: passes -test.blockprofilerate to test
-outputdir=$PWD: passes -test.outputdir to test
-parallel=0: passes -test.parallel to test -parallel=0: passes -test.parallel to test
-run="": passes -test.run to test -run="": passes -test.run to test
-short=false: passes -test.short to test -short=false: passes -test.short to test
...@@ -87,6 +88,7 @@ var testFlagDefn = []*testFlagSpec{ ...@@ -87,6 +88,7 @@ var testFlagDefn = []*testFlagSpec{
{name: "memprofilerate", passToTest: true}, {name: "memprofilerate", passToTest: true},
{name: "blockprofile", passToTest: true}, {name: "blockprofile", passToTest: true},
{name: "blockprofilerate", passToTest: true}, {name: "blockprofilerate", passToTest: true},
{name: "outputdir", passToTest: true},
{name: "parallel", passToTest: true}, {name: "parallel", passToTest: true},
{name: "run", passToTest: true}, {name: "run", passToTest: true},
{name: "short", boolVar: new(bool), passToTest: true}, {name: "short", boolVar: new(bool), passToTest: true},
...@@ -105,6 +107,7 @@ var testFlagDefn = []*testFlagSpec{ ...@@ -105,6 +107,7 @@ var testFlagDefn = []*testFlagSpec{
// go test -x math // go test -x math
func testFlags(args []string) (packageNames, passToTest []string) { func testFlags(args []string) (packageNames, passToTest []string) {
inPkg := false inPkg := false
outputDir := ""
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
if !strings.HasPrefix(args[i], "-") { if !strings.HasPrefix(args[i], "-") {
if !inPkg && packageNames == nil { if !inPkg && packageNames == nil {
...@@ -170,6 +173,8 @@ func testFlags(args []string) (packageNames, passToTest []string) { ...@@ -170,6 +173,8 @@ func testFlags(args []string) (packageNames, passToTest []string) {
testTimeout = value testTimeout = value
case "blockprofile", "cpuprofile", "memprofile": case "blockprofile", "cpuprofile", "memprofile":
testProfile = true testProfile = true
case "outputdir":
outputDir = value
case "cover": case "cover":
switch value { switch value {
case "set", "count", "atomic": case "set", "count", "atomic":
...@@ -185,6 +190,14 @@ func testFlags(args []string) (packageNames, passToTest []string) { ...@@ -185,6 +190,14 @@ func testFlags(args []string) (packageNames, passToTest []string) {
passToTest = append(passToTest, "-test."+f.name+"="+value) passToTest = append(passToTest, "-test."+f.name+"="+value)
} }
} }
// Tell the test what directory we're running in, so it can write the profiles there.
if testProfile && outputDir == "" {
dir, err := os.Getwd()
if err != nil {
fatalf("error from os.Getwd: %s", err)
}
passToTest = append(passToTest, "-test.outputdir", dir)
}
return return
} }
......
...@@ -114,6 +114,12 @@ var ( ...@@ -114,6 +114,12 @@ var (
// full test of the package. // full test of the package.
short = flag.Bool("test.short", false, "run smaller test suite to save time") short = flag.Bool("test.short", false, "run smaller test suite to save time")
// The directory in which to create profile files and the like. When run from
// "go test", the binary always runs in the source directory for the package;
// this flag lets "go test" tell the binary to write the files in the directory where
// the "go test" command is run.
outputDir = flag.String("test.outputdir", "", "directory in which to write profiles")
// Report as tests are run; default is silent for success. // Report as tests are run; default is silent for success.
chatty = flag.Bool("test.v", false, "verbose: print additional output") chatty = flag.Bool("test.v", false, "verbose: print additional output")
match = flag.String("test.run", "", "regular expression to select tests and examples to run") match = flag.String("test.run", "", "regular expression to select tests and examples to run")
...@@ -466,7 +472,7 @@ func before() { ...@@ -466,7 +472,7 @@ func before() {
runtime.MemProfileRate = *memProfileRate runtime.MemProfileRate = *memProfileRate
} }
if *cpuProfile != "" { if *cpuProfile != "" {
f, err := os.Create(*cpuProfile) f, err := os.Create(toOutputDir(*cpuProfile))
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "testing: %s", err) fmt.Fprintf(os.Stderr, "testing: %s", err)
return return
...@@ -489,29 +495,59 @@ func after() { ...@@ -489,29 +495,59 @@ func after() {
pprof.StopCPUProfile() // flushes profile to disk pprof.StopCPUProfile() // flushes profile to disk
} }
if *memProfile != "" { if *memProfile != "" {
f, err := os.Create(*memProfile) f, err := os.Create(toOutputDir(*memProfile))
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "testing: %s", err) fmt.Fprintf(os.Stderr, "testing: %s\n", err)
return os.Exit(2)
} }
if err = pprof.WriteHeapProfile(f); err != nil { if err = pprof.WriteHeapProfile(f); err != nil {
fmt.Fprintf(os.Stderr, "testing: can't write %s: %s", *memProfile, err) fmt.Fprintf(os.Stderr, "testing: can't write %s: %s\n", *memProfile, err)
os.Exit(2)
} }
f.Close() f.Close()
} }
if *blockProfile != "" && *blockProfileRate >= 0 { if *blockProfile != "" && *blockProfileRate >= 0 {
f, err := os.Create(*blockProfile) f, err := os.Create(toOutputDir(*blockProfile))
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "testing: %s", err) fmt.Fprintf(os.Stderr, "testing: %s\n", err)
return os.Exit(2)
} }
if err = pprof.Lookup("block").WriteTo(f, 0); err != nil { if err = pprof.Lookup("block").WriteTo(f, 0); err != nil {
fmt.Fprintf(os.Stderr, "testing: can't write %s: %s", *blockProfile, err) fmt.Fprintf(os.Stderr, "testing: can't write %s: %s\n", *blockProfile, err)
os.Exit(2)
} }
f.Close() f.Close()
} }
} }
// toOutputDir returns the file name relocated, if required, to outputDir.
// Simple implementation to avoid pulling in path/filepath.
func toOutputDir(path string) string {
if *outputDir == "" || path == "" {
return path
}
if runtime.GOOS == "windows" {
// On Windows, it's clumsy, but we can be almost always correct
// by just looking for a drive letter and a colon.
// Absolute paths always have a drive letter (ignoring UNC).
// Problem: if path == "C:A" and outputdir == "C:\Go" it's unclear
// what to do, but even then path/filepath doesn't help.
// TODO: Worth doing better? Probably not, because we're here only
// under the management of go test.
if len(path) >= 2 {
letter, colon := path[0], path[1]
if ('a' <= letter && letter <= 'z' || 'A' <= letter && letter <= 'Z') && colon == ':' {
// If path starts with a drive letter we're stuck with it regardless.
return path
}
}
}
if os.IsPathSeparator(path[0]) {
return path
}
return fmt.Sprintf("%s%c%s", *outputDir, os.PathSeparator, path)
}
var timer *time.Timer var timer *time.Timer
// startAlarm starts an alarm if requested. // startAlarm starts an alarm if requested.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment