mirror of
https://github.com/golang/go
synced 2024-11-17 08:54:41 -07:00
cmd/go: avoid rebuilding itself in TestMain
An extra "go build" was happening, for the sake of -tags=testgo, which would insert some extra behavior into ./internal/work. Instead, reuse the test binary as cmd/go directly, by calling the main func when a special env var is set. We still duplicate the test binary into testBin, because we need a "go" executable in that directory for $PATH. Finally, the special behavior is instead inserted via TestMain. The numbers below represent how long it takes to run zero tests, measured via: benchcmd GoTestNothing go test -run=- That is, the time it takes to run the first test is reduced by half. Note that these numbers are on a warm build cache, so if the -tags=testgo build were to be done from scratch, the speed-up would be significantly more noticeable. name old time/op new time/op delta GoTestNothing 830ms ± 2% 380ms ± 7% -54.23% (p=0.008 n=5+5) name old user-time/op new user-time/op delta GoTestNothing 1.64s ± 1% 0.82s ± 3% -50.24% (p=0.008 n=5+5) name old sys-time/op new sys-time/op delta GoTestNothing 306ms ± 7% 159ms ±28% -48.15% (p=0.008 n=5+5) name old peak-RSS-bytes new peak-RSS-bytes delta GoTestNothing 173MB ± 1% 147MB ± 1% -14.96% (p=0.008 n=5+5) Change-Id: I1f8fc71269a7b45bc5b82b7228e13f56589d44c3 Reviewed-on: https://go-review.googlesource.com/c/go/+/378294 Trust: Daniel Martí <mvdan@mvdan.cc> Run-TryBot: Daniel Martí <mvdan@mvdan.cc> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Bryan Mills <bcmills@google.com>
This commit is contained in:
parent
46afa893eb
commit
ca384f7629
7
src/cmd/go/export_test.go
Normal file
7
src/cmd/go/export_test.go
Normal file
@ -0,0 +1,7 @@
|
||||
// Copyright 2015 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
|
||||
|
||||
func Main() { main() }
|
@ -14,7 +14,6 @@ import (
|
||||
"fmt"
|
||||
"go/format"
|
||||
"internal/godebug"
|
||||
"internal/race"
|
||||
"internal/testenv"
|
||||
"io"
|
||||
"io/fs"
|
||||
@ -32,7 +31,11 @@ import (
|
||||
"cmd/go/internal/cache"
|
||||
"cmd/go/internal/cfg"
|
||||
"cmd/go/internal/robustio"
|
||||
"cmd/go/internal/search"
|
||||
"cmd/go/internal/work"
|
||||
"cmd/internal/sys"
|
||||
|
||||
cmdgo "cmd/go"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -84,6 +87,43 @@ var testBin string
|
||||
// The TestMain function creates a go command for testing purposes and
|
||||
// deletes it after the tests have been run.
|
||||
func TestMain(m *testing.M) {
|
||||
// When CMDGO_TEST_RUN_MAIN is set, we're reusing the test binary as cmd/go.
|
||||
// Enable the special behavior needed in cmd/go/internal/work,
|
||||
// run the main func exported via export_test.go, and exit.
|
||||
// We set CMDGO_TEST_RUN_MAIN via os.Setenv and testScript.setup.
|
||||
if os.Getenv("CMDGO_TEST_RUN_MAIN") != "" {
|
||||
if v := os.Getenv("TESTGO_VERSION"); v != "" {
|
||||
work.RuntimeVersion = v
|
||||
}
|
||||
|
||||
if testGOROOT := os.Getenv("TESTGO_GOROOT"); testGOROOT != "" {
|
||||
// Disallow installs to the GOROOT from which testgo was built.
|
||||
// Installs to other GOROOTs — such as one set explicitly within a test — are ok.
|
||||
work.AllowInstall = func(a *work.Action) error {
|
||||
if cfg.BuildN {
|
||||
return nil
|
||||
}
|
||||
|
||||
rel := search.InDir(a.Target, testGOROOT)
|
||||
if rel == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
callerPos := ""
|
||||
if _, file, line, ok := runtime.Caller(1); ok {
|
||||
if shortFile := search.InDir(file, filepath.Join(testGOROOT, "src")); shortFile != "" {
|
||||
file = shortFile
|
||||
}
|
||||
callerPos = fmt.Sprintf("%s:%d: ", file, line)
|
||||
}
|
||||
return fmt.Errorf("%stestgo must not write to GOROOT (installing to %s)", callerPos, filepath.Join("GOROOT", rel))
|
||||
}
|
||||
}
|
||||
cmdgo.Main()
|
||||
os.Exit(0)
|
||||
}
|
||||
os.Setenv("CMDGO_TEST_RUN_MAIN", "true")
|
||||
|
||||
// $GO_GCFLAGS a compiler debug flag known to cmd/dist, make.bash, etc.
|
||||
// It is not a standard go command flag; use os.Getenv, not cfg.Getenv.
|
||||
if os.Getenv("GO_GCFLAGS") != "" {
|
||||
@ -127,10 +167,6 @@ func TestMain(m *testing.M) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
testGo = filepath.Join(testBin, "go"+exeSuffix)
|
||||
args := []string{"build", "-tags", "testgo", "-o", testGo}
|
||||
if race.Enabled {
|
||||
args = append(args, "-race")
|
||||
}
|
||||
gotool, err := testenv.GoTool()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "locating go tool: ", err)
|
||||
@ -173,12 +209,32 @@ func TestMain(m *testing.M) {
|
||||
return
|
||||
}
|
||||
|
||||
buildCmd := exec.Command(gotool, args...)
|
||||
buildCmd.Env = append(os.Environ(), "GOFLAGS=-mod=vendor")
|
||||
out, err := buildCmd.CombinedOutput()
|
||||
// Duplicate the test executable into the path at testGo, for $PATH.
|
||||
// If the OS supports symlinks, use them instead of copying bytes.
|
||||
testExe, err := os.Executable()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "building testgo failed: %v\n%s", err, out)
|
||||
os.Exit(2)
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink(testExe, testGo); err != nil {
|
||||
// Otherwise, copy the bytes.
|
||||
src, err := os.Open(testExe)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
dst, err := os.OpenFile(testGo, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o777)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = io.Copy(dst, src)
|
||||
if closeErr := dst.Close(); err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command(testGo, "env", "CGO_ENABLED")
|
||||
@ -193,7 +249,7 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
}
|
||||
|
||||
out, err = exec.Command(gotool, "env", "GOCACHE").CombinedOutput()
|
||||
out, err := exec.Command(gotool, "env", "GOCACHE").CombinedOutput()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "could not find testing GOCACHE: %v\n%s", err, out)
|
||||
os.Exit(2)
|
||||
|
@ -371,7 +371,7 @@ func oneMainPkg(pkgs []*load.Package) []*load.Package {
|
||||
|
||||
var pkgsFilter = func(pkgs []*load.Package) []*load.Package { return pkgs }
|
||||
|
||||
var runtimeVersion = runtime.Version()
|
||||
var RuntimeVersion = runtime.Version()
|
||||
|
||||
func runBuild(ctx context.Context, cmd *base.Command, args []string) {
|
||||
modload.InitWorkfile()
|
||||
|
@ -560,7 +560,7 @@ func (b *Builder) build(ctx context.Context, a *Action) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := allowInstall(a); err != nil {
|
||||
if err := AllowInstall(a); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -1346,7 +1346,7 @@ func (b *Builder) link(ctx context.Context, a *Action) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := allowInstall(a); err != nil {
|
||||
if err := AllowInstall(a); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -1527,7 +1527,7 @@ func (b *Builder) getPkgConfigFlags(p *load.Package) (cflags, ldflags []string,
|
||||
}
|
||||
|
||||
func (b *Builder) installShlibname(ctx context.Context, a *Action) error {
|
||||
if err := allowInstall(a); err != nil {
|
||||
if err := AllowInstall(a); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -1581,7 +1581,7 @@ func (b *Builder) linkShared(ctx context.Context, a *Action) (err error) {
|
||||
}
|
||||
defer b.flushOutput(a)
|
||||
|
||||
if err := allowInstall(a); err != nil {
|
||||
if err := AllowInstall(a); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -1652,7 +1652,7 @@ func BuildInstallFunc(b *Builder, ctx context.Context, a *Action) (err error) {
|
||||
if !a.buggyInstall && !b.IsCmdList {
|
||||
if cfg.BuildN {
|
||||
b.Showcmd("", "touch %s", a.Target)
|
||||
} else if err := allowInstall(a); err == nil {
|
||||
} else if err := AllowInstall(a); err == nil {
|
||||
now := time.Now()
|
||||
os.Chtimes(a.Target, now, now)
|
||||
}
|
||||
@ -1666,7 +1666,7 @@ func BuildInstallFunc(b *Builder, ctx context.Context, a *Action) (err error) {
|
||||
a.built = a1.built
|
||||
return nil
|
||||
}
|
||||
if err := allowInstall(a); err != nil {
|
||||
if err := AllowInstall(a); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -1698,12 +1698,12 @@ func BuildInstallFunc(b *Builder, ctx context.Context, a *Action) (err error) {
|
||||
return b.moveOrCopyFile(a.Target, a1.built, perm, false)
|
||||
}
|
||||
|
||||
// allowInstall returns a non-nil error if this invocation of the go command is
|
||||
// AllowInstall returns a non-nil error if this invocation of the go command is
|
||||
// allowed to install a.Target.
|
||||
//
|
||||
// (The build of cmd/go running under its own test is forbidden from installing
|
||||
// to its original GOROOT.)
|
||||
var allowInstall = func(*Action) error { return nil }
|
||||
// The build of cmd/go running under its own test is forbidden from installing
|
||||
// to its original GOROOT. The var is exported so it can be set by TestMain.
|
||||
var AllowInstall = func(*Action) error { return nil }
|
||||
|
||||
// cleanup removes a's object dir to keep the amount of
|
||||
// on-disk garbage down in a large build. On an operating system
|
||||
@ -1868,7 +1868,7 @@ func (b *Builder) installHeader(ctx context.Context, a *Action) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := allowInstall(a); err != nil {
|
||||
if err := AllowInstall(a); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -137,8 +137,8 @@ func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg
|
||||
if p.Internal.OmitDebug || cfg.Goos == "plan9" || cfg.Goarch == "wasm" {
|
||||
defaultGcFlags = append(defaultGcFlags, "-dwarf=false")
|
||||
}
|
||||
if strings.HasPrefix(runtimeVersion, "go1") && !strings.Contains(os.Args[0], "go_bootstrap") {
|
||||
defaultGcFlags = append(defaultGcFlags, "-goversion", runtimeVersion)
|
||||
if strings.HasPrefix(RuntimeVersion, "go1") && !strings.Contains(os.Args[0], "go_bootstrap") {
|
||||
defaultGcFlags = append(defaultGcFlags, "-goversion", RuntimeVersion)
|
||||
}
|
||||
if symabis != "" {
|
||||
defaultGcFlags = append(defaultGcFlags, "-symabis", symabis)
|
||||
|
@ -1,48 +0,0 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
// This file contains extra hooks for testing the go command.
|
||||
|
||||
//go:build testgo
|
||||
|
||||
package work
|
||||
|
||||
import (
|
||||
"cmd/go/internal/cfg"
|
||||
"cmd/go/internal/search"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if v := os.Getenv("TESTGO_VERSION"); v != "" {
|
||||
runtimeVersion = v
|
||||
}
|
||||
|
||||
if testGOROOT := os.Getenv("TESTGO_GOROOT"); testGOROOT != "" {
|
||||
// Disallow installs to the GOROOT from which testgo was built.
|
||||
// Installs to other GOROOTs — such as one set explicitly within a test — are ok.
|
||||
allowInstall = func(a *Action) error {
|
||||
if cfg.BuildN {
|
||||
return nil
|
||||
}
|
||||
|
||||
rel := search.InDir(a.Target, testGOROOT)
|
||||
if rel == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
callerPos := ""
|
||||
if _, file, line, ok := runtime.Caller(1); ok {
|
||||
if shortFile := search.InDir(file, filepath.Join(testGOROOT, "src")); shortFile != "" {
|
||||
file = shortFile
|
||||
}
|
||||
callerPos = fmt.Sprintf("%s:%d: ", file, line)
|
||||
}
|
||||
return fmt.Errorf("%stestgo must not write to GOROOT (installing to %s)", callerPos, filepath.Join("GOROOT", rel))
|
||||
}
|
||||
}
|
||||
}
|
@ -188,6 +188,7 @@ func (ts *testScript) setup() {
|
||||
"goversion=" + goVersion(ts),
|
||||
":=" + string(os.PathListSeparator),
|
||||
"/=" + string(os.PathSeparator),
|
||||
"CMDGO_TEST_RUN_MAIN=true",
|
||||
}
|
||||
if !testenv.HasExternalNetwork() {
|
||||
ts.env = append(ts.env, "TESTGONETWORK=panic", "TESTGOVCS=panic")
|
||||
|
Loading…
Reference in New Issue
Block a user