]> Cypherpunks repositories - gostls13.git/commitdiff
internal/testenv: use sync.OnceValue[s]
authorKir Kolyshkin <kolyshkin@gmail.com>
Fri, 30 Aug 2024 00:31:03 +0000 (17:31 -0700)
committerGopher Robot <gobot@golang.org>
Fri, 30 Aug 2024 17:08:17 +0000 (17:08 +0000)
Modernize the code to use sync.OnceValue[s] instead of sync.Once.

While at it, reuse the result of exec.LookPath("go") in tryGoBuild.

Change-Id: I13eff3dd55797846680e506fffb7c49c8296829d
Reviewed-on: https://go-review.googlesource.com/c/go/+/609796
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Alan Donovan <adonovan@google.com>
Commit-Queue: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
src/internal/testenv/testenv.go

index 9fb92406e8d85e89b6d60c687b07de62ba57ec74..80da6e6c57b6a02d01f0643945490238dda21e49 100644 (file)
@@ -53,63 +53,59 @@ func HasGoBuild() bool {
                return false
        }
 
-       goBuildOnce.Do(func() {
-               // To run 'go build', we need to be able to exec a 'go' command.
-               // We somewhat arbitrarily choose to exec 'go tool -n compile' because that
-               // also confirms that cmd/go can find the compiler. (Before CL 472096,
-               // we sometimes ended up with cmd/go installed in the test environment
-               // without a cmd/compile it could use to actually build things.)
-               cmd := exec.Command("go", "tool", "-n", "compile")
-               cmd.Env = origEnv
-               out, err := cmd.Output()
-               if err != nil {
-                       goBuildErr = fmt.Errorf("%v: %w", cmd, err)
-                       return
-               }
-               out = bytes.TrimSpace(out)
-               if len(out) == 0 {
-                       goBuildErr = fmt.Errorf("%v: no tool reported", cmd)
-                       return
-               }
-               if _, err := exec.LookPath(string(out)); err != nil {
-                       goBuildErr = err
-                       return
-               }
+       return tryGoBuild() == nil
+}
 
-               if platform.MustLinkExternal(runtime.GOOS, runtime.GOARCH, false) {
-                       // We can assume that we always have a complete Go toolchain available.
-                       // However, this platform requires a C linker to build even pure Go
-                       // programs, including tests. Do we have one in the test environment?
-                       // (On Android, for example, the device running the test might not have a
-                       // C toolchain installed.)
-                       //
-                       // If CC is set explicitly, assume that we do. Otherwise, use 'go env CC'
-                       // to determine which toolchain it would use by default.
-                       if os.Getenv("CC") == "" {
-                               cmd := exec.Command("go", "env", "CC")
-                               cmd.Env = origEnv
-                               out, err := cmd.Output()
-                               if err != nil {
-                                       goBuildErr = fmt.Errorf("%v: %w", cmd, err)
-                                       return
-                               }
-                               out = bytes.TrimSpace(out)
-                               if len(out) == 0 {
-                                       goBuildErr = fmt.Errorf("%v: no CC reported", cmd)
-                                       return
-                               }
-                               _, goBuildErr = exec.LookPath(string(out))
+var tryGoBuild = sync.OnceValue(func() error {
+       // To run 'go build', we need to be able to exec a 'go' command.
+       // We somewhat arbitrarily choose to exec 'go tool -n compile' because that
+       // also confirms that cmd/go can find the compiler. (Before CL 472096,
+       // we sometimes ended up with cmd/go installed in the test environment
+       // without a cmd/compile it could use to actually build things.)
+       goTool, err := goTool()
+       if err != nil {
+               return err
+       }
+       cmd := exec.Command(goTool, "tool", "-n", "compile")
+       cmd.Env = origEnv
+       out, err := cmd.Output()
+       if err != nil {
+               return fmt.Errorf("%v: %w", cmd, err)
+       }
+       out = bytes.TrimSpace(out)
+       if len(out) == 0 {
+               return fmt.Errorf("%v: no tool reported", cmd)
+       }
+       if _, err := exec.LookPath(string(out)); err != nil {
+               return err
+       }
+
+       if platform.MustLinkExternal(runtime.GOOS, runtime.GOARCH, false) {
+               // We can assume that we always have a complete Go toolchain available.
+               // However, this platform requires a C linker to build even pure Go
+               // programs, including tests. Do we have one in the test environment?
+               // (On Android, for example, the device running the test might not have a
+               // C toolchain installed.)
+               //
+               // If CC is set explicitly, assume that we do. Otherwise, use 'go env CC'
+               // to determine which toolchain it would use by default.
+               if os.Getenv("CC") == "" {
+                       cmd := exec.Command(goTool, "env", "CC")
+                       cmd.Env = origEnv
+                       out, err := cmd.Output()
+                       if err != nil {
+                               return fmt.Errorf("%v: %w", cmd, err)
                        }
+                       out = bytes.TrimSpace(out)
+                       if len(out) == 0 {
+                               return fmt.Errorf("%v: no CC reported", cmd)
+                       }
+                       _, err = exec.LookPath(string(out))
+                       return err
                }
-       })
-
-       return goBuildErr == nil
-}
-
-var (
-       goBuildOnce sync.Once
-       goBuildErr  error
-)
+       }
+       return nil
+})
 
 // MustHaveGoBuild checks that the current system can build programs with “go build”
 // and then run them with os.StartProcess or exec.Command.
@@ -121,7 +117,7 @@ func MustHaveGoBuild(t testing.TB) {
        }
        if !HasGoBuild() {
                t.Helper()
-               t.Skipf("skipping test: 'go build' unavailable: %v", goBuildErr)
+               t.Skipf("skipping test: 'go build' unavailable: %v", tryGoBuild())
        }
 }
 
@@ -177,82 +173,67 @@ func GoToolPath(t testing.TB) string {
        return path
 }
 
-var (
-       gorootOnce sync.Once
-       gorootPath string
-       gorootErr  error
-)
-
-func findGOROOT() (string, error) {
-       gorootOnce.Do(func() {
-               gorootPath = runtime.GOROOT()
-               if gorootPath != "" {
-                       // If runtime.GOROOT() is non-empty, assume that it is valid.
-                       //
-                       // (It might not be: for example, the user may have explicitly set GOROOT
-                       // to the wrong directory. But this case is
-                       // rare, and if that happens the user can fix what they broke.)
-                       return
-               }
-
-               // runtime.GOROOT doesn't know where GOROOT is (perhaps because the test
-               // binary was built with -trimpath).
+var findGOROOT = sync.OnceValues(func() (path string, err error) {
+       if path := runtime.GOROOT(); path != "" {
+               // If runtime.GOROOT() is non-empty, assume that it is valid.
                //
-               // Since this is internal/testenv, we can cheat and assume that the caller
-               // is a test of some package in a subdirectory of GOROOT/src. ('go test'
-               // runs the test in the directory containing the packaged under test.) That
-               // means that if we start walking up the tree, we should eventually find
-               // GOROOT/src/go.mod, and we can report the parent directory of that.
-               //
-               // Notably, this works even if we can't run 'go env GOROOT' as a
-               // subprocess.
+               // (It might not be: for example, the user may have explicitly set GOROOT
+               // to the wrong directory. But this case is
+               // rare, and if that happens the user can fix what they broke.)
+               return path, nil
+       }
 
-               cwd, err := os.Getwd()
-               if err != nil {
-                       gorootErr = fmt.Errorf("finding GOROOT: %w", err)
-                       return
+       // runtime.GOROOT doesn't know where GOROOT is (perhaps because the test
+       // binary was built with -trimpath).
+       //
+       // Since this is internal/testenv, we can cheat and assume that the caller
+       // is a test of some package in a subdirectory of GOROOT/src. ('go test'
+       // runs the test in the directory containing the packaged under test.) That
+       // means that if we start walking up the tree, we should eventually find
+       // GOROOT/src/go.mod, and we can report the parent directory of that.
+       //
+       // Notably, this works even if we can't run 'go env GOROOT' as a
+       // subprocess.
+
+       cwd, err := os.Getwd()
+       if err != nil {
+               return "", fmt.Errorf("finding GOROOT: %w", err)
+       }
+
+       dir := cwd
+       for {
+               parent := filepath.Dir(dir)
+               if parent == dir {
+                       // dir is either "." or only a volume name.
+                       return "", fmt.Errorf("failed to locate GOROOT/src in any parent directory")
                }
 
-               dir := cwd
-               for {
-                       parent := filepath.Dir(dir)
-                       if parent == dir {
-                               // dir is either "." or only a volume name.
-                               gorootErr = fmt.Errorf("failed to locate GOROOT/src in any parent directory")
-                               return
-                       }
+               if base := filepath.Base(dir); base != "src" {
+                       dir = parent
+                       continue // dir cannot be GOROOT/src if it doesn't end in "src".
+               }
 
-                       if base := filepath.Base(dir); base != "src" {
+               b, err := os.ReadFile(filepath.Join(dir, "go.mod"))
+               if err != nil {
+                       if os.IsNotExist(err) {
                                dir = parent
-                               continue // dir cannot be GOROOT/src if it doesn't end in "src".
-                       }
-
-                       b, err := os.ReadFile(filepath.Join(dir, "go.mod"))
-                       if err != nil {
-                               if os.IsNotExist(err) {
-                                       dir = parent
-                                       continue
-                               }
-                               gorootErr = fmt.Errorf("finding GOROOT: %w", err)
-                               return
+                               continue
                        }
-                       goMod := string(b)
-
-                       for goMod != "" {
-                               var line string
-                               line, goMod, _ = strings.Cut(goMod, "\n")
-                               fields := strings.Fields(line)
-                               if len(fields) >= 2 && fields[0] == "module" && fields[1] == "std" {
-                                       // Found "module std", which is the module declaration in GOROOT/src!
-                                       gorootPath = parent
-                                       return
-                               }
+                       return "", fmt.Errorf("finding GOROOT: %w", err)
+               }
+               goMod := string(b)
+
+               for goMod != "" {
+                       var line string
+                       line, goMod, _ = strings.Cut(goMod, "\n")
+                       fields := strings.Fields(line)
+                       if len(fields) >= 2 && fields[0] == "module" && fields[1] == "std" {
+                               // Found "module std", which is the module declaration in GOROOT/src!
+                               return parent, nil
                        }
                }
-       })
-
-       return gorootPath, gorootErr
-}
+       }
+})
 
 // GOROOT reports the path to the directory containing the root of the Go
 // project source tree. This is normally equivalent to runtime.GOROOT, but
@@ -278,17 +259,12 @@ func GoTool() (string, error) {
        if !HasGoBuild() {
                return "", errors.New("platform cannot run go tool")
        }
-       goToolOnce.Do(func() {
-               goToolPath, goToolErr = exec.LookPath("go")
-       })
-       return goToolPath, goToolErr
+       return goTool()
 }
 
-var (
-       goToolOnce sync.Once
-       goToolPath string
-       goToolErr  error
-)
+var goTool = sync.OnceValues(func() (string, error) {
+       return exec.LookPath("go")
+})
 
 // HasSrc reports whether the entire source tree is available under GOROOT.
 func HasSrc() bool {
@@ -321,29 +297,26 @@ func MustHaveExternalNetwork(t testing.TB) {
 
 // HasCGO reports whether the current system can use cgo.
 func HasCGO() bool {
-       hasCgoOnce.Do(func() {
-               goTool, err := GoTool()
-               if err != nil {
-                       return
-               }
-               cmd := exec.Command(goTool, "env", "CGO_ENABLED")
-               cmd.Env = origEnv
-               out, err := cmd.Output()
-               if err != nil {
-                       panic(fmt.Sprintf("%v: %v", cmd, out))
-               }
-               hasCgo, err = strconv.ParseBool(string(bytes.TrimSpace(out)))
-               if err != nil {
-                       panic(fmt.Sprintf("%v: non-boolean output %q", cmd, out))
-               }
-       })
-       return hasCgo
+       return hasCgo()
 }
 
-var (
-       hasCgoOnce sync.Once
-       hasCgo     bool
-)
+var hasCgo = sync.OnceValue(func() bool {
+       goTool, err := goTool()
+       if err != nil {
+               return false
+       }
+       cmd := exec.Command(goTool, "env", "CGO_ENABLED")
+       cmd.Env = origEnv
+       out, err := cmd.Output()
+       if err != nil {
+               panic(fmt.Sprintf("%v: %v", cmd, out))
+       }
+       ok, err := strconv.ParseBool(string(bytes.TrimSpace(out)))
+       if err != nil {
+               panic(fmt.Sprintf("%v: non-boolean output %q", cmd, out))
+       }
+       return ok
+})
 
 // MustHaveCGO calls t.Skip if cgo is not available.
 func MustHaveCGO(t testing.TB) {