Skip to content

Commit f960b73

Browse files
make version check more robust
1 parent fab9955 commit f960b73

2 files changed

Lines changed: 71 additions & 1 deletion

File tree

version/version.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"os"
77
"os/exec"
8+
"strings"
89
"time"
910

1011
"github.com/goccy/go-json"
@@ -70,16 +71,29 @@ func getLatestVersion() (string, error) {
7071
return getLatestVersionWithTimeout(updateCheckTimeout)
7172
}
7273

74+
func latestVersionCommand(ctx context.Context) *exec.Cmd {
75+
// Keep update checks independent of the caller's module and workspace
76+
cmd := exec.CommandContext(ctx, "go", "list", "-m", "-mod=mod", "-json", modulePath+"@latest")
77+
cmd.Dir = os.TempDir()
78+
cmd.Env = append(os.Environ(), "GOWORK=off", "GOFLAGS=")
79+
return cmd
80+
}
81+
7382
func getLatestVersionWithTimeout(timeout time.Duration) (string, error) {
7483
ctx, cancel := context.WithTimeout(context.Background(), timeout)
7584
defer cancel()
7685

77-
cmd := exec.CommandContext(ctx, "go", "list", "-m", "-json", modulePath+"@latest")
86+
cmd := latestVersionCommand(ctx)
7887
output, err := cmd.Output()
7988
if ctxErr := ctx.Err(); ctxErr != nil {
8089
return "", fmt.Errorf("latest version check failed: %w", ctxErr)
8190
}
8291
if err != nil {
92+
if exitErr, ok := err.(*exec.ExitError); ok {
93+
if detail := strings.TrimSpace(string(exitErr.Stderr)); detail != "" {
94+
return "", fmt.Errorf("failed to query latest version: %w: %s", err, detail)
95+
}
96+
}
8397
return "", fmt.Errorf("failed to query latest version: %w", err)
8498
}
8599

version/version_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"os"
77
"path/filepath"
88
"runtime"
9+
"slices"
10+
"strings"
911
"testing"
1012
"time"
1113
)
@@ -31,3 +33,57 @@ func TestGetLatestVersionHasOverallTimeout(t *testing.T) {
3133
t.Fatalf("version lookup exceeded timeout allowance: %s", elapsed)
3234
}
3335
}
36+
37+
func TestLatestVersionCommandIsIsolated(t *testing.T) {
38+
t.Setenv("GOFLAGS", "-mod=vendor")
39+
t.Setenv("GOWORK", filepath.Join(t.TempDir(), "go.work"))
40+
t.Setenv("GOPROXY", "https://proxy.example.com")
41+
42+
cmd := latestVersionCommand(context.Background())
43+
if cmd.Dir != os.TempDir() {
44+
t.Fatalf("command directory = %q, want %q", cmd.Dir, os.TempDir())
45+
}
46+
if args := strings.Join(cmd.Args, " "); !strings.Contains(args, " -mod=mod ") {
47+
t.Fatalf("command arguments %q do not force module mode", args)
48+
}
49+
if got := lastEnvValue(cmd.Env, "GOWORK"); got != "off" {
50+
t.Fatalf("GOWORK = %q, want off", got)
51+
}
52+
if got := lastEnvValue(cmd.Env, "GOFLAGS"); got != "" {
53+
t.Fatalf("GOFLAGS = %q, want empty", got)
54+
}
55+
if got := lastEnvValue(cmd.Env, "GOPROXY"); got != "https://proxy.example.com" {
56+
t.Fatalf("GOPROXY = %q, want inherited value", got)
57+
}
58+
}
59+
60+
func lastEnvValue(env []string, key string) string {
61+
prefix := key + "="
62+
for _, entry := range slices.Backward(env) {
63+
if after, ok := strings.CutPrefix(entry, prefix); ok {
64+
return after
65+
}
66+
}
67+
return ""
68+
}
69+
70+
func TestGetLatestVersionIncludesGoDiagnostic(t *testing.T) {
71+
if runtime.GOOS == "windows" {
72+
t.Skip("test uses a POSIX executable script")
73+
}
74+
75+
dir := t.TempDir()
76+
fakeGo := filepath.Join(dir, "go")
77+
if err := os.WriteFile(fakeGo, []byte("#!/bin/sh\necho 'go: module lookup disabled by GOPROXY=off' >&2\nexit 1\n"), 0o755); err != nil {
78+
t.Fatalf("create fake go command: %v", err)
79+
}
80+
t.Setenv("PATH", dir)
81+
82+
_, err := getLatestVersionWithTimeout(time.Second)
83+
if err == nil {
84+
t.Fatal("expected latest version lookup to fail")
85+
}
86+
if !strings.Contains(err.Error(), "module lookup disabled by GOPROXY=off") {
87+
t.Fatalf("error %q does not include go command diagnostic", err)
88+
}
89+
}

0 commit comments

Comments
 (0)