nflag bool // the -n flag
vflag bool // the -v flag
arch string // e.g., "6"
+ goroot string // the $GOROOT
actionCache map[cacheKey]*action // a cache of already-constructed actions
}
type action struct {
f func(*builder, *action) error // the action itself
- p *Package // the package this action works on
- deps []*action // actions that must happen before this one
- done bool // whether the action is done (might have failed)
- failed bool // whether the action failed
+ p *Package // the package this action works on
+ deps []*action // actions that must happen before this one
+ done bool // whether the action is done (might have failed)
+ failed bool // whether the action failed
+ pkgdir string // the -I or -L argument to use when importing this package
+ ignoreFail bool // whether to run f even if dependencies fail
// Results left for communication with other code.
pkgobj string // the built .a file
b.nflag = nflag
b.vflag = vflag
b.actionCache = make(map[cacheKey]*action)
+ b.goroot = runtime.GOROOT()
b.arch, err = build.ArchChar(build.DefaultContext.GOARCH)
if err != nil {
return a
}
- a = &action{p: p}
+ a = &action{p: p, pkgdir: p.t.PkgDir()}
+ if p.pkgdir != "" { // overrides p.t
+ a.pkgdir = p.pkgdir
+ }
+
b.actionCache[key] = a
switch mode {
case modeBuild, modeInstall:
+ for _, p1 := range p.imports {
+ a.deps = append(a.deps, b.action(depMode, depMode, p1))
+ }
+
if !needInstall(p) && !b.aflag {
+ // TODO: This is not right if the deps above
+ // are not all no-ops too. If fmt is up to date
+ // wrt its own source files, but strconv has
+ // changed, then fmt is not up to date.
a.f = (*builder).nop
return a
}
}
a.f = (*builder).build
- for _, p1 := range p.imports {
- a.deps = append(a.deps, b.action(depMode, depMode, p1))
- }
}
return a
b.do(a1)
if a1.failed {
a.failed = true
- a.done = true
- return
+ if !a.ignoreFail {
+ a.done = true
+ return
+ }
}
}
if err := a.f(b, a); err != nil {
return nil
}
-// build is the action for building a single package.
+// build is the action for building a single package or command.
func (b *builder) build(a *action) error {
obj := filepath.Join(b.work, filepath.FromSlash(a.p.ImportPath+"/_obj")) + string(filepath.Separator)
- a.pkgobj = filepath.Join(b.work, filepath.FromSlash(a.p.ImportPath+".a"))
+ if a.pkgobj == "" {
+ a.pkgobj = filepath.Join(b.work, filepath.FromSlash(a.p.ImportPath+".a"))
+ }
// make build directory
if err := b.mkdir(obj); err != nil {
inc = append(inc, "-I", b.work)
incMap := map[string]bool{}
for _, a1 := range a.deps {
- p1 := a1.p
- if p1.t.Goroot {
+ pkgdir := a1.pkgdir
+ if pkgdir == build.Path[0].PkgDir() || pkgdir == "" {
continue
}
- pkgdir := p1.t.PkgDir()
if !incMap[pkgdir] {
incMap[pkgdir] = true
inc = append(inc, "-I", pkgdir)
// install is the action for installing a single package.
func (b *builder) install(a *action) error {
- if err := b.build(a); err != nil {
- return err
- }
-
+ a1 := a.deps[0]
var src string
var perm uint32
- if a.pkgbin != "" {
- src = a.pkgbin
+ if a1.pkgbin != "" {
+ src = a1.pkgbin
perm = 0777
} else {
- src = a.pkgobj
+ src = a1.pkgobj
perm = 0666
}
package main
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/build"
+ "go/doc"
+ "go/parser"
+ "go/token"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "text/template"
+ "unicode"
+ "unicode/utf8"
+)
+
+// Break init loop.
+func init() {
+ cmdTest.Run = runTest
+}
+
var cmdTest = &Command{
- Run: runTest,
- UsageLine: "test [importpath...] [-file a.go -file b.go ...] [-c] [-x] [flags for test binary]",
- Short: "test packages",
+ CustomFlags: true,
+ UsageLine: "test [importpath...] [-file a.go -file b.go ...] [-c] [-x] [flags for test binary]",
+ Short: "test packages",
Long: `
'Go test' automates testing the packages named by the import paths.
It prints a summary of the test results in the format:
`,
}
+// TODO(rsc): Rethink the flag handling.
+// It might be better to do
+// go test [go-test-flags] [importpaths] [flags for test binary]
+// If there are no import paths then the two flag sections
+// run together, but we can deal with that. Right now,
+// go test -x (ok)
+// go test -x math (NOT OK)
+// go test math -x (ok)
+// which is weird, because the -x really does apply to go test, not to math.
+// It is also possible that -file can go away.
+// For now just use the gotest code.
+
+var (
+ testC bool // -c flag
+ testX bool // -x flag
+ testFiles []string // -file flag(s) TODO: not respected
+ testArgs []string
+)
+
func runTest(cmd *Command, args []string) {
- args = importPaths(args)
- _ = args
- panic("test not implemented")
+ // Determine which are the import paths
+ // (leading arguments not starting with -).
+ i := 0
+ for i < len(args) && !strings.HasPrefix(args[i], "-") {
+ i++
+ }
+ pkgs := packages(args[:i])
+ if len(pkgs) == 0 {
+ fatalf("no packages to test")
+ }
+
+ testArgs = testFlags(args[i:])
+ if testC && len(pkgs) != 1 {
+ fatalf("cannot use -c flag with multiple packages")
+ }
+
+ var b builder
+ b.init(false, false, testX)
+
+ var builds, runs []*action
+
+ // Prepare build + run actions for all packages being tested.
+ for _, p := range pkgs {
+ buildTest, runTest, err := b.test(p)
+ if err != nil {
+ errorf("%s: %s", p, err)
+ continue
+ }
+ if buildTest == nil {
+ // no test at all
+ continue
+ }
+ builds = append(builds, buildTest)
+ runs = append(runs, runTest)
+ }
+
+ // Build+run the tests one at a time in the order
+ // specified on the command line.
+ // May want to revisit when we parallelize things,
+ // although probably not for benchmark runs.
+ for i, a := range builds {
+ if i > 0 {
+ // Make build of test i depend on
+ // completing the run of test i-1.
+ a.deps = append(a.deps, runs[i-1])
+ }
+ }
+
+ allRuns := &action{f: (*builder).nop, deps: runs}
+ b.do(allRuns)
+}
+
+func (b *builder) test(p *Package) (buildAction, runAction *action, err error) {
+ if len(p.info.TestGoFiles)+len(p.info.XTestGoFiles) == 0 {
+ return &action{f: (*builder).nop, p: p}, &action{f: (*builder).notest, p: p}, nil
+ }
+
+ // Build Package structs describing:
+ // ptest - package + test files
+ // pxtest - package of external test files
+ // pmain - test.out binary
+ var ptest, pxtest, pmain *Package
+
+ // go/build does not distinguish the dependencies used
+ // by the TestGoFiles from the dependencies used by the
+ // XTestGoFiles, so we build one list and use it for both
+ // ptest and pxtest. No harm done.
+ var imports []*Package
+ for _, path := range p.info.TestImports {
+ p1, err := loadPackage(path)
+ if err != nil {
+ return nil, nil, err
+ }
+ imports = append(imports, p1)
+ }
+
+ // The ptest package needs to be importable under the
+ // same import path that p has, but we cannot put it in
+ // the usual place in the temporary tree, because then
+ // other tests will see it as the real package.
+ // Instead we make a _test directory under the import path
+ // and then repeat the import path there. We tell the
+ // compiler and linker to look in that _test directory first.
+ //
+ // That is, if the package under test is unicode/utf8,
+ // then the normal place to write the package archive is
+ // $WORK/unicode/utf8.a, but we write the test package archive to
+ // $WORK/unicode/utf8/_test/unicode/utf8.a.
+ // We write the external test package archive to
+ // $WORK/unicode/utf8/_test/unicode/utf8_test.a.
+ testDir := filepath.Join(b.work, filepath.FromSlash(p.ImportPath+"/_test"))
+ ptestObj := filepath.Join(testDir, filepath.FromSlash(p.ImportPath+".a"))
+
+ // Create the directory for the .a files.
+ ptestDir, _ := filepath.Split(ptestObj)
+ if err := b.mkdir(ptestDir); err != nil {
+ return nil, nil, err
+ }
+ if err := writeTestmain(filepath.Join(testDir, "_testmain.go"), p); err != nil {
+ return nil, nil, err
+ }
+
+ // Test package.
+ if len(p.info.TestGoFiles) > 0 {
+ ptest = new(Package)
+ *ptest = *p
+ ptest.GoFiles = nil
+ ptest.GoFiles = append(ptest.GoFiles, p.GoFiles...)
+ ptest.GoFiles = append(ptest.GoFiles, p.info.TestGoFiles...)
+ ptest.targ = "" // must rebuild
+ ptest.Imports = p.info.TestImports
+ ptest.imports = imports
+ ptest.pkgdir = testDir
+ } else {
+ ptest = p
+ }
+
+ // External test package.
+ if len(p.info.XTestGoFiles) > 0 {
+ pxtest = &Package{
+ Name: p.Name + "_test",
+ ImportPath: p.ImportPath + "_test",
+ Dir: p.Dir,
+ GoFiles: p.info.XTestGoFiles,
+ Imports: p.info.TestImports,
+ t: p.t,
+ info: &build.DirInfo{},
+ imports: imports,
+ pkgdir: testDir,
+ }
+ }
+
+ // Action for building test.out.
+ pmain = &Package{
+ Name: "main",
+ Dir: testDir,
+ GoFiles: []string{"_testmain.go"},
+ t: p.t,
+ info: &build.DirInfo{},
+ imports: []*Package{ptest},
+ }
+ if pxtest != nil {
+ pmain.imports = append(pmain.imports, pxtest)
+ }
+ pmainAction := b.action(modeBuild, modeBuild, pmain)
+ pmainAction.pkgbin = filepath.Join(testDir, "test.out")
+
+ if testC {
+ // -c flag: create action to copy binary to ./test.out.
+ pmain.targ = "test.out"
+ runAction = &action{
+ f: (*builder).install,
+ deps: []*action{pmainAction},
+ p: pmain,
+ }
+ } else {
+ // run test
+ runAction = &action{
+ f: (*builder).runTest,
+ deps: []*action{pmainAction},
+ p: p,
+ ignoreFail: true,
+ }
+ }
+
+ return pmainAction, runAction, nil
+}
+
+var pass = []byte("\nPASS\n")
+
+// runTest is the action for running a test binary.
+func (b *builder) runTest(a *action) error {
+ if b.nflag || b.vflag {
+ b.showcmd("%s", strings.Join(append([]string{a.deps[0].pkgbin}, testArgs...), " "))
+ if b.nflag {
+ return nil
+ }
+ }
+
+ if a.failed {
+ // We were unable to build the binary.
+ a.failed = false
+ fmt.Printf("FAIL\t%s [build failed]\n", a.p.ImportPath)
+ exitStatus = 1
+ return nil
+ }
+
+ cmd := exec.Command(a.deps[0].pkgbin, testArgs...)
+ cmd.Dir = a.p.Dir
+ out, err := cmd.CombinedOutput()
+ if err == nil && (bytes.Equal(out, pass[1:]) || bytes.HasSuffix(out, pass)) {
+ fmt.Printf("ok \t%s\n", a.p.ImportPath)
+ return nil
+ }
+
+ fmt.Printf("FAIL\t%s\n", a.p.ImportPath)
+ exitStatus = 1
+ if len(out) > 0 {
+ os.Stdout.Write(out)
+ // assume printing the test binary's exit status is superfluous
+ } else {
+ fmt.Printf("%s\n", err)
+ }
+ return nil
+}
+
+// notest is the action for testing a package with no test files.
+func (b *builder) notest(a *action) error {
+ fmt.Printf("? \t%s [no test files]\n", a.p.ImportPath)
+ return nil
+}
+
+// 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.
+func isTest(name, prefix string) bool {
+ if !strings.HasPrefix(name, prefix) {
+ return false
+ }
+ if len(name) == len(prefix) { // "Test" is ok
+ return true
+ }
+ rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
+ return !unicode.IsLower(rune)
+}
+
+// writeTestmain writes the _testmain.go file for package p to
+// the file named out.
+func writeTestmain(out string, p *Package) error {
+ t := &testFuncs{
+ Package: p,
+ Info: p.info,
+ }
+ for _, file := range p.info.TestGoFiles {
+ if err := t.load(filepath.Join(p.Dir, file), "_test", &t.NeedTest); err != nil {
+ return err
+ }
+ }
+ for _, file := range p.info.XTestGoFiles {
+ if err := t.load(filepath.Join(p.Dir, file), "_xtest", &t.NeedXtest); err != nil {
+ return err
+ }
+ }
+
+ f, err := os.Create(out)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ if err := testmainTmpl.Execute(f, t); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+type testFuncs struct {
+ Tests []testFunc
+ Benchmarks []testFunc
+ Examples []testFunc
+ Package *Package
+ Info *build.DirInfo
+ NeedTest bool
+ NeedXtest bool
+}
+
+type testFunc struct {
+ Package string // imported package name (_test or _xtest)
+ Name string // function name
+ Output string // output, for examples
+}
+
+var testFileSet = token.NewFileSet()
+
+func (t *testFuncs) load(filename, pkg string, seen *bool) error {
+ f, err := parser.ParseFile(testFileSet, filename, nil, parser.ParseComments)
+ if err != nil {
+ return err
+ }
+ for _, d := range f.Decls {
+ n, ok := d.(*ast.FuncDecl)
+ if !ok {
+ continue
+ }
+ if n.Recv != nil {
+ continue
+ }
+ name := n.Name.String()
+ switch {
+ case isTest(name, "Test"):
+ t.Tests = append(t.Tests, testFunc{pkg, name, ""})
+ *seen = true
+ case isTest(name, "Benchmark"):
+ t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, ""})
+ *seen = true
+ case isTest(name, "Example"):
+ output := doc.CommentText(n.Doc)
+ if output == "" {
+ // Don't run examples with no output.
+ continue
+ }
+ t.Examples = append(t.Examples, testFunc{pkg, name, output})
+ *seen = true
+ }
+ }
+
+ return nil
+}
+
+var testmainTmpl = template.Must(template.New("main").Parse(`
+package main
+
+import (
+ "regexp"
+ "testing"
+
+{{if .NeedTest}}
+ _test {{.Package.ImportPath | printf "%q"}}
+{{end}}
+{{if .NeedXtest}}
+ _xtest {{.Package.ImportPath | printf "%s_test" | printf "%q"}}
+{{end}}
+)
+
+var tests = []testing.InternalTest{
+{{range .Tests}}
+ {"{{.Name}}", {{.Package}}.{{.Name}}},
+{{end}}
+}
+
+var benchmarks = []testing.InternalBenchmark{
+{{range .Benchmarks}}
+ {"{{.Name}}", {{.Package}}.{{.Name}}},
+{{end}}
+}
+
+var examples = []testing.InternalExample{
+{{range .Examples}}
+ {"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}},
+{{end}}
+}
+
+var matchPat string
+var matchRe *regexp.Regexp
+
+func matchString(pat, str string) (result bool, err error) {
+ if matchRe == nil || matchPat != pat {
+ matchPat = pat
+ matchRe, err = regexp.Compile(matchPat)
+ if err != nil {
+ return
+ }
+ }
+ return matchRe.MatchString(str), nil
}
+
+func main() {
+ testing.Main(matchString, tests, benchmarks, examples)
+}
+
+`))
--- /dev/null
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+)
+
+// The flag handling part of go test is large and distracting.
+// We can't use the flag package because some of the flags from
+// our command line are for us, and some are for 6.out, and
+// some are for both.
+
+var usageMessage = `Usage of go test:
+ -c=false: compile but do not run the test binary
+ -file=file_test.go: specify file to use for tests;
+ use multiple times for multiple files
+ -x=false: print command lines as they are executed
+
+ // These flags can be passed with or without a "test." prefix: -v or -test.v.
+ -bench="": passes -test.bench to test
+ -benchtime=1: passes -test.benchtime to test
+ -cpu="": passes -test.cpu to test
+ -cpuprofile="": passes -test.cpuprofile to test
+ -memprofile="": passes -test.memprofile to test
+ -memprofilerate=0: passes -test.memprofilerate to test
+ -parallel=0: passes -test.parallel to test
+ -run="": passes -test.run to test
+ -short=false: passes -test.short to test
+ -timeout=0: passes -test.timeout to test
+ -v=false: passes -test.v to test
+`
+
+// usage prints a usage message and exits.
+func testUsage() {
+ fmt.Fprint(os.Stderr, usageMessage)
+ exitStatus = 2
+ exit()
+}
+
+// testFlagSpec defines a flag we know about.
+type testFlagSpec struct {
+ name string
+ isBool bool
+ passToTest bool // pass to Test
+ multiOK bool // OK to have multiple instances
+ present bool // flag has been seen
+}
+
+// testFlagDefn is the set of flags we process.
+var testFlagDefn = []*testFlagSpec{
+ // local.
+ {name: "c", isBool: true},
+ {name: "file", multiOK: true},
+ {name: "x", isBool: true},
+
+ // passed to 6.out, adding a "test." prefix to the name if necessary: -v becomes -test.v.
+ {name: "bench", passToTest: true},
+ {name: "benchtime", passToTest: true},
+ {name: "cpu", passToTest: true},
+ {name: "cpuprofile", passToTest: true},
+ {name: "memprofile", passToTest: true},
+ {name: "memprofilerate", passToTest: true},
+ {name: "parallel", passToTest: true},
+ {name: "run", passToTest: true},
+ {name: "short", isBool: true, passToTest: true},
+ {name: "timeout", passToTest: true},
+ {name: "v", isBool: true, passToTest: true},
+}
+
+// testFlags processes the command line, grabbing -x and -c, rewriting known flags
+// to have "test" before them, and reading the command line for the 6.out.
+// Unfortunately for us, we need to do our own flag processing because go test
+// grabs some flags but otherwise its command line is just a holding place for
+// test.out's arguments.
+func testFlags(args []string) (passToTest []string) {
+ for i := 0; i < len(args); i++ {
+ arg := args[i]
+ f, value, extraWord := testFlag(args, i)
+ if f == nil {
+ args = append(args, arg)
+ continue
+ }
+ switch f.name {
+ case "c":
+ setBoolFlag(&testC, value)
+ case "x":
+ setBoolFlag(&testX, value)
+ case "file":
+ testFiles = append(testFiles, value)
+ }
+ if extraWord {
+ i++
+ }
+ if f.passToTest {
+ passToTest = append(passToTest, "-test."+f.name+"="+value)
+ }
+ }
+ return
+}
+
+// testFlag sees if argument i is a known flag and returns its definition, value, and whether it consumed an extra word.
+func testFlag(args []string, i int) (f *testFlagSpec, value string, extra bool) {
+ arg := args[i]
+ if strings.HasPrefix(arg, "--") { // reduce two minuses to one
+ arg = arg[1:]
+ }
+ switch arg {
+ case "-?", "-h", "-help":
+ usage()
+ }
+ if arg == "" || arg[0] != '-' {
+ return
+ }
+ name := arg[1:]
+ // If there's already "test.", drop it for now.
+ if strings.HasPrefix(name, "test.") {
+ name = name[5:]
+ }
+ equals := strings.Index(name, "=")
+ if equals >= 0 {
+ value = name[equals+1:]
+ name = name[:equals]
+ }
+ for _, f = range testFlagDefn {
+ if name == f.name {
+ // Booleans are special because they have modes -x, -x=true, -x=false.
+ if f.isBool {
+ if equals < 0 { // otherwise, it's been set and will be verified in setBoolFlag
+ value = "true"
+ } else {
+ // verify it parses
+ setBoolFlag(new(bool), value)
+ }
+ } else { // Non-booleans must have a value.
+ extra = equals < 0
+ if extra {
+ if i+1 >= len(args) {
+ usage()
+ }
+ value = args[i+1]
+ }
+ }
+ if f.present && !f.multiOK {
+ usage()
+ }
+ f.present = true
+ return
+ }
+ }
+ f = nil
+ return
+}
+
+// setBoolFlag sets the addressed boolean to the value.
+func setBoolFlag(flag *bool, value string) {
+ x, err := strconv.ParseBool(value)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "go test: illegal bool flag value %s\n", value)
+ usage()
+ }
+ *flag = x
+}