exp/govulncheck: DefaultCache now returns an error
DefaultCache can now return an error.
Disables TestGoEnv on js/wasm which does not support "go env".
Additionally calls GoEnv lazier. GoEnv now returns an error.
Fixes golang/go#57628
Change-Id: Iced82b30de6e53556fde410c25863edd69d8206e
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/460421
Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com>
Run-TryBot: Tim King <taking@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Bryan Mills <bcmills@google.com>
diff --git a/cmd/govulncheck/main.go b/cmd/govulncheck/main.go
index d62086e..d14ee1a 100644
--- a/cmd/govulncheck/main.go
+++ b/cmd/govulncheck/main.go
@@ -86,8 +86,14 @@
if db := os.Getenv(envGOVULNDB); db != "" {
dbs = strings.Split(db, ",")
}
+
+ cache, err := govulncheck.DefaultCache()
+ if err != nil {
+ return err
+ }
+
dbClient, err := client.NewClient(dbs, client.Options{
- HTTPCache: govulncheck.DefaultCache(),
+ HTTPCache: cache,
})
if err != nil {
return err
diff --git a/internal/goenv.go b/internal/goenv.go
index b3bb86e..05bcade 100644
--- a/internal/goenv.go
+++ b/internal/goenv.go
@@ -12,17 +12,15 @@
"os/exec"
)
-// GoEnv returns the value for key in `go env`. In
-// the unlikely case that `go env` fails, prints an
-// error message and returns an empty string.
-func GoEnv(key string) string {
+// GoEnv returns the value for key in `go env`.
+func GoEnv(key string) (string, error) {
out, err := exec.Command("go", "env", "-json", key).Output()
if err != nil {
- return ""
+ return "", err
}
env := make(map[string]string)
if err := json.Unmarshal(out, &env); err != nil {
- return ""
+ return "", err
}
- return env[key]
+ return env[key], nil
}
diff --git a/internal/goenv_test.go b/internal/goenv_test.go
index 7d02e4f..edb1a1c 100644
--- a/internal/goenv_test.go
+++ b/internal/goenv_test.go
@@ -6,12 +6,38 @@
import (
"testing"
+
+ "golang.org/x/vuln/internal/testenv"
)
func TestGoEnv(t *testing.T) {
+ testenv.NeedsGoEnv(t)
+
for _, key := range []string{"GOVERSION", "GOROOT", "GOPATH", "GOMODCACHE"} {
- if GoEnv(key) == "" {
+ if val, err := GoEnv(key); val == "" {
t.Errorf("want something for go env %s; got nothing", key)
+ } else if err != nil {
+ t.Errorf("unexpected error for go env %s: %v", key, err)
}
}
}
+
+func TestGoEnvNonVariable(t *testing.T) {
+ testenv.NeedsGoEnv(t)
+
+ key := "NOT_A_GO_ENV_VARIABLE"
+ if val, err := GoEnv(key); val != "" {
+ t.Errorf("expected nothing for go env %s; got %s", key, val)
+ } else if err != nil {
+ t.Errorf("unexpected error for go env %s: %v", key, err)
+ }
+}
+
+func TestGoEnvErr(t *testing.T) {
+ testenv.NeedsGoEnv(t)
+
+ key := "--not-a-flag"
+ if val, err := GoEnv(key); err == nil {
+ t.Errorf("wanted an error from go env %s; got value %q", key, val)
+ }
+}
diff --git a/internal/goenv_testmode.go b/internal/goenv_testmode.go
index c29f602..d187d14 100644
--- a/internal/goenv_testmode.go
+++ b/internal/goenv_testmode.go
@@ -13,21 +13,19 @@
"os/exec"
)
-// GoEnv returns the value for key in `go env`. In
-// the unlikely case that `go env` fails, prints an
-// error message and returns an empty string.
+// GoEnv returns the value for key in `go env`.
//
// For debugging and testing purposes, the value of
// undocumented environment variable TEST_GOVERSION
// is used for go env GOVERSION.
-func GoEnv(key string) string {
+func GoEnv(key string) (string, error) {
out, err := exec.Command("go", "env", "-json", key).Output()
if err != nil {
- return ""
+ return "", err
}
env := make(map[string]string)
if err := json.Unmarshal(out, &env); err != nil {
- return ""
+ return "", err
}
if v := os.Getenv("TEST_GOVERSION"); v != "" {
@@ -35,5 +33,5 @@
env["GOVERSION"] = v
}
- return env[key]
+ return env[key], nil
}
diff --git a/internal/govulncheck/cache.go b/internal/govulncheck/cache.go
index 0229c79..24f3a4d 100644
--- a/internal/govulncheck/cache.go
+++ b/internal/govulncheck/cache.go
@@ -45,10 +45,24 @@
// Assert that *FSCache implements client.Cache.
var _ client.Cache = (*FSCache)(nil)
-var defaultCacheRoot = filepath.Join(internal.GoEnv("GOMODCACHE"), "/cache/download/vulndb")
+var (
+ initDefaultCache sync.Once
+ defaultCache *FSCache
+ defaultCacheErr error
+)
-func DefaultCache() *FSCache {
- return &FSCache{rootDir: defaultCacheRoot}
+func DefaultCache() (*FSCache, error) {
+ initDefaultCache.Do(func() {
+ mod, err := internal.GoEnv("GOMODCACHE")
+ if err != nil {
+ defaultCacheErr = err
+ return
+ }
+ defaultCache = &FSCache{
+ rootDir: filepath.Join(mod, "/cache/download/vulndb"),
+ }
+ })
+ return defaultCache, defaultCacheErr
}
type cachedIndex struct {
diff --git a/internal/testenv/testenv.go b/internal/testenv/testenv.go
new file mode 100644
index 0000000..8e9021e
--- /dev/null
+++ b/internal/testenv/testenv.go
@@ -0,0 +1,20 @@
+// Copyright 2023 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 testenv
+
+import (
+ "os/exec"
+ "testing"
+)
+
+// NeedsGoEnv skips t if the current system can't get the environment with
+// “go env” in a subprocess.
+func NeedsGoEnv(t testing.TB) {
+ t.Helper()
+
+ if _, err := exec.LookPath("go"); err != nil {
+ t.Skip("skipping test: can't run go env")
+ }
+}
diff --git a/vulncheck/source.go b/vulncheck/source.go
index fcdfb26..9cbed6b 100644
--- a/vulncheck/source.go
+++ b/vulncheck/source.go
@@ -52,7 +52,11 @@
if cfg.SourceGoVersion != "" {
stdlibModule.Version = semver.GoTagToSemver(cfg.SourceGoVersion)
} else {
- stdlibModule.Version = semver.GoTagToSemver(internal.GoEnv("GOVERSION"))
+ gover, err := internal.GoEnv("GOVERSION")
+ if err != nil {
+ return nil, err
+ }
+ stdlibModule.Version = semver.GoTagToSemver(gover)
}
ctx, cancel := context.WithCancel(ctx)