From 66bedf82210220e45ca2eb4919fe764e6a022630 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Tue, 23 Aug 2011 22:45:30 -0400 Subject: [PATCH] go/build: add test support & use in gotest A side-effect is that, just like foo_386.go is only built on 386, foo_386_test.go is only built for testing on 386. R=adg, r, mattn.jp CC=golang-dev https://golang.org/cl/4942050 --- src/cmd/gotest/gotest.go | 15 ++- src/pkg/go/build/build_test.go | 64 ++++++++-- src/pkg/go/build/dir.go | 134 +++++++++++++------- src/pkg/go/build/pkgtest/sqrt_386_test.go | 1 + src/pkg/go/build/pkgtest/sqrt_amd64_test.go | 1 + src/pkg/go/build/pkgtest/sqrt_arm_test.go | 1 + src/pkg/go/build/pkgtest/sqrt_test.go | 1 + src/pkg/go/build/pkgtest/xsqrt_test.go | 1 + src/pkg/go/build/syslist_test.go | 2 +- 9 files changed, 153 insertions(+), 67 deletions(-) create mode 100644 src/pkg/go/build/pkgtest/sqrt_386_test.go create mode 100644 src/pkg/go/build/pkgtest/sqrt_amd64_test.go create mode 100644 src/pkg/go/build/pkgtest/sqrt_arm_test.go create mode 100644 src/pkg/go/build/pkgtest/sqrt_test.go create mode 100644 src/pkg/go/build/pkgtest/xsqrt_test.go diff --git a/src/cmd/gotest/gotest.go b/src/cmd/gotest/gotest.go index 4cb3da23c8..8e3a422329 100644 --- a/src/cmd/gotest/gotest.go +++ b/src/cmd/gotest/gotest.go @@ -9,12 +9,13 @@ import ( "exec" "fmt" "go/ast" + "go/build" "go/parser" "go/token" "io/ioutil" "os" - "path/filepath" "runtime" + "sort" "strings" "time" "unicode" @@ -159,17 +160,19 @@ func setEnvironment() { } // getTestFileNames gets the set of files we're looking at. -// If gotest has no arguments, it scans for file names matching "[^.]*_test.go". +// If gotest has no arguments, it scans the current directory +// for test files. func getTestFileNames() { names := fileNames if len(names) == 0 { - var err os.Error - names, err = filepath.Glob("[^.]*_test.go") + info, err := build.ScanDir(".", true) if err != nil { - Fatalf("Glob pattern error: %s", err) + Fatalf("scanning directory: %v", err) } + names = append(info.TestGoFiles, info.XTestGoFiles...) + sort.Strings(names) if len(names) == 0 { - Fatalf(`no test files found: no match for "[^.]*_test.go"`) + Fatalf("no test files found in current directory") } } for _, n := range names { diff --git a/src/pkg/go/build/build_test.go b/src/pkg/go/build/build_test.go index e59d87672c..8670785442 100644 --- a/src/pkg/go/build/build_test.go +++ b/src/pkg/go/build/build_test.go @@ -7,44 +7,83 @@ package build import ( "exec" "path/filepath" + "reflect" + "runtime" + "sort" "testing" ) -var buildPkgs = []string{ - "go/build/pkgtest", - "go/build/cmdtest", - "go/build/cgotest", +func sortstr(x []string) []string { + sort.Strings(x) + return x +} + +var buildPkgs = []struct { + dir string + info *DirInfo +}{ + { + "go/build/pkgtest", + &DirInfo{ + GoFiles: []string{"pkgtest.go"}, + SFiles: []string{"sqrt_" + runtime.GOARCH + ".s"}, + PkgName: "pkgtest", + TestGoFiles: sortstr([]string{"sqrt_test.go", "sqrt_" + runtime.GOARCH + "_test.go"}), + XTestGoFiles: []string{"xsqrt_test.go"}, + }, + }, + { + "go/build/cmdtest", + &DirInfo{ + GoFiles: []string{"main.go"}, + PkgName: "main", + Imports: []string{"go/build/pkgtest"}, + }, + }, + { + "go/build/cgotest", + &DirInfo{ + CgoFiles: []string{"cgotest.go"}, + CFiles: []string{"cgotest.c"}, + Imports: []string{"C", "unsafe"}, + PkgName: "cgotest", + }, + }, } const cmdtestOutput = "3" func TestBuild(t *testing.T) { - for _, pkg := range buildPkgs { + for _, tt := range buildPkgs { tree := Path[0] // Goroot - dir := filepath.Join(tree.SrcDir(), pkg) + dir := filepath.Join(tree.SrcDir(), tt.dir) info, err := ScanDir(dir, true) if err != nil { - t.Error("ScanDir:", err) + t.Errorf("ScanDir(%#q): %v", tt.dir, err) + continue + } + if !reflect.DeepEqual(info, tt.info) { + t.Errorf("ScanDir(%#q) = %#v, want %#v\n", tt.dir, info, tt.info) continue } - s, err := Build(tree, pkg, info) + s, err := Build(tree, tt.dir, info) if err != nil { - t.Error("Build:", err) + t.Errorf("Build(%#q): %v", tt.dir, err) continue } if err := s.Run(); err != nil { - t.Error("Run:", err) + t.Errorf("Run(%#q): %v", tt.dir, err) continue } - if pkg == "go/build/cmdtest" { + if tt.dir == "go/build/cmdtest" { bin := s.Output[0] b, err := exec.Command(bin).CombinedOutput() if err != nil { - t.Errorf("exec: %s: %v", bin, err) + t.Errorf("exec %s: %v", bin, err) continue } if string(b) != cmdtestOutput { @@ -52,6 +91,7 @@ func TestBuild(t *testing.T) { } } + // Deferred because cmdtest depends on pkgtest. defer func(s *Script) { if err := s.Nuke(); err != nil { t.Errorf("nuking: %v", err) diff --git a/src/pkg/go/build/dir.go b/src/pkg/go/build/dir.go index e0000b5344..558b6cf957 100644 --- a/src/pkg/go/build/dir.go +++ b/src/pkg/go/build/dir.go @@ -7,27 +7,59 @@ package build import ( "go/parser" "go/token" + "io/ioutil" "log" "os" "path/filepath" + "sort" "strconv" "strings" "runtime" ) +// A Context specifies the supporting context for a build. +type Context struct { + GOARCH string // target architecture + GOOS string // target operating system + // TODO(rsc,adg): GOPATH +} + +// The DefaultContext is the default Context for builds. +// It uses the GOARCH and GOOS environment variables +// if set, or else the compiled code's GOARCH and GOOS. +var DefaultContext = Context{ + envOr("GOARCH", runtime.GOARCH), + envOr("GOOS", runtime.GOOS), +} + +func envOr(name, def string) string { + s := os.Getenv(name) + if s == "" { + return def + } + return s +} + type DirInfo struct { - GoFiles []string // .go files in dir (excluding CgoFiles) - CgoFiles []string // .go files that import "C" - CFiles []string // .c files in dir - SFiles []string // .s files in dir - Imports []string // All packages imported by goFiles - PkgName string // Name of package in dir + GoFiles []string // .go files in dir (excluding CgoFiles) + CgoFiles []string // .go files that import "C" + CFiles []string // .c files in dir + SFiles []string // .s files in dir + Imports []string // All packages imported by goFiles + PkgName string // Name of package in dir + TestGoFiles []string // _test.go files in package + XTestGoFiles []string // _test.go files outside package } func (d *DirInfo) IsCommand() bool { return d.PkgName == "main" } +// ScanDir calls DefaultContext.ScanDir. +func ScanDir(dir string, allowMain bool) (info *DirInfo, err os.Error) { + return DefaultContext.ScanDir(dir, allowMain) +} + // ScanDir returns a structure with details about the Go content found // in the given directory. The file lists exclude: // @@ -36,14 +68,8 @@ func (d *DirInfo) IsCommand() bool { // - files ending in _test.go // - files starting with _ or . // -// Only files that satisfy the goodOSArch function are included. -func ScanDir(dir string, allowMain bool) (info *DirInfo, err os.Error) { - f, err := os.Open(dir) - if err != nil { - return nil, err - } - dirs, err := f.Readdir(-1) - f.Close() +func (ctxt *Context) ScanDir(dir string, allowMain bool) (info *DirInfo, err os.Error) { + dirs, err := ioutil.ReadDir(dir) if err != nil { return nil, err } @@ -51,21 +77,19 @@ func ScanDir(dir string, allowMain bool) (info *DirInfo, err os.Error) { var di DirInfo imported := make(map[string]bool) fset := token.NewFileSet() - for i := range dirs { - d := &dirs[i] + for _, d := range dirs { if strings.HasPrefix(d.Name, "_") || strings.HasPrefix(d.Name, ".") { continue } - if !goodOSArch(d.Name) { + if !ctxt.goodOSArch(d.Name) { continue } + isTest := false switch filepath.Ext(d.Name) { case ".go": - if strings.HasSuffix(d.Name, "_test.go") { - continue - } + isTest = strings.HasSuffix(d.Name, "_test.go") case ".c": di.CFiles = append(di.CFiles, d.Name) continue @@ -81,21 +105,24 @@ func ScanDir(dir string, allowMain bool) (info *DirInfo, err os.Error) { if err != nil { return nil, err } - s := string(pf.Name.Name) - if s == "main" && !allowMain { + pkg := string(pf.Name.Name) + if pkg == "main" && !allowMain { continue } - if s == "documentation" { + if pkg == "documentation" { continue } + if isTest && strings.HasSuffix(pkg, "_test") { + pkg = pkg[:len(pkg)-len("_test")] + } if di.PkgName == "" { - di.PkgName = s - } else if di.PkgName != s { + di.PkgName = pkg + } else if di.PkgName != pkg { // Only if all files in the directory are in package main // do we return PkgName=="main". // A mix of main and another package reverts // to the original (allowMain=false) behaviour. - if s == "main" || di.PkgName == "main" { + if pkg == "main" || di.PkgName == "main" { return ScanDir(dir, false) } return nil, os.NewError("multiple package names in " + dir) @@ -109,11 +136,20 @@ func ScanDir(dir string, allowMain bool) (info *DirInfo, err os.Error) { } imported[path] = true if path == "C" { + if isTest { + return nil, os.NewError("use of cgo in test " + filename) + } isCgo = true } } if isCgo { di.CgoFiles = append(di.CgoFiles, d.Name) + } else if isTest { + if pkg == string(pf.Name.Name) { + di.TestGoFiles = append(di.TestGoFiles, d.Name) + } else { + di.XTestGoFiles = append(di.XTestGoFiles, d.Name) + } } else { di.GoFiles = append(di.GoFiles, d.Name) } @@ -124,49 +160,51 @@ func ScanDir(dir string, allowMain bool) (info *DirInfo, err os.Error) { di.Imports[i] = p i++ } + // File name lists are sorted because ioutil.ReadDir sorts. + sort.Strings(di.Imports) return &di, nil } -// goodOSArch returns false if the filename contains a $GOOS or $GOARCH +// goodOSArch returns false if the name contains a $GOOS or $GOARCH // suffix which does not match the current system. -// The recognized filename formats are: +// The recognized name formats are: // // name_$(GOOS).* // name_$(GOARCH).* // name_$(GOOS)_$(GOARCH).* +// name_$(GOOS)_test.* +// name_$(GOARCH)_test.* +// name_$(GOOS)_$(GOARCH)_test.* // -func goodOSArch(filename string) bool { - if dot := strings.Index(filename, "."); dot != -1 { - filename = filename[:dot] +func (ctxt *Context) goodOSArch(name string) bool { + if dot := strings.Index(name, "."); dot != -1 { + name = name[:dot] + } + l := strings.Split(name, "_") + if n := len(l); n > 0 && l[n-1] == "test" { + l = l[:n-1] } - l := strings.Split(filename, "_") n := len(l) - if n == 0 { - return true + if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] { + return l[n-2] == ctxt.GOOS && l[n-1] == ctxt.GOARCH } - if good, known := goodOS[l[n-1]]; known { - return good + if n >= 1 && knownOS[l[n-1]] { + return l[n-1] == ctxt.GOOS } - if good, known := goodArch[l[n-1]]; known { - if !good || n < 2 { - return false - } - good, known = goodOS[l[n-2]] - return good || !known + if n >= 1 && knownArch[l[n-1]] { + return l[n-1] == ctxt.GOARCH } return true } -var goodOS = make(map[string]bool) -var goodArch = make(map[string]bool) +var knownOS = make(map[string]bool) +var knownArch = make(map[string]bool) func init() { - goodOS = make(map[string]bool) - goodArch = make(map[string]bool) for _, v := range strings.Fields(goosList) { - goodOS[v] = v == runtime.GOOS + knownOS[v] = true } for _, v := range strings.Fields(goarchList) { - goodArch[v] = v == runtime.GOARCH + knownArch[v] = true } } diff --git a/src/pkg/go/build/pkgtest/sqrt_386_test.go b/src/pkg/go/build/pkgtest/sqrt_386_test.go new file mode 100644 index 0000000000..26b483fa0b --- /dev/null +++ b/src/pkg/go/build/pkgtest/sqrt_386_test.go @@ -0,0 +1 @@ +package pkgtest diff --git a/src/pkg/go/build/pkgtest/sqrt_amd64_test.go b/src/pkg/go/build/pkgtest/sqrt_amd64_test.go new file mode 100644 index 0000000000..26b483fa0b --- /dev/null +++ b/src/pkg/go/build/pkgtest/sqrt_amd64_test.go @@ -0,0 +1 @@ +package pkgtest diff --git a/src/pkg/go/build/pkgtest/sqrt_arm_test.go b/src/pkg/go/build/pkgtest/sqrt_arm_test.go new file mode 100644 index 0000000000..26b483fa0b --- /dev/null +++ b/src/pkg/go/build/pkgtest/sqrt_arm_test.go @@ -0,0 +1 @@ +package pkgtest diff --git a/src/pkg/go/build/pkgtest/sqrt_test.go b/src/pkg/go/build/pkgtest/sqrt_test.go new file mode 100644 index 0000000000..26b483fa0b --- /dev/null +++ b/src/pkg/go/build/pkgtest/sqrt_test.go @@ -0,0 +1 @@ +package pkgtest diff --git a/src/pkg/go/build/pkgtest/xsqrt_test.go b/src/pkg/go/build/pkgtest/xsqrt_test.go new file mode 100644 index 0000000000..bd2964e03e --- /dev/null +++ b/src/pkg/go/build/pkgtest/xsqrt_test.go @@ -0,0 +1 @@ +package pkgtest_test diff --git a/src/pkg/go/build/syslist_test.go b/src/pkg/go/build/syslist_test.go index eb0e5dcb6b..2e8b4c8656 100644 --- a/src/pkg/go/build/syslist_test.go +++ b/src/pkg/go/build/syslist_test.go @@ -55,7 +55,7 @@ var tests = []GoodFileTest{ func TestGoodOSArch(t *testing.T) { for _, test := range tests { - if goodOSArch(test.name) != test.result { + if DefaultContext.goodOSArch(test.name) != test.result { t.Fatalf("goodOSArch(%q) != %v", test.name, test.result) } } -- 2.48.1