internal/buildtest: add
Add the buildtest package, to aid running `go build` in tests.
Copied from the x/vuln repo.
Change-Id: I68c38ab28ed893e6cd7334ad79dda1d087ad7f19
Reviewed-on: https://go-review.googlesource.com/c/pkgsite-metrics/+/467717
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Julie Qiu <julieqiu@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/internal/buildtest/buildtest.go b/internal/buildtest/buildtest.go
new file mode 100644
index 0000000..559ca08
--- /dev/null
+++ b/internal/buildtest/buildtest.go
@@ -0,0 +1,97 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package buildtest provides support for running "go build"
+// in tests.
+package buildtest
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "testing"
+)
+
+var unsupportedGoosGoarch = map[string]bool{
+ "darwin/386": true,
+ "darwin/arm": true,
+}
+
+// GoBuild runs "go build" on dir using the additional environment variables in
+// envVarVals, which should be an alternating list of variables and values.
+// It returns the path to the resulting binary, and a function
+// to call when finished with the binary.
+func GoBuild(t *testing.T, dir, tags string, envVarVals ...string) (binaryPath string, cleanup func()) {
+ switch runtime.GOOS {
+ case "android", "js", "ios":
+ t.Skipf("skipping on OS without 'go build' %s", runtime.GOOS)
+ }
+
+ if len(envVarVals)%2 != 0 {
+ t.Fatal("last args should be alternating variables and values")
+ }
+ var env []string
+ if len(envVarVals) > 0 {
+ env = os.Environ()
+ for i := 0; i < len(envVarVals); i += 2 {
+ env = append(env, fmt.Sprintf("%s=%s", envVarVals[i], envVarVals[i+1]))
+ }
+ }
+
+ gg := lookupEnv("GOOS", env, runtime.GOOS) + "/" + lookupEnv("GOARCH", env, runtime.GOARCH)
+ if unsupportedGoosGoarch[gg] {
+ t.Skipf("skipping unsupported GOOS/GOARCH pair %s", gg)
+ }
+
+ tmpDir, err := os.MkdirTemp("", "buildtest")
+ if err != nil {
+ t.Fatal(err)
+ }
+ abs, err := filepath.Abs(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ binaryPath = filepath.Join(tmpDir, filepath.Base(abs))
+ var exeSuffix string
+ if runtime.GOOS == "windows" {
+ exeSuffix = ".exe"
+ }
+ // Make sure we use the same version of go that is running this test.
+ goCommandPath := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix)
+ if _, err := os.Stat(goCommandPath); err != nil {
+ t.Fatal(err)
+ }
+ args := []string{"build", "-o", binaryPath + exeSuffix}
+ if tags != "" {
+ args = append(args, "-tags", tags)
+ }
+ cmd := exec.Command(goCommandPath, args...)
+ cmd.Dir = dir
+ cmd.Env = env
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ t.Fatal(err)
+ }
+ return binaryPath + exeSuffix, func() { os.RemoveAll(tmpDir) }
+}
+
+// lookEnv looks for name in env, a list of "VAR=VALUE" strings. It returns
+// the value if name is found, and defaultValue if it is not.
+func lookupEnv(name string, env []string, defaultValue string) string {
+ for _, vv := range env {
+ i := strings.IndexByte(vv, '=')
+ if i < 0 {
+ // malformed env entry; just ignore it
+ continue
+ }
+ if name == vv[:i] {
+ return vv[i+1:]
+ }
+ }
+ return defaultValue
+}