internal/imports: use a clean GOMODCACHE for the scan root directory

The directories processed by gopathwalk are clean, yet in the scan
callback are assumed to have the root as a prefix. For the module cache,
this root was previous not guaranteed to be clean, if it came from a
GOMODCACHE environment variable. As a result, the computed relative path
may be inaccurate, and may even panic if the unclean root is much longer
than its clean form.

Reproduce the crash of golang/go#67156 in a test, update
scanDirForPackage to be more robust, and fix the uncleanliness of the
module cache root.

Also fix some handling of GOMODCACHE cleanup in imports tests.

Fixes golang/go#67156

Change-Id: Ia899256fed9629b7e753a52feb02b4235bfc8388
Reviewed-on: https://go-review.googlesource.com/c/tools/+/603635
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
Rob Findley 2024-08-06 19:59:04 +00:00 коммит произвёл Robert Findley
Родитель d47b4fb9d8
Коммит 7cc3be7d11
2 изменённых файлов: 67 добавлений и 14 удалений

Просмотреть файл

@ -6,6 +6,7 @@ package misc
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
@ -195,8 +196,7 @@ func main() {
})
}
func TestGOMODCACHE(t *testing.T) {
const proxy = `
const exampleProxy = `
-- example.com@v1.2.3/go.mod --
module example.com
@ -210,6 +210,8 @@ package y
const Y = 2
`
func TestGOMODCACHE(t *testing.T) {
const files = `
-- go.mod --
module mod.com
@ -217,9 +219,6 @@ module mod.com
go 1.12
require example.com v1.2.3
-- go.sum --
example.com v1.2.3 h1:6vTQqzX+pnwngZF1+5gcO3ZEWmix1jJ/h+pWS8wUxK0=
example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo=
-- main.go --
package main
@ -227,14 +226,13 @@ import "example.com/x"
var _, _ = x.X, y.Y
`
modcache, err := os.MkdirTemp("", "TestGOMODCACHE-modcache")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(modcache)
modcache := t.TempDir()
defer cleanModCache(t, modcache) // see doc comment of cleanModCache
WithOptions(
EnvVars{"GOMODCACHE": modcache},
ProxyFiles(proxy),
ProxyFiles(exampleProxy),
WriteGoSum("."),
).Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("main.go")
env.AfterChange(Diagnostics(env.AtRegexp("main.go", `y.Y`)))
@ -248,6 +246,58 @@ var _, _ = x.X, y.Y
})
}
func TestRelativeReplace(t *testing.T) {
const files = `
-- go.mod --
module mod.com/a
go 1.20
require (
example.com v1.2.3
)
replace example.com/b => ../b
-- main.go --
package main
import "example.com/x"
var _, _ = x.X, y.Y
`
modcache := t.TempDir()
base := filepath.Base(modcache)
defer cleanModCache(t, modcache) // see doc comment of cleanModCache
// Construct a very unclean module cache whose length exceeds the length of
// the clean directory path, to reproduce the crash in golang/go#67156
const sep = string(filepath.Separator)
modcache += strings.Repeat(sep+".."+sep+base, 10)
WithOptions(
EnvVars{"GOMODCACHE": modcache},
ProxyFiles(exampleProxy),
WriteGoSum("."),
).Run(t, files, func(t *testing.T, env *Env) {
env.OpenFile("main.go")
env.AfterChange(Diagnostics(env.AtRegexp("main.go", `y.Y`)))
env.SaveBuffer("main.go")
env.AfterChange(NoDiagnostics(ForFile("main.go")))
})
}
// TODO(rfindley): this is only necessary as the module cache cleaning of the
// sandbox does not respect GOMODCACHE set via EnvVars. We should fix this, but
// that is probably part of a larger refactoring of the sandbox that I'm not
// inclined to undertake.
func cleanModCache(t *testing.T, modcache string) {
cmd := exec.Command("go", "clean", "-modcache")
cmd.Env = append(os.Environ(), "GOMODCACHE="+modcache)
if err := cmd.Run(); err != nil {
t.Errorf("cleaning modcache: %v", err)
}
}
// Tests golang/go#40685.
func TestAcceptImportsQuickFixTestVariant(t *testing.T) {
const pkg = `

Просмотреть файл

@ -245,7 +245,10 @@ func newModuleResolver(e *ProcessEnv, moduleCacheCache *DirInfoCache) (*ModuleRe
// 2. Use this to separate module cache scanning from other scanning.
func gomodcacheForEnv(goenv map[string]string) string {
if gmc := goenv["GOMODCACHE"]; gmc != "" {
return gmc
// golang/go#67156: ensure that the module cache is clean, since it is
// assumed as a prefix to directories scanned by gopathwalk, which are
// themselves clean.
return filepath.Clean(gmc)
}
gopaths := filepath.SplitList(goenv["GOPATH"])
if len(gopaths) == 0 {
@ -740,8 +743,8 @@ func (r *ModuleResolver) loadExports(ctx context.Context, pkg *pkg, includeTest
func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) directoryPackageInfo {
subdir := ""
if dir != root.Path {
subdir = dir[len(root.Path)+len("/"):]
if prefix := root.Path + string(filepath.Separator); strings.HasPrefix(dir, prefix) {
subdir = dir[len(prefix):]
}
importPath := filepath.ToSlash(subdir)
if strings.HasPrefix(importPath, "vendor/") {