GoPLS Viewer

Home|gopls/internal/testenv/testenv.go
1// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package testenv contains helper functions for skipping tests
6// based on which tools are present in the environment.
7package testenv
8
9import (
10    "bytes"
11    "fmt"
12    "go/build"
13    "io/ioutil"
14    "os"
15    "runtime"
16    "runtime/debug"
17    "strings"
18    "sync"
19    "testing"
20    "time"
21
22    "golang.org/x/tools/internal/goroot"
23
24    exec "golang.org/x/sys/execabs"
25)
26
27// packageMainIsDevel reports whether the module containing package main
28// is a development version (if module information is available).
29func packageMainIsDevel() bool {
30    infook := debug.ReadBuildInfo()
31    if !ok {
32        // Most test binaries currently lack build info, but this should become more
33        // permissive once https://golang.org/issue/33976 is fixed.
34        return true
35    }
36
37    // Note: info.Main.Version describes the version of the module containing
38    // package main, not the version of “the main module”.
39    // See https://golang.org/issue/33975.
40    return info.Main.Version == "(devel)"
41}
42
43var checkGoGoroot struct {
44    once sync.Once
45    err  error
46}
47
48func hasTool(tool stringerror {
49    if tool == "cgo" {
50        enablederr := cgoEnabled(false)
51        if err != nil {
52            return fmt.Errorf("checking cgo: %v"err)
53        }
54        if !enabled {
55            return fmt.Errorf("cgo not enabled")
56        }
57        return nil
58    }
59
60    _err := exec.LookPath(tool)
61    if err != nil {
62        return err
63    }
64
65    switch tool {
66    case "patch":
67        // check that the patch tools supports the -o argument
68        temperr := ioutil.TempFile("""patch-test")
69        if err != nil {
70            return err
71        }
72        temp.Close()
73        defer os.Remove(temp.Name())
74        cmd := exec.Command(tool"-o"temp.Name())
75        if err := cmd.Run(); err != nil {
76            return err
77        }
78
79    case "go":
80        checkGoGoroot.once.Do(func() {
81            // Ensure that the 'go' command found by exec.LookPath is from the correct
82            // GOROOT. Otherwise, 'some/path/go test ./...' will test against some
83            // version of the 'go' binary other than 'some/path/go', which is almost
84            // certainly not what the user intended.
85            outerr := exec.Command(tool"env""GOROOT").CombinedOutput()
86            if err != nil {
87                checkGoGoroot.err = err
88                return
89            }
90            GOROOT := strings.TrimSpace(string(out))
91            if GOROOT != runtime.GOROOT() {
92                checkGoGoroot.err = fmt.Errorf("'go env GOROOT' does not match runtime.GOROOT:\n\tgo env: %s\n\tGOROOT: %s"GOROOTruntime.GOROOT())
93                return
94            }
95
96            // Also ensure that that GOROOT includes a compiler: 'go' commands
97            // don't in general work without it, and some builders
98            // (such as android-amd64-emu) seem to lack it in the test environment.
99            cmd := exec.Command(tool"tool""-n""compile")
100            stderr := new(bytes.Buffer)
101            stderr.Write([]byte("\n"))
102            cmd.Stderr = stderr
103            outerr = cmd.Output()
104            if err != nil {
105                checkGoGoroot.err = fmt.Errorf("%v: %v%s"cmderrstderr)
106                return
107            }
108            if _err := os.Stat(string(bytes.TrimSpace(out))); err != nil {
109                checkGoGoroot.err = err
110            }
111        })
112        if checkGoGoroot.err != nil {
113            return checkGoGoroot.err
114        }
115
116    case "diff":
117        // Check that diff is the GNU version, needed for the -u argument and
118        // to report missing newlines at the end of files.
119        outerr := exec.Command(tool"-version").Output()
120        if err != nil {
121            return err
122        }
123        if !bytes.Contains(out, []byte("GNU diffutils")) {
124            return fmt.Errorf("diff is not the GNU version")
125        }
126    }
127
128    return nil
129}
130
131func cgoEnabled(bypassEnvironment bool) (boolerror) {
132    cmd := exec.Command("go""env""CGO_ENABLED")
133    if bypassEnvironment {
134        cmd.Env = append(append([]string(nil), os.Environ()...), "CGO_ENABLED=")
135    }
136    outerr := cmd.CombinedOutput()
137    if err != nil {
138        return falseerr
139    }
140    enabled := strings.TrimSpace(string(out))
141    return enabled == "1"nil
142}
143
144func allowMissingTool(tool stringbool {
145    if runtime.GOOS == "android" {
146        // Android builds generally run tests on a separate machine from the build,
147        // so don't expect any external tools to be available.
148        return true
149    }
150
151    switch tool {
152    case "cgo":
153        if strings.HasSuffix(os.Getenv("GO_BUILDER_NAME"), "-nocgo") {
154            // Explicitly disabled on -nocgo builders.
155            return true
156        }
157        if enablederr := cgoEnabled(true); err == nil && !enabled {
158            // No platform support.
159            return true
160        }
161    case "go":
162        if os.Getenv("GO_BUILDER_NAME") == "illumos-amd64-joyent" {
163            // Work around a misconfigured builder (see https://golang.org/issue/33950).
164            return true
165        }
166    case "diff":
167        if os.Getenv("GO_BUILDER_NAME") != "" {
168            return true
169        }
170    case "patch":
171        if os.Getenv("GO_BUILDER_NAME") != "" {
172            return true
173        }
174    }
175
176    // If a developer is actively working on this test, we expect them to have all
177    // of its dependencies installed. However, if it's just a dependency of some
178    // other module (for example, being run via 'go test all'), we should be more
179    // tolerant of unusual environments.
180    return !packageMainIsDevel()
181}
182
183// NeedsTool skips t if the named tool is not present in the path.
184// As a special case, "cgo" means "go" is present and can compile cgo programs.
185func NeedsTool(t testing.TBtool string) {
186    err := hasTool(tool)
187    if err == nil {
188        return
189    }
190
191    t.Helper()
192    if allowMissingTool(tool) {
193        t.Skipf("skipping because %s tool not available: %v"toolerr)
194    } else {
195        t.Fatalf("%s tool not available: %v"toolerr)
196    }
197}
198
199// NeedsGoPackages skips t if the go/packages driver (or 'go' tool) implied by
200// the current process environment is not present in the path.
201func NeedsGoPackages(t testing.TB) {
202    t.Helper()
203
204    tool := os.Getenv("GOPACKAGESDRIVER")
205    switch tool {
206    case "off":
207        // "off" forces go/packages to use the go command.
208        tool = "go"
209    case "":
210        if _err := exec.LookPath("gopackagesdriver"); err == nil {
211            tool = "gopackagesdriver"
212        } else {
213            tool = "go"
214        }
215    }
216
217    NeedsTool(ttool)
218}
219
220// NeedsGoPackagesEnv skips t if the go/packages driver (or 'go' tool) implied
221// by env is not present in the path.
222func NeedsGoPackagesEnv(t testing.TBenv []string) {
223    t.Helper()
224
225    for _v := range env {
226        if strings.HasPrefix(v"GOPACKAGESDRIVER=") {
227            tool := strings.TrimPrefix(v"GOPACKAGESDRIVER=")
228            if tool == "off" {
229                NeedsTool(t"go")
230            } else {
231                NeedsTool(ttool)
232            }
233            return
234        }
235    }
236
237    NeedsGoPackages(t)
238}
239
240// NeedsGoBuild skips t if the current system can't build programs with “go build”
241// and then run them with os.StartProcess or exec.Command.
242// Android doesn't have the userspace go build needs to run,
243// and js/wasm doesn't support running subprocesses.
244func NeedsGoBuild(t testing.TB) {
245    t.Helper()
246
247    // This logic was derived from internal/testing.HasGoBuild and
248    // may need to be updated as that function evolves.
249
250    NeedsTool(t"go")
251
252    switch runtime.GOOS {
253    case "android""js":
254        t.Skipf("skipping test: %v can't build and run Go binaries"runtime.GOOS)
255    }
256}
257
258// ExitIfSmallMachine emits a helpful diagnostic and calls os.Exit(0) if the
259// current machine is a builder known to have scarce resources.
260//
261// It should be called from within a TestMain function.
262func ExitIfSmallMachine() {
263    switch b := os.Getenv("GO_BUILDER_NAME"); b {
264    case "linux-arm-scaleway":
265        // "linux-arm" was renamed to "linux-arm-scaleway" in CL 303230.
266        fmt.Fprintln(os.Stderr"skipping test: linux-arm-scaleway builder lacks sufficient memory (https://golang.org/issue/32834)")
267    case "plan9-arm":
268        fmt.Fprintln(os.Stderr"skipping test: plan9-arm builder lacks sufficient memory (https://golang.org/issue/38772)")
269    case "netbsd-arm-bsiegert""netbsd-arm64-bsiegert":
270        // As of 2021-06-02, these builders are running with GO_TEST_TIMEOUT_SCALE=10,
271        // and there is only one of each. We shouldn't waste those scarce resources
272        // running very slow tests.
273        fmt.Fprintf(os.Stderr"skipping test: %s builder is very slow\n"b)
274    case "dragonfly-amd64":
275        // As of 2021-11-02, this builder is running with GO_TEST_TIMEOUT_SCALE=2,
276        // and seems to have unusually slow disk performance.
277        fmt.Fprintln(os.Stderr"skipping test: dragonfly-amd64 has slow disk (https://golang.org/issue/45216)")
278    case "linux-riscv64-unmatched":
279        // As of 2021-11-03, this builder is empirically not fast enough to run
280        // gopls tests. Ideally we should make the tests faster in short mode
281        // and/or fix them to not assume arbitrary deadlines.
282        // For now, we'll skip them instead.
283        fmt.Fprintf(os.Stderr"skipping test: %s builder is too slow (https://golang.org/issue/49321)\n"b)
284    default:
285        return
286    }
287    os.Exit(0)
288}
289
290// Go1Point returns the x in Go 1.x.
291func Go1Point() int {
292    for i := len(build.Default.ReleaseTags) - 1i >= 0i-- {
293        var version int
294        if _err := fmt.Sscanf(build.Default.ReleaseTags[i], "go1.%d", &version); err != nil {
295            continue
296        }
297        return version
298    }
299    panic("bad release tags")
300}
301
302// NeedsGo1Point skips t if the Go version used to run the test is older than
303// 1.x.
304func NeedsGo1Point(t testing.TBx int) {
305    if Go1Point() < x {
306        t.Helper()
307        t.Skipf("running Go version %q is version 1.%d, older than required 1.%d"runtime.Version(), Go1Point(), x)
308    }
309}
310
311// SkipAfterGo1Point skips t if the Go version used to run the test is newer than
312// 1.x.
313func SkipAfterGo1Point(t testing.TBx int) {
314    if Go1Point() > x {
315        t.Helper()
316        t.Skipf("running Go version %q is version 1.%d, newer than maximum 1.%d"runtime.Version(), Go1Point(), x)
317    }
318}
319
320// Deadline returns the deadline of t, if known,
321// using the Deadline method added in Go 1.15.
322func Deadline(t testing.TB) (time.Timebool) {
323    tdok := t.(interface {
324        Deadline() (time.Timebool)
325    })
326    if !ok {
327        return time.Time{}, false
328    }
329    return td.Deadline()
330}
331
332// WriteImportcfg writes an importcfg file used by the compiler or linker to
333// dstPath containing entries for the packages in std and cmd in addition
334// to the package to package file mappings in additionalPackageFiles.
335func WriteImportcfg(t testing.TBdstPath stringadditionalPackageFiles map[string]string) {
336    importcfgerr := goroot.Importcfg()
337    for kv := range additionalPackageFiles {
338        importcfg += fmt.Sprintf("\npackagefile %s=%s"kv)
339    }
340    if err != nil {
341        t.Fatalf("preparing the importcfg failed: %s"err)
342    }
343    ioutil.WriteFile(dstPath, []byte(importcfg), 0655)
344    if err != nil {
345        t.Fatalf("writing the importcfg failed: %s"err)
346    }
347}
348
349var (
350    gorootOnce sync.Once
351    gorootPath string
352    gorootErr  error
353)
354
355func findGOROOT() (stringerror) {
356    gorootOnce.Do(func() {
357        gorootPath = runtime.GOROOT()
358        if gorootPath != "" {
359            // If runtime.GOROOT() is non-empty, assume that it is valid. (It might
360            // not be: for example, the user may have explicitly set GOROOT
361            // to the wrong directory.)
362            return
363        }
364
365        cmd := exec.Command("go""env""GOROOT")
366        outerr := cmd.Output()
367        if err != nil {
368            gorootErr = fmt.Errorf("%v: %v"cmderr)
369        }
370        gorootPath = strings.TrimSpace(string(out))
371    })
372
373    return gorootPathgorootErr
374}
375
376// GOROOT reports the path to the directory containing the root of the Go
377// project source tree. This is normally equivalent to runtime.GOROOT, but
378// works even if the test binary was built with -trimpath.
379//
380// If GOROOT cannot be found, GOROOT skips t if t is non-nil,
381// or panics otherwise.
382func GOROOT(t testing.TBstring {
383    patherr := findGOROOT()
384    if err != nil {
385        if t == nil {
386            panic(err)
387        }
388        t.Helper()
389        t.Skip(err)
390    }
391    return path
392}
393
MembersX
packageMainIsDevel.info
hasTool.BlockStmt.BlockStmt.GOROOT
GOROOT.err
packageMainIsDevel
cgoEnabled.cmd
NeedsGoPackages.BlockStmt._
ExitIfSmallMachine
ExitIfSmallMachine.b
findGOROOT.BlockStmt.out
build
goroot
hasTool._
hasTool.err
cgoEnabled.err
gorootErr
fmt
hasTool.BlockStmt.temp
NeedsTool
SkipAfterGo1Point
cgoEnabled
allowMissingTool
NeedsTool.t
NeedsGoPackages.tool
Go1Point.BlockStmt.err
WriteImportcfg.RangeStmt_9962.k
GOROOT
bytes
debug
sync
hasTool.BlockStmt.BlockStmt.stderr
NeedsGo1Point.t
SkipAfterGo1Point.x
NeedsGoPackages
WriteImportcfg.importcfg
WriteImportcfg.dstPath
hasTool.BlockStmt.err
hasTool.BlockStmt.out
cgoEnabled.out
cgoEnabled.enabled
allowMissingTool.BlockStmt.enabled
Deadline
ioutil
cgoEnabled.bypassEnvironment
NeedsTool.err
strings
allowMissingTool.tool
allowMissingTool.BlockStmt.err
findGOROOT.BlockStmt.err
WriteImportcfg
gorootOnce
NeedsTool.tool
NeedsGoPackagesEnv.RangeStmt_6020.BlockStmt.BlockStmt.tool
NeedsGoBuild.t
Go1Point.BlockStmt.version
SkipAfterGo1Point.t
Deadline.t
gorootPath
findGOROOT
GOROOT.path
packageMainIsDevel.ok
hasTool.BlockStmt.enabled
NeedsGoPackagesEnv
NeedsGoPackagesEnv.t
WriteImportcfg.additionalPackageFiles
GOROOT.t
hasTool.BlockStmt.BlockStmt.out
hasTool.BlockStmt.BlockStmt._
NeedsGoPackagesEnv.env
Go1Point.BlockStmt._
NeedsGo1Point
hasTool
hasTool.BlockStmt.cmd
NeedsGoPackagesEnv.RangeStmt_6020.v
findGOROOT.BlockStmt.cmd
hasTool.BlockStmt.BlockStmt.err
NeedsGoBuild
WriteImportcfg.err
WriteImportcfg.RangeStmt_9962.v
WriteImportcfg.t
hasTool.tool
hasTool.BlockStmt.BlockStmt.cmd
NeedsGoPackages.t
NeedsGoPackages.BlockStmt.err
Go1Point
NeedsGo1Point.x
Members
X