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