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