1// Copyright 2018 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 5package main_test 6 7import ( 8 "cmd/go/internal/cfg" 9 "cmd/go/internal/script" 10 "cmd/go/internal/script/scripttest" 11 "errors" 12 "fmt" 13 "internal/buildcfg" 14 "internal/platform" 15 "internal/testenv" 16 "os" 17 "os/exec" 18 "path/filepath" 19 "runtime" 20 "runtime/debug" 21 "strings" 22 "sync" 23) 24 25func scriptConditions() map[string]script.Cond { 26 conds := scripttest.DefaultConds() 27 28 add := func(name string, cond script.Cond) { 29 if _, ok := conds[name]; ok { 30 panic(fmt.Sprintf("condition %q is already registered", name)) 31 } 32 conds[name] = cond 33 } 34 35 lazyBool := func(summary string, f func() bool) script.Cond { 36 return script.OnceCondition(summary, func() (bool, error) { return f(), nil }) 37 } 38 39 add("abscc", script.Condition("default $CC path is absolute and exists", defaultCCIsAbsolute)) 40 add("asan", sysCondition("-asan", platform.ASanSupported, true)) 41 add("buildmode", script.PrefixCondition("go supports -buildmode=<suffix>", hasBuildmode)) 42 add("case-sensitive", script.OnceCondition("$WORK filesystem is case-sensitive", isCaseSensitive)) 43 add("cc", script.PrefixCondition("go env CC = <suffix> (ignoring the go/env file)", ccIs)) 44 add("cgo", script.BoolCondition("host CGO_ENABLED", testenv.HasCGO())) 45 add("cgolinkext", script.Condition("platform requires external linking for cgo", cgoLinkExt)) 46 add("cross", script.BoolCondition("cmd/go GOOS/GOARCH != GOHOSTOS/GOHOSTARCH", goHostOS != runtime.GOOS || goHostArch != runtime.GOARCH)) 47 add("fuzz", sysCondition("-fuzz", platform.FuzzSupported, false)) 48 add("fuzz-instrumented", sysCondition("-fuzz with instrumentation", platform.FuzzInstrumented, false)) 49 add("git", lazyBool("the 'git' executable exists and provides the standard CLI", hasWorkingGit)) 50 add("GODEBUG", script.PrefixCondition("GODEBUG contains <suffix>", hasGodebug)) 51 add("GOEXPERIMENT", script.PrefixCondition("GOEXPERIMENT <suffix> is enabled", hasGoexperiment)) 52 add("go-builder", script.BoolCondition("GO_BUILDER_NAME is non-empty", testenv.Builder() != "")) 53 add("link", lazyBool("testenv.HasLink()", testenv.HasLink)) 54 add("msan", sysCondition("-msan", platform.MSanSupported, true)) 55 add("mustlinkext", script.Condition("platform always requires external linking", mustLinkExt)) 56 add("net", script.PrefixCondition("can connect to external network host <suffix>", hasNet)) 57 add("pielinkext", script.Condition("platform requires external linking for PIE", pieLinkExt)) 58 add("race", sysCondition("-race", platform.RaceDetectorSupported, true)) 59 add("symlink", lazyBool("testenv.HasSymlink()", testenv.HasSymlink)) 60 add("trimpath", script.OnceCondition("test binary was built with -trimpath", isTrimpath)) 61 62 return conds 63} 64 65func defaultCCIsAbsolute(s *script.State) (bool, error) { 66 GOOS, _ := s.LookupEnv("GOOS") 67 GOARCH, _ := s.LookupEnv("GOARCH") 68 defaultCC := cfg.DefaultCC(GOOS, GOARCH) 69 if filepath.IsAbs(defaultCC) { 70 if _, err := exec.LookPath(defaultCC); err == nil { 71 return true, nil 72 } 73 } 74 return false, nil 75} 76 77func ccIs(s *script.State, want string) (bool, error) { 78 CC, _ := s.LookupEnv("CC") 79 if CC != "" { 80 return CC == want, nil 81 } 82 GOOS, _ := s.LookupEnv("GOOS") 83 GOARCH, _ := s.LookupEnv("GOARCH") 84 return cfg.DefaultCC(GOOS, GOARCH) == want, nil 85} 86 87func sysCondition(flag string, f func(goos, goarch string) bool, needsCgo bool) script.Cond { 88 return script.Condition( 89 "GOOS/GOARCH supports "+flag, 90 func(s *script.State) (bool, error) { 91 GOOS, _ := s.LookupEnv("GOOS") 92 GOARCH, _ := s.LookupEnv("GOARCH") 93 cross := goHostOS != GOOS || goHostArch != GOARCH 94 return (!needsCgo || (testenv.HasCGO() && !cross)) && f(GOOS, GOARCH), nil 95 }) 96} 97 98func hasBuildmode(s *script.State, mode string) (bool, error) { 99 GOOS, _ := s.LookupEnv("GOOS") 100 GOARCH, _ := s.LookupEnv("GOARCH") 101 return platform.BuildModeSupported(runtime.Compiler, mode, GOOS, GOARCH), nil 102} 103 104var scriptNetEnabled sync.Map // testing.TB → already enabled 105 106func hasNet(s *script.State, host string) (bool, error) { 107 if !testenv.HasExternalNetwork() { 108 return false, nil 109 } 110 111 // TODO(bcmills): Add a flag or environment variable to allow skipping tests 112 // for specific hosts and/or skipping all net tests except for specific hosts. 113 114 t, ok := tbFromContext(s.Context()) 115 if !ok { 116 return false, errors.New("script Context unexpectedly missing testing.TB key") 117 } 118 119 if netTestSem != nil { 120 // When the number of external network connections is limited, we limit the 121 // number of net tests that can run concurrently so that the overall number 122 // of network connections won't exceed the limit. 123 _, dup := scriptNetEnabled.LoadOrStore(t, true) 124 if !dup { 125 // Acquire a net token for this test until the test completes. 126 netTestSem <- struct{}{} 127 t.Cleanup(func() { 128 <-netTestSem 129 scriptNetEnabled.Delete(t) 130 }) 131 } 132 } 133 134 // Since we have confirmed that the network is available, 135 // allow cmd/go to use it. 136 s.Setenv("TESTGONETWORK", "") 137 return true, nil 138} 139 140func hasGodebug(s *script.State, value string) (bool, error) { 141 godebug, _ := s.LookupEnv("GODEBUG") 142 for _, p := range strings.Split(godebug, ",") { 143 if strings.TrimSpace(p) == value { 144 return true, nil 145 } 146 } 147 return false, nil 148} 149 150func hasGoexperiment(s *script.State, value string) (bool, error) { 151 GOOS, _ := s.LookupEnv("GOOS") 152 GOARCH, _ := s.LookupEnv("GOARCH") 153 goexp, _ := s.LookupEnv("GOEXPERIMENT") 154 flags, err := buildcfg.ParseGOEXPERIMENT(GOOS, GOARCH, goexp) 155 if err != nil { 156 return false, err 157 } 158 for _, exp := range flags.All() { 159 if value == exp { 160 return true, nil 161 } 162 if strings.TrimPrefix(value, "no") == strings.TrimPrefix(exp, "no") { 163 return false, nil 164 } 165 } 166 return false, fmt.Errorf("unrecognized GOEXPERIMENT %q", value) 167} 168 169func isCaseSensitive() (bool, error) { 170 tmpdir, err := os.MkdirTemp(testTmpDir, "case-sensitive") 171 if err != nil { 172 return false, fmt.Errorf("failed to create directory to determine case-sensitivity: %w", err) 173 } 174 defer os.RemoveAll(tmpdir) 175 176 fcap := filepath.Join(tmpdir, "FILE") 177 if err := os.WriteFile(fcap, []byte{}, 0644); err != nil { 178 return false, fmt.Errorf("error writing file to determine case-sensitivity: %w", err) 179 } 180 181 flow := filepath.Join(tmpdir, "file") 182 _, err = os.ReadFile(flow) 183 switch { 184 case err == nil: 185 return false, nil 186 case os.IsNotExist(err): 187 return true, nil 188 default: 189 return false, fmt.Errorf("unexpected error reading file when determining case-sensitivity: %w", err) 190 } 191} 192 193func isTrimpath() (bool, error) { 194 info, _ := debug.ReadBuildInfo() 195 if info == nil { 196 return false, errors.New("missing build info") 197 } 198 199 for _, s := range info.Settings { 200 if s.Key == "-trimpath" && s.Value == "true" { 201 return true, nil 202 } 203 } 204 return false, nil 205} 206 207func hasWorkingGit() bool { 208 if runtime.GOOS == "plan9" { 209 // The Git command is usually not the real Git on Plan 9. 210 // See https://golang.org/issues/29640. 211 return false 212 } 213 _, err := exec.LookPath("git") 214 return err == nil 215} 216 217func cgoLinkExt(s *script.State) (bool, error) { 218 GOOS, _ := s.LookupEnv("GOOS") 219 GOARCH, _ := s.LookupEnv("GOARCH") 220 return platform.MustLinkExternal(GOOS, GOARCH, true), nil 221} 222 223func mustLinkExt(s *script.State) (bool, error) { 224 GOOS, _ := s.LookupEnv("GOOS") 225 GOARCH, _ := s.LookupEnv("GOARCH") 226 return platform.MustLinkExternal(GOOS, GOARCH, false), nil 227} 228 229func pieLinkExt(s *script.State) (bool, error) { 230 GOOS, _ := s.LookupEnv("GOOS") 231 GOARCH, _ := s.LookupEnv("GOARCH") 232 return !platform.InternalLinkPIESupported(GOOS, GOARCH), nil 233} 234