Skip to content

Commit

Permalink
internal/imports: test Source for go mod cache
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
pjweinbgo committed Nov 20, 2024
1 parent 9387a39 commit a287481
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 3 deletions.
2 changes: 1 addition & 1 deletion internal/imports/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
8 changes: 6 additions & 2 deletions internal/imports/source_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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
}
103 changes: 103 additions & 0 deletions internal/imports/source_modindex.go
Original file line number Diff line number Diff line change
@@ -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
}
107 changes: 107 additions & 0 deletions internal/imports/sourcex_test.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit a287481

Please sign in to comment.