1
2
3
4
5
6
7 package testenv
8
9 import (
10 "bytes"
11 "fmt"
12 "go/build"
13 "os"
14 "os/exec"
15 "path/filepath"
16 "runtime"
17 "runtime/debug"
18 "strings"
19 "sync"
20 "testing"
21 "time"
22
23 "golang.org/x/mod/modfile"
24 "golang.org/x/tools/internal/goroot"
25 )
26
27
28
29 func packageMainIsDevel() bool {
30 info, ok := debug.ReadBuildInfo()
31 if !ok {
32
33
34 return true
35 }
36
37
38
39
40 return info.Main.Version == "(devel)"
41 }
42
43 var checkGoBuild struct {
44 once sync.Once
45 err error
46 }
47
48
49
50
51 func HasTool(tool string) error {
52 if tool == "cgo" {
53 enabled, err := cgoEnabled(false)
54 if err != nil {
55 return fmt.Errorf("checking cgo: %v", err)
56 }
57 if !enabled {
58 return fmt.Errorf("cgo not enabled")
59 }
60 return nil
61 }
62
63 _, err := exec.LookPath(tool)
64 if err != nil {
65 return err
66 }
67
68 switch tool {
69 case "patch":
70
71 temp, err := os.CreateTemp("", "patch-test")
72 if err != nil {
73 return err
74 }
75 temp.Close()
76 defer os.Remove(temp.Name())
77 cmd := exec.Command(tool, "-o", temp.Name())
78 if err := cmd.Run(); err != nil {
79 return err
80 }
81
82 case "go":
83 checkGoBuild.once.Do(func() {
84 if runtime.GOROOT() != "" {
85
86
87
88
89 out, err := exec.Command(tool, "env", "GOROOT").Output()
90 if err != nil {
91 if exit, ok := err.(*exec.ExitError); ok && len(exit.Stderr) > 0 {
92 err = fmt.Errorf("%w\nstderr:\n%s)", err, exit.Stderr)
93 }
94 checkGoBuild.err = err
95 return
96 }
97 GOROOT := strings.TrimSpace(string(out))
98 if GOROOT != runtime.GOROOT() {
99 checkGoBuild.err = fmt.Errorf("'go env GOROOT' does not match runtime.GOROOT:\n\tgo env: %s\n\tGOROOT: %s", GOROOT, runtime.GOROOT())
100 return
101 }
102 }
103
104 dir, err := os.MkdirTemp("", "testenv-*")
105 if err != nil {
106 checkGoBuild.err = err
107 return
108 }
109 defer os.RemoveAll(dir)
110
111 mainGo := filepath.Join(dir, "main.go")
112 if err := os.WriteFile(mainGo, []byte("package main\nfunc main() {}\n"), 0644); err != nil {
113 checkGoBuild.err = err
114 return
115 }
116 cmd := exec.Command("go", "build", "-o", os.DevNull, mainGo)
117 cmd.Dir = dir
118 if out, err := cmd.CombinedOutput(); err != nil {
119 if len(out) > 0 {
120 checkGoBuild.err = fmt.Errorf("%v: %v\n%s", cmd, err, out)
121 } else {
122 checkGoBuild.err = fmt.Errorf("%v: %v", cmd, err)
123 }
124 }
125 })
126 if checkGoBuild.err != nil {
127 return checkGoBuild.err
128 }
129
130 case "diff":
131
132
133 out, err := exec.Command(tool, "-version").Output()
134 if err != nil {
135 return err
136 }
137 if !bytes.Contains(out, []byte("GNU diffutils")) {
138 return fmt.Errorf("diff is not the GNU version")
139 }
140 }
141
142 return nil
143 }
144
145 func cgoEnabled(bypassEnvironment bool) (bool, error) {
146 cmd := exec.Command("go", "env", "CGO_ENABLED")
147 if bypassEnvironment {
148 cmd.Env = append(append([]string(nil), os.Environ()...), "CGO_ENABLED=")
149 }
150 out, err := cmd.Output()
151 if err != nil {
152 if exit, ok := err.(*exec.ExitError); ok && len(exit.Stderr) > 0 {
153 err = fmt.Errorf("%w\nstderr:\n%s", err, exit.Stderr)
154 }
155 return false, err
156 }
157 enabled := strings.TrimSpace(string(out))
158 return enabled == "1", nil
159 }
160
161 func allowMissingTool(tool string) bool {
162 switch runtime.GOOS {
163 case "aix", "darwin", "dragonfly", "freebsd", "illumos", "linux", "netbsd", "openbsd", "plan9", "solaris", "windows":
164
165 default:
166 return true
167 }
168
169 switch tool {
170 case "cgo":
171 if strings.HasSuffix(os.Getenv("GO_BUILDER_NAME"), "-nocgo") {
172
173 return true
174 }
175 if enabled, err := cgoEnabled(true); err == nil && !enabled {
176
177 return true
178 }
179 case "go":
180 if os.Getenv("GO_BUILDER_NAME") == "illumos-amd64-joyent" {
181
182 return true
183 }
184 case "diff":
185 if os.Getenv("GO_BUILDER_NAME") != "" {
186 return true
187 }
188 case "patch":
189 if os.Getenv("GO_BUILDER_NAME") != "" {
190 return true
191 }
192 }
193
194
195
196
197
198 return !packageMainIsDevel()
199 }
200
201
202
203 func NeedsTool(t testing.TB, tool string) {
204 err := HasTool(tool)
205 if err == nil {
206 return
207 }
208
209 t.Helper()
210 if allowMissingTool(tool) {
211
212
213
214
215
216
217 t.Skipf("skipping because %s tool not available: %v", tool, err)
218 } else {
219 t.Fatalf("%s tool not available: %v", tool, err)
220 }
221 }
222
223
224
225 func NeedsGoPackages(t testing.TB) {
226 t.Helper()
227
228 tool := os.Getenv("GOPACKAGESDRIVER")
229 switch tool {
230 case "off":
231
232 tool = "go"
233 case "":
234 if _, err := exec.LookPath("gopackagesdriver"); err == nil {
235 tool = "gopackagesdriver"
236 } else {
237 tool = "go"
238 }
239 }
240
241 NeedsTool(t, tool)
242 }
243
244
245
246 func NeedsGoPackagesEnv(t testing.TB, env []string) {
247 t.Helper()
248
249 for _, v := range env {
250 if strings.HasPrefix(v, "GOPACKAGESDRIVER=") {
251 tool := strings.TrimPrefix(v, "GOPACKAGESDRIVER=")
252 if tool == "off" {
253 NeedsTool(t, "go")
254 } else {
255 NeedsTool(t, tool)
256 }
257 return
258 }
259 }
260
261 NeedsGoPackages(t)
262 }
263
264
265
266
267
268 func NeedsGoBuild(t testing.TB) {
269 t.Helper()
270
271
272
273
274 NeedsTool(t, "go")
275 }
276
277
278
279
280
281 func ExitIfSmallMachine() {
282 switch b := os.Getenv("GO_BUILDER_NAME"); b {
283 case "linux-arm-scaleway":
284
285 fmt.Fprintln(os.Stderr, "skipping test: linux-arm-scaleway builder lacks sufficient memory (https://golang.org/issue/32834)")
286 case "plan9-arm":
287 fmt.Fprintln(os.Stderr, "skipping test: plan9-arm builder lacks sufficient memory (https://golang.org/issue/38772)")
288 case "netbsd-arm-bsiegert", "netbsd-arm64-bsiegert":
289
290
291
292 fmt.Fprintf(os.Stderr, "skipping test: %s builder is very slow\n", b)
293 case "dragonfly-amd64":
294
295
296 fmt.Fprintln(os.Stderr, "skipping test: dragonfly-amd64 has slow disk (https://golang.org/issue/45216)")
297 case "linux-riscv64-unmatched":
298
299
300
301
302 fmt.Fprintf(os.Stderr, "skipping test: %s builder is too slow (https://golang.org/issue/49321)\n", b)
303 default:
304 switch runtime.GOOS {
305 case "android", "ios":
306 fmt.Fprintf(os.Stderr, "skipping test: assuming that %s is resource-constrained\n", runtime.GOOS)
307 default:
308 return
309 }
310 }
311 os.Exit(0)
312 }
313
314
315 func Go1Point() int {
316 for i := len(build.Default.ReleaseTags) - 1; i >= 0; i-- {
317 var version int
318 if _, err := fmt.Sscanf(build.Default.ReleaseTags[i], "go1.%d", &version); err != nil {
319 continue
320 }
321 return version
322 }
323 panic("bad release tags")
324 }
325
326
327
328 func NeedsGo1Point(t testing.TB, x int) {
329 if Go1Point() < x {
330 t.Helper()
331 t.Skipf("running Go version %q is version 1.%d, older than required 1.%d", runtime.Version(), Go1Point(), x)
332 }
333 }
334
335
336
337 func SkipAfterGo1Point(t testing.TB, x int) {
338 if Go1Point() > x {
339 t.Helper()
340 t.Skipf("running Go version %q is version 1.%d, newer than maximum 1.%d", runtime.Version(), Go1Point(), x)
341 }
342 }
343
344
345
346 func NeedsLocalhostNet(t testing.TB) {
347 switch runtime.GOOS {
348 case "js", "wasip1":
349 t.Skipf(`Listening on "localhost" fails on %s; see https://go.dev/issue/59718`, runtime.GOOS)
350 }
351 }
352
353
354
355 func Deadline(t testing.TB) (time.Time, bool) {
356 td, ok := t.(interface {
357 Deadline() (time.Time, bool)
358 })
359 if !ok {
360 return time.Time{}, false
361 }
362 return td.Deadline()
363 }
364
365
366
367
368 func WriteImportcfg(t testing.TB, dstPath string, additionalPackageFiles map[string]string) {
369 importcfg, err := goroot.Importcfg()
370 for k, v := range additionalPackageFiles {
371 importcfg += fmt.Sprintf("\npackagefile %s=%s", k, v)
372 }
373 if err != nil {
374 t.Fatalf("preparing the importcfg failed: %s", err)
375 }
376 os.WriteFile(dstPath, []byte(importcfg), 0655)
377 if err != nil {
378 t.Fatalf("writing the importcfg failed: %s", err)
379 }
380 }
381
382 var (
383 gorootOnce sync.Once
384 gorootPath string
385 gorootErr error
386 )
387
388 func findGOROOT() (string, error) {
389 gorootOnce.Do(func() {
390 gorootPath = runtime.GOROOT()
391 if gorootPath != "" {
392
393
394
395 return
396 }
397
398 cmd := exec.Command("go", "env", "GOROOT")
399 out, err := cmd.Output()
400 if err != nil {
401 gorootErr = fmt.Errorf("%v: %v", cmd, err)
402 }
403 gorootPath = strings.TrimSpace(string(out))
404 })
405
406 return gorootPath, gorootErr
407 }
408
409
410
411
412
413
414
415 func GOROOT(t testing.TB) string {
416 path, err := findGOROOT()
417 if err != nil {
418 if t == nil {
419 panic(err)
420 }
421 t.Helper()
422 t.Skip(err)
423 }
424 return path
425 }
426
427
428
429 func NeedsLocalXTools(t testing.TB) {
430 t.Helper()
431
432 NeedsTool(t, "go")
433
434 cmd := Command(t, "go", "list", "-f", "{{with .Replace}}{{.Dir}}{{end}}", "-m", "golang.org/x/tools")
435 out, err := cmd.Output()
436 if err != nil {
437 if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
438 t.Skipf("skipping test: %v: %v\n%s", cmd, err, ee.Stderr)
439 }
440 t.Skipf("skipping test: %v: %v", cmd, err)
441 }
442
443 dir := string(bytes.TrimSpace(out))
444 if dir == "" {
445
446
447
448 return
449 }
450
451
452
453 modFilePath := filepath.Join(dir, "go.mod")
454 b, err := os.ReadFile(modFilePath)
455 if err != nil {
456 t.Skipf("skipping test: x/tools replacement not found: %v", err)
457 }
458 modulePath := modfile.ModulePath(b)
459
460 if want := "golang.org/x/tools"; modulePath != want {
461 t.Skipf("skipping test: %s module path is %q, not %q", modFilePath, modulePath, want)
462 }
463 }
464
465
466
467 func NeedsGoExperiment(t testing.TB, flag string) {
468 t.Helper()
469
470 goexp := os.Getenv("GOEXPERIMENT")
471 set := false
472 for _, f := range strings.Split(goexp, ",") {
473 if f == "" {
474 continue
475 }
476 if f == "none" {
477
478 set = false
479 break
480 }
481 val := true
482 if strings.HasPrefix(f, "no") {
483 f, val = f[2:], false
484 }
485 if f == flag {
486 set = val
487 }
488 }
489 if !set {
490 t.Skipf("skipping test: flag %q is not set in GOEXPERIMENT=%q", flag, goexp)
491 }
492 }
493
View as plain text