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