github.com/bir3/gocompiler@v0.9.2202/src/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 "github.com/bir3/gocompiler/src/internal/cfg" 19 "github.com/bir3/gocompiler/src/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, or explicitly set GOROOT_FINAL but not GOROOT 193 // and hasn't moved the tree to GOROOT_FINAL yet. But those cases are 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, or perhaps because GOROOT_FINAL was set 200 // without GOROOT and the tree hasn't been moved there yet). 201 // 202 // Since this is internal/testenv, we can cheat and assume that the caller 203 // is a test of some package in a subdirectory of GOROOT/src. ('go test' 204 // runs the test in the directory containing the packaged under test.) That 205 // means that if we start walking up the tree, we should eventually find 206 // GOROOT/src/go.mod, and we can report the parent directory of that. 207 // 208 // Notably, this works even if we can't run 'go env GOROOT' as a 209 // subprocess. 210 211 cwd, err := os.Getwd() 212 if err != nil { 213 gorootErr = fmt.Errorf("finding GOROOT: %w", err) 214 return 215 } 216 217 dir := cwd 218 for { 219 parent := filepath.Dir(dir) 220 if parent == dir { 221 // dir is either "." or only a volume name. 222 gorootErr = fmt.Errorf("failed to locate GOROOT/src in any parent directory") 223 return 224 } 225 226 if base := filepath.Base(dir); base != "src" { 227 dir = parent 228 continue // dir cannot be GOROOT/src if it doesn't end in "src". 229 } 230 231 b, err := os.ReadFile(filepath.Join(dir, "go.mod")) 232 if err != nil { 233 if os.IsNotExist(err) { 234 dir = parent 235 continue 236 } 237 gorootErr = fmt.Errorf("finding GOROOT: %w", err) 238 return 239 } 240 goMod := string(b) 241 242 for goMod != "" { 243 var line string 244 line, goMod, _ = strings.Cut(goMod, "\n") 245 fields := strings.Fields(line) 246 if len(fields) >= 2 && fields[0] == "module" && fields[1] == "std" { 247 // Found "module std", which is the module declaration in GOROOT/src! 248 gorootPath = parent 249 return 250 } 251 } 252 } 253 }) 254 255 return gorootPath, gorootErr 256 } 257 258 // GOROOT reports the path to the directory containing the root of the Go 259 // project source tree. This is normally equivalent to runtime.GOROOT, but 260 // works even if the test binary was built with -trimpath and cannot exec 261 // 'go env GOROOT'. 262 // 263 // If GOROOT cannot be found, GOROOT skips t if t is non-nil, 264 // or panics otherwise. 265 func GOROOT(t testing.TB) string { 266 path, err := findGOROOT() 267 if err != nil { 268 if t == nil { 269 panic(err) 270 } 271 t.Helper() 272 t.Skip(err) 273 } 274 return path 275 } 276 277 // GoTool reports the path to the Go tool. 278 func GoTool() (string, error) { 279 if !HasGoBuild() { 280 return "", errors.New("platform cannot run go tool") 281 } 282 goToolOnce.Do(func() { 283 goToolPath, goToolErr = exec.LookPath("go") 284 }) 285 return goToolPath, goToolErr 286 } 287 288 var ( 289 goToolOnce sync.Once 290 goToolPath string 291 goToolErr error 292 ) 293 294 // HasSrc reports whether the entire source tree is available under GOROOT. 295 func HasSrc() bool { 296 switch runtime.GOOS { 297 case "ios": 298 return false 299 } 300 return true 301 } 302 303 // HasExternalNetwork reports whether the current system can use 304 // external (non-localhost) networks. 305 func HasExternalNetwork() bool { 306 return !testing.Short() && runtime.GOOS != "js" && runtime.GOOS != "wasip1" 307 } 308 309 // MustHaveExternalNetwork checks that the current system can use 310 // external (non-localhost) networks. 311 // If not, MustHaveExternalNetwork calls t.Skip with an explanation. 312 func MustHaveExternalNetwork(t testing.TB) { 313 if runtime.GOOS == "js" || runtime.GOOS == "wasip1" { 314 t.Helper() 315 t.Skipf("skipping test: no external network on %s", runtime.GOOS) 316 } 317 if testing.Short() { 318 t.Helper() 319 t.Skipf("skipping test: no external network in -short mode") 320 } 321 } 322 323 // HasCGO reports whether the current system can use cgo. 324 func HasCGO() bool { 325 hasCgoOnce.Do(func() { 326 goTool, err := GoTool() 327 if err != nil { 328 return 329 } 330 cmd := exec.Command(goTool, "env", "CGO_ENABLED") 331 cmd.Env = origEnv 332 out, err := cmd.Output() 333 if err != nil { 334 panic(fmt.Sprintf("%v: %v", cmd, out)) 335 } 336 hasCgo, err = strconv.ParseBool(string(bytes.TrimSpace(out))) 337 if err != nil { 338 panic(fmt.Sprintf("%v: non-boolean output %q", cmd, out)) 339 } 340 }) 341 return hasCgo 342 } 343 344 var ( 345 hasCgoOnce sync.Once 346 hasCgo bool 347 ) 348 349 // MustHaveCGO calls t.Skip if cgo is not available. 350 func MustHaveCGO(t testing.TB) { 351 if !HasCGO() { 352 t.Skipf("skipping test: no cgo") 353 } 354 } 355 356 // CanInternalLink reports whether the current system can link programs with 357 // internal linking. 358 func CanInternalLink(withCgo bool) bool { 359 return !platform.MustLinkExternal(runtime.GOOS, runtime.GOARCH, withCgo) 360 } 361 362 // MustInternalLink checks that the current system can link programs with internal 363 // linking. 364 // If not, MustInternalLink calls t.Skip with an explanation. 365 func MustInternalLink(t testing.TB, withCgo bool) { 366 if !CanInternalLink(withCgo) { 367 if withCgo && CanInternalLink(false) { 368 t.Skipf("skipping test: internal linking on %s/%s is not supported with cgo", runtime.GOOS, runtime.GOARCH) 369 } 370 t.Skipf("skipping test: internal linking 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 string) { 378 if !platform.BuildModeSupported(runtime.Compiler, buildmode, runtime.GOOS, runtime.GOARCH) { 379 t.Skipf("skipping test: build mode %s on %s/%s is not supported by the %s compiler", buildmode, runtime.GOOS, runtime.GOARCH, runtime.Compiler) 380 } 381 } 382 383 // HasSymlink reports whether the current system can use os.Symlink. 384 func HasSymlink() bool { 385 ok, _ := hasSymlink() 386 return ok 387 } 388 389 // MustHaveSymlink reports whether the current system can use os.Symlink. 390 // If not, MustHaveSymlink calls t.Skip with an explanation. 391 func MustHaveSymlink(t testing.TB) { 392 ok, reason := hasSymlink() 393 if !ok { 394 t.Skipf("skipping test: cannot make symlinks on %s/%s: %s", runtime.GOOS, runtime.GOARCH, reason) 395 } 396 } 397 398 // HasLink reports whether the current system can use os.Link. 399 func HasLink() bool { 400 // From Android release M (Marshmallow), hard linking files is blocked 401 // and an attempt to call link() on a file will return EACCES. 402 // - https://code.google.com/p/android-developer-preview/issues/detail?id=3150 403 return runtime.GOOS != "plan9" && runtime.GOOS != "android" 404 } 405 406 // MustHaveLink reports whether the current system can use os.Link. 407 // If not, MustHaveLink calls t.Skip with an explanation. 408 func MustHaveLink(t testing.TB) { 409 if !HasLink() { 410 t.Skipf("skipping test: hardlinks are not supported on %s/%s", runtime.GOOS, runtime.GOARCH) 411 } 412 } 413 414 var flaky = flag.Bool("flaky", false, "run known-flaky tests too") 415 416 func SkipFlaky(t testing.TB, issue int) { 417 t.Helper() 418 if !*flaky { 419 t.Skipf("skipping known flaky test without the -flaky flag; see golang.org/issue/%d", issue) 420 } 421 } 422 423 func SkipFlakyNet(t testing.TB) { 424 t.Helper() 425 if v, _ := strconv.ParseBool(os.Getenv("GO_BUILDER_FLAKY_NET")); v { 426 t.Skip("skipping test on builder known to have frequent network failures") 427 } 428 } 429 430 // CPUIsSlow reports whether the CPU running the test is suspected to be slow. 431 func CPUIsSlow() bool { 432 switch runtime.GOARCH { 433 case "arm", "mips", "mipsle", "mips64", "mips64le", "wasm": 434 return true 435 } 436 return false 437 } 438 439 // SkipIfShortAndSlow skips t if -short is set and the CPU running the test is 440 // suspected to be slow. 441 // 442 // (This is useful for CPU-intensive tests that otherwise complete quickly.) 443 func SkipIfShortAndSlow(t testing.TB) { 444 if testing.Short() && CPUIsSlow() { 445 t.Helper() 446 t.Skipf("skipping test in -short mode on %s", runtime.GOARCH) 447 } 448 } 449 450 // SkipIfOptimizationOff skips t if optimization is disabled. 451 func SkipIfOptimizationOff(t testing.TB) { 452 if OptimizationOff() { 453 t.Helper() 454 t.Skip("skipping test with optimization disabled") 455 } 456 } 457 458 // WriteImportcfg writes an importcfg file used by the compiler or linker to 459 // dstPath containing entries for the file mappings in packageFiles, as well 460 // as for the packages transitively imported by the package(s) in pkgs. 461 // 462 // pkgs may include any package pattern that is valid to pass to 'go list', 463 // so it may also be a list of Go source files all in the same directory. 464 func WriteImportcfg(t testing.TB, dstPath string, packageFiles map[string]string, pkgs ...string) { 465 t.Helper() 466 467 icfg := new(bytes.Buffer) 468 icfg.WriteString("# import config\n") 469 for k, v := range packageFiles { 470 fmt.Fprintf(icfg, "packagefile %s=%s\n", k, v) 471 } 472 473 if len(pkgs) > 0 { 474 // Use 'go list' to resolve any missing packages and rewrite the import map. 475 cmd := Command(t, GoToolPath(t), "list", "-export", "-deps", "-f", `{{if ne .ImportPath "command-line-arguments"}}{{if .Export}}{{.ImportPath}}={{.Export}}{{end}}{{end}}`) 476 cmd.Args = append(cmd.Args, pkgs...) 477 cmd.Stderr = new(strings.Builder) 478 out, err := cmd.Output() 479 if err != nil { 480 t.Fatalf("%v: %v\n%s", cmd, err, cmd.Stderr) 481 } 482 483 for _, line := range strings.Split(string(out), "\n") { 484 if line == "" { 485 continue 486 } 487 importPath, export, ok := strings.Cut(line, "=") 488 if !ok { 489 t.Fatalf("invalid line in output from %v:\n%s", cmd, line) 490 } 491 if packageFiles[importPath] == "" { 492 fmt.Fprintf(icfg, "packagefile %s=%s\n", importPath, export) 493 } 494 } 495 } 496 497 if err := os.WriteFile(dstPath, icfg.Bytes(), 0666); err != nil { 498 t.Fatal(err) 499 } 500 } 501 502 // SyscallIsNotSupported reports whether err may indicate that a system call is 503 // not supported by the current platform or execution environment. 504 func SyscallIsNotSupported(err error) bool { 505 return syscallIsNotSupported(err) 506 }