1
2
3
4
5
6
7
8
9
10
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 "strings"
27 "sync"
28 "testing"
29 )
30
31
32
33
34
35 var origEnv = os.Environ()
36
37
38
39
40
41
42
43
44
45
46 func Builder() string {
47 return os.Getenv("GO_BUILDER_NAME")
48 }
49
50
51
52 func HasGoBuild() bool {
53 if os.Getenv("GO_GCFLAGS") != "" {
54
55
56
57
58 return false
59 }
60
61 return tryGoBuild() == nil
62 }
63
64 var tryGoBuild = sync.OnceValue(func() error {
65
66
67
68
69
70 goTool, err := goTool()
71 if err != nil {
72 return err
73 }
74 cmd := exec.Command(goTool, "tool", "-n", "compile")
75 cmd.Env = origEnv
76 out, err := cmd.Output()
77 if err != nil {
78 return fmt.Errorf("%v: %w", cmd, err)
79 }
80 out = bytes.TrimSpace(out)
81 if len(out) == 0 {
82 return fmt.Errorf("%v: no tool reported", cmd)
83 }
84 if _, err := exec.LookPath(string(out)); err != nil {
85 return err
86 }
87
88 if platform.MustLinkExternal(runtime.GOOS, runtime.GOARCH, false) {
89
90
91
92
93
94
95
96
97 if os.Getenv("CC") == "" {
98 cmd := exec.Command(goTool, "env", "CC")
99 cmd.Env = origEnv
100 out, err := cmd.Output()
101 if err != nil {
102 return fmt.Errorf("%v: %w", cmd, err)
103 }
104 out = bytes.TrimSpace(out)
105 if len(out) == 0 {
106 return fmt.Errorf("%v: no CC reported", cmd)
107 }
108 _, err = exec.LookPath(string(out))
109 return err
110 }
111 }
112 return nil
113 })
114
115
116
117
118 func MustHaveGoBuild(t testing.TB) {
119 if os.Getenv("GO_GCFLAGS") != "" {
120 t.Helper()
121 t.Skipf("skipping test: 'go build' not compatible with setting $GO_GCFLAGS")
122 }
123 if !HasGoBuild() {
124 t.Helper()
125 t.Skipf("skipping test: 'go build' unavailable: %v", tryGoBuild())
126 }
127 }
128
129
130 func HasGoRun() bool {
131
132 return HasGoBuild()
133 }
134
135
136
137 func MustHaveGoRun(t testing.TB) {
138 if !HasGoRun() {
139 t.Helper()
140 t.Skipf("skipping test: 'go run' not available on %s/%s", runtime.GOOS, runtime.GOARCH)
141 }
142 }
143
144
145
146
147 func HasParallelism() bool {
148 switch runtime.GOOS {
149 case "js", "wasip1":
150 return false
151 }
152 return true
153 }
154
155
156
157 func MustHaveParallelism(t testing.TB) {
158 if !HasParallelism() {
159 t.Helper()
160 t.Skipf("skipping test: no parallelism available on %s/%s", runtime.GOOS, runtime.GOARCH)
161 }
162 }
163
164
165
166
167
168 func GoToolPath(t testing.TB) string {
169 MustHaveGoBuild(t)
170 path, err := GoTool()
171 if err != nil {
172 t.Fatal(err)
173 }
174
175
176
177 for _, envVar := range strings.Fields(cfg.KnownEnv) {
178 os.Getenv(envVar)
179 }
180 return path
181 }
182
183 var findGOROOT = sync.OnceValues(func() (path string, err error) {
184 if path := runtime.GOROOT(); path != "" {
185
186
187
188
189
190 return path, nil
191 }
192
193
194
195
196
197
198
199
200
201
202
203
204
205 cwd, err := os.Getwd()
206 if err != nil {
207 return "", fmt.Errorf("finding GOROOT: %w", err)
208 }
209
210 dir := cwd
211 for {
212 parent := filepath.Dir(dir)
213 if parent == dir {
214
215 return "", fmt.Errorf("failed to locate GOROOT/src in any parent directory")
216 }
217
218 if base := filepath.Base(dir); base != "src" {
219 dir = parent
220 continue
221 }
222
223 b, err := os.ReadFile(filepath.Join(dir, "go.mod"))
224 if err != nil {
225 if os.IsNotExist(err) {
226 dir = parent
227 continue
228 }
229 return "", fmt.Errorf("finding GOROOT: %w", err)
230 }
231 goMod := string(b)
232
233 for goMod != "" {
234 var line string
235 line, goMod, _ = strings.Cut(goMod, "\n")
236 fields := strings.Fields(line)
237 if len(fields) >= 2 && fields[0] == "module" && fields[1] == "std" {
238
239 return parent, nil
240 }
241 }
242 }
243 })
244
245
246
247
248
249
250
251
252 func GOROOT(t testing.TB) string {
253 path, err := findGOROOT()
254 if err != nil {
255 if t == nil {
256 panic(err)
257 }
258 t.Helper()
259 t.Skip(err)
260 }
261 return path
262 }
263
264
265 func GoTool() (string, error) {
266 if !HasGoBuild() {
267 return "", errors.New("platform cannot run go tool")
268 }
269 return goTool()
270 }
271
272 var goTool = sync.OnceValues(func() (string, error) {
273 return exec.LookPath("go")
274 })
275
276
277
278
279
280 func MustHaveSource(t testing.TB) {
281 t.Helper()
282 switch runtime.GOOS {
283 case "ios":
284 t.Skip("skipping test: no source tree on " + runtime.GOOS)
285 }
286 if Builder() != "" {
287
288
289 return
290 }
291
292
293 goroot, err := findGOROOT()
294 if err != nil {
295 t.Skipf("skipping test: cannot locate GOROOT: %v", err)
296 }
297 if _, err := os.Stat(filepath.Join(goroot, "src", "go.mod")); err != nil {
298 t.Skipf("skipping test: GOROOT/src not available: %v", err)
299 }
300 }
301
302
303
304 func HasExternalNetwork() bool {
305 return !testing.Short() && runtime.GOOS != "js" && runtime.GOOS != "wasip1"
306 }
307
308
309
310
311 func 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
323 func HasCGO() bool {
324 return hasCgo()
325 }
326
327 var hasCgo = sync.OnceValue(func() bool {
328 goTool, err := goTool()
329 if err != nil {
330 return false
331 }
332 cmd := exec.Command(goTool, "env", "CGO_ENABLED")
333 cmd.Env = origEnv
334 out, err := cmd.Output()
335 if err != nil {
336 panic(fmt.Sprintf("%v: %v", cmd, out))
337 }
338 ok, err := strconv.ParseBool(string(bytes.TrimSpace(out)))
339 if err != nil {
340 panic(fmt.Sprintf("%v: non-boolean output %q", cmd, out))
341 }
342 return ok
343 })
344
345
346 func MustHaveCGO(t testing.TB) {
347 if !HasCGO() {
348 t.Helper()
349 t.Skipf("skipping test: no cgo")
350 }
351 }
352
353
354
355 func CanInternalLink(withCgo bool) bool {
356 return !platform.MustLinkExternal(runtime.GOOS, runtime.GOARCH, withCgo)
357 }
358
359
360 type SpecialBuildTypes struct {
361 Cgo bool
362 Asan bool
363 Msan bool
364 Race bool
365 }
366
367
368 var NoSpecialBuildTypes SpecialBuildTypes
369
370
371
372
373 func MustInternalLink(t testing.TB, with SpecialBuildTypes) {
374 if with.Asan || with.Msan || with.Race {
375 t.Skipf("skipping test: internal linking with sanitizers is not supported")
376 }
377 if !CanInternalLink(with.Cgo) {
378 t.Helper()
379 if with.Cgo && CanInternalLink(false) {
380 t.Skipf("skipping test: internal linking on %s/%s is not supported with cgo", runtime.GOOS, runtime.GOARCH)
381 }
382 t.Skipf("skipping test: internal linking on %s/%s is not supported", runtime.GOOS, runtime.GOARCH)
383 }
384 }
385
386
387
388
389 func MustInternalLinkPIE(t testing.TB) {
390 if !platform.InternalLinkPIESupported(runtime.GOOS, runtime.GOARCH) {
391 t.Helper()
392 t.Skipf("skipping test: internal linking for buildmode=pie on %s/%s is not supported", runtime.GOOS, runtime.GOARCH)
393 }
394 }
395
396
397
398
399 func MustHaveBuildMode(t testing.TB, buildmode string) {
400 if !platform.BuildModeSupported(runtime.Compiler, buildmode, runtime.GOOS, runtime.GOARCH) {
401 t.Helper()
402 t.Skipf("skipping test: build mode %s on %s/%s is not supported by the %s compiler", buildmode, runtime.GOOS, runtime.GOARCH, runtime.Compiler)
403 }
404 }
405
406
407 func HasSymlink() bool {
408 ok, _ := hasSymlink()
409 return ok
410 }
411
412
413
414 func MustHaveSymlink(t testing.TB) {
415 ok, reason := hasSymlink()
416 if !ok {
417 t.Helper()
418 t.Skipf("skipping test: cannot make symlinks on %s/%s: %s", runtime.GOOS, runtime.GOARCH, reason)
419 }
420 }
421
422
423 func HasLink() bool {
424
425
426
427 return runtime.GOOS != "plan9" && runtime.GOOS != "android"
428 }
429
430
431
432 func MustHaveLink(t testing.TB) {
433 if !HasLink() {
434 t.Helper()
435 t.Skipf("skipping test: hardlinks are not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
436 }
437 }
438
439 var flaky = flag.Bool("flaky", false, "run known-flaky tests too")
440
441 func SkipFlaky(t testing.TB, issue int) {
442 if !*flaky {
443 t.Helper()
444 t.Skipf("skipping known flaky test without the -flaky flag; see golang.org/issue/%d", issue)
445 }
446 }
447
448 func SkipFlakyNet(t testing.TB) {
449 if v, _ := strconv.ParseBool(os.Getenv("GO_BUILDER_FLAKY_NET")); v {
450 t.Helper()
451 t.Skip("skipping test on builder known to have frequent network failures")
452 }
453 }
454
455
456 func CPUIsSlow() bool {
457 switch runtime.GOARCH {
458 case "arm", "mips", "mipsle", "mips64", "mips64le", "wasm":
459 return true
460 }
461 return false
462 }
463
464
465
466
467
468 func SkipIfShortAndSlow(t testing.TB) {
469 if testing.Short() && CPUIsSlow() {
470 t.Helper()
471 t.Skipf("skipping test in -short mode on %s", runtime.GOARCH)
472 }
473 }
474
475
476 func SkipIfOptimizationOff(t testing.TB) {
477 if OptimizationOff() {
478 t.Helper()
479 t.Skip("skipping test with optimization disabled")
480 }
481 }
482
483
484
485
486
487
488
489 func WriteImportcfg(t testing.TB, dstPath string, packageFiles map[string]string, pkgs ...string) {
490 t.Helper()
491
492 icfg := new(bytes.Buffer)
493 icfg.WriteString("# import config\n")
494 for k, v := range packageFiles {
495 fmt.Fprintf(icfg, "packagefile %s=%s\n", k, v)
496 }
497
498 if len(pkgs) > 0 {
499
500 cmd := Command(t, GoToolPath(t), "list", "-export", "-deps", "-f", `{{if ne .ImportPath "command-line-arguments"}}{{if .Export}}{{.ImportPath}}={{.Export}}{{end}}{{end}}`)
501 cmd.Args = append(cmd.Args, pkgs...)
502 cmd.Stderr = new(strings.Builder)
503 out, err := cmd.Output()
504 if err != nil {
505 t.Fatalf("%v: %v\n%s", cmd, err, cmd.Stderr)
506 }
507
508 for line := range strings.SplitSeq(string(out), "\n") {
509 if line == "" {
510 continue
511 }
512 importPath, export, ok := strings.Cut(line, "=")
513 if !ok {
514 t.Fatalf("invalid line in output from %v:\n%s", cmd, line)
515 }
516 if packageFiles[importPath] == "" {
517 fmt.Fprintf(icfg, "packagefile %s=%s\n", importPath, export)
518 }
519 }
520 }
521
522 if err := os.WriteFile(dstPath, icfg.Bytes(), 0666); err != nil {
523 t.Fatal(err)
524 }
525 }
526
527
528
529 func SyscallIsNotSupported(err error) bool {
530 return syscallIsNotSupported(err)
531 }
532
533
534
535
536 func ParallelOn64Bit(t *testing.T) {
537 if goarch.PtrSize == 4 {
538 return
539 }
540 t.Parallel()
541 }
542
543
544
545 func CPUProfilingBroken() bool {
546 switch runtime.GOOS {
547 case "plan9":
548
549 return true
550 case "aix":
551
552 return true
553 case "ios", "dragonfly", "netbsd", "illumos", "solaris":
554
555 return true
556 case "openbsd":
557 if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" {
558
559 return true
560 }
561 }
562
563 return false
564 }
565
566
567
568 func SetGODEBUG(t testing.TB, v string) {
569 t.Helper()
570 t.Setenv("GODEBUG", os.Getenv("GODEBUG")+","+v)
571 }
572
View as plain text