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