From 4e4c1f9c4d101d5578656c06a42784856fec9f0d Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 27 May 2015 16:33:03 -0700 Subject: [PATCH] cmd/dist: don't run go list when running a specific test This speeds up sharded builds notably, by 1 second * the number of tests. Change-Id: Ib0295c31e4974f3003f72cb16c48949812b6f22b Reviewed-on: https://go-review.googlesource.com/10460 Reviewed-by: Andrew Gerrand Run-TryBot: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/cmd/dist/test.go | 138 +++++++++++++++++++++++++++++++------------ 1 file changed, 101 insertions(+), 37 deletions(-) diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go index 0b6b592eef..f5a0dc50f1 100644 --- a/src/cmd/dist/test.go +++ b/src/cmd/dist/test.go @@ -201,45 +201,71 @@ func (t *tester) timeout(sec int) string { return "-timeout=" + fmt.Sprint(time.Duration(sec)*time.Second*time.Duration(t.timeoutScale)) } +// ranGoTest and stdMatches are state closed over by the stdlib +// testing func in registerStdTest below. The tests are run +// sequentially, so there's no need for locks. +var ( + ranGoTest bool + stdMatches []string +) + +func (t *tester) registerStdTest(pkg string) { + testName := "go_test:" + pkg + if t.runRx == nil || t.runRx.MatchString(testName) { + stdMatches = append(stdMatches, pkg) + } + t.tests = append(t.tests, distTest{ + name: testName, + heading: "Testing packages.", + fn: func() error { + if ranGoTest { + return nil + } + ranGoTest = true + cmd := exec.Command("go", append([]string{ + "test", + "-short", + t.timeout(120), + "-gcflags=" + os.Getenv("GO_GCFLAGS"), + }, stdMatches...)...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() + }, + }) +} + +// validStdPkg reports whether pkg looks like a standard library package name. +// Notably, it's not blank and doesn't contain regexp characters. +func validStdPkg(pkg string) bool { + if pkg == "" { + return false + } + for _, r := range pkg { + switch { + case 'a' <= r && r <= 'z': + case 'A' <= r && r <= 'Z': + case '0' <= r && r <= '9': + case r == '_': + case r == '/': + default: + return false + } + } + return true +} + func (t *tester) registerTests() { - // Register a separate logical test for each package in the standard library - // but actually group them together at execution time to share the cost of - // building packages shared between them. - all, err := exec.Command("go", "list", "std", "cmd").Output() - if err != nil { - log.Fatalf("Error running go list std cmd: %v", err) - } - // ranGoTest and stdMatches are state closed over by the - // stdlib testing func below. The tests are run sequentially, - // so there's no need for locks. - var ( - ranGoTest bool - stdMatches []string - ) - for _, pkg := range strings.Fields(string(all)) { - testName := "go_test:" + pkg - if t.runRx == nil || t.runRx.MatchString(testName) { - stdMatches = append(stdMatches, pkg) + // Fast path to avoid the ~1 second of `go list std cmd` when + // the caller passed -run=^go_test:foo/bar$ (as the continuous + // build coordinator does). + if strings.HasPrefix(t.runRxStr, "^go_test:") && strings.HasSuffix(t.runRxStr, "$") { + pkg := strings.TrimPrefix(t.runRxStr, "^go_test:") + pkg = strings.TrimSuffix(pkg, "$") + if validStdPkg(pkg) { + t.registerStdTest(pkg) + return } - t.tests = append(t.tests, distTest{ - name: testName, - heading: "Testing packages.", - fn: func() error { - if ranGoTest { - return nil - } - ranGoTest = true - cmd := exec.Command("go", append([]string{ - "test", - "-short", - t.timeout(120), - "-gcflags=" + os.Getenv("GO_GCFLAGS"), - }, stdMatches...)...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() - }, - }) } // Runtime CPU tests. @@ -365,6 +391,44 @@ func (t *tester) registerTests() { }) } + // Register the standard library tests lasts, to avoid the ~1 second latency + // of running `go list std cmd` if we're running a specific test. + // Now we know the names of all the other tests registered so far. + if !t.wantSpecificRegisteredTest() { + all, err := exec.Command("go", "list", "std", "cmd").Output() + if err != nil { + log.Fatalf("Error running go list std cmd: %v", err) + } + // Put the standard library tests first. + orig := t.tests + t.tests = nil + for _, pkg := range strings.Fields(string(all)) { + t.registerStdTest(pkg) + } + t.tests = append(t.tests, orig...) + } +} + +// wantSpecificRegisteredTest reports whether the caller is requesting a +// run of a specific test via the flag -run=^TESTNAME$ (as is done by the +// continuous build coordinator). +func (t *tester) wantSpecificRegisteredTest() bool { + if !strings.HasPrefix(t.runRxStr, "^") || !strings.HasSuffix(t.runRxStr, "$") { + return false + } + test := t.runRxStr[1 : len(t.runRxStr)-1] + return t.isRegisteredTestName(test) +} + +// isRegisteredTestName reports whether a test named testName has already +// been registered. +func (t *tester) isRegisteredTestName(testName string) bool { + for _, tt := range t.tests { + if tt.name == testName { + return true + } + } + return false } func (t *tester) registerTest(name, dirBanner, bin string, args ...string) { -- 2.48.1