1
0
mirror of https://github.com/golang/go synced 2024-10-01 01:18:32 -06:00

internal/imports: load test exports of package under test

x_tests can access exports from the test variant of the package under
test. Change loadExportsFromFiles to understand that mode, and use it
where appropriate.

I didn't want to come up with a cache key for for the test variant, so
for now we bypass the cache in these situations.

Fixes golang/go#29979.

Change-Id: I9959a08da97bbee64c5bcd56e06f548486693156
Reviewed-on: https://go-review.googlesource.com/c/tools/+/213221
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Heschi Kreinick 2020-01-03 18:01:15 -05:00
parent e23c44e711
commit 3d14842334
7 changed files with 75 additions and 26 deletions

View File

@ -629,6 +629,14 @@ func getCandidatePkgs(ctx context.Context, wrappedCallback *scanCallback, filena
return notSelf(pkg) && wrappedCallback.packageNameLoaded(pkg) return notSelf(pkg) && wrappedCallback.packageNameLoaded(pkg)
}, },
exportsLoaded: func(pkg *pkg, exports []string) { exportsLoaded: func(pkg *pkg, exports []string) {
// If we're an x_test, load the package under test's test variant.
if strings.HasSuffix(filePkg, "_test") && pkg.dir == filepath.Dir(filename) {
var err error
_, exports, err = loadExportsFromFiles(ctx, env, pkg.dir, true)
if err != nil {
return
}
}
wrappedCallback.exportsLoaded(pkg, exports) wrappedCallback.exportsLoaded(pkg, exports)
}, },
} }
@ -842,7 +850,7 @@ type Resolver interface {
scan(ctx context.Context, callback *scanCallback) error scan(ctx context.Context, callback *scanCallback) error
// loadExports returns the set of exported symbols in the package at dir. // loadExports returns the set of exported symbols in the package at dir.
// loadExports may be called concurrently. // loadExports may be called concurrently.
loadExports(ctx context.Context, pkg *pkg) (string, []string, error) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error)
ClearForNewScan() ClearForNewScan()
} }
@ -1192,7 +1200,7 @@ func (r *gopathResolver) scan(ctx context.Context, callback *scanCallback) error
if !callback.packageNameLoaded(p) { if !callback.packageNameLoaded(p) {
return return
} }
if _, exports, err := r.loadExports(ctx, p); err == nil { if _, exports, err := r.loadExports(ctx, p, false); err == nil {
callback.exportsLoaded(p, exports) callback.exportsLoaded(p, exports)
} }
} }
@ -1231,11 +1239,11 @@ func filterRoots(roots []gopathwalk.Root, include func(gopathwalk.Root) bool) []
return result return result
} }
func (r *gopathResolver) loadExports(ctx context.Context, pkg *pkg) (string, []string, error) { func (r *gopathResolver) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error) {
if info, ok := r.cache.Load(pkg.dir); ok { if info, ok := r.cache.Load(pkg.dir); ok && !includeTest {
return r.cache.CacheExports(ctx, r.env, info) return r.cache.CacheExports(ctx, r.env, info)
} }
return loadExportsFromFiles(ctx, r.env, pkg.dir) return loadExportsFromFiles(ctx, r.env, pkg.dir, includeTest)
} }
// VendorlessPath returns the devendorized version of the import path ipath. // VendorlessPath returns the devendorized version of the import path ipath.
@ -1251,7 +1259,7 @@ func VendorlessPath(ipath string) string {
return ipath return ipath
} }
func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string) (string, []string, error) { func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string, includeTest bool) (string, []string, error) {
var exports []string var exports []string
// Look for non-test, buildable .go files which could provide exports. // Look for non-test, buildable .go files which could provide exports.
@ -1262,7 +1270,7 @@ func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string) (str
var files []os.FileInfo var files []os.FileInfo
for _, fi := range all { for _, fi := range all {
name := fi.Name() name := fi.Name()
if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") { if !strings.HasSuffix(name, ".go") || (!includeTest && strings.HasSuffix(name, "_test.go")) {
continue continue
} }
match, err := env.buildContext().MatchFile(dir, fi.Name()) match, err := env.buildContext().MatchFile(dir, fi.Name())
@ -1295,6 +1303,10 @@ func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string) (str
// handled by MatchFile above. // handled by MatchFile above.
continue continue
} }
if includeTest && strings.HasSuffix(f.Name.Name, "_test") {
// x_test package. We want internal test files only.
continue
}
pkgName = f.Name.Name pkgName = f.Name.Name
for name := range f.Scope.Objects { for name := range f.Scope.Objects {
if ast.IsExported(name) { if ast.IsExported(name) {
@ -1360,7 +1372,9 @@ func findImport(ctx context.Context, pass *pass, candidates []pkgDistance, pkgNa
if pass.env.Debug { if pass.env.Debug {
pass.env.Logf("loading exports in dir %s (seeking package %s)", c.pkg.dir, pkgName) pass.env.Logf("loading exports in dir %s (seeking package %s)", c.pkg.dir, pkgName)
} }
exports, err := loadExportsForPackage(ctx, pass.env, pkgName, c.pkg) // If we're an x_test, load the package under test's test variant.
includeTest := strings.HasSuffix(pass.f.Name.Name, "_test") && c.pkg.dir == pass.srcDir
_, exports, err := pass.env.GetResolver().loadExports(ctx, c.pkg, includeTest)
if err != nil { if err != nil {
if pass.env.Debug { if pass.env.Debug {
pass.env.Logf("loading exports in dir %s (seeking package %s): %v", c.pkg.dir, pkgName, err) pass.env.Logf("loading exports in dir %s (seeking package %s): %v", c.pkg.dir, pkgName, err)
@ -1397,17 +1411,6 @@ func findImport(ctx context.Context, pass *pass, candidates []pkgDistance, pkgNa
return nil, nil return nil, nil
} }
func loadExportsForPackage(ctx context.Context, env *ProcessEnv, expectPkg string, pkg *pkg) ([]string, error) {
pkgName, exports, err := env.GetResolver().loadExports(ctx, pkg)
if err != nil {
return nil, err
}
if expectPkg != pkgName {
return nil, fmt.Errorf("dir %v is package %v, wanted %v", pkg.dir, pkgName, expectPkg)
}
return exports, err
}
// pkgIsCandidate reports whether pkg is a candidate for satisfying the // pkgIsCandidate reports whether pkg is a candidate for satisfying the
// finding which package pkgIdent in the file named by filename is trying // finding which package pkgIdent in the file named by filename is trying
// to refer to. // to refer to.

View File

@ -2489,6 +2489,40 @@ var _ = bytes.Buffer{}
}.processTest(t, "foo.com", "foo.go", nil, nil, want) }.processTest(t, "foo.com", "foo.go", nil, nil, want)
} }
// Tests that an external test package will import the package under test if it
// also uses symbols exported only in test files.
// https://golang.org/issues/29979
func TestExternalTest(t *testing.T) {
const input = `package a_test
func TestX() {
a.X()
a.Y()
}
`
const want = `package a_test
import "foo.com/a"
func TestX() {
a.X()
a.Y()
}
`
testConfig{
modules: []packagestest.Module{
{
Name: "foo.com/a",
Files: fm{
"a.go": "package a\n func X() {}",
"export_test.go": "package a\n func Y() {}",
"a_test.go": input,
},
},
},
}.processTest(t, "foo.com/a", "a_test.go", nil, nil, want)
}
// TestStdLibGetCandidates tests that get packages finds std library packages // TestStdLibGetCandidates tests that get packages finds std library packages
// with correct priorities. // with correct priorities.
func TestGetCandidates(t *testing.T) { func TestGetCandidates(t *testing.T) {

View File

@ -413,7 +413,7 @@ func (r *ModuleResolver) scan(ctx context.Context, callback *scanCallback) error
if !callback.packageNameLoaded(pkg) { if !callback.packageNameLoaded(pkg) {
return return
} }
_, exports, err := r.loadExports(ctx, pkg) _, exports, err := r.loadExports(ctx, pkg, false)
if err != nil { if err != nil {
return return
} }
@ -529,14 +529,14 @@ func (r *ModuleResolver) canonicalize(info directoryPackageInfo) (*pkg, error) {
return res, nil return res, nil
} }
func (r *ModuleResolver) loadExports(ctx context.Context, pkg *pkg) (string, []string, error) { func (r *ModuleResolver) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error) {
if err := r.init(); err != nil { if err := r.init(); err != nil {
return "", nil, err return "", nil, err
} }
if info, ok := r.cacheLoad(pkg.dir); ok { if info, ok := r.cacheLoad(pkg.dir); ok && !includeTest {
return r.cacheExports(ctx, r.env, info) return r.cacheExports(ctx, r.env, info)
} }
return loadExportsFromFiles(ctx, r.env, pkg.dir) return loadExportsFromFiles(ctx, r.env, pkg.dir, includeTest)
} }
func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) directoryPackageInfo { func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) directoryPackageInfo {

View File

@ -215,7 +215,7 @@ func (d *dirInfoCache) CacheExports(ctx context.Context, env *ProcessEnv, info d
if reached, err := info.reachedStatus(nameLoaded); reached && err != nil { if reached, err := info.reachedStatus(nameLoaded); reached && err != nil {
return "", nil, err return "", nil, err
} }
info.packageName, info.exports, info.err = loadExportsFromFiles(ctx, env, info.dir) info.packageName, info.exports, info.err = loadExportsFromFiles(ctx, env, info.dir, false)
if info.err == context.Canceled || info.err == context.DeadlineExceeded { if info.err == context.Canceled || info.err == context.DeadlineExceeded {
return info.packageName, info.exports, info.err return info.packageName, info.exports, info.err
} }

View File

@ -1,7 +1,7 @@
-- summary -- -- summary --
CompletionsCount = 225 CompletionsCount = 225
CompletionSnippetCount = 62 CompletionSnippetCount = 63
UnimportedCompletionsCount = 4 UnimportedCompletionsCount = 9
DeepCompletionsCount = 5 DeepCompletionsCount = 5
FuzzyCompletionsCount = 8 FuzzyCompletionsCount = 8
RankedCompletionsCount = 56 RankedCompletionsCount = 56

View File

@ -0,0 +1,3 @@
package unimported
var TestExport int //@item(testexport, "TestExport", "(from \"golang.org/x/tools/internal/lsp/unimported\")", "var")

View File

@ -0,0 +1,9 @@
package unimported_test
import (
"testing"
)
func TestSomething(t *testing.T) {
_ = unimported.TestExport //@unimported("TestExport", testexport)
}