github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/testenv/testenv.go (about) 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. 7 package testenv 8 9 import ( 10 "bytes" 11 "fmt" 12 "go/build" 13 "io/ioutil" 14 "os" 15 "runtime" 16 "strings" 17 "sync" 18 "time" 19 20 exec "golang.org/x/sys/execabs" 21 ) 22 23 // Testing is an abstraction of a *testing.T. 24 type Testing interface { 25 Skipf(format string, args ...interface{}) 26 Fatalf(format string, args ...interface{}) 27 } 28 29 type helperer interface { 30 Helper() 31 } 32 33 // packageMainIsDevel reports whether the module containing package main 34 // is a development version (if module information is available). 35 // 36 // Builds in GOPATH mode and builds that lack module information are assumed to 37 // be development versions. 38 var packageMainIsDevel = func() bool { return true } 39 40 var checkGoGoroot struct { 41 once sync.Once 42 err error 43 } 44 45 func hasTool(tool string) error { 46 if tool == "cgo" { 47 enabled, err := cgoEnabled(false) 48 if err != nil { 49 return fmt.Errorf("checking cgo: %v", err) 50 } 51 if !enabled { 52 return fmt.Errorf("cgo not enabled") 53 } 54 return nil 55 } 56 57 _, err := exec.LookPath(tool) 58 if err != nil { 59 return err 60 } 61 62 switch tool { 63 case "patch": 64 // check that the patch tools supports the -o argument 65 temp, err := ioutil.TempFile("", "patch-test") 66 if err != nil { 67 return err 68 } 69 temp.Close() 70 defer os.Remove(temp.Name()) 71 cmd := exec.Command(tool, "-o", temp.Name()) 72 if err := cmd.Run(); err != nil { 73 return err 74 } 75 76 case "go": 77 checkGoGoroot.once.Do(func() { 78 // Ensure that the 'go' command found by exec.LookPath is from the correct 79 // GOROOT. Otherwise, 'some/path/go test ./...' will test against some 80 // version of the 'go' binary other than 'some/path/go', which is almost 81 // certainly not what the user intended. 82 out, err := exec.Command(tool, "env", "GOROOT").CombinedOutput() 83 if err != nil { 84 checkGoGoroot.err = err 85 return 86 } 87 GOROOT := strings.TrimSpace(string(out)) 88 if GOROOT != runtime.GOROOT() { 89 checkGoGoroot.err = fmt.Errorf("'go env GOROOT' does not match runtime.GOROOT:\n\tgo env: %s\n\tGOROOT: %s", GOROOT, runtime.GOROOT()) 90 } 91 }) 92 if checkGoGoroot.err != nil { 93 return checkGoGoroot.err 94 } 95 96 case "diff": 97 // Check that diff is the GNU version, needed for the -u argument and 98 // to report missing newlines at the end of files. 99 out, err := exec.Command(tool, "-version").Output() 100 if err != nil { 101 return err 102 } 103 if !bytes.Contains(out, []byte("GNU diffutils")) { 104 return fmt.Errorf("diff is not the GNU version") 105 } 106 } 107 108 return nil 109 } 110 111 func cgoEnabled(bypassEnvironment bool) (bool, error) { 112 cmd := exec.Command("go", "env", "CGO_ENABLED") 113 if bypassEnvironment { 114 cmd.Env = append(append([]string(nil), os.Environ()...), "CGO_ENABLED=") 115 } 116 out, err := cmd.CombinedOutput() 117 if err != nil { 118 return false, err 119 } 120 enabled := strings.TrimSpace(string(out)) 121 return enabled == "1", nil 122 } 123 124 func allowMissingTool(tool string) bool { 125 if runtime.GOOS == "android" { 126 // Android builds generally run tests on a separate machine from the build, 127 // so don't expect any external tools to be available. 128 return true 129 } 130 131 switch tool { 132 case "cgo": 133 if strings.HasSuffix(os.Getenv("GO_BUILDER_NAME"), "-nocgo") { 134 // Explicitly disabled on -nocgo builders. 135 return true 136 } 137 if enabled, err := cgoEnabled(true); err == nil && !enabled { 138 // No platform support. 139 return true 140 } 141 case "go": 142 if os.Getenv("GO_BUILDER_NAME") == "illumos-amd64-joyent" { 143 // Work around a misconfigured builder (see https://golang.org/issue/33950). 144 return true 145 } 146 case "diff": 147 if os.Getenv("GO_BUILDER_NAME") != "" { 148 return true 149 } 150 case "patch": 151 if os.Getenv("GO_BUILDER_NAME") != "" { 152 return true 153 } 154 } 155 156 // If a developer is actively working on this test, we expect them to have all 157 // of its dependencies installed. However, if it's just a dependency of some 158 // other module (for example, being run via 'go test all'), we should be more 159 // tolerant of unusual environments. 160 return !packageMainIsDevel() 161 } 162 163 // NeedsTool skips t if the named tool is not present in the path. 164 // As a special case, "cgo" means "go" is present and can compile cgo programs. 165 func NeedsTool(t Testing, tool string) { 166 if t, ok := t.(helperer); ok { 167 t.Helper() 168 } 169 err := hasTool(tool) 170 if err == nil { 171 return 172 } 173 if allowMissingTool(tool) { 174 t.Skipf("skipping because %s tool not available: %v", tool, err) 175 } else { 176 t.Fatalf("%s tool not available: %v", tool, err) 177 } 178 } 179 180 // NeedsGoPackages skips t if the go/packages driver (or 'go' tool) implied by 181 // the current process environment is not present in the path. 182 func NeedsGoPackages(t Testing) { 183 if t, ok := t.(helperer); ok { 184 t.Helper() 185 } 186 187 tool := os.Getenv("GOPACKAGESDRIVER") 188 switch tool { 189 case "off": 190 // "off" forces go/packages to use the go command. 191 tool = "go" 192 case "": 193 if _, err := exec.LookPath("gopackagesdriver"); err == nil { 194 tool = "gopackagesdriver" 195 } else { 196 tool = "go" 197 } 198 } 199 200 NeedsTool(t, tool) 201 } 202 203 // NeedsGoPackagesEnv skips t if the go/packages driver (or 'go' tool) implied 204 // by env is not present in the path. 205 func NeedsGoPackagesEnv(t Testing, env []string) { 206 if t, ok := t.(helperer); ok { 207 t.Helper() 208 } 209 210 for _, v := range env { 211 if strings.HasPrefix(v, "GOPACKAGESDRIVER=") { 212 tool := strings.TrimPrefix(v, "GOPACKAGESDRIVER=") 213 if tool == "off" { 214 NeedsTool(t, "go") 215 } else { 216 NeedsTool(t, tool) 217 } 218 return 219 } 220 } 221 222 NeedsGoPackages(t) 223 } 224 225 // NeedsGoBuild skips t if the current system can't build programs with ``go build'' 226 // and then run them with os.StartProcess or exec.Command. 227 // android, and darwin/arm systems don't have the userspace go build needs to run, 228 // and js/wasm doesn't support running subprocesses. 229 func NeedsGoBuild(t Testing) { 230 if t, ok := t.(helperer); ok { 231 t.Helper() 232 } 233 234 NeedsTool(t, "go") 235 236 switch runtime.GOOS { 237 case "android", "js": 238 t.Skipf("skipping test: %v can't build and run Go binaries", runtime.GOOS) 239 case "darwin": 240 if strings.HasPrefix(runtime.GOARCH, "arm") { 241 t.Skipf("skipping test: darwin/arm can't build and run Go binaries") 242 } 243 } 244 } 245 246 // ExitIfSmallMachine emits a helpful diagnostic and calls os.Exit(0) if the 247 // current machine is a builder known to have scarce resources. 248 // 249 // It should be called from within a TestMain function. 250 func ExitIfSmallMachine() { 251 switch b := os.Getenv("GO_BUILDER_NAME"); b { 252 case "linux-arm-scaleway": 253 // "linux-arm" was renamed to "linux-arm-scaleway" in CL 303230. 254 fmt.Fprintln(os.Stderr, "skipping test: linux-arm-scaleway builder lacks sufficient memory (https://golang.org/issue/32834)") 255 case "plan9-arm": 256 fmt.Fprintln(os.Stderr, "skipping test: plan9-arm builder lacks sufficient memory (https://golang.org/issue/38772)") 257 case "netbsd-arm-bsiegert", "netbsd-arm64-bsiegert": 258 // As of 2021-06-02, these builders are running with GO_TEST_TIMEOUT_SCALE=10, 259 // and there is only one of each. We shouldn't waste those scarce resources 260 // running very slow tests. 261 fmt.Fprintf(os.Stderr, "skipping test: %s builder is very slow\n", b) 262 case "dragonfly-amd64": 263 // As of 2021-11-02, this builder is running with GO_TEST_TIMEOUT_SCALE=2, 264 // and seems to have unusually slow disk performance. 265 fmt.Fprintln(os.Stderr, "skipping test: dragonfly-amd64 has slow disk (https://golang.org/issue/45216)") 266 case "linux-riscv64-unmatched": 267 // As of 2021-11-03, this builder is empirically not fast enough to run 268 // gopls tests. Ideally we should make the tests faster in short mode 269 // and/or fix them to not assume arbitrary deadlines. 270 // For now, we'll skip them instead. 271 fmt.Fprintf(os.Stderr, "skipping test: %s builder is too slow (https://golang.org/issue/49321)\n", b) 272 default: 273 return 274 } 275 os.Exit(0) 276 } 277 278 // Go1Point returns the x in Go 1.x. 279 func Go1Point() int { 280 for i := len(build.Default.ReleaseTags) - 1; i >= 0; i-- { 281 var version int 282 if _, err := fmt.Sscanf(build.Default.ReleaseTags[i], "go1.%d", &version); err != nil { 283 continue 284 } 285 return version 286 } 287 panic("bad release tags") 288 } 289 290 // NeedsGo1Point skips t if the Go version used to run the test is older than 291 // 1.x. 292 func NeedsGo1Point(t Testing, x int) { 293 if t, ok := t.(helperer); ok { 294 t.Helper() 295 } 296 if Go1Point() < x { 297 t.Skipf("running Go version %q is version 1.%d, older than required 1.%d", runtime.Version(), Go1Point(), x) 298 } 299 } 300 301 // SkipAfterGo1Point skips t if the Go version used to run the test is newer than 302 // 1.x. 303 func SkipAfterGo1Point(t Testing, x int) { 304 if t, ok := t.(helperer); ok { 305 t.Helper() 306 } 307 if Go1Point() > x { 308 t.Skipf("running Go version %q is version 1.%d, newer than maximum 1.%d", runtime.Version(), Go1Point(), x) 309 } 310 } 311 312 // Deadline returns the deadline of t, if known, 313 // using the Deadline method added in Go 1.15. 314 func Deadline(t Testing) (time.Time, bool) { 315 td, ok := t.(interface { 316 Deadline() (time.Time, bool) 317 }) 318 if !ok { 319 return time.Time{}, false 320 } 321 return td.Deadline() 322 }