github.com/mtsmfm/go/src@v0.0.0-20221020090648-44bdcb9f8fde/internal/testenv/testenv.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package testenv provides information about what functionality
     6  // is available in different testing environments run by the Go team.
     7  //
     8  // It is an internal package because these details are specific
     9  // to the Go team's test setup (on build.golang.org) and not
    10  // fundamental to tests in general.
    11  package testenv
    12  
    13  import (
    14  	"bytes"
    15  	"errors"
    16  	"flag"
    17  	"fmt"
    18  	"internal/cfg"
    19  	"internal/goroot"
    20  	"internal/platform"
    21  	"os"
    22  	"os/exec"
    23  	"path/filepath"
    24  	"runtime"
    25  	"strconv"
    26  	"strings"
    27  	"sync"
    28  	"testing"
    29  	"time"
    30  )
    31  
    32  // Builder reports the name of the builder running this test
    33  // (for example, "linux-amd64" or "windows-386-gce").
    34  // If the test is not running on the build infrastructure,
    35  // Builder returns the empty string.
    36  func Builder() string {
    37  	return os.Getenv("GO_BUILDER_NAME")
    38  }
    39  
    40  // HasGoBuild reports whether the current system can build programs with “go build”
    41  // and then run them with os.StartProcess or exec.Command.
    42  func HasGoBuild() bool {
    43  	if os.Getenv("GO_GCFLAGS") != "" {
    44  		// It's too much work to require every caller of the go command
    45  		// to pass along "-gcflags="+os.Getenv("GO_GCFLAGS").
    46  		// For now, if $GO_GCFLAGS is set, report that we simply can't
    47  		// run go build.
    48  		return false
    49  	}
    50  	switch runtime.GOOS {
    51  	case "android", "js", "ios":
    52  		return false
    53  	}
    54  	return true
    55  }
    56  
    57  // MustHaveGoBuild checks that the current system can build programs with “go build”
    58  // and then run them with os.StartProcess or exec.Command.
    59  // If not, MustHaveGoBuild calls t.Skip with an explanation.
    60  func MustHaveGoBuild(t testing.TB) {
    61  	if os.Getenv("GO_GCFLAGS") != "" {
    62  		t.Skipf("skipping test: 'go build' not compatible with setting $GO_GCFLAGS")
    63  	}
    64  	if !HasGoBuild() {
    65  		t.Skipf("skipping test: 'go build' not available on %s/%s", runtime.GOOS, runtime.GOARCH)
    66  	}
    67  }
    68  
    69  // HasGoRun reports whether the current system can run programs with “go run.”
    70  func HasGoRun() bool {
    71  	// For now, having go run and having go build are the same.
    72  	return HasGoBuild()
    73  }
    74  
    75  // MustHaveGoRun checks that the current system can run programs with “go run.”
    76  // If not, MustHaveGoRun calls t.Skip with an explanation.
    77  func MustHaveGoRun(t testing.TB) {
    78  	if !HasGoRun() {
    79  		t.Skipf("skipping test: 'go run' not available on %s/%s", runtime.GOOS, runtime.GOARCH)
    80  	}
    81  }
    82  
    83  // GoToolPath reports the path to the Go tool.
    84  // It is a convenience wrapper around GoTool.
    85  // If the tool is unavailable GoToolPath calls t.Skip.
    86  // If the tool should be available and isn't, GoToolPath calls t.Fatal.
    87  func GoToolPath(t testing.TB) string {
    88  	MustHaveGoBuild(t)
    89  	path, err := GoTool()
    90  	if err != nil {
    91  		t.Fatal(err)
    92  	}
    93  	// Add all environment variables that affect the Go command to test metadata.
    94  	// Cached test results will be invalidate when these variables change.
    95  	// See golang.org/issue/32285.
    96  	for _, envVar := range strings.Fields(cfg.KnownEnv) {
    97  		os.Getenv(envVar)
    98  	}
    99  	return path
   100  }
   101  
   102  var (
   103  	gorootOnce sync.Once
   104  	gorootPath string
   105  	gorootErr  error
   106  )
   107  
   108  func findGOROOT() (string, error) {
   109  	gorootOnce.Do(func() {
   110  		gorootPath = runtime.GOROOT()
   111  		if gorootPath != "" {
   112  			// If runtime.GOROOT() is non-empty, assume that it is valid.
   113  			//
   114  			// (It might not be: for example, the user may have explicitly set GOROOT
   115  			// to the wrong directory, or explicitly set GOROOT_FINAL but not GOROOT
   116  			// and hasn't moved the tree to GOROOT_FINAL yet. But those cases are
   117  			// rare, and if that happens the user can fix what they broke.)
   118  			return
   119  		}
   120  
   121  		// runtime.GOROOT doesn't know where GOROOT is (perhaps because the test
   122  		// binary was built with -trimpath, or perhaps because GOROOT_FINAL was set
   123  		// without GOROOT and the tree hasn't been moved there yet).
   124  		//
   125  		// Since this is internal/testenv, we can cheat and assume that the caller
   126  		// is a test of some package in a subdirectory of GOROOT/src. ('go test'
   127  		// runs the test in the directory containing the packaged under test.) That
   128  		// means that if we start walking up the tree, we should eventually find
   129  		// GOROOT/src/go.mod, and we can report the parent directory of that.
   130  
   131  		cwd, err := os.Getwd()
   132  		if err != nil {
   133  			gorootErr = fmt.Errorf("finding GOROOT: %w", err)
   134  			return
   135  		}
   136  
   137  		dir := cwd
   138  		for {
   139  			parent := filepath.Dir(dir)
   140  			if parent == dir {
   141  				// dir is either "." or only a volume name.
   142  				gorootErr = fmt.Errorf("failed to locate GOROOT/src in any parent directory")
   143  				return
   144  			}
   145  
   146  			if base := filepath.Base(dir); base != "src" {
   147  				dir = parent
   148  				continue // dir cannot be GOROOT/src if it doesn't end in "src".
   149  			}
   150  
   151  			b, err := os.ReadFile(filepath.Join(dir, "go.mod"))
   152  			if err != nil {
   153  				if os.IsNotExist(err) {
   154  					dir = parent
   155  					continue
   156  				}
   157  				gorootErr = fmt.Errorf("finding GOROOT: %w", err)
   158  				return
   159  			}
   160  			goMod := string(b)
   161  
   162  			for goMod != "" {
   163  				var line string
   164  				line, goMod, _ = strings.Cut(goMod, "\n")
   165  				fields := strings.Fields(line)
   166  				if len(fields) >= 2 && fields[0] == "module" && fields[1] == "std" {
   167  					// Found "module std", which is the module declaration in GOROOT/src!
   168  					gorootPath = parent
   169  					return
   170  				}
   171  			}
   172  		}
   173  	})
   174  
   175  	return gorootPath, gorootErr
   176  }
   177  
   178  // GOROOT reports the path to the directory containing the root of the Go
   179  // project source tree. This is normally equivalent to runtime.GOROOT, but
   180  // works even if the test binary was built with -trimpath.
   181  //
   182  // If GOROOT cannot be found, GOROOT skips t if t is non-nil,
   183  // or panics otherwise.
   184  func GOROOT(t testing.TB) string {
   185  	path, err := findGOROOT()
   186  	if err != nil {
   187  		if t == nil {
   188  			panic(err)
   189  		}
   190  		t.Helper()
   191  		t.Skip(err)
   192  	}
   193  	return path
   194  }
   195  
   196  // GoTool reports the path to the Go tool.
   197  func GoTool() (string, error) {
   198  	if !HasGoBuild() {
   199  		return "", errors.New("platform cannot run go tool")
   200  	}
   201  	var exeSuffix string
   202  	if runtime.GOOS == "windows" {
   203  		exeSuffix = ".exe"
   204  	}
   205  	goroot, err := findGOROOT()
   206  	if err != nil {
   207  		return "", fmt.Errorf("cannot find go tool: %w", err)
   208  	}
   209  	path := filepath.Join(goroot, "bin", "go"+exeSuffix)
   210  	if _, err := os.Stat(path); err == nil {
   211  		return path, nil
   212  	}
   213  	goBin, err := exec.LookPath("go" + exeSuffix)
   214  	if err != nil {
   215  		return "", errors.New("cannot find go tool: " + err.Error())
   216  	}
   217  	return goBin, nil
   218  }
   219  
   220  // HasExec reports whether the current system can start new processes
   221  // using os.StartProcess or (more commonly) exec.Command.
   222  func HasExec() bool {
   223  	switch runtime.GOOS {
   224  	case "js", "ios":
   225  		return false
   226  	}
   227  	return true
   228  }
   229  
   230  // HasSrc reports whether the entire source tree is available under GOROOT.
   231  func HasSrc() bool {
   232  	switch runtime.GOOS {
   233  	case "ios":
   234  		return false
   235  	}
   236  	return true
   237  }
   238  
   239  // MustHaveExec checks that the current system can start new processes
   240  // using os.StartProcess or (more commonly) exec.Command.
   241  // If not, MustHaveExec calls t.Skip with an explanation.
   242  func MustHaveExec(t testing.TB) {
   243  	if !HasExec() {
   244  		t.Skipf("skipping test: cannot exec subprocess on %s/%s", runtime.GOOS, runtime.GOARCH)
   245  	}
   246  }
   247  
   248  var execPaths sync.Map // path -> error
   249  
   250  // MustHaveExecPath checks that the current system can start the named executable
   251  // using os.StartProcess or (more commonly) exec.Command.
   252  // If not, MustHaveExecPath calls t.Skip with an explanation.
   253  func MustHaveExecPath(t testing.TB, path string) {
   254  	MustHaveExec(t)
   255  
   256  	err, found := execPaths.Load(path)
   257  	if !found {
   258  		_, err = exec.LookPath(path)
   259  		err, _ = execPaths.LoadOrStore(path, err)
   260  	}
   261  	if err != nil {
   262  		t.Skipf("skipping test: %s: %s", path, err)
   263  	}
   264  }
   265  
   266  // HasExternalNetwork reports whether the current system can use
   267  // external (non-localhost) networks.
   268  func HasExternalNetwork() bool {
   269  	return !testing.Short() && runtime.GOOS != "js"
   270  }
   271  
   272  // MustHaveExternalNetwork checks that the current system can use
   273  // external (non-localhost) networks.
   274  // If not, MustHaveExternalNetwork calls t.Skip with an explanation.
   275  func MustHaveExternalNetwork(t testing.TB) {
   276  	if runtime.GOOS == "js" {
   277  		t.Skipf("skipping test: no external network on %s", runtime.GOOS)
   278  	}
   279  	if testing.Short() {
   280  		t.Skipf("skipping test: no external network in -short mode")
   281  	}
   282  }
   283  
   284  var haveCGO bool
   285  
   286  // HasCGO reports whether the current system can use cgo.
   287  func HasCGO() bool {
   288  	return haveCGO
   289  }
   290  
   291  // MustHaveCGO calls t.Skip if cgo is not available.
   292  func MustHaveCGO(t testing.TB) {
   293  	if !haveCGO {
   294  		t.Skipf("skipping test: no cgo")
   295  	}
   296  }
   297  
   298  // CanInternalLink reports whether the current system can link programs with
   299  // internal linking.
   300  func CanInternalLink() bool {
   301  	return !platform.MustLinkExternal(runtime.GOOS, runtime.GOARCH)
   302  }
   303  
   304  // MustInternalLink checks that the current system can link programs with internal
   305  // linking.
   306  // If not, MustInternalLink calls t.Skip with an explanation.
   307  func MustInternalLink(t testing.TB) {
   308  	if !CanInternalLink() {
   309  		t.Skipf("skipping test: internal linking on %s/%s is not supported", runtime.GOOS, runtime.GOARCH)
   310  	}
   311  }
   312  
   313  // HasSymlink reports whether the current system can use os.Symlink.
   314  func HasSymlink() bool {
   315  	ok, _ := hasSymlink()
   316  	return ok
   317  }
   318  
   319  // MustHaveSymlink reports whether the current system can use os.Symlink.
   320  // If not, MustHaveSymlink calls t.Skip with an explanation.
   321  func MustHaveSymlink(t testing.TB) {
   322  	ok, reason := hasSymlink()
   323  	if !ok {
   324  		t.Skipf("skipping test: cannot make symlinks on %s/%s%s", runtime.GOOS, runtime.GOARCH, reason)
   325  	}
   326  }
   327  
   328  // HasLink reports whether the current system can use os.Link.
   329  func HasLink() bool {
   330  	// From Android release M (Marshmallow), hard linking files is blocked
   331  	// and an attempt to call link() on a file will return EACCES.
   332  	// - https://code.google.com/p/android-developer-preview/issues/detail?id=3150
   333  	return runtime.GOOS != "plan9" && runtime.GOOS != "android"
   334  }
   335  
   336  // MustHaveLink reports whether the current system can use os.Link.
   337  // If not, MustHaveLink calls t.Skip with an explanation.
   338  func MustHaveLink(t testing.TB) {
   339  	if !HasLink() {
   340  		t.Skipf("skipping test: hardlinks are not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
   341  	}
   342  }
   343  
   344  var flaky = flag.Bool("flaky", false, "run known-flaky tests too")
   345  
   346  func SkipFlaky(t testing.TB, issue int) {
   347  	t.Helper()
   348  	if !*flaky {
   349  		t.Skipf("skipping known flaky test without the -flaky flag; see golang.org/issue/%d", issue)
   350  	}
   351  }
   352  
   353  func SkipFlakyNet(t testing.TB) {
   354  	t.Helper()
   355  	if v, _ := strconv.ParseBool(os.Getenv("GO_BUILDER_FLAKY_NET")); v {
   356  		t.Skip("skipping test on builder known to have frequent network failures")
   357  	}
   358  }
   359  
   360  // CleanCmdEnv will fill cmd.Env with the environment, excluding certain
   361  // variables that could modify the behavior of the Go tools such as
   362  // GODEBUG and GOTRACEBACK.
   363  func CleanCmdEnv(cmd *exec.Cmd) *exec.Cmd {
   364  	if cmd.Env != nil {
   365  		panic("environment already set")
   366  	}
   367  	for _, env := range os.Environ() {
   368  		// Exclude GODEBUG from the environment to prevent its output
   369  		// from breaking tests that are trying to parse other command output.
   370  		if strings.HasPrefix(env, "GODEBUG=") {
   371  			continue
   372  		}
   373  		// Exclude GOTRACEBACK for the same reason.
   374  		if strings.HasPrefix(env, "GOTRACEBACK=") {
   375  			continue
   376  		}
   377  		cmd.Env = append(cmd.Env, env)
   378  	}
   379  	return cmd
   380  }
   381  
   382  // CPUIsSlow reports whether the CPU running the test is suspected to be slow.
   383  func CPUIsSlow() bool {
   384  	switch runtime.GOARCH {
   385  	case "arm", "mips", "mipsle", "mips64", "mips64le":
   386  		return true
   387  	}
   388  	return false
   389  }
   390  
   391  // SkipIfShortAndSlow skips t if -short is set and the CPU running the test is
   392  // suspected to be slow.
   393  //
   394  // (This is useful for CPU-intensive tests that otherwise complete quickly.)
   395  func SkipIfShortAndSlow(t testing.TB) {
   396  	if testing.Short() && CPUIsSlow() {
   397  		t.Helper()
   398  		t.Skipf("skipping test in -short mode on %s", runtime.GOARCH)
   399  	}
   400  }
   401  
   402  // SkipIfOptimizationOff skips t if optimization is disabled.
   403  func SkipIfOptimizationOff(t testing.TB) {
   404  	if OptimizationOff() {
   405  		t.Helper()
   406  		t.Skip("skipping test with optimization disabled")
   407  	}
   408  }
   409  
   410  // RunWithTimeout runs cmd and returns its combined output. If the
   411  // subprocess exits with a non-zero status, it will log that status
   412  // and return a non-nil error, but this is not considered fatal.
   413  func RunWithTimeout(t testing.TB, cmd *exec.Cmd) ([]byte, error) {
   414  	args := cmd.Args
   415  	if args == nil {
   416  		args = []string{cmd.Path}
   417  	}
   418  
   419  	var b bytes.Buffer
   420  	cmd.Stdout = &b
   421  	cmd.Stderr = &b
   422  	if err := cmd.Start(); err != nil {
   423  		t.Fatalf("starting %s: %v", args, err)
   424  	}
   425  
   426  	// If the process doesn't complete within 1 minute,
   427  	// assume it is hanging and kill it to get a stack trace.
   428  	p := cmd.Process
   429  	done := make(chan bool)
   430  	go func() {
   431  		scale := 1
   432  		// This GOARCH/GOOS test is copied from cmd/dist/test.go.
   433  		// TODO(iant): Have cmd/dist update the environment variable.
   434  		if runtime.GOARCH == "arm" || runtime.GOOS == "windows" {
   435  			scale = 2
   436  		}
   437  		if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
   438  			if sc, err := strconv.Atoi(s); err == nil {
   439  				scale = sc
   440  			}
   441  		}
   442  
   443  		select {
   444  		case <-done:
   445  		case <-time.After(time.Duration(scale) * time.Minute):
   446  			p.Signal(Sigquit)
   447  			// If SIGQUIT doesn't do it after a little
   448  			// while, kill the process.
   449  			select {
   450  			case <-done:
   451  			case <-time.After(time.Duration(scale) * 30 * time.Second):
   452  				p.Signal(os.Kill)
   453  			}
   454  		}
   455  	}()
   456  
   457  	err := cmd.Wait()
   458  	if err != nil {
   459  		t.Logf("%s exit status: %v", args, err)
   460  	}
   461  	close(done)
   462  
   463  	return b.Bytes(), err
   464  }
   465  
   466  // WriteImportcfg writes an importcfg file used by the compiler or linker to
   467  // dstPath containing entries for the packages in std and cmd in addition
   468  // to the package to package file mappings in additionalPackageFiles.
   469  func WriteImportcfg(t testing.TB, dstPath string, additionalPackageFiles map[string]string) {
   470  	importcfg, err := goroot.Importcfg()
   471  	for k, v := range additionalPackageFiles {
   472  		importcfg += fmt.Sprintf("\npackagefile %s=%s", k, v)
   473  	}
   474  	if err != nil {
   475  		t.Fatalf("preparing the importcfg failed: %s", err)
   476  	}
   477  	os.WriteFile(dstPath, []byte(importcfg), 0655)
   478  	if err != nil {
   479  		t.Fatalf("writing the importcfg failed: %s", err)
   480  	}
   481  }