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
340
341 func MustInternalLink(t testing.TB, withCgo bool) {
342 if !CanInternalLink(withCgo) {
343 t.Helper()
344 if withCgo && CanInternalLink(false) {
345 t.Skipf("skipping test: internal linking on %s/%s is not supported with cgo", runtime.GOOS, runtime.GOARCH)
346 }
347 t.Skipf("skipping test: internal linking on %s/%s is not supported", runtime.GOOS, runtime.GOARCH)
348 }
349 }
350
351
352
353
354 func MustInternalLinkPIE(t testing.TB) {
355 if !platform.InternalLinkPIESupported(runtime.GOOS, runtime.GOARCH) {
356 t.Helper()
357 t.Skipf("skipping test: internal linking for buildmode=pie on %s/%s is not supported", runtime.GOOS, runtime.GOARCH)
358 }
359 }
360
361
362
363
364 func MustHaveBuildMode(t testing.TB, buildmode string) {
365 if !platform.BuildModeSupported(runtime.Compiler, buildmode, runtime.GOOS, runtime.GOARCH) {
366 t.Helper()
367 t.Skipf("skipping test: build mode %s on %s/%s is not supported by the %s compiler", buildmode, runtime.GOOS, runtime.GOARCH, runtime.Compiler)
368 }
369 }
370
371
372 func HasSymlink() bool {
373 ok, _ := hasSymlink()
374 return ok
375 }
376
377
378
379 func MustHaveSymlink(t testing.TB) {
380 ok, reason := hasSymlink()
381 if !ok {
382 t.Helper()
383 t.Skipf("skipping test: cannot make symlinks on %s/%s: %s", runtime.GOOS, runtime.GOARCH, reason)
384 }
385 }
386
387
388 func HasLink() bool {
389
390
391
392 return runtime.GOOS != "plan9" && runtime.GOOS != "android"
393 }
394
395
396
397 func MustHaveLink(t testing.TB) {
398 if !HasLink() {
399 t.Helper()
400 t.Skipf("skipping test: hardlinks are not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
401 }
402 }
403
404 var flaky = flag.Bool("flaky", false, "run known-flaky tests too")
405
406 func SkipFlaky(t testing.TB, issue int) {
407 if !*flaky {
408 t.Helper()
409 t.Skipf("skipping known flaky test without the -flaky flag; see golang.org/issue/%d", issue)
410 }
411 }
412
413 func SkipFlakyNet(t testing.TB) {
414 if v, _ := strconv.ParseBool(os.Getenv("GO_BUILDER_FLAKY_NET")); v {
415 t.Helper()
416 t.Skip("skipping test on builder known to have frequent network failures")
417 }
418 }
419
420
421 func CPUIsSlow() bool {
422 switch runtime.GOARCH {
423 case "arm", "mips", "mipsle", "mips64", "mips64le", "wasm":
424 return true
425 }
426 return false
427 }
428
429
430
431
432
433 func SkipIfShortAndSlow(t testing.TB) {
434 if testing.Short() && CPUIsSlow() {
435 t.Helper()
436 t.Skipf("skipping test in -short mode on %s", runtime.GOARCH)
437 }
438 }
439
440
441 func SkipIfOptimizationOff(t testing.TB) {
442 if OptimizationOff() {
443 t.Helper()
444 t.Skip("skipping test with optimization disabled")
445 }
446 }
447
448
449
450
451
452
453
454 func WriteImportcfg(t testing.TB, dstPath string, packageFiles map[string]string, pkgs ...string) {
455 t.Helper()
456
457 icfg := new(bytes.Buffer)
458 icfg.WriteString("# import config\n")
459 for k, v := range packageFiles {
460 fmt.Fprintf(icfg, "packagefile %s=%s\n", k, v)
461 }
462
463 if len(pkgs) > 0 {
464
465 cmd := Command(t, GoToolPath(t), "list", "-export", "-deps", "-f", `{{if ne .ImportPath "command-line-arguments"}}{{if .Export}}{{.ImportPath}}={{.Export}}{{end}}{{end}}`)
466 cmd.Args = append(cmd.Args, pkgs...)
467 cmd.Stderr = new(strings.Builder)
468 out, err := cmd.Output()
469 if err != nil {
470 t.Fatalf("%v: %v\n%s", cmd, err, cmd.Stderr)
471 }
472
473 for _, line := range strings.Split(string(out), "\n") {
474 if line == "" {
475 continue
476 }
477 importPath, export, ok := strings.Cut(line, "=")
478 if !ok {
479 t.Fatalf("invalid line in output from %v:\n%s", cmd, line)
480 }
481 if packageFiles[importPath] == "" {
482 fmt.Fprintf(icfg, "packagefile %s=%s\n", importPath, export)
483 }
484 }
485 }
486
487 if err := os.WriteFile(dstPath, icfg.Bytes(), 0666); err != nil {
488 t.Fatal(err)
489 }
490 }
491
492
493
494 func SyscallIsNotSupported(err error) bool {
495 return syscallIsNotSupported(err)
496 }
497
498
499
500
501 func ParallelOn64Bit(t *testing.T) {
502 if goarch.PtrSize == 4 {
503 return
504 }
505 t.Parallel()
506 }
507
View as plain text