go/packages: add missing test variants to fallback loader

For all packages p, q where there's an dependency path of
p.test -> ... -> q -> ... -> p, Go creates test variants
q [p.test] of each q and replaces the dependency on q with
a dependency on q [p.test], and replaces p with the expanded
test variant p[p.test]. Fix the fallback logic to add
these missing test variants. (Before this change, it was
only producing the variant of p: p [p.test].)

Fixes golang/go#27670

Change-Id: Ic56ba35fadcdf8c5928ec76f5a7b0ebe650c9f02
Reviewed-on: https://go-review.googlesource.com/136176
Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
Michael Matloob 2018-09-17 15:17:20 -07:00
parent e2857128af
commit 0b24b358f4
3 changed files with 133 additions and 34 deletions

View File

@ -33,14 +33,20 @@ func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error)
}
var tmpdir string // used for generated cgo files
var needsTestVariant []struct {
pkg, xtestPkg *Package
}
var response driverResponse
allPkgs := make(map[string]bool)
addPackage := func(p *jsonPackage) {
if p.Name == "" {
id := p.ImportPath
if p.Name == "" || allPkgs[id] {
return
}
allPkgs[id] = true
id := p.ImportPath
isRoot := original[id] != nil
pkgpath := id
@ -110,7 +116,7 @@ func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error)
if isRoot {
response.Roots = append(response.Roots, id)
}
response.Packages = append(response.Packages, &Package{
pkg := &Package{
ID: id,
Name: p.Name,
GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles),
@ -119,13 +125,12 @@ func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error)
PkgPath: pkgpath,
Imports: importMap(p.Imports),
// TODO(matloob): set errors on the Package to cgoErrors
})
if cfg.Tests {
}
response.Packages = append(response.Packages, pkg)
if cfg.Tests && isRoot {
testID := fmt.Sprintf("%s [%s.test]", id, id)
if len(p.TestGoFiles) > 0 || len(p.XTestGoFiles) > 0 {
if isRoot {
response.Roots = append(response.Roots, testID)
}
response.Roots = append(response.Roots, testID)
testPkg := &Package{
ID: testID,
Name: p.Name,
@ -140,32 +145,28 @@ func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error)
var xtestPkg *Package
if len(p.XTestGoFiles) > 0 {
xtestID := fmt.Sprintf("%s_test [%s.test]", id, id)
if isRoot {
response.Roots = append(response.Roots, xtestID)
}
// Rewrite import to package under test to refer to test variant.
imports := importMap(p.XTestImports)
for imp := range imports {
if imp == p.ImportPath {
imports[imp] = &Package{ID: testID}
break
}
}
response.Roots = append(response.Roots, xtestID)
// Generate test variants for all packages q where a path exists
// such that xtestPkg -> ... -> q -> ... -> p (where p is the package under test)
// and rewrite all import map entries of p to point to testPkg (the test variant of
// p), and of each q to point to the test variant of that q.
xtestPkg = &Package{
ID: xtestID,
Name: p.Name + "_test",
GoFiles: absJoin(p.Dir, p.XTestGoFiles),
CompiledGoFiles: absJoin(p.Dir, p.XTestGoFiles),
PkgPath: pkgpath + "_test",
Imports: imports,
Imports: importMap(p.XTestImports),
}
// Add to list of packages we need to rewrite imports for to refer to test variants.
// We may need to create a test variant of a package that hasn't been loaded yet, so
// the test variants need to be created later.
needsTestVariant = append(needsTestVariant, struct{ pkg, xtestPkg *Package }{pkg, xtestPkg})
response.Packages = append(response.Packages, xtestPkg)
}
// testmain package
testmainID := id + ".test"
if isRoot {
response.Roots = append(response.Roots, testmainID)
}
response.Roots = append(response.Roots, testmainID)
imports := map[string]*Package{}
imports[testPkg.PkgPath] = &Package{ID: testPkg.ID}
if xtestPkg != nil {
@ -185,13 +186,13 @@ func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error)
return
}
testmain := filepath.Join(outdir, "testmain.go")
extradeps, err := generateTestmain(testmain, testPkg, xtestPkg)
extraimports, extradeps, err := generateTestmain(testmain, testPkg, xtestPkg)
if err != nil {
testmainPkg.Errors = append(testmainPkg.Errors,
Error{"-", fmt.Sprintf("failed to generate testmain: %v", err)})
}
deps = append(deps, extradeps...)
for _, imp := range extradeps { // testing, testing/internal/testdeps, and maybe os
for _, imp := range extraimports { // testing, testing/internal/testdeps, and maybe os
imports[imp] = &Package{ID: imp}
}
testmainPkg.GoFiles = []string{testmain}
@ -222,6 +223,10 @@ func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error)
addPackage(p)
}
for _, v := range needsTestVariant {
createTestVariants(&response, v.pkg, v.xtestPkg)
}
// TODO(matloob): Is this the right ordering?
sort.SliceStable(response.Packages, func(i, j int) bool {
return response.Packages[i].PkgPath < response.Packages[j].PkgPath
@ -230,6 +235,54 @@ func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error)
return &response, nil
}
func createTestVariants(response *driverResponse, pkgUnderTest, xtestPkg *Package) {
allPkgs := make(map[string]*Package)
for _, pkg := range response.Packages {
allPkgs[pkg.ID] = pkg
}
needsTestVariant := make(map[string]bool)
needsTestVariant[pkgUnderTest.ID] = true
var needsVariantRec func(p *Package) bool
needsVariantRec = func(p *Package) bool {
if needsTestVariant[p.ID] {
return true
}
for _, imp := range p.Imports {
if needsVariantRec(allPkgs[imp.ID]) {
// Don't break because we want to make sure all dependencies
// have been processed, and all required test variants of our dependencies
// exist.
needsTestVariant[p.ID] = true
}
}
if !needsTestVariant[p.ID] {
return false
}
// Create a clone of the package. It will share the same strings and lists of source files,
// but that's okay. It's only necessary for the Imports map to have a separate identity.
testVariant := *p
testVariant.ID = fmt.Sprintf("%s [%s.test]", p.ID, pkgUnderTest.ID)
testVariant.Imports = make(map[string]*Package)
for imp, pkg := range p.Imports {
testVariant.Imports[imp] = pkg
if needsTestVariant[pkg.ID] {
testVariant.Imports[imp] = &Package{ID: fmt.Sprintf("%s [%s.test]", pkg.ID, pkgUnderTest.ID)}
}
}
response.Packages = append(response.Packages, &testVariant)
return needsTestVariant[p.ID]
}
// finally, update the xtest package's imports
for imp, pkg := range xtestPkg.Imports {
if allPkgs[pkg.ID] == nil {
fmt.Printf("for %s: package %s doesn't exist\n", xtestPkg.ID, pkg.ID)
}
if needsVariantRec(allPkgs[pkg.ID]) {
xtestPkg.Imports[imp] = &Package{ID: fmt.Sprintf("%s [%s.test]", pkg.ID, pkgUnderTest.ID)}
}
}
}
// vendorlessPath returns the devendorized version of the import path ipath.
// For example, VendorlessPath("foo/bar/vendor/a/b") returns "a/b".
// Copied from golang.org/x/tools/imports/fix.go.

View File

@ -27,16 +27,66 @@ import (
// This file complements golist_fallback.go by providing
// support for generating testmains.
func generateTestmain(out string, testPkg, xtestPkg *Package) (extradeps []string, err error) {
func generateTestmain(out string, testPkg, xtestPkg *Package) (extraimports, extradeps []string, err error) {
testFuncs, err := loadTestFuncs(testPkg, xtestPkg)
if err != nil {
return nil, err
return nil, nil, err
}
extradeps = []string{"testing/internal/testdeps", "testing"}
extraimports = []string{"testing", "testing/internal/testdeps"}
if testFuncs.TestMain == nil {
extradeps = append(extradeps, "os")
extraimports = append(extraimports, "os")
}
return extradeps, writeTestmain(out, testFuncs)
// Transitive dependencies of ("testing", "testing/internal/testdeps").
// os is part of the transitive closure so it and its transitive dependencies are
// included regardless of whether it's imported in the template below.
extradeps = []string{
"errors",
"internal/cpu",
"unsafe",
"internal/bytealg",
"internal/race",
"runtime/internal/atomic",
"runtime/internal/sys",
"runtime",
"sync/atomic",
"sync",
"io",
"unicode",
"unicode/utf8",
"bytes",
"math",
"syscall",
"time",
"internal/poll",
"internal/syscall/unix",
"internal/testlog",
"os",
"math/bits",
"strconv",
"reflect",
"fmt",
"sort",
"strings",
"flag",
"runtime/debug",
"context",
"runtime/trace",
"testing",
"bufio",
"regexp/syntax",
"regexp",
"compress/flate",
"encoding/binary",
"hash",
"hash/crc32",
"compress/gzip",
"path/filepath",
"io/ioutil",
"text/tabwriter",
"runtime/pprof",
"testing/internal/testdeps",
}
return extraimports, extradeps, writeTestmain(out, testFuncs)
}
// The following is adapted from the cmd/go testmain generation code.

View File

@ -240,10 +240,6 @@ func TestLoadImportsGraph(t *testing.T) {
}
func TestLoadImportsTestVariants(t *testing.T) {
if usesOldGolist {
t.Skip("not yet supported in pre-Go 1.10.4 golist fallback implementation")
}
tmp, cleanup := makeTree(t, map[string]string{
"src/a/a.go": `package a; import _ "b"`,
"src/b/b.go": `package b`,