internal/imports: test Source for go mod cache

This CL provides an implementation of the Source interface to
use an index to the go module cache to satisfy imports.

There is also a test.

Change-Id: Ic931cb132fcf7253add7fc7faadd89726ee65567
Reviewed-on: https://go-review.googlesource.com/c/tools/+/627235
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Peter Weinberger 2024-11-12 07:25:35 -05:00
Родитель 9387a3910c
Коммит a2874818ed
4 изменённых файлов: 217 добавлений и 3 удалений

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

@ -59,5 +59,5 @@ type Source interface {
// candidates satisfy all missing references for that package name. It is up
// to each data source to select the best result for each entry in the
// missing map.
ResolveReferences(ctx context.Context, filename string, missing References) (map[PackageName]*Result, error)
ResolveReferences(ctx context.Context, filename string, missing References) ([]*Result, error)
}

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

@ -48,7 +48,7 @@ func (s *ProcessEnvSource) LoadPackageNames(ctx context.Context, srcDir string,
return r.loadPackageNames(unknown, srcDir)
}
func (s *ProcessEnvSource) ResolveReferences(ctx context.Context, filename string, refs map[string]map[string]bool) (map[string]*Result, error) {
func (s *ProcessEnvSource) ResolveReferences(ctx context.Context, filename string, refs map[string]map[string]bool) ([]*Result, error) {
var mu sync.Mutex
found := make(map[string][]pkgDistance)
callback := &scanCallback{
@ -121,5 +121,9 @@ func (s *ProcessEnvSource) ResolveReferences(ctx context.Context, filename strin
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
var ans []*Result
for _, x := range results {
ans = append(ans, x)
}
return ans, nil
}

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

@ -0,0 +1,103 @@
// Copyright 2024 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 imports
import (
"context"
"sync"
"time"
"golang.org/x/tools/internal/modindex"
)
// This code is here rather than in the modindex package
// to avoid import loops
// implements Source using modindex, so only for module cache.
//
// this is perhaps over-engineered. A new Index is read at first use.
// And then Update is called after every 15 minutes, and a new Index
// is read if the index changed. It is not clear the Mutex is needed.
type IndexSource struct {
modcachedir string
mutex sync.Mutex
ix *modindex.Index
expires time.Time
}
// create a new Source. Called from NewView in cache/session.go.
func NewIndexSource(cachedir string) *IndexSource {
return &IndexSource{modcachedir: cachedir}
}
func (s *IndexSource) LoadPackageNames(ctx context.Context, srcDir string, paths []ImportPath) (map[ImportPath]PackageName, error) {
/// This is used by goimports to resolve the package names of imports of the
// current package, which is irrelevant for the module cache.
return nil, nil
}
func (s *IndexSource) ResolveReferences(ctx context.Context, filename string, missing References) ([]*Result, error) {
if err := s.maybeReadIndex(); err != nil {
return nil, err
}
var cs []modindex.Candidate
for pkg, nms := range missing {
for nm := range nms {
x := s.ix.Lookup(pkg, nm, false)
cs = append(cs, x...)
}
}
found := make(map[string]*Result)
for _, c := range cs {
var x *Result
if x = found[c.ImportPath]; x == nil {
x = &Result{
Import: &ImportInfo{
ImportPath: c.ImportPath,
Name: "",
},
Package: &PackageInfo{
Name: c.PkgName,
Exports: make(map[string]bool),
},
}
found[c.ImportPath] = x
}
x.Package.Exports[c.Name] = true
}
var ans []*Result
for _, x := range found {
ans = append(ans, x)
}
return ans, nil
}
func (s *IndexSource) maybeReadIndex() error {
s.mutex.Lock()
defer s.mutex.Unlock()
var readIndex bool
if time.Now().After(s.expires) {
ok, err := modindex.Update(s.modcachedir)
if err != nil {
return err
}
if ok {
readIndex = true
}
}
if readIndex || s.ix == nil {
ix, err := modindex.ReadIndex(s.modcachedir)
if err != nil {
return err
}
s.ix = ix
// for now refresh every 15 minutes
s.expires = time.Now().Add(time.Minute * 15)
}
return nil
}

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

@ -0,0 +1,107 @@
// Copyright 2019 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 imports_test
import (
"context"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"golang.org/x/tools/internal/imports"
"golang.org/x/tools/internal/modindex"
)
// There are two cached packages, both resolving foo.Foo,
// but only one resolving foo.Bar
var (
foo = tpkg{
repo: "foo.com",
dir: "foo@v1.0.0",
syms: []string{"Foo"},
}
foobar = tpkg{
repo: "bar.com",
dir: "foo@v1.0.0",
syms: []string{"Foo", "Bar"},
}
fx = `package main
var _ = foo.Foo
var _ = foo.Bar
`
)
type tpkg struct {
// all packages are named foo
repo string // e.g. foo.com
dir string // e.g., foo@v1.0.0
syms []string // exported syms
}
func newpkgs(cachedir string, pks ...*tpkg) error {
for _, p := range pks {
fname := filepath.Join(cachedir, p.repo, p.dir, "foo.go")
if err := os.MkdirAll(filepath.Dir(fname), 0755); err != nil {
return err
}
fd, err := os.Create(fname)
if err != nil {
return err
}
fmt.Fprintf(fd, "package foo\n")
for _, s := range p.syms {
fmt.Fprintf(fd, "func %s() {}\n", s)
}
fd.Close()
}
return nil
}
func TestSource(t *testing.T) {
dirs := testDirs(t)
if err := newpkgs(dirs.cachedir, &foo, &foobar); err != nil {
t.Fatal(err)
}
source := imports.NewIndexSource(dirs.cachedir)
ctx := context.Background()
fixes, err := imports.FixImports(ctx, "tfile.go", []byte(fx), "unused", nil, source)
if err != nil {
t.Fatal(err)
}
opts := imports.Options{}
// ApplyFixes needs a non-nil opts
got, err := imports.ApplyFixes(fixes, "tfile.go", []byte(fx), &opts, 0)
fxwant := "package main\n\nimport \"bar.com/foo\"\n\nvar _ = foo.Foo\nvar _ = foo.Bar\n"
if diff := cmp.Diff(string(got), fxwant); diff != "" {
t.Errorf("FixImports got\n%q, wanted\n%q\ndiff is\n%s", string(got), fxwant, diff)
}
}
type dirs struct {
tmpdir string
cachedir string
rootdir string // goroot if we need it, which we don't
}
func testDirs(t *testing.T) dirs {
t.Helper()
dir := t.TempDir()
modindex.IndexDir = func() (string, error) { return dir, nil }
x := dirs{
tmpdir: dir,
cachedir: filepath.Join(dir, "pkg", "mod"),
rootdir: filepath.Join(dir, "root"),
}
if err := os.MkdirAll(x.cachedir, 0755); err != nil {
t.Fatal(err)
}
os.MkdirAll(x.rootdir, 0755)
return x
}