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