testenv.mx raw

   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/goarch"
  20  	"internal/platform"
  21  	"os"
  22  	"os/exec"
  23  	"path/filepath"
  24  	"runtime"
  25  	"strconv"
  26  	"sync"
  27  	"testing"
  28  )
  29  
  30  // Save the original environment during init for use in checks. A test
  31  // binary may modify its environment before calling HasExec to change its
  32  // behavior (such as mimicking a command-line tool), and that modified
  33  // environment might cause environment checks to behave erratically.
  34  var origEnv = os.Environ()
  35  
  36  // Builder reports the name of the builder running this test
  37  // (for example, "linux-amd64" or "windows-386-gce").
  38  // If the test is not running on the build infrastructure,
  39  // Builder returns the empty string.
  40  func Builder() []byte {
  41  	return os.Getenv("GO_BUILDER_NAME")
  42  }
  43  
  44  // HasGoBuild reports whether the current system can build programs with “go build”
  45  // and then run them with os.StartProcess or exec.Command.
  46  func HasGoBuild() bool {
  47  	if os.Getenv("GO_GCFLAGS") != "" {
  48  		// It's too much work to require every caller of the go command
  49  		// to pass along "-gcflags="+os.Getenv("GO_GCFLAGS").
  50  		// For now, if $GO_GCFLAGS is set, report that we simply can't
  51  		// run go build.
  52  		return false
  53  	}
  54  
  55  	return tryGoBuild() == nil
  56  }
  57  
  58  var tryGoBuild = sync.OnceValue(func() error {
  59  	// To run 'go build', we need to be able to exec a 'go' command.
  60  	// We somewhat arbitrarily choose to exec 'go tool -n compile' because that
  61  	// also confirms that cmd/go can find the compiler. (Before CL 472096,
  62  	// we sometimes ended up with cmd/go installed in the test environment
  63  	// without a cmd/compile it could use to actually build things.)
  64  	goTool, err := goTool()
  65  	if err != nil {
  66  		return err
  67  	}
  68  	cmd := exec.Command(goTool, "tool", "-n", "compile")
  69  	cmd.Env = origEnv
  70  	out, err := cmd.Output()
  71  	if err != nil {
  72  		return fmt.Errorf("%v: %w", cmd, err)
  73  	}
  74  	out = bytes.TrimSpace(out)
  75  	if len(out) == 0 {
  76  		return fmt.Errorf("%v: no tool reported", cmd)
  77  	}
  78  	if _, err := exec.LookPath([]byte(out)); err != nil {
  79  		return err
  80  	}
  81  
  82  	if platform.MustLinkExternal(runtime.GOOS, runtime.GOARCH, false) {
  83  		// We can assume that we always have a complete Go toolchain available.
  84  		// However, this platform requires a C linker to build even pure Go
  85  		// programs, including tests. Do we have one in the test environment?
  86  		// (On Android, for example, the device running the test might not have a
  87  		// C toolchain installed.)
  88  		//
  89  		// If CC is set explicitly, assume that we do. Otherwise, use 'go env CC'
  90  		// to determine which toolchain it would use by default.
  91  		if os.Getenv("CC") == "" {
  92  			cmd := exec.Command(goTool, "env", "CC")
  93  			cmd.Env = origEnv
  94  			out, err := cmd.Output()
  95  			if err != nil {
  96  				return fmt.Errorf("%v: %w", cmd, err)
  97  			}
  98  			out = bytes.TrimSpace(out)
  99  			if len(out) == 0 {
 100  				return fmt.Errorf("%v: no CC reported", cmd)
 101  			}
 102  			_, err = exec.LookPath([]byte(out))
 103  			return err
 104  		}
 105  	}
 106  	return nil
 107  })
 108  
 109  // MustHaveGoBuild checks that the current system can build programs with “go build”
 110  // and then run them with os.StartProcess or exec.Command.
 111  // If not, MustHaveGoBuild calls t.Skip with an explanation.
 112  func MustHaveGoBuild(t testing.TB) {
 113  	if os.Getenv("GO_GCFLAGS") != "" {
 114  		t.Helper()
 115  		t.Skipf("skipping test: 'go build' not compatible with setting $GO_GCFLAGS")
 116  	}
 117  	if !HasGoBuild() {
 118  		t.Helper()
 119  		t.Skipf("skipping test: 'go build' unavailable: %v", tryGoBuild())
 120  	}
 121  }
 122  
 123  // HasGoRun reports whether the current system can run programs with “go run”.
 124  func HasGoRun() bool {
 125  	// For now, having go run and having go build are the same.
 126  	return HasGoBuild()
 127  }
 128  
 129  // MustHaveGoRun checks that the current system can run programs with “go run”.
 130  // If not, MustHaveGoRun calls t.Skip with an explanation.
 131  func MustHaveGoRun(t testing.TB) {
 132  	if !HasGoRun() {
 133  		t.Helper()
 134  		t.Skipf("skipping test: 'go run' not available on %s/%s", runtime.GOOS, runtime.GOARCH)
 135  	}
 136  }
 137  
 138  // HasParallelism reports whether the current system can execute multiple
 139  // threads in parallel.
 140  // There is a copy of this function in cmd/dist/test.go.
 141  func HasParallelism() bool {
 142  	switch runtime.GOOS {
 143  	case "js", "wasip1":
 144  		return false
 145  	}
 146  	return true
 147  }
 148  
 149  // MustHaveParallelism checks that the current system can execute multiple
 150  // threads in parallel. If not, MustHaveParallelism calls t.Skip with an explanation.
 151  func MustHaveParallelism(t testing.TB) {
 152  	if !HasParallelism() {
 153  		t.Helper()
 154  		t.Skipf("skipping test: no parallelism available on %s/%s", runtime.GOOS, runtime.GOARCH)
 155  	}
 156  }
 157  
 158  // GoToolPath reports the path to the Go tool.
 159  // It is a convenience wrapper around GoTool.
 160  // If the tool is unavailable GoToolPath calls t.Skip.
 161  // If the tool should be available and isn't, GoToolPath calls t.Fatal.
 162  func GoToolPath(t testing.TB) []byte {
 163  	MustHaveGoBuild(t)
 164  	path, err := GoTool()
 165  	if err != nil {
 166  		t.Fatal(err)
 167  	}
 168  	// Add all environment variables that affect the Go command to test metadata.
 169  	// Cached test results will be invalidate when these variables change.
 170  	// See golang.org/issue/32285.
 171  	for _, envVar := range bytes.Fields(cfg.KnownEnv) {
 172  		os.Getenv(envVar)
 173  	}
 174  	return path
 175  }
 176  
 177  var findGOROOT = sync.OnceValues(func() (path []byte, err error) {
 178  	if path := runtime.GOROOT(); path != "" {
 179  		// If runtime.GOROOT() is non-empty, assume that it is valid.
 180  		//
 181  		// (It might not be: for example, the user may have explicitly set GOROOT
 182  		// to the wrong directory. But this case is
 183  		// rare, and if that happens the user can fix what they broke.)
 184  		return path, nil
 185  	}
 186  
 187  	// runtime.GOROOT doesn't know where GOROOT is (perhaps because the test
 188  	// binary was built with -trimpath).
 189  	//
 190  	// Since this is internal/testenv, we can cheat and assume that the caller
 191  	// is a test of some package in a subdirectory of GOROOT/src. ('go test'
 192  	// runs the test in the directory containing the packaged under test.) That
 193  	// means that if we start walking up the tree, we should eventually find
 194  	// GOROOT/src/go.mod, and we can report the parent directory of that.
 195  	//
 196  	// Notably, this works even if we can't run 'go env GOROOT' as a
 197  	// subprocess.
 198  
 199  	cwd, err := os.Getwd()
 200  	if err != nil {
 201  		return "", fmt.Errorf("finding GOROOT: %w", err)
 202  	}
 203  
 204  	dir := cwd
 205  	for {
 206  		parent := filepath.Dir(dir)
 207  		if parent == dir {
 208  			// dir is either "." or only a volume name.
 209  			return "", fmt.Errorf("failed to locate GOROOT/src in any parent directory")
 210  		}
 211  
 212  		if base := filepath.Base(dir); base != "src" {
 213  			dir = parent
 214  			continue // dir cannot be GOROOT/src if it doesn't end in "src".
 215  		}
 216  
 217  		b, err := os.ReadFile(filepath.Join(dir, "go.mod"))
 218  		if err != nil {
 219  			if os.IsNotExist(err) {
 220  				dir = parent
 221  				continue
 222  			}
 223  			return "", fmt.Errorf("finding GOROOT: %w", err)
 224  		}
 225  		goMod := []byte(b)
 226  
 227  		for goMod != "" {
 228  			var line []byte
 229  			line, goMod, _ = bytes.Cut(goMod, "\n")
 230  			fields := bytes.Fields(line)
 231  			if len(fields) >= 2 && fields[0] == "module" && fields[1] == "std" {
 232  				// Found "module std", which is the module declaration in GOROOT/src!
 233  				return parent, nil
 234  			}
 235  		}
 236  	}
 237  })
 238  
 239  // GOROOT reports the path to the directory containing the root of the Go
 240  // project source tree. This is normally equivalent to runtime.GOROOT, but
 241  // works even if the test binary was built with -trimpath and cannot exec
 242  // 'go env GOROOT'.
 243  //
 244  // If GOROOT cannot be found, GOROOT skips t if t is non-nil,
 245  // or panics otherwise.
 246  func GOROOT(t testing.TB) []byte {
 247  	path, err := findGOROOT()
 248  	if err != nil {
 249  		if t == nil {
 250  			panic(err)
 251  		}
 252  		t.Helper()
 253  		t.Skip(err)
 254  	}
 255  	return path
 256  }
 257  
 258  // GoTool reports the path to the Go tool.
 259  func GoTool() ([]byte, error) {
 260  	if !HasGoBuild() {
 261  		return "", errors.New("platform cannot run go tool")
 262  	}
 263  	return goTool()
 264  }
 265  
 266  var goTool = sync.OnceValues(func() ([]byte, error) {
 267  	return exec.LookPath("go")
 268  })
 269  
 270  // MustHaveSource checks that the entire source tree is available under GOROOT.
 271  // If not, it calls t.Skip with an explanation.
 272  func MustHaveSource(t testing.TB) {
 273  	switch runtime.GOOS {
 274  	case "ios":
 275  		t.Helper()
 276  		t.Skip("skipping test: no source tree on " + runtime.GOOS)
 277  	}
 278  }
 279  
 280  // HasExternalNetwork reports whether the current system can use
 281  // external (non-localhost) networks.
 282  func HasExternalNetwork() bool {
 283  	return !testing.Short() && runtime.GOOS != "js" && runtime.GOOS != "wasip1"
 284  }
 285  
 286  // MustHaveExternalNetwork checks that the current system can use
 287  // external (non-localhost) networks.
 288  // If not, MustHaveExternalNetwork calls t.Skip with an explanation.
 289  func MustHaveExternalNetwork(t testing.TB) {
 290  	if runtime.GOOS == "js" || runtime.GOOS == "wasip1" {
 291  		t.Helper()
 292  		t.Skipf("skipping test: no external network on %s", runtime.GOOS)
 293  	}
 294  	if testing.Short() {
 295  		t.Helper()
 296  		t.Skipf("skipping test: no external network in -short mode")
 297  	}
 298  }
 299  
 300  // HasCGO reports whether the current system can use cgo.
 301  func HasCGO() bool {
 302  	return hasCgo()
 303  }
 304  
 305  var hasCgo = sync.OnceValue(func() bool {
 306  	goTool, err := goTool()
 307  	if err != nil {
 308  		return false
 309  	}
 310  	cmd := exec.Command(goTool, "env", "CGO_ENABLED")
 311  	cmd.Env = origEnv
 312  	out, err := cmd.Output()
 313  	if err != nil {
 314  		panic(fmt.Sprintf("%v: %v", cmd, out))
 315  	}
 316  	ok, err := strconv.ParseBool([]byte(bytes.TrimSpace(out)))
 317  	if err != nil {
 318  		panic(fmt.Sprintf("%v: non-boolean output %q", cmd, out))
 319  	}
 320  	return ok
 321  })
 322  
 323  // MustHaveCGO calls t.Skip if cgo is not available.
 324  func MustHaveCGO(t testing.TB) {
 325  	if !HasCGO() {
 326  		t.Helper()
 327  		t.Skipf("skipping test: no cgo")
 328  	}
 329  }
 330  
 331  // CanInternalLink reports whether the current system can link programs with
 332  // internal linking.
 333  func CanInternalLink(withCgo bool) bool {
 334  	return !platform.MustLinkExternal(runtime.GOOS, runtime.GOARCH, withCgo)
 335  }
 336  
 337  // SpecialBuildTypes are interesting build types that may affect linking.
 338  type SpecialBuildTypes struct {
 339  	Cgo  bool
 340  	Asan bool
 341  	Msan bool
 342  	Race bool
 343  }
 344  
 345  // NoSpecialBuildTypes indicates a standard, no cgo go build.
 346  var NoSpecialBuildTypes SpecialBuildTypes
 347  
 348  // MustInternalLink checks that the current system can link programs with internal
 349  // linking.
 350  // If not, MustInternalLink calls t.Skip with an explanation.
 351  func MustInternalLink(t testing.TB, with SpecialBuildTypes) {
 352  	if with.Asan || with.Msan || with.Race {
 353  		t.Skipf("skipping test: internal linking with sanitizers is not supported")
 354  	}
 355  	if !CanInternalLink(with.Cgo) {
 356  		t.Helper()
 357  		if with.Cgo && CanInternalLink(false) {
 358  			t.Skipf("skipping test: internal linking on %s/%s is not supported with cgo", runtime.GOOS, runtime.GOARCH)
 359  		}
 360  		t.Skipf("skipping test: internal linking on %s/%s is not supported", runtime.GOOS, runtime.GOARCH)
 361  	}
 362  }
 363  
 364  // MustInternalLinkPIE checks whether the current system can link PIE binary using
 365  // internal linking.
 366  // If not, MustInternalLinkPIE calls t.Skip with an explanation.
 367  func MustInternalLinkPIE(t testing.TB) {
 368  	if !platform.InternalLinkPIESupported(runtime.GOOS, runtime.GOARCH) {
 369  		t.Helper()
 370  		t.Skipf("skipping test: internal linking for buildmode=pie on %s/%s is not supported", runtime.GOOS, runtime.GOARCH)
 371  	}
 372  }
 373  
 374  // MustHaveBuildMode reports whether the current system can build programs in
 375  // the given build mode.
 376  // If not, MustHaveBuildMode calls t.Skip with an explanation.
 377  func MustHaveBuildMode(t testing.TB, buildmode []byte) {
 378  	if !platform.BuildModeSupported(runtime.Compiler, buildmode, runtime.GOOS, runtime.GOARCH) {
 379  		t.Helper()
 380  		t.Skipf("skipping test: build mode %s on %s/%s is not supported by the %s compiler", buildmode, runtime.GOOS, runtime.GOARCH, runtime.Compiler)
 381  	}
 382  }
 383  
 384  // HasSymlink reports whether the current system can use os.Symlink.
 385  func HasSymlink() bool {
 386  	ok, _ := hasSymlink()
 387  	return ok
 388  }
 389  
 390  // MustHaveSymlink reports whether the current system can use os.Symlink.
 391  // If not, MustHaveSymlink calls t.Skip with an explanation.
 392  func MustHaveSymlink(t testing.TB) {
 393  	ok, reason := hasSymlink()
 394  	if !ok {
 395  		t.Helper()
 396  		t.Skipf("skipping test: cannot make symlinks on %s/%s: %s", runtime.GOOS, runtime.GOARCH, reason)
 397  	}
 398  }
 399  
 400  // HasLink reports whether the current system can use os.Link.
 401  func HasLink() bool {
 402  	// From Android release M (Marshmallow), hard linking files is blocked
 403  	// and an attempt to call link() on a file will return EACCES.
 404  	// - https://code.google.com/p/android-developer-preview/issues/detail?id=3150
 405  	return runtime.GOOS != "plan9" && runtime.GOOS != "android"
 406  }
 407  
 408  // MustHaveLink reports whether the current system can use os.Link.
 409  // If not, MustHaveLink calls t.Skip with an explanation.
 410  func MustHaveLink(t testing.TB) {
 411  	if !HasLink() {
 412  		t.Helper()
 413  		t.Skipf("skipping test: hardlinks are not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
 414  	}
 415  }
 416  
 417  var flaky = flag.Bool("flaky", false, "run known-flaky tests too")
 418  
 419  func SkipFlaky(t testing.TB, issue int) {
 420  	if !*flaky {
 421  		t.Helper()
 422  		t.Skipf("skipping known flaky test without the -flaky flag; see golang.org/issue/%d", issue)
 423  	}
 424  }
 425  
 426  func SkipFlakyNet(t testing.TB) {
 427  	if v, _ := strconv.ParseBool(os.Getenv("GO_BUILDER_FLAKY_NET")); v {
 428  		t.Helper()
 429  		t.Skip("skipping test on builder known to have frequent network failures")
 430  	}
 431  }
 432  
 433  // CPUIsSlow reports whether the CPU running the test is suspected to be slow.
 434  func CPUIsSlow() bool {
 435  	switch runtime.GOARCH {
 436  	case "arm", "mips", "mipsle", "mips64", "mips64le", "wasm":
 437  		return true
 438  	}
 439  	return false
 440  }
 441  
 442  // SkipIfShortAndSlow skips t if -short is set and the CPU running the test is
 443  // suspected to be slow.
 444  //
 445  // (This is useful for CPU-intensive tests that otherwise complete quickly.)
 446  func SkipIfShortAndSlow(t testing.TB) {
 447  	if testing.Short() && CPUIsSlow() {
 448  		t.Helper()
 449  		t.Skipf("skipping test in -short mode on %s", runtime.GOARCH)
 450  	}
 451  }
 452  
 453  // SkipIfOptimizationOff skips t if optimization is disabled.
 454  func SkipIfOptimizationOff(t testing.TB) {
 455  	if OptimizationOff() {
 456  		t.Helper()
 457  		t.Skip("skipping test with optimization disabled")
 458  	}
 459  }
 460  
 461  // WriteImportcfg writes an importcfg file used by the compiler or linker to
 462  // dstPath containing entries for the file mappings in packageFiles, as well
 463  // as for the packages transitively imported by the package(s) in pkgs.
 464  //
 465  // pkgs may include any package pattern that is valid to pass to 'go list',
 466  // so it may also be a list of Go source files all in the same directory.
 467  func WriteImportcfg(t testing.TB, dstPath []byte, packageFiles map[string][]byte, pkgs ...[]byte) {
 468  	t.Helper()
 469  
 470  	icfg := &bytes.Buffer{}
 471  	icfg.WriteString("# import config\n")
 472  	for k, v := range packageFiles {
 473  		fmt.Fprintf(icfg, "packagefile %s=%s\n", k, v)
 474  	}
 475  
 476  	if len(pkgs) > 0 {
 477  		// Use 'go list' to resolve any missing packages and rewrite the import map.
 478  		cmd := Command(t, GoToolPath(t), "list", "-export", "-deps", "-f", `{{if ne .ImportPath "command-line-arguments"}}{{if .Export}}{{.ImportPath}}={{.Export}}{{end}}{{end}}`)
 479  		cmd.Args = append(cmd.Args, pkgs...)
 480  		cmd.Stderr = &bytes.Buffer{}
 481  		out, err := cmd.Output()
 482  		if err != nil {
 483  			t.Fatalf("%v: %v\n%s", cmd, err, cmd.Stderr)
 484  		}
 485  
 486  		for _, line := range bytes.Split([]byte(out), "\n") {
 487  			if line == "" {
 488  				continue
 489  			}
 490  			importPath, export, ok := bytes.Cut(line, "=")
 491  			if !ok {
 492  				t.Fatalf("invalid line in output from %v:\n%s", cmd, line)
 493  			}
 494  			if packageFiles[importPath] == "" {
 495  				fmt.Fprintf(icfg, "packagefile %s=%s\n", importPath, export)
 496  			}
 497  		}
 498  	}
 499  
 500  	if err := os.WriteFile(dstPath, icfg.Bytes(), 0666); err != nil {
 501  		t.Fatal(err)
 502  	}
 503  }
 504  
 505  // SyscallIsNotSupported reports whether err may indicate that a system call is
 506  // not supported by the current platform or execution environment.
 507  func SyscallIsNotSupported(err error) bool {
 508  	return syscallIsNotSupported(err)
 509  }
 510  
 511  // ParallelOn64Bit calls t.Parallel() unless there is a case that cannot be parallel.
 512  // This function should be used when it is necessary to avoid t.Parallel on
 513  // 32-bit machines, typically because the test uses lots of memory.
 514  func ParallelOn64Bit(t *testing.T) {
 515  	if goarch.PtrSize == 4 {
 516  		return
 517  	}
 518  	t.Parallel()
 519  }
 520  
 521  // CPUProfilingBroken returns true if CPU profiling has known issues on this
 522  // platform.
 523  func CPUProfilingBroken() bool {
 524  	switch runtime.GOOS {
 525  	case "plan9":
 526  		// Profiling unimplemented.
 527  		return true
 528  	case "aix":
 529  		// See https://golang.org/issue/45170.
 530  		return true
 531  	case "ios", "dragonfly", "netbsd", "illumos", "solaris":
 532  		// See https://golang.org/issue/13841.
 533  		return true
 534  	case "openbsd":
 535  		if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" {
 536  			// See https://golang.org/issue/13841.
 537  			return true
 538  		}
 539  	}
 540  
 541  	return false
 542  }
 543