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 }