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 func MustHaveSource(t testing.TB) {
279 switch runtime.GOOS {
280 case "ios":
281 t.Helper()
282 t.Skip("skipping test: no source tree on " + runtime.GOOS)
283 }
284 }
285
286
287
288 func HasExternalNetwork() bool {
289 return !testing.Short() && runtime.GOOS != "js" && runtime.GOOS != "wasip1"
290 }
291
292
293
294
295 func MustHaveExternalNetwork(t testing.TB) {
296 if runtime.GOOS == "js" || runtime.GOOS == "wasip1" {
297 t.Helper()
298 t.Skipf("skipping test: no external network on %s", runtime.GOOS)
299 }
300 if testing.Short() {
301 t.Helper()
302 t.Skipf("skipping test: no external network in -short mode")
303 }
304 }
305
306
307 func HasCGO() bool {
308 return hasCgo()
309 }
310
311 var hasCgo = sync.OnceValue(func() bool {
312 goTool, err := goTool()
313 if err != nil {
314 return false
315 }
316 cmd := exec.Command(goTool, "env", "CGO_ENABLED")
317 cmd.Env = origEnv
318 out, err := cmd.Output()
319 if err != nil {
320 panic(fmt.Sprintf("%v: %v", cmd, out))
321 }
322 ok, err := strconv.ParseBool(string(bytes.TrimSpace(out)))
323 if err != nil {
324 panic(fmt.Sprintf("%v: non-boolean output %q", cmd, out))
325 }
326 return ok
327 })
328
329
330 func MustHaveCGO(t testing.TB) {
331 if !HasCGO() {
332 t.Helper()
333 t.Skipf("skipping test: no cgo")
334 }
335 }
336
337
338
339 func CanInternalLink(withCgo bool) bool {
340 return !platform.MustLinkExternal(runtime.GOOS, runtime.GOARCH, withCgo)
341 }
342
343
344 type SpecialBuildTypes struct {
345 Cgo bool
346 Asan bool
347 Msan bool
348 Race bool
349 }
350
351
352 var NoSpecialBuildTypes SpecialBuildTypes
353
354
355
356
357 func MustInternalLink(t testing.TB, with SpecialBuildTypes) {
358 if with.Asan || with.Msan || with.Race {
359 t.Skipf("skipping test: internal linking with sanitizers is not supported")
360 }
361 if !CanInternalLink(with.Cgo) {
362 t.Helper()
363 if with.Cgo && CanInternalLink(false) {
364 t.Skipf("skipping test: internal linking on %s/%s is not supported with cgo", runtime.GOOS, runtime.GOARCH)
365 }
366 t.Skipf("skipping test: internal linking on %s/%s is not supported", runtime.GOOS, runtime.GOARCH)
367 }
368 }
369
370
371
372
373 func MustInternalLinkPIE(t testing.TB) {
374 if !platform.InternalLinkPIESupported(runtime.GOOS, runtime.GOARCH) {
375 t.Helper()
376 t.Skipf("skipping test: internal linking for buildmode=pie on %s/%s is not supported", runtime.GOOS, runtime.GOARCH)
377 }
378 }
379
380
381
382
383 func MustHaveBuildMode(t testing.TB, buildmode string) {
384 if !platform.BuildModeSupported(runtime.Compiler, buildmode, runtime.GOOS, runtime.GOARCH) {
385 t.Helper()
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
391 func HasSymlink() bool {
392 ok, _ := hasSymlink()
393 return ok
394 }
395
396
397
398 func MustHaveSymlink(t testing.TB) {
399 ok, reason := hasSymlink()
400 if !ok {
401 t.Helper()
402 t.Skipf("skipping test: cannot make symlinks on %s/%s: %s", runtime.GOOS, runtime.GOARCH, reason)
403 }
404 }
405
406
407 func HasLink() bool {
408
409
410
411 return runtime.GOOS != "plan9" && runtime.GOOS != "android"
412 }
413
414
415
416 func MustHaveLink(t testing.TB) {
417 if !HasLink() {
418 t.Helper()
419 t.Skipf("skipping test: hardlinks are not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
420 }
421 }
422
423 var flaky = flag.Bool("flaky", false, "run known-flaky tests too")
424
425 func SkipFlaky(t testing.TB, issue int) {
426 if !*flaky {
427 t.Helper()
428 t.Skipf("skipping known flaky test without the -flaky flag; see golang.org/issue/%d", issue)
429 }
430 }
431
432 func SkipFlakyNet(t testing.TB) {
433 if v, _ := strconv.ParseBool(os.Getenv("GO_BUILDER_FLAKY_NET")); v {
434 t.Helper()
435 t.Skip("skipping test on builder known to have frequent network failures")
436 }
437 }
438
439
440 func CPUIsSlow() bool {
441 switch runtime.GOARCH {
442 case "arm", "mips", "mipsle", "mips64", "mips64le", "wasm":
443 return true
444 }
445 return false
446 }
447
448
449
450
451
452 func SkipIfShortAndSlow(t testing.TB) {
453 if testing.Short() && CPUIsSlow() {
454 t.Helper()
455 t.Skipf("skipping test in -short mode on %s", runtime.GOARCH)
456 }
457 }
458
459
460 func SkipIfOptimizationOff(t testing.TB) {
461 if OptimizationOff() {
462 t.Helper()
463 t.Skip("skipping test with optimization disabled")
464 }
465 }
466
467
468
469
470
471
472
473 func WriteImportcfg(t testing.TB, dstPath string, packageFiles map[string]string, pkgs ...string) {
474 t.Helper()
475
476 icfg := new(bytes.Buffer)
477 icfg.WriteString("# import config\n")
478 for k, v := range packageFiles {
479 fmt.Fprintf(icfg, "packagefile %s=%s\n", k, v)
480 }
481
482 if len(pkgs) > 0 {
483
484 cmd := Command(t, GoToolPath(t), "list", "-export", "-deps", "-f", `{{if ne .ImportPath "command-line-arguments"}}{{if .Export}}{{.ImportPath}}={{.Export}}{{end}}{{end}}`)
485 cmd.Args = append(cmd.Args, pkgs...)
486 cmd.Stderr = new(strings.Builder)
487 out, err := cmd.Output()
488 if err != nil {
489 t.Fatalf("%v: %v\n%s", cmd, err, cmd.Stderr)
490 }
491
492 for line := range strings.SplitSeq(string(out), "\n") {
493 if line == "" {
494 continue
495 }
496 importPath, export, ok := strings.Cut(line, "=")
497 if !ok {
498 t.Fatalf("invalid line in output from %v:\n%s", cmd, line)
499 }
500 if packageFiles[importPath] == "" {
501 fmt.Fprintf(icfg, "packagefile %s=%s\n", importPath, export)
502 }
503 }
504 }
505
506 if err := os.WriteFile(dstPath, icfg.Bytes(), 0666); err != nil {
507 t.Fatal(err)
508 }
509 }
510
511
512
513 func SyscallIsNotSupported(err error) bool {
514 return syscallIsNotSupported(err)
515 }
516
517
518
519
520 func ParallelOn64Bit(t *testing.T) {
521 if goarch.PtrSize == 4 {
522 return
523 }
524 t.Parallel()
525 }
526
527
528
529 func CPUProfilingBroken() bool {
530 switch runtime.GOOS {
531 case "plan9":
532
533 return true
534 case "aix":
535
536 return true
537 case "ios", "dragonfly", "netbsd", "illumos", "solaris":
538
539 return true
540 case "openbsd":
541 if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" {
542
543 return true
544 }
545 }
546
547 return false
548 }
549
View as plain text