subprocess_windows_test.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. // Copyright 2015 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. package winutil
  5. import (
  6. "bytes"
  7. "errors"
  8. "fmt"
  9. "os"
  10. "os/exec"
  11. "path/filepath"
  12. "runtime"
  13. "strings"
  14. "sync"
  15. "testing"
  16. )
  17. // The code in this file is adapted from internal/testenv in the Go source tree
  18. // and is used for writing tests that require spawning subprocesses.
  19. var toRemove []string
  20. func TestMain(m *testing.M) {
  21. status := m.Run()
  22. for _, file := range toRemove {
  23. os.RemoveAll(file)
  24. }
  25. os.Exit(status)
  26. }
  27. var testprog struct {
  28. sync.Mutex
  29. dir string
  30. target map[string]*buildexe
  31. }
  32. type buildexe struct {
  33. once sync.Once
  34. exe string
  35. err error
  36. }
  37. func pathToTestProg(t *testing.T, binary string) string {
  38. exe, err := buildTestProg(t, binary, "-buildvcs=false")
  39. if err != nil {
  40. t.Fatal(err)
  41. }
  42. return exe
  43. }
  44. func startTestProg(t *testing.T, binary, name string, env ...string) {
  45. exe, err := buildTestProg(t, binary, "-buildvcs=false")
  46. if err != nil {
  47. t.Fatal(err)
  48. }
  49. startBuiltTestProg(t, exe, name, env...)
  50. }
  51. func startBuiltTestProg(t *testing.T, exe, name string, env ...string) {
  52. cmd := exec.Command(exe, name)
  53. cmd.Env = append(cmd.Env, env...)
  54. if testing.Short() {
  55. cmd.Env = append(cmd.Env, "RUNTIME_TEST_SHORT=1")
  56. }
  57. start(t, cmd)
  58. }
  59. var serializeBuild = make(chan bool, 2)
  60. func buildTestProg(t *testing.T, binary string, flags ...string) (string, error) {
  61. testprog.Lock()
  62. if testprog.dir == "" {
  63. dir, err := os.MkdirTemp("", "go-build")
  64. if err != nil {
  65. t.Fatalf("failed to create temp directory: %v", err)
  66. }
  67. testprog.dir = dir
  68. toRemove = append(toRemove, dir)
  69. }
  70. if testprog.target == nil {
  71. testprog.target = make(map[string]*buildexe)
  72. }
  73. name := binary
  74. if len(flags) > 0 {
  75. nameFlags := make([]string, 0, len(flags))
  76. for _, flag := range flags {
  77. nameFlags = append(nameFlags, strings.ReplaceAll(flag, "=", "_"))
  78. }
  79. name += "_" + strings.Join(nameFlags, "_")
  80. }
  81. target, ok := testprog.target[name]
  82. if !ok {
  83. target = &buildexe{}
  84. testprog.target[name] = target
  85. }
  86. dir := testprog.dir
  87. // Unlock testprog while actually building, so that other
  88. // tests can look up executables that were already built.
  89. testprog.Unlock()
  90. target.once.Do(func() {
  91. // Only do two "go build"'s at a time,
  92. // to keep load from getting too high.
  93. serializeBuild <- true
  94. defer func() { <-serializeBuild }()
  95. // Don't get confused if goToolPath calls t.Skip.
  96. target.err = errors.New("building test called t.Skip")
  97. exe := filepath.Join(dir, name+".exe")
  98. t.Logf("running go build -o %s %s", exe, strings.Join(flags, " "))
  99. cmd := exec.Command(goToolPath(t), append([]string{"build", "-o", exe}, flags...)...)
  100. cmd.Dir = "testdata/" + binary
  101. out, err := cmd.CombinedOutput()
  102. if err != nil {
  103. target.err = fmt.Errorf("building %s %v: %v\n%s", binary, flags, err, out)
  104. } else {
  105. target.exe = exe
  106. target.err = nil
  107. }
  108. })
  109. return target.exe, target.err
  110. }
  111. // goTool reports the path to the Go tool.
  112. func goTool() (string, error) {
  113. if !hasGoBuild() {
  114. return "", errors.New("platform cannot run go tool")
  115. }
  116. exeSuffix := ".exe"
  117. goroot, err := findGOROOT()
  118. if err != nil {
  119. return "", fmt.Errorf("cannot find go tool: %w", err)
  120. }
  121. path := filepath.Join(goroot, "bin", "go"+exeSuffix)
  122. if _, err := os.Stat(path); err == nil {
  123. return path, nil
  124. }
  125. goBin, err := exec.LookPath("go" + exeSuffix)
  126. if err != nil {
  127. return "", errors.New("cannot find go tool: " + err.Error())
  128. }
  129. return goBin, nil
  130. }
  131. // knownEnv is a list of environment variables that affect the operation
  132. // of the Go command.
  133. const knownEnv = `
  134. AR
  135. CC
  136. CGO_CFLAGS
  137. CGO_CFLAGS_ALLOW
  138. CGO_CFLAGS_DISALLOW
  139. CGO_CPPFLAGS
  140. CGO_CPPFLAGS_ALLOW
  141. CGO_CPPFLAGS_DISALLOW
  142. CGO_CXXFLAGS
  143. CGO_CXXFLAGS_ALLOW
  144. CGO_CXXFLAGS_DISALLOW
  145. CGO_ENABLED
  146. CGO_FFLAGS
  147. CGO_FFLAGS_ALLOW
  148. CGO_FFLAGS_DISALLOW
  149. CGO_LDFLAGS
  150. CGO_LDFLAGS_ALLOW
  151. CGO_LDFLAGS_DISALLOW
  152. CXX
  153. FC
  154. GCCGO
  155. GO111MODULE
  156. GO386
  157. GOAMD64
  158. GOARCH
  159. GOARM
  160. GOBIN
  161. GOCACHE
  162. GOENV
  163. GOEXE
  164. GOEXPERIMENT
  165. GOFLAGS
  166. GOGCCFLAGS
  167. GOHOSTARCH
  168. GOHOSTOS
  169. GOINSECURE
  170. GOMIPS
  171. GOMIPS64
  172. GOMODCACHE
  173. GONOPROXY
  174. GONOSUMDB
  175. GOOS
  176. GOPATH
  177. GOPPC64
  178. GOPRIVATE
  179. GOPROXY
  180. GOROOT
  181. GOSUMDB
  182. GOTMPDIR
  183. GOTOOLDIR
  184. GOVCS
  185. GOWASM
  186. GOWORK
  187. GO_EXTLINK_ENABLED
  188. PKG_CONFIG
  189. `
  190. // goToolPath reports the path to the Go tool.
  191. // It is a convenience wrapper around goTool.
  192. // If the tool is unavailable goToolPath calls t.Skip.
  193. // If the tool should be available and isn't, goToolPath calls t.Fatal.
  194. func goToolPath(t testing.TB) string {
  195. mustHaveGoBuild(t)
  196. path, err := goTool()
  197. if err != nil {
  198. t.Fatal(err)
  199. }
  200. // Add all environment variables that affect the Go command to test metadata.
  201. // Cached test results will be invalidate when these variables change.
  202. // See golang.org/issue/32285.
  203. for _, envVar := range strings.Fields(knownEnv) {
  204. os.Getenv(envVar)
  205. }
  206. return path
  207. }
  208. // hasGoBuild reports whether the current system can build programs with “go build”
  209. // and then run them with os.StartProcess or exec.Command.
  210. func hasGoBuild() bool {
  211. if os.Getenv("GO_GCFLAGS") != "" {
  212. // It's too much work to require every caller of the go command
  213. // to pass along "-gcflags="+os.Getenv("GO_GCFLAGS").
  214. // For now, if $GO_GCFLAGS is set, report that we simply can't
  215. // run go build.
  216. return false
  217. }
  218. return true
  219. }
  220. // mustHaveGoBuild checks that the current system can build programs with “go build”
  221. // and then run them with os.StartProcess or exec.Command.
  222. // If not, mustHaveGoBuild calls t.Skip with an explanation.
  223. func mustHaveGoBuild(t testing.TB) {
  224. if os.Getenv("GO_GCFLAGS") != "" {
  225. t.Skipf("skipping test: 'go build' not compatible with setting $GO_GCFLAGS")
  226. }
  227. if !hasGoBuild() {
  228. t.Skipf("skipping test: 'go build' not available on %s/%s", runtime.GOOS, runtime.GOARCH)
  229. }
  230. }
  231. var (
  232. gorootOnce sync.Once
  233. gorootPath string
  234. gorootErr error
  235. )
  236. func findGOROOT() (string, error) {
  237. gorootOnce.Do(func() {
  238. gorootPath = runtime.GOROOT()
  239. if gorootPath != "" {
  240. // If runtime.GOROOT() is non-empty, assume that it is valid.
  241. //
  242. // (It might not be: for example, the user may have explicitly set GOROOT
  243. // to the wrong directory, or explicitly set GOROOT_FINAL but not GOROOT
  244. // and hasn't moved the tree to GOROOT_FINAL yet. But those cases are
  245. // rare, and if that happens the user can fix what they broke.)
  246. return
  247. }
  248. // runtime.GOROOT doesn't know where GOROOT is (perhaps because the test
  249. // binary was built with -trimpath, or perhaps because GOROOT_FINAL was set
  250. // without GOROOT and the tree hasn't been moved there yet).
  251. //
  252. // Since this is internal/testenv, we can cheat and assume that the caller
  253. // is a test of some package in a subdirectory of GOROOT/src. ('go test'
  254. // runs the test in the directory containing the packaged under test.) That
  255. // means that if we start walking up the tree, we should eventually find
  256. // GOROOT/src/go.mod, and we can report the parent directory of that.
  257. cwd, err := os.Getwd()
  258. if err != nil {
  259. gorootErr = fmt.Errorf("finding GOROOT: %w", err)
  260. return
  261. }
  262. dir := cwd
  263. for {
  264. parent := filepath.Dir(dir)
  265. if parent == dir {
  266. // dir is either "." or only a volume name.
  267. gorootErr = fmt.Errorf("failed to locate GOROOT/src in any parent directory")
  268. return
  269. }
  270. if base := filepath.Base(dir); base != "src" {
  271. dir = parent
  272. continue // dir cannot be GOROOT/src if it doesn't end in "src".
  273. }
  274. b, err := os.ReadFile(filepath.Join(dir, "go.mod"))
  275. if err != nil {
  276. if os.IsNotExist(err) {
  277. dir = parent
  278. continue
  279. }
  280. gorootErr = fmt.Errorf("finding GOROOT: %w", err)
  281. return
  282. }
  283. goMod := string(b)
  284. for goMod != "" {
  285. var line string
  286. line, goMod, _ = strings.Cut(goMod, "\n")
  287. fields := strings.Fields(line)
  288. if len(fields) >= 2 && fields[0] == "module" && fields[1] == "std" {
  289. // Found "module std", which is the module declaration in GOROOT/src!
  290. gorootPath = parent
  291. return
  292. }
  293. }
  294. }
  295. })
  296. return gorootPath, gorootErr
  297. }
  298. // start runs cmd asynchronously and returns immediately.
  299. func start(t testing.TB, cmd *exec.Cmd) {
  300. args := cmd.Args
  301. if args == nil {
  302. args = []string{cmd.Path}
  303. }
  304. var b bytes.Buffer
  305. cmd.Stdout = &b
  306. cmd.Stderr = &b
  307. if err := cmd.Start(); err != nil {
  308. t.Fatalf("starting %s: %v", args, err)
  309. }
  310. }