summaryrefslogtreecommitdiffstats
path: root/tpl/internal/go_templates/testenv/testenv.go
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2022-11-14 19:13:09 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2022-11-14 22:31:50 +0100
commitf6ab9553f4c0429586fc9221d1779c460cf4922a (patch)
tree0f4efed30fb9750b800a4865c5065285bbc4d1fc /tpl/internal/go_templates/testenv/testenv.go
parent58a98c7758f90a16df51e4fee9ead0233157a1e4 (diff)
tpl/internal: Sync go_templates
Closes #10411
Diffstat (limited to 'tpl/internal/go_templates/testenv/testenv.go')
-rw-r--r--tpl/internal/go_templates/testenv/testenv.go256
1 files changed, 111 insertions, 145 deletions
diff --git a/tpl/internal/go_templates/testenv/testenv.go b/tpl/internal/go_templates/testenv/testenv.go
index 510b5406e..4e38f5f04 100644
--- a/tpl/internal/go_templates/testenv/testenv.go
+++ b/tpl/internal/go_templates/testenv/testenv.go
@@ -11,10 +11,12 @@
package testenv
import (
- "bytes"
"errors"
"flag"
+ "fmt"
+
"github.com/gohugoio/hugo/tpl/internal/go_templates/cfg"
+
"os"
"os/exec"
"path/filepath"
@@ -23,7 +25,6 @@ import (
"strings"
"sync"
"testing"
- "time"
)
// Builder reports the name of the builder running this test
@@ -34,7 +35,7 @@ func Builder() string {
return os.Getenv("GO_BUILDER_NAME")
}
-// HasGoBuild reports whether the current system can build programs with ``go build''
+// HasGoBuild reports whether the current system can build programs with “go build”
// and then run them with os.StartProcess or exec.Command.
func HasGoBuild() bool {
if os.Getenv("GO_GCFLAGS") != "" {
@@ -51,7 +52,7 @@ func HasGoBuild() bool {
return true
}
-// MustHaveGoBuild checks that the current system can build programs with ``go build''
+// MustHaveGoBuild checks that the current system can build programs with “go build”
// and then run them with os.StartProcess or exec.Command.
// If not, MustHaveGoBuild calls t.Skip with an explanation.
func MustHaveGoBuild(t testing.TB) {
@@ -63,13 +64,13 @@ func MustHaveGoBuild(t testing.TB) {
}
}
-// HasGoRun reports whether the current system can run programs with ``go run.''
+// HasGoRun reports whether the current system can run programs with “go run.”
func HasGoRun() bool {
// For now, having go run and having go build are the same.
return HasGoBuild()
}
-// MustHaveGoRun checks that the current system can run programs with ``go run.''
+// MustHaveGoRun checks that the current system can run programs with “go run.”
// If not, MustHaveGoRun calls t.Skip with an explanation.
func MustHaveGoRun(t testing.TB) {
if !HasGoRun() {
@@ -96,6 +97,100 @@ 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, or explicitly set GOROOT_FINAL but not GOROOT
+ // and hasn't moved the tree to GOROOT_FINAL yet. But those cases are
+ // 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, or perhaps because GOROOT_FINAL was set
+ // without GOROOT and the tree hasn't been moved there yet).
+ //
+ // 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.
+
+ cwd, err := os.Getwd()
+ if err != nil {
+ gorootErr = fmt.Errorf("finding GOROOT: %w", err)
+ return
+ }
+
+ 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".
+ }
+
+ 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
+ }
+ 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 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
+// works even if the test binary was built with -trimpath.
+//
+// If GOROOT cannot be found, GOROOT skips t if t is non-nil,
+// or panics otherwise.
+func GOROOT(t testing.TB) string {
+ path, err := findGOROOT()
+ if err != nil {
+ if t == nil {
+ panic(err)
+ }
+ t.Helper()
+ t.Skip(err)
+ }
+ return path
+}
+
// GoTool reports the path to the Go tool.
func GoTool() (string, error) {
if !HasGoBuild() {
@@ -105,7 +200,11 @@ func GoTool() (string, error) {
if runtime.GOOS == "windows" {
exeSuffix = ".exe"
}
- path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix)
+ goroot, err := findGOROOT()
+ if err != nil {
+ return "", fmt.Errorf("cannot find go tool: %w", err)
+ }
+ path := filepath.Join(goroot, "bin", "go"+exeSuffix)
if _, err := os.Stat(path); err == nil {
return path, nil
}
@@ -116,16 +215,6 @@ func GoTool() (string, error) {
return goBin, nil
}
-// HasExec reports whether the current system can start new processes
-// using os.StartProcess or (more commonly) exec.Command.
-func HasExec() bool {
- switch runtime.GOOS {
- case "js", "ios":
- return false
- }
- return true
-}
-
// HasSrc reports whether the entire source tree is available under GOROOT.
func HasSrc() bool {
switch runtime.GOOS {
@@ -135,33 +224,6 @@ func HasSrc() bool {
return true
}
-// MustHaveExec checks that the current system can start new processes
-// using os.StartProcess or (more commonly) exec.Command.
-// If not, MustHaveExec calls t.Skip with an explanation.
-func MustHaveExec(t testing.TB) {
- if !HasExec() {
- t.Skipf("skipping test: cannot exec subprocess on %s/%s", runtime.GOOS, runtime.GOARCH)
- }
-}
-
-var execPaths sync.Map // path -> error
-
-// MustHaveExecPath checks that the current system can start the named executable
-// using os.StartProcess or (more commonly) exec.Command.
-// If not, MustHaveExecPath calls t.Skip with an explanation.
-func MustHaveExecPath(t testing.TB, path string) {
- MustHaveExec(t)
-
- err, found := execPaths.Load(path)
- if !found {
- _, err = exec.LookPath(path)
- err, _ = execPaths.LoadOrStore(path, err)
- }
- if err != nil {
- t.Skipf("skipping test: %s: %s", path, err)
- }
-}
-
// HasExternalNetwork reports whether the current system can use
// external (non-localhost) networks.
func HasExternalNetwork() bool {
@@ -194,32 +256,6 @@ func MustHaveCGO(t testing.TB) {
}
}
-// CanInternalLink reports whether the current system can link programs with
-// internal linking.
-// (This is the opposite of cmd/internal/sys.MustLinkExternal. Keep them in sync.)
-func CanInternalLink() bool {
- switch runtime.GOOS {
- case "android":
- if runtime.GOARCH != "arm64" {
- return false
- }
- case "ios":
- if runtime.GOARCH == "arm64" {
- return false
- }
- }
- return true
-}
-
-// MustInternalLink checks that the current system can link programs with internal
-// linking.
-// If not, MustInternalLink calls t.Skip with an explanation.
-func MustInternalLink(t testing.TB) {
- if !CanInternalLink() {
- t.Skipf("skipping test: internal linking on %s/%s is not supported", runtime.GOOS, runtime.GOARCH)
- }
-}
-
// HasSymlink reports whether the current system can use os.Symlink.
func HasSymlink() bool {
ok, _ := hasSymlink()
@@ -267,28 +303,6 @@ func SkipFlakyNet(t testing.TB) {
}
}
-// CleanCmdEnv will fill cmd.Env with the environment, excluding certain
-// variables that could modify the behavior of the Go tools such as
-// GODEBUG and GOTRACEBACK.
-func CleanCmdEnv(cmd *exec.Cmd) *exec.Cmd {
- if cmd.Env != nil {
- panic("environment already set")
- }
- for _, env := range os.Environ() {
- // Exclude GODEBUG from the environment to prevent its output
- // from breaking tests that are trying to parse other command output.
- if strings.HasPrefix(env, "GODEBUG=") {
- continue
- }
- // Exclude GOTRACEBACK for the same reason.
- if strings.HasPrefix(env, "GOTRACEBACK=") {
- continue
- }
- cmd.Env = append(cmd.Env, env)
- }
- return cmd
-}
-
// CPUIsSlow reports whether the CPU running the test is suspected to be slow.
func CPUIsSlow() bool {
switch runtime.GOARCH {
@@ -309,58 +323,10 @@ func SkipIfShortAndSlow(t testing.TB) {
}
}
-// RunWithTimeout runs cmd and returns its combined output. If the
-// subprocess exits with a non-zero status, it will log that status
-// and return a non-nil error, but this is not considered fatal.
-func RunWithTimeout(t testing.TB, cmd *exec.Cmd) ([]byte, error) {
- args := cmd.Args
- if args == nil {
- args = []string{cmd.Path}
- }
-
- var b bytes.Buffer
- cmd.Stdout = &b
- cmd.Stderr = &b
- if err := cmd.Start(); err != nil {
- t.Fatalf("starting %s: %v", args, err)
- }
-
- // If the process doesn't complete within 1 minute,
- // assume it is hanging and kill it to get a stack trace.
- p := cmd.Process
- done := make(chan bool)
- go func() {
- scale := 1
- // This GOARCH/GOOS test is copied from cmd/dist/test.go.
- // TODO(iant): Have cmd/dist update the environment variable.
- if runtime.GOARCH == "arm" || runtime.GOOS == "windows" {
- scale = 2
- }
- if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
- if sc, err := strconv.Atoi(s); err == nil {
- scale = sc
- }
- }
-
- select {
- case <-done:
- case <-time.After(time.Duration(scale) * time.Minute):
- p.Signal(Sigquit)
- // If SIGQUIT doesn't do it after a little
- // while, kill the process.
- select {
- case <-done:
- case <-time.After(time.Duration(scale) * 30 * time.Second):
- p.Signal(os.Kill)
- }
- }
- }()
-
- err := cmd.Wait()
- if err != nil {
- t.Logf("%s exit status: %v", args, err)
+// SkipIfOptimizationOff skips t if optimization is disabled.
+func SkipIfOptimizationOff(t testing.TB) {
+ if OptimizationOff() {
+ t.Helper()
+ t.Skip("skipping test with optimization disabled")
}
- close(done)
-
- return b.Bytes(), err
}