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
195 return
196 }
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211 cwd, err := os.Getwd()
212 if err != nil {
213 gorootErr = fmt.Errorf("finding GOROOT: %w", err)
214 return
215 }
216
217 dir := cwd
218 for {
219 parent := filepath.Dir(dir)
220 if parent == dir {
221
222 gorootErr = fmt.Errorf("failed to locate GOROOT/src in any parent directory")
223 return
224 }
225
226 if base := filepath.Base(dir); base != "src" {
227 dir = parent
228 continue
229 }
230
231 b, err := os.ReadFile(filepath.Join(dir, "go.mod"))
232 if err != nil {
233 if os.IsNotExist(err) {
234 dir = parent
235 continue
236 }
237 gorootErr = fmt.Errorf("finding GOROOT: %w", err)
238 return
239 }
240 goMod := string(b)
241
242 for goMod != "" {
243 var line string
244 line, goMod, _ = strings.Cut(goMod, "\n")
245 fields := strings.Fields(line)
246 if len(fields) >= 2 && fields[0] == "module" && fields[1] == "std" {
247
248 gorootPath = parent
249 return
250 }
251 }
252 }
253 })
254
255 return gorootPath, gorootErr
256 }
257
258
259
260
261
262
263
264
265 func GOROOT(t testing.TB) string {
266 path, err := findGOROOT()
267 if err != nil {
268 if t == nil {
269 panic(err)
270 }
271 t.Helper()
272 t.Skip(err)
273 }
274 return path
275 }
276
277
278 func GoTool() (string, error) {
279 if !HasGoBuild() {
280 return "", errors.New("platform cannot run go tool")
281 }
282 goToolOnce.Do(func() {
283 goToolPath, goToolErr = exec.LookPath("go")
284 })
285 return goToolPath, goToolErr
286 }
287
288 var (
289 goToolOnce sync.Once
290 goToolPath string
291 goToolErr error
292 )
293
294
295 func HasSrc() bool {
296 switch runtime.GOOS {
297 case "ios":
298 return false
299 }
300 return true
301 }
302
303
304
305 func HasExternalNetwork() bool {
306 return !testing.Short() && runtime.GOOS != "js" && runtime.GOOS != "wasip1"
307 }
308
309
310
311
312 func MustHaveExternalNetwork(t testing.TB) {
313 if runtime.GOOS == "js" || runtime.GOOS == "wasip1" {
314 t.Helper()
315 t.Skipf("skipping test: no external network on %s", runtime.GOOS)
316 }
317 if testing.Short() {
318 t.Helper()
319 t.Skipf("skipping test: no external network in -short mode")
320 }
321 }
322
323
324 func HasCGO() bool {
325 hasCgoOnce.Do(func() {
326 goTool, err := GoTool()
327 if err != nil {
328 return
329 }
330 cmd := exec.Command(goTool, "env", "CGO_ENABLED")
331 cmd.Env = origEnv
332 out, err := cmd.Output()
333 if err != nil {
334 panic(fmt.Sprintf("%v: %v", cmd, out))
335 }
336 hasCgo, err = strconv.ParseBool(string(bytes.TrimSpace(out)))
337 if err != nil {
338 panic(fmt.Sprintf("%v: non-boolean output %q", cmd, out))
339 }
340 })
341 return hasCgo
342 }
343
344 var (
345 hasCgoOnce sync.Once
346 hasCgo bool
347 )
348
349
350 func MustHaveCGO(t testing.TB) {
351 if !HasCGO() {
352 t.Skipf("skipping test: no cgo")
353 }
354 }
355
356
357
358 func CanInternalLink(withCgo bool) bool {
359 return !platform.MustLinkExternal(runtime.GOOS, runtime.GOARCH, withCgo)
360 }
361
362
363
364
365 func MustInternalLink(t testing.TB, withCgo bool) {
366 if !CanInternalLink(withCgo) {
367 if withCgo && CanInternalLink(false) {
368 t.Skipf("skipping test: internal linking on %s/%s is not supported with cgo", runtime.GOOS, runtime.GOARCH)
369 }
370 t.Skipf("skipping test: internal linking on %s/%s is not supported", runtime.GOOS, runtime.GOARCH)
371 }
372 }
373
374
375
376
377 func MustHaveBuildMode(t testing.TB, buildmode string) {
378 if !platform.BuildModeSupported(runtime.Compiler, buildmode, runtime.GOOS, runtime.GOARCH) {
379 t.Skipf("skipping test: build mode %s on %s/%s is not supported by the %s compiler", buildmode, runtime.GOOS, runtime.GOARCH, runtime.Compiler)
380 }
381 }
382
383
384 func HasSymlink() bool {
385 ok, _ := hasSymlink()
386 return ok
387 }
388
389
390
391 func MustHaveSymlink(t testing.TB) {
392 ok, reason := hasSymlink()
393 if !ok {
394 t.Skipf("skipping test: cannot make symlinks on %s/%s: %s", runtime.GOOS, runtime.GOARCH, reason)
395 }
396 }
397
398
399 func HasLink() bool {
400
401
402
403 return runtime.GOOS != "plan9" && runtime.GOOS != "android"
404 }
405
406
407
408 func MustHaveLink(t testing.TB) {
409 if !HasLink() {
410 t.Skipf("skipping test: hardlinks are not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
411 }
412 }
413
414 var flaky = flag.Bool("flaky", false, "run known-flaky tests too")
415
416 func SkipFlaky(t testing.TB, issue int) {
417 t.Helper()
418 if !*flaky {
419 t.Skipf("skipping known flaky test without the -flaky flag; see golang.org/issue/%d", issue)
420 }
421 }
422
423 func SkipFlakyNet(t testing.TB) {
424 t.Helper()
425 if v, _ := strconv.ParseBool(os.Getenv("GO_BUILDER_FLAKY_NET")); v {
426 t.Skip("skipping test on builder known to have frequent network failures")
427 }
428 }
429
430
431 func CPUIsSlow() bool {
432 switch runtime.GOARCH {
433 case "arm", "mips", "mipsle", "mips64", "mips64le", "wasm":
434 return true
435 }
436 return false
437 }
438
439
440
441
442
443 func SkipIfShortAndSlow(t testing.TB) {
444 if testing.Short() && CPUIsSlow() {
445 t.Helper()
446 t.Skipf("skipping test in -short mode on %s", runtime.GOARCH)
447 }
448 }
449
450
451 func SkipIfOptimizationOff(t testing.TB) {
452 if OptimizationOff() {
453 t.Helper()
454 t.Skip("skipping test with optimization disabled")
455 }
456 }
457
458
459
460
461
462
463
464 func WriteImportcfg(t testing.TB, dstPath string, packageFiles map[string]string, pkgs ...string) {
465 t.Helper()
466
467 icfg := new(bytes.Buffer)
468 icfg.WriteString("# import config\n")
469 for k, v := range packageFiles {
470 fmt.Fprintf(icfg, "packagefile %s=%s\n", k, v)
471 }
472
473 if len(pkgs) > 0 {
474
475 cmd := Command(t, GoToolPath(t), "list", "-export", "-deps", "-f", `{{if ne .ImportPath "command-line-arguments"}}{{if .Export}}{{.ImportPath}}={{.Export}}{{end}}{{end}}`)
476 cmd.Args = append(cmd.Args, pkgs...)
477 cmd.Stderr = new(strings.Builder)
478 out, err := cmd.Output()
479 if err != nil {
480 t.Fatalf("%v: %v\n%s", cmd, err, cmd.Stderr)
481 }
482
483 for _, line := range strings.Split(string(out), "\n") {
484 if line == "" {
485 continue
486 }
487 importPath, export, ok := strings.Cut(line, "=")
488 if !ok {
489 t.Fatalf("invalid line in output from %v:\n%s", cmd, line)
490 }
491 if packageFiles[importPath] == "" {
492 fmt.Fprintf(icfg, "packagefile %s=%s\n", importPath, export)
493 }
494 }
495 }
496
497 if err := os.WriteFile(dstPath, icfg.Bytes(), 0666); err != nil {
498 t.Fatal(err)
499 }
500 }
501
502
503
504 func SyscallIsNotSupported(err error) bool {
505 return syscallIsNotSupported(err)
506 }
507
View as plain text