diff --git a/go/packages/golist.go b/go/packages/golist.go index 62b28293890..0af3a46deb1 100644 --- a/go/packages/golist.go +++ b/go/packages/golist.go @@ -25,7 +25,7 @@ type GoTooOldError struct{ error } // golistPackages uses the "go list" command to expand the // pattern words and return metadata for the specified packages. -func golistPackages(ctx context.Context, gopath string, cgo, export bool, words []string) ([]*Package, error) { +func golistPackages(ctx context.Context, gopath string, cgo, export, tests bool, words []string) ([]*Package, error) { // Fields must match go list; // see $GOROOT/src/cmd/go/internal/load/pkg.go. type jsonPackage struct { @@ -62,7 +62,7 @@ func golistPackages(ctx context.Context, gopath string, cgo, export bool, words // Run "go list" for complete // information on the specified packages. - buf, err := golist(ctx, gopath, cgo, export, words) + buf, err := golist(ctx, gopath, cgo, export, tests, words) if err != nil { return nil, err } @@ -176,19 +176,13 @@ func absJoin(dir string, fileses ...[]string) (res []string) { } // golist returns the JSON-encoded result of a "go list args..." query. -func golist(ctx context.Context, gopath string, cgo, export bool, args []string) (*bytes.Buffer, error) { +func golist(ctx context.Context, gopath string, cgo, export, tests bool, args []string) (*bytes.Buffer, error) { out := new(bytes.Buffer) - if len(args) == 0 { - return out, nil - } - - const test = true // TODO(adonovan): expose a flag for this. - cmd := exec.CommandContext(ctx, "go", append([]string{ "list", "-e", fmt.Sprintf("-cgo=%t", cgo), - fmt.Sprintf("-test=%t", test), + fmt.Sprintf("-test=%t", tests), fmt.Sprintf("-export=%t", export), "-deps", "-json", diff --git a/go/packages/gopackages/main.go b/go/packages/gopackages/main.go index b864e821c84..6be503df2e7 100644 --- a/go/packages/gopackages/main.go +++ b/go/packages/gopackages/main.go @@ -27,6 +27,7 @@ import ( // flags var ( depsFlag = flag.Bool("deps", false, "show dependencies too") + testFlag = flag.Bool("test", false, "include any tests implied by the patterns") cgoFlag = flag.Bool("cgo", true, "process cgo files") mode = flag.String("mode", "metadata", "mode (one of metadata, typecheck, wholeprogram)") private = flag.Bool("private", false, "show non-exported declarations too") @@ -119,6 +120,7 @@ func main() { opts := &packages.Options{ Error: func(error) {}, // we'll take responsibility for printing errors DisableCgo: !*cgoFlag, + Tests: *testFlag, } lpkgs, err := load(opts, flag.Args()...) if err != nil { diff --git a/go/packages/packages.go b/go/packages/packages.go index 18e7460c274..2f2bb85b4b5 100644 --- a/go/packages/packages.go +++ b/go/packages/packages.go @@ -40,6 +40,19 @@ type Options struct { // Replace with flags/cwd/environ pass-through. GOPATH string + // The Tests flag causes the result to include any test packages + // implied by the patterns. + // + // For example, under 'go build', the "fmt" pattern ordinarily + // identifies a single importable package, but with the Tests + // flag it additionally denotes the "fmt.test" executable, which + // in turn depends on the variant of "fmt" augmented by its + // in-packages tests, and the "fmt_test" external test package. + // + // For build systems in which test names are explicit, + // this flag may have no effect. + Tests bool + // DisableCgo disables cgo-processing of files that import "C", // and removes the 'cgo' build tag, which may affect source file selection. // By default, TypeCheck, and WholeProgram queries process such @@ -298,9 +311,13 @@ func (ld *loader) load(patterns ...string) ([]*Package, error) { ld.GOPATH = os.Getenv("GOPATH") } + if len(patterns) == 0 { + return nil, fmt.Errorf("no packages to load") + } + // Do the metadata query and partial build. // TODO(adonovan): support alternative build systems at this seam. - list, err := golistPackages(ld.Context, ld.GOPATH, ld.cgo, ld.mode == typeCheck, patterns) + list, err := golistPackages(ld.Context, ld.GOPATH, ld.cgo, ld.mode == typeCheck, ld.Tests, patterns) if err != nil { return nil, err } @@ -320,7 +337,7 @@ func (ld *loader) load(patterns ...string) ([]*Package, error) { } } if len(pkgs) == 0 { - return nil, fmt.Errorf("no packages to load") + return nil, fmt.Errorf("packages not found") } // Materialize the import graph. diff --git a/go/packages/packages_test.go b/go/packages/packages_test.go index d24ff3b2518..befd25e0934 100644 --- a/go/packages/packages_test.go +++ b/go/packages/packages_test.go @@ -36,7 +36,6 @@ import ( // import error) will result in a JSON blob with no name and a // nonexistent testmain file in GoFiles. Test that we handle this // gracefully. -// - import graph for synthetic testmain and "p [t.test]" packages. // - IsTest boolean // // TypeCheck & WholeProgram modes: @@ -44,6 +43,7 @@ import ( // - Packages.Info is correctly set. // - typechecker configuration is honored // - import cycles are gracefully handled in type checker. +// - test typechecking of generated test main and cgo. func TestMetadataImportGraph(t *testing.T) { tmp, cleanup := enterTree(t, map[string]string{ @@ -74,6 +74,34 @@ func TestMetadataImportGraph(t *testing.T) { a b * c +* e + errors +* subdir/d + unsafe + b -> a + b -> errors + c -> b + c -> unsafe + e -> b + e -> c +`[1:] + + if graph != wantGraph { + t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph) + } + + opts.Tests = true + initial, err = packages.Metadata(opts, "c", "subdir/d", "e") + if err != nil { + t.Fatal(err) + } + + // Check graph topology. + graph, all = importGraph(initial) + wantGraph = ` + a + b +* c * e errors math/bits @@ -114,7 +142,7 @@ func TestMetadataImportGraph(t *testing.T) { {"e", "main", "command", "e.go e2.go"}, {"errors", "errors", "package", "errors.go"}, {"subdir/d", "d", "package", "d.go"}, - // {"subdir/d.test", "main", "test command", ""}, + {"subdir/d.test", "main", "test command", "0.go"}, {"unsafe", "unsafe", "package", ""}, } { p, ok := all[test.id] @@ -489,8 +517,13 @@ func errorMessages(errors []error) []string { func srcs(p *packages.Package) (basenames []string) { // Ideally we would show the root-relative portion (e.g. after // src/) but vgo doesn't necessarily have a src/ dir. - for _, src := range p.Srcs { - basenames = append(basenames, filepath.Base(src)) + for i, src := range p.Srcs { + if strings.Contains(src, ".cache/go-build") { + src = fmt.Sprintf("%d.go", i) // make cache names predictable + } else { + src = filepath.Base(src) + } + basenames = append(basenames, src) } return basenames } diff --git a/go/packages/stdlib_test.go b/go/packages/stdlib_test.go index d70518c00a3..4c3175d39ed 100644 --- a/go/packages/stdlib_test.go +++ b/go/packages/stdlib_test.go @@ -44,7 +44,7 @@ func TestStdlibMetadata(t *testing.T) { t.Logf("Loaded %d packages", len(pkgs)) numPkgs := len(pkgs) - if want := 340; numPkgs < want { + if want := 186; numPkgs < want { t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want) }