--- /dev/null
+// Copyright 2018 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 packages_test
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "go/ast"
+ constantpkg "go/constant"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "sort"
+ "strings"
+ "testing"
+ "time"
+
+ "golang.org/x/tools/go/packages"
+ "golang.org/x/tools/go/packages/packagestest"
+ "golang.org/x/tools/internal/packagesinternal"
+ "golang.org/x/tools/internal/testenv"
+)
+
+// testCtx is canceled when the test binary is about to time out.
+//
+// If https://golang.org/issue/28135 is accepted, uses of this variable in test
+// functions should be replaced by t.Context().
+var testCtx = context.Background()
+
+func TestMain(m *testing.M) {
+ testenv.ExitIfSmallMachine()
+
+ timeoutFlag := flag.Lookup("test.timeout")
+ if timeoutFlag != nil {
+ if d := timeoutFlag.Value.(flag.Getter).Get().(time.Duration); d != 0 {
+ aBitShorter := d * 95 / 100
+ var cancel context.CancelFunc
+ testCtx, cancel = context.WithTimeout(testCtx, aBitShorter)
+ defer cancel()
+ }
+ }
+
+ os.Exit(m.Run())
+}
+
+// TODO(adonovan): more test cases to write:
+//
+// - When the tests fail, make them print a 'cd & load' command
+// that will allow the maintainer to interact with the failing scenario.
+// - errors in go-list metadata
+// - a foo.test package that cannot be built for some reason (e.g.
+// import error) will result in a JSON blob with no name and a
+// nonexistent testmain file in GoFiles. Test that we handle this
+// gracefully.
+// - test more Flags.
+//
+// LoadSyntax & LoadAllSyntax modes:
+// - Fset may be user-supplied or not.
+// - 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.
+
+// The zero-value of Config has LoadFiles mode.
+func TestLoadZeroConfig(t *testing.T) {
+ testenv.NeedsGoPackages(t)
+
+ initial, err := packages.Load(nil, "hash")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(initial) != 1 {
+ t.Fatalf("got %s, want [hash]", initial)
+ }
+ hash := initial[0]
+ // Even though the hash package has imports,
+ // they are not reported.
+ got := fmt.Sprintf("iamashamedtousethedisabledqueryname=%s srcs=%v imports=%v", hash.Name, srcs(hash), hash.Imports)
+ want := "iamashamedtousethedisabledqueryname=hash srcs=[hash.go] imports=map[]"
+ if got != want {
+ t.Fatalf("got %s, want %s", got, want)
+ }
+}
+
+func TestLoadImportsGraph(t *testing.T) { packagestest.TestAll(t, testLoadImportsGraph) }
+func testLoadImportsGraph(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; const A = 1`,
+ "b/b.go": `package b; import ("golang.org/fake/a"; _ "container/list"); var B = a.A`,
+ "c/c.go": `package c; import (_ "golang.org/fake/b"; _ "unsafe")`,
+ "c/c2.go": "// +build ignore\n\n" + `package c; import _ "fmt"`,
+ "subdir/d/d.go": `package d`,
+ "subdir/d/d_test.go": `package d; import _ "math/bits"`,
+ "subdir/d/x_test.go": `package d_test; import _ "golang.org/fake/subdir/d"`, // TODO(adonovan): test bad import here
+ "subdir/e/d.go": `package e`,
+ "e/e.go": `package main; import _ "golang.org/fake/b"`,
+ "e/e2.go": `package main; import _ "golang.org/fake/c"`,
+ "f/f.go": `package f`,
+ }}})
+ defer exported.Cleanup()
+ exported.Config.Mode = packages.LoadImports
+ initial, err := packages.Load(exported.Config, "golang.org/fake/c", "golang.org/fake/subdir/d", "golang.org/fake/e")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Check graph topology.
+ graph, _ := importGraph(initial)
+ wantGraph := `
+ container/list
+ golang.org/fake/a
+ golang.org/fake/b
+* golang.org/fake/c
+* golang.org/fake/e
+* golang.org/fake/subdir/d
+* golang.org/fake/subdir/d [golang.org/fake/subdir/d.test]
+* golang.org/fake/subdir/d.test
+* golang.org/fake/subdir/d_test [golang.org/fake/subdir/d.test]
+ math/bits
+ unsafe
+ golang.org/fake/b -> container/list
+ golang.org/fake/b -> golang.org/fake/a
+ golang.org/fake/c -> golang.org/fake/b
+ golang.org/fake/c -> unsafe
+ golang.org/fake/e -> golang.org/fake/b
+ golang.org/fake/e -> golang.org/fake/c
+ golang.org/fake/subdir/d [golang.org/fake/subdir/d.test] -> math/bits
+ golang.org/fake/subdir/d.test -> golang.org/fake/subdir/d [golang.org/fake/subdir/d.test]
+ golang.org/fake/subdir/d.test -> golang.org/fake/subdir/d_test [golang.org/fake/subdir/d.test]
+ golang.org/fake/subdir/d_test [golang.org/fake/subdir/d.test] -> golang.org/fake/subdir/d [golang.org/fake/subdir/d.test]
+`[1:]
+
+ if graph != wantGraph {
+ t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
+ }
+
+ exported.Config.Tests = true
+ initial, err = packages.Load(exported.Config, "golang.org/fake/c", "golang.org/fake/subdir/d", "golang.org/fake/e")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Check graph topology.
+ graph, all := importGraph(initial)
+ wantGraph = `
+ container/list
+ golang.org/fake/a
+ golang.org/fake/b
+* golang.org/fake/c
+* golang.org/fake/e
+* golang.org/fake/subdir/d
+* golang.org/fake/subdir/d [golang.org/fake/subdir/d.test]
+* golang.org/fake/subdir/d.test
+* golang.org/fake/subdir/d_test [golang.org/fake/subdir/d.test]
+ math/bits
+ unsafe
+ golang.org/fake/b -> container/list
+ golang.org/fake/b -> golang.org/fake/a
+ golang.org/fake/c -> golang.org/fake/b
+ golang.org/fake/c -> unsafe
+ golang.org/fake/e -> golang.org/fake/b
+ golang.org/fake/e -> golang.org/fake/c
+ golang.org/fake/subdir/d [golang.org/fake/subdir/d.test] -> math/bits
+ golang.org/fake/subdir/d.test -> golang.org/fake/subdir/d [golang.org/fake/subdir/d.test]
+ golang.org/fake/subdir/d.test -> golang.org/fake/subdir/d_test [golang.org/fake/subdir/d.test]
+ golang.org/fake/subdir/d_test [golang.org/fake/subdir/d.test] -> golang.org/fake/subdir/d [golang.org/fake/subdir/d.test]
+`[1:]
+
+ if graph != wantGraph {
+ t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
+ }
+
+ // Check node information: kind, name, srcs.
+ for _, test := range []struct {
+ id string
+ wantName string
+ wantKind string
+ wantSrcs string
+ wantIgnored string
+ }{
+ {"golang.org/fake/a", "a", "package", "a.go", ""},
+ {"golang.org/fake/b", "b", "package", "b.go", ""},
+ {"golang.org/fake/c", "c", "package", "c.go", "c2.go"}, // c2.go is ignored
+ {"golang.org/fake/e", "main", "command", "e.go e2.go", ""},
+ {"container/list", "list", "package", "list.go", ""},
+ {"golang.org/fake/subdir/d", "d", "package", "d.go", ""},
+ {"golang.org/fake/subdir/d.test", "main", "command", "0.go", ""},
+ {"unsafe", "unsafe", "package", "", ""},
+ } {
+ p, ok := all[test.id]
+ if !ok {
+ t.Errorf("no package %s", test.id)
+ continue
+ }
+ if p.Name != test.wantName {
+ t.Errorf("%s.Name = %q, want %q", test.id, p.Name, test.wantName)
+ }
+
+ // kind
+ var kind string
+ if p.Name == "main" {
+ kind += "command"
+ } else {
+ kind += "package"
+ }
+ if kind != test.wantKind {
+ t.Errorf("%s.Kind = %q, want %q", test.id, kind, test.wantKind)
+ }
+
+ if srcs := strings.Join(srcs(p), " "); srcs != test.wantSrcs {
+ t.Errorf("%s.Srcs = [%s], want [%s]", test.id, srcs, test.wantSrcs)
+ }
+ if ignored := strings.Join(cleanPaths(p.IgnoredFiles), " "); ignored != test.wantIgnored {
+ t.Errorf("%s.Srcs = [%s], want [%s]", test.id, ignored, test.wantIgnored)
+ }
+ }
+
+ // Test an ad-hoc package, analogous to "go run hello.go".
+ if initial, err := packages.Load(exported.Config, exported.File("golang.org/fake", "c/c.go")); len(initial) == 0 {
+ t.Errorf("failed to obtain metadata for ad-hoc package: %s", err)
+ } else {
+ got := fmt.Sprintf("%s %s", initial[0].ID, srcs(initial[0]))
+ if want := "command-line-arguments [c.go]"; got != want {
+ t.Errorf("oops: got %s, want %s", got, want)
+ }
+ }
+
+ // Wildcards
+ // See StdlibTest for effective test of "std" wildcard.
+ // TODO(adonovan): test "all" returns everything in the current module.
+ {
+ // "..." (subdirectory)
+ initial, err = packages.Load(exported.Config, "golang.org/fake/subdir/...")
+ if err != nil {
+ t.Fatal(err)
+ }
+ graph, _ = importGraph(initial)
+ wantGraph = `
+* golang.org/fake/subdir/d
+* golang.org/fake/subdir/d [golang.org/fake/subdir/d.test]
+* golang.org/fake/subdir/d.test
+* golang.org/fake/subdir/d_test [golang.org/fake/subdir/d.test]
+* golang.org/fake/subdir/e
+ math/bits
+ golang.org/fake/subdir/d [golang.org/fake/subdir/d.test] -> math/bits
+ golang.org/fake/subdir/d.test -> golang.org/fake/subdir/d [golang.org/fake/subdir/d.test]
+ golang.org/fake/subdir/d.test -> golang.org/fake/subdir/d_test [golang.org/fake/subdir/d.test]
+ golang.org/fake/subdir/d_test [golang.org/fake/subdir/d.test] -> golang.org/fake/subdir/d [golang.org/fake/subdir/d.test]
+`[1:]
+
+ if graph != wantGraph {
+ t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
+ }
+ }
+}
+
+func TestLoadImportsTestVariants(t *testing.T) { packagestest.TestAll(t, testLoadImportsTestVariants) }
+func testLoadImportsTestVariants(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; import _ "golang.org/fake/b"`,
+ "b/b.go": `package b`,
+ "b/b_test.go": `package b`,
+ "b/bx_test.go": `package b_test; import _ "golang.org/fake/a"`,
+ }}})
+ defer exported.Cleanup()
+ exported.Config.Mode = packages.LoadImports
+ exported.Config.Tests = true
+
+ initial, err := packages.Load(exported.Config, "golang.org/fake/a", "golang.org/fake/b")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Check graph topology.
+ graph, _ := importGraph(initial)
+ wantGraph := `
+* golang.org/fake/a
+ golang.org/fake/a [golang.org/fake/b.test]
+* golang.org/fake/b
+* golang.org/fake/b [golang.org/fake/b.test]
+* golang.org/fake/b.test
+* golang.org/fake/b_test [golang.org/fake/b.test]
+ golang.org/fake/a -> golang.org/fake/b
+ golang.org/fake/a [golang.org/fake/b.test] -> golang.org/fake/b [golang.org/fake/b.test]
+ golang.org/fake/b.test -> golang.org/fake/b [golang.org/fake/b.test]
+ golang.org/fake/b.test -> golang.org/fake/b_test [golang.org/fake/b.test]
+ golang.org/fake/b_test [golang.org/fake/b.test] -> golang.org/fake/a [golang.org/fake/b.test]
+`[1:]
+
+ if graph != wantGraph {
+ t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
+ }
+}
+
+func TestLoadAbsolutePath(t *testing.T) {
+ exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{{
+ Name: "golang.org/gopatha",
+ Files: map[string]interface{}{
+ "a/a.go": `package a`,
+ }}, {
+ Name: "golang.org/gopathb",
+ Files: map[string]interface{}{
+ "b/b.go": `package b`,
+ }}})
+ defer exported.Cleanup()
+
+ initial, err := packages.Load(exported.Config, filepath.Dir(exported.File("golang.org/gopatha", "a/a.go")), filepath.Dir(exported.File("golang.org/gopathb", "b/b.go")))
+ if err != nil {
+ t.Fatalf("failed to load imports: %v", err)
+ }
+
+ got := []string{}
+ for _, p := range initial {
+ got = append(got, p.ID)
+ }
+ sort.Strings(got)
+ want := []string{"golang.org/gopatha/a", "golang.org/gopathb/b"}
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("initial packages loaded: got [%s], want [%s]", got, want)
+ }
+}
+
+func TestVendorImports(t *testing.T) {
+ exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; import _ "b"; import _ "golang.org/fake/c";`,
+ "a/vendor/b/b.go": `package b; import _ "golang.org/fake/c"`,
+ "c/c.go": `package c; import _ "b"`,
+ "c/vendor/b/b.go": `package b`,
+ }}})
+ defer exported.Cleanup()
+ exported.Config.Mode = packages.LoadImports
+ initial, err := packages.Load(exported.Config, "golang.org/fake/a", "golang.org/fake/c")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ graph, all := importGraph(initial)
+ wantGraph := `
+* golang.org/fake/a
+ golang.org/fake/a/vendor/b
+* golang.org/fake/c
+ golang.org/fake/c/vendor/b
+ golang.org/fake/a -> golang.org/fake/a/vendor/b
+ golang.org/fake/a -> golang.org/fake/c
+ golang.org/fake/a/vendor/b -> golang.org/fake/c
+ golang.org/fake/c -> golang.org/fake/c/vendor/b
+`[1:]
+ if graph != wantGraph {
+ t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
+ }
+
+ for _, test := range []struct {
+ pattern string
+ wantImports string
+ }{
+ {"golang.org/fake/a", "b:golang.org/fake/a/vendor/b golang.org/fake/c:golang.org/fake/c"},
+ {"golang.org/fake/c", "b:golang.org/fake/c/vendor/b"},
+ {"golang.org/fake/a/vendor/b", "golang.org/fake/c:golang.org/fake/c"},
+ {"golang.org/fake/c/vendor/b", ""},
+ } {
+ // Test the import paths.
+ pkg := all[test.pattern]
+ if imports := strings.Join(imports(pkg), " "); imports != test.wantImports {
+ t.Errorf("package %q: got %s, want %s", test.pattern, imports, test.wantImports)
+ }
+ }
+}
+
+func imports(p *packages.Package) []string {
+ if p == nil {
+ return nil
+ }
+ keys := make([]string, 0, len(p.Imports))
+ for k, v := range p.Imports {
+ keys = append(keys, fmt.Sprintf("%s:%s", k, v.ID))
+ }
+ sort.Strings(keys)
+ return keys
+}
+
+func TestConfigDir(t *testing.T) { packagestest.TestAll(t, testConfigDir) }
+func testConfigDir(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; const Name = "a" `,
+ "a/b/b.go": `package b; const Name = "a/b"`,
+ "b/b.go": `package b; const Name = "b"`,
+ }}})
+ defer exported.Cleanup()
+ aDir := filepath.Dir(exported.File("golang.org/fake", "a/a.go"))
+ bDir := filepath.Dir(exported.File("golang.org/fake", "b/b.go"))
+ baseDir := filepath.Dir(aDir)
+
+ for _, test := range []struct {
+ dir string
+ pattern string
+ want string // value of Name constant
+ fails bool
+ }{
+ {dir: bDir, pattern: "golang.org/fake/a", want: `"a"`},
+ {dir: bDir, pattern: "golang.org/fake/b", want: `"b"`},
+ {dir: bDir, pattern: "./a", fails: true},
+ {dir: bDir, pattern: "./b", fails: true},
+ {dir: baseDir, pattern: "golang.org/fake/a", want: `"a"`},
+ {dir: baseDir, pattern: "golang.org/fake/b", want: `"b"`},
+ {dir: baseDir, pattern: "./a", want: `"a"`},
+ {dir: baseDir, pattern: "./b", want: `"b"`},
+ {dir: aDir, pattern: "golang.org/fake/a", want: `"a"`},
+ {dir: aDir, pattern: "golang.org/fake/b", want: `"b"`},
+ {dir: aDir, pattern: "./a", fails: true},
+ {dir: aDir, pattern: "./b", want: `"a/b"`},
+ } {
+ exported.Config.Mode = packages.LoadSyntax // Use LoadSyntax to ensure that files can be opened.
+ exported.Config.Dir = test.dir
+ initial, err := packages.Load(exported.Config, test.pattern)
+ var got string
+ fails := false
+ if err != nil {
+ fails = true
+ } else if len(initial) > 0 {
+ if len(initial[0].Errors) > 0 {
+ fails = true
+ } else if c := constant(initial[0], "Name"); c != nil {
+ got = c.Val().String()
+ }
+ }
+ if got != test.want {
+ t.Errorf("dir %q, pattern %q: got %s, want %s",
+ test.dir, test.pattern, got, test.want)
+ }
+ if fails != test.fails {
+ // TODO: remove when go#28023 is fixed
+ if test.fails && strings.HasPrefix(test.pattern, "./") && exporter == packagestest.Modules {
+ // Currently go list in module mode does not handle missing directories correctly.
+ continue
+ }
+ t.Errorf("dir %q, pattern %q: error %v, want %v",
+ test.dir, test.pattern, fails, test.fails)
+ }
+ }
+}
+
+func TestConfigFlags(t *testing.T) { packagestest.TestAll(t, testConfigFlags) }
+func testConfigFlags(t *testing.T, exporter packagestest.Exporter) {
+ // Test satisfying +build line tags, with -tags flag.
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ // package a
+ "a/a.go": `package a; import _ "golang.org/fake/a/b"`,
+ "a/b.go": `// +build tag
+
+package a`,
+ "a/c.go": `// +build tag tag2
+
+package a`,
+ "a/d.go": `// +build tag,tag2
+
+package a`,
+ // package a/b
+ "a/b/a.go": `package b`,
+ "a/b/b.go": `// +build tag
+
+package b`,
+ }}})
+ defer exported.Cleanup()
+
+ for _, test := range []struct {
+ pattern string
+ tags []string
+ wantSrcs string
+ wantImportSrcs string
+ }{
+ {`golang.org/fake/a`, []string{}, "a.go", "a.go"},
+ {`golang.org/fake/a`, []string{`-tags=tag`}, "a.go b.go c.go", "a.go b.go"},
+ {`golang.org/fake/a`, []string{`-tags=tag2`}, "a.go c.go", "a.go"},
+ {`golang.org/fake/a`, []string{`-tags=tag tag2`}, "a.go b.go c.go d.go", "a.go b.go"},
+ } {
+ exported.Config.Mode = packages.LoadImports
+ exported.Config.BuildFlags = test.tags
+
+ initial, err := packages.Load(exported.Config, test.pattern)
+ if err != nil {
+ t.Error(err)
+ continue
+ }
+ if len(initial) != 1 {
+ t.Errorf("test tags %v: pattern %s, expected 1 package, got %d packages.", test.tags, test.pattern, len(initial))
+ continue
+ }
+ pkg := initial[0]
+ if srcs := strings.Join(srcs(pkg), " "); srcs != test.wantSrcs {
+ t.Errorf("test tags %v: srcs of package %s = [%s], want [%s]", test.tags, test.pattern, srcs, test.wantSrcs)
+ }
+ for path, ipkg := range pkg.Imports {
+ if srcs := strings.Join(srcs(ipkg), " "); srcs != test.wantImportSrcs {
+ t.Errorf("build tags %v: srcs of imported package %s = [%s], want [%s]", test.tags, path, srcs, test.wantImportSrcs)
+ }
+ }
+
+ }
+}
+
+func TestLoadTypes(t *testing.T) { packagestest.TestAll(t, testLoadTypes) }
+func testLoadTypes(t *testing.T, exporter packagestest.Exporter) {
+ // In LoadTypes and LoadSyntax modes, the compiler will
+ // fail to generate an export data file for c, because it has
+ // a type error. The loader should fall back loading a and c
+ // from source, but use the export data for b.
+
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; import "golang.org/fake/b"; import "golang.org/fake/c"; const A = "a" + b.B + c.C`,
+ "b/b.go": `package b; const B = "b"`,
+ "c/c.go": `package c; const C = "c" + 1`,
+ }}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.LoadTypes
+ initial, err := packages.Load(exported.Config, "golang.org/fake/a")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ graph, all := importGraph(initial)
+ wantGraph := `
+* golang.org/fake/a
+ golang.org/fake/b
+ golang.org/fake/c
+ golang.org/fake/a -> golang.org/fake/b
+ golang.org/fake/a -> golang.org/fake/c
+`[1:]
+ if graph != wantGraph {
+ t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
+ }
+
+ for _, id := range []string{
+ "golang.org/fake/a",
+ "golang.org/fake/b",
+ "golang.org/fake/c",
+ } {
+ p := all[id]
+ if p == nil {
+ t.Errorf("missing package: %s", id)
+ continue
+ }
+ if p.Types == nil {
+ t.Errorf("missing types.Package for %s", p)
+ continue
+ } else if !p.Types.Complete() {
+ t.Errorf("incomplete types.Package for %s", p)
+ } else if p.TypesSizes == nil {
+ t.Errorf("TypesSizes is not filled in for %s", p)
+ }
+
+ }
+}
+
+// TestLoadTypesBits is equivalent to TestLoadTypes except that it only requests
+// the types using the NeedTypes bit.
+func TestLoadTypesBits(t *testing.T) { packagestest.TestAll(t, testLoadTypesBits) }
+func testLoadTypesBits(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; import "golang.org/fake/b"; const A = "a" + b.B`,
+ "b/b.go": `package b; import "golang.org/fake/c"; const B = "b" + c.C`,
+ "c/c.go": `package c; import "golang.org/fake/d"; const C = "c" + d.D`,
+ "d/d.go": `package d; import "golang.org/fake/e"; const D = "d" + e.E`,
+ "e/e.go": `package e; import "golang.org/fake/f"; const E = "e" + f.F`,
+ "f/f.go": `package f; const F = "f"`,
+ }}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.NeedTypes | packages.NeedImports
+ initial, err := packages.Load(exported.Config, "golang.org/fake/a", "golang.org/fake/c")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ graph, all := importGraph(initial)
+ wantGraph := `
+* golang.org/fake/a
+ golang.org/fake/b
+* golang.org/fake/c
+ golang.org/fake/d
+ golang.org/fake/e
+ golang.org/fake/f
+ golang.org/fake/a -> golang.org/fake/b
+ golang.org/fake/b -> golang.org/fake/c
+ golang.org/fake/c -> golang.org/fake/d
+ golang.org/fake/d -> golang.org/fake/e
+ golang.org/fake/e -> golang.org/fake/f
+`[1:]
+ if graph != wantGraph {
+ t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
+ }
+
+ for _, test := range []struct {
+ id string
+ }{
+ {"golang.org/fake/a"},
+ {"golang.org/fake/b"},
+ {"golang.org/fake/c"},
+ {"golang.org/fake/d"},
+ {"golang.org/fake/e"},
+ {"golang.org/fake/f"},
+ } {
+ p := all[test.id]
+ if p == nil {
+ t.Errorf("missing package: %s", test.id)
+ continue
+ }
+ if p.Types == nil {
+ t.Errorf("missing types.Package for %s", p)
+ continue
+ }
+ // We don't request the syntax, so we shouldn't get it.
+ if p.Syntax != nil {
+ t.Errorf("Syntax unexpectedly provided for %s", p)
+ }
+ if p.Errors != nil {
+ t.Errorf("errors in package: %s: %s", p, p.Errors)
+ }
+ }
+
+ // Check value of constant.
+ aA := constant(all["golang.org/fake/a"], "A")
+ if aA == nil {
+ t.Fatalf("a.A: got nil")
+ }
+ if got, want := fmt.Sprintf("%v %v", aA, aA.Val()), `const golang.org/fake/a.A untyped string "abcdef"`; got != want {
+ t.Errorf("a.A: got %s, want %s", got, want)
+ }
+}
+
+func TestLoadSyntaxOK(t *testing.T) { packagestest.TestAll(t, testLoadSyntaxOK) }
+func testLoadSyntaxOK(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; import "golang.org/fake/b"; const A = "a" + b.B`,
+ "b/b.go": `package b; import "golang.org/fake/c"; const B = "b" + c.C`,
+ "c/c.go": `package c; import "golang.org/fake/d"; const C = "c" + d.D`,
+ "d/d.go": `package d; import "golang.org/fake/e"; const D = "d" + e.E`,
+ "e/e.go": `package e; import "golang.org/fake/f"; const E = "e" + f.F`,
+ "f/f.go": `package f; const F = "f"`,
+ }}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.LoadSyntax
+ initial, err := packages.Load(exported.Config, "golang.org/fake/a", "golang.org/fake/c")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ graph, all := importGraph(initial)
+ wantGraph := `
+* golang.org/fake/a
+ golang.org/fake/b
+* golang.org/fake/c
+ golang.org/fake/d
+ golang.org/fake/e
+ golang.org/fake/f
+ golang.org/fake/a -> golang.org/fake/b
+ golang.org/fake/b -> golang.org/fake/c
+ golang.org/fake/c -> golang.org/fake/d
+ golang.org/fake/d -> golang.org/fake/e
+ golang.org/fake/e -> golang.org/fake/f
+`[1:]
+ if graph != wantGraph {
+ t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
+ }
+
+ for _, test := range []struct {
+ id string
+ wantSyntax bool
+ wantComplete bool
+ }{
+ {"golang.org/fake/a", true, true}, // source package
+ {"golang.org/fake/b", true, true}, // source package because depends on initial package
+ {"golang.org/fake/c", true, true}, // source package
+ {"golang.org/fake/d", false, true}, // export data package
+ {"golang.org/fake/e", false, false}, // export data package
+ {"golang.org/fake/f", false, false}, // export data package
+ } {
+ // TODO(matloob): LoadSyntax and LoadAllSyntax are now equivalent, wantSyntax and wantComplete
+ // are true for all packages in the transitive dependency set. Add test cases on the individual
+ // Need* fields to check the equivalents on the new API.
+ p := all[test.id]
+ if p == nil {
+ t.Errorf("missing package: %s", test.id)
+ continue
+ }
+ if p.Types == nil {
+ t.Errorf("missing types.Package for %s", p)
+ continue
+ } else if p.Types.Complete() != test.wantComplete {
+ if test.wantComplete {
+ t.Errorf("incomplete types.Package for %s", p)
+ } else {
+ t.Errorf("unexpected complete types.Package for %s", p)
+ }
+ }
+ if (p.Syntax != nil) != test.wantSyntax {
+ if test.wantSyntax {
+ t.Errorf("missing ast.Files for %s", p)
+ } else {
+ t.Errorf("unexpected ast.Files for for %s", p)
+ }
+ }
+ if p.Errors != nil {
+ t.Errorf("errors in package: %s: %s", p, p.Errors)
+ }
+ }
+
+ // Check value of constant.
+ aA := constant(all["golang.org/fake/a"], "A")
+ if aA == nil {
+ t.Fatalf("a.A: got nil")
+ }
+ if got, want := fmt.Sprintf("%v %v", aA, aA.Val()), `const golang.org/fake/a.A untyped string "abcdef"`; got != want {
+ t.Errorf("a.A: got %s, want %s", got, want)
+ }
+}
+
+func TestLoadDiamondTypes(t *testing.T) { packagestest.TestAll(t, testLoadDiamondTypes) }
+func testLoadDiamondTypes(t *testing.T, exporter packagestest.Exporter) {
+ // We make a diamond dependency and check the type d.D is the same through both paths
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; import ("golang.org/fake/b"; "golang.org/fake/c"); var _ = b.B == c.C`,
+ "b/b.go": `package b; import "golang.org/fake/d"; var B d.D`,
+ "c/c.go": `package c; import "golang.org/fake/d"; var C d.D`,
+ "d/d.go": `package d; type D int`,
+ }}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.LoadSyntax
+ initial, err := packages.Load(exported.Config, "golang.org/fake/a")
+ if err != nil {
+ t.Fatal(err)
+ }
+ packages.Visit(initial, nil, func(pkg *packages.Package) {
+ for _, err := range pkg.Errors {
+ t.Errorf("package %s: %v", pkg.ID, err)
+ }
+ })
+
+ graph, _ := importGraph(initial)
+ wantGraph := `
+* golang.org/fake/a
+ golang.org/fake/b
+ golang.org/fake/c
+ golang.org/fake/d
+ golang.org/fake/a -> golang.org/fake/b
+ golang.org/fake/a -> golang.org/fake/c
+ golang.org/fake/b -> golang.org/fake/d
+ golang.org/fake/c -> golang.org/fake/d
+`[1:]
+ if graph != wantGraph {
+ t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
+ }
+}
+
+func TestLoadSyntaxError(t *testing.T) { packagestest.TestAll(t, testLoadSyntaxError) }
+func testLoadSyntaxError(t *testing.T, exporter packagestest.Exporter) {
+ // A type error in a lower-level package (e) prevents go list
+ // from producing export data for all packages that depend on it
+ // [a-e]. Only f should be loaded from export data, and the rest
+ // should be IllTyped.
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; import "golang.org/fake/b"; const A = "a" + b.B`,
+ "b/b.go": `package b; import "golang.org/fake/c"; const B = "b" + c.C`,
+ "c/c.go": `package c; import "golang.org/fake/d"; const C = "c" + d.D`,
+ "d/d.go": `package d; import "golang.org/fake/e"; const D = "d" + e.E`,
+ "e/e.go": `package e; import "golang.org/fake/f"; const E = "e" + f.F + 1`, // type error
+ "f/f.go": `package f; const F = "f"`,
+ }}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.LoadSyntax
+ initial, err := packages.Load(exported.Config, "golang.org/fake/a", "golang.org/fake/c")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ all := make(map[string]*packages.Package)
+ packages.Visit(initial, nil, func(p *packages.Package) {
+ all[p.ID] = p
+ })
+
+ for _, test := range []struct {
+ id string
+ wantSyntax bool
+ wantIllTyped bool
+ }{
+ {"golang.org/fake/a", true, true},
+ {"golang.org/fake/b", true, true},
+ {"golang.org/fake/c", true, true},
+ {"golang.org/fake/d", true, true},
+ {"golang.org/fake/e", true, true},
+ {"golang.org/fake/f", false, false},
+ } {
+ p := all[test.id]
+ if p == nil {
+ t.Errorf("missing package: %s", test.id)
+ continue
+ }
+ if p.Types == nil {
+ t.Errorf("missing types.Package for %s", p)
+ continue
+ } else if !p.Types.Complete() {
+ t.Errorf("incomplete types.Package for %s", p)
+ }
+ if (p.Syntax != nil) != test.wantSyntax {
+ if test.wantSyntax {
+ t.Errorf("missing ast.Files for %s", test.id)
+ } else {
+ t.Errorf("unexpected ast.Files for for %s", test.id)
+ }
+ }
+ if p.IllTyped != test.wantIllTyped {
+ t.Errorf("IllTyped was %t for %s", p.IllTyped, test.id)
+ }
+ }
+
+ // Check value of constant.
+ aA := constant(all["golang.org/fake/a"], "A")
+ if aA == nil {
+ t.Fatalf("a.A: got nil")
+ }
+ if got, want := aA.String(), `const golang.org/fake/a.A invalid type`; got != want {
+ t.Errorf("a.A: got %s, want %s", got, want)
+ }
+}
+
+// This function tests use of the ParseFile hook to modify
+// the AST after parsing.
+func TestParseFileModifyAST(t *testing.T) { packagestest.TestAll(t, testParseFileModifyAST) }
+func testParseFileModifyAST(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; const A = "a" `,
+ }}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.LoadAllSyntax
+ exported.Config.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
+ const mode = parser.AllErrors | parser.ParseComments
+ f, err := parser.ParseFile(fset, filename, src, mode)
+ // modify AST to change `const A = "a"` to `const A = "b"`
+ spec := f.Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec)
+ spec.Values[0].(*ast.BasicLit).Value = `"b"`
+ return f, err
+ }
+ initial, err := packages.Load(exported.Config, "golang.org/fake/a")
+ if err != nil {
+ t.Error(err)
+ }
+
+ // Check value of a.A has been set to "b"
+ a := initial[0]
+ got := constant(a, "A").Val().String()
+ if got != `"b"` {
+ t.Errorf("a.A: got %s, want %s", got, `"b"`)
+ }
+}
+
+func TestAdHocPackagesBadImport(t *testing.T) {
+ // This test doesn't use packagestest because we are testing ad-hoc packages,
+ // which are outside of $GOPATH and outside of a module.
+ tmp, err := ioutil.TempDir("", "a")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmp)
+
+ filename := filepath.Join(tmp, "a.go")
+ content := []byte(`package a
+import _ "badimport"
+const A = 1
+`)
+ if err := ioutil.WriteFile(filename, content, 0775); err != nil {
+ t.Fatal(err)
+ }
+
+ // Make sure that the user's value of GO111MODULE does not affect test results.
+ for _, go111module := range []string{"off", "auto", "on"} {
+ config := &packages.Config{
+ Env: append(os.Environ(), "GOPACKAGESDRIVER=off", fmt.Sprintf("GO111MODULE=%s", go111module)),
+ Dir: tmp,
+ Mode: packages.LoadAllSyntax,
+ Logf: t.Logf,
+ }
+ initial, err := packages.Load(config, fmt.Sprintf("file=%s", filename))
+ if err != nil {
+ t.Error(err)
+ }
+ if len(initial) == 0 {
+ t.Fatalf("no packages for %s with GO111MODULE=%s", filename, go111module)
+ }
+ // Check value of a.A.
+ a := initial[0]
+ // There's an error because there's a bad import.
+ aA := constant(a, "A")
+ if aA == nil {
+ t.Errorf("a.A: got nil")
+ return
+ }
+ got := aA.Val().String()
+ if want := "1"; got != want {
+ t.Errorf("a.A: got %s, want %s", got, want)
+ }
+ }
+}
+
+func TestLoadAllSyntaxImportErrors(t *testing.T) {
+ packagestest.TestAll(t, testLoadAllSyntaxImportErrors)
+}
+func testLoadAllSyntaxImportErrors(t *testing.T, exporter packagestest.Exporter) {
+ // TODO(matloob): Remove this once go list -e -compiled is fixed.
+ // See https://golang.org/issue/26755
+ t.Skip("go list -compiled -e fails with non-zero exit status for empty packages")
+
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "unicycle/unicycle.go": `package unicycle; import _ "unicycle"`,
+ "bicycle1/bicycle1.go": `package bicycle1; import _ "bicycle2"`,
+ "bicycle2/bicycle2.go": `package bicycle2; import _ "bicycle1"`,
+ "bad/bad.go": `not a package declaration`,
+ "empty/README.txt": `not a go file`,
+ "root/root.go": `package root
+import (
+ _ "bicycle1"
+ _ "unicycle"
+ _ "nonesuch"
+ _ "empty"
+ _ "bad"
+)`,
+ }}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.LoadAllSyntax
+ initial, err := packages.Load(exported.Config, "root")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Cycle-forming edges are removed from the graph:
+ // bicycle2 -> bicycle1
+ // unicycle -> unicycle
+ graph, all := importGraph(initial)
+ wantGraph := `
+ bicycle1
+ bicycle2
+* root
+ unicycle
+ bicycle1 -> bicycle2
+ root -> bicycle1
+ root -> unicycle
+`[1:]
+ if graph != wantGraph {
+ t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
+ }
+ for _, test := range []struct {
+ id string
+ wantErrs []string
+ }{
+ {"bicycle1", nil},
+ {"bicycle2", []string{
+ "could not import bicycle1 (import cycle: [root bicycle1 bicycle2])",
+ }},
+ {"unicycle", []string{
+ "could not import unicycle (import cycle: [root unicycle])",
+ }},
+ {"root", []string{
+ `could not import bad (missing package: "bad")`,
+ `could not import empty (missing package: "empty")`,
+ `could not import nonesuch (missing package: "nonesuch")`,
+ }},
+ } {
+ p := all[test.id]
+ if p == nil {
+ t.Errorf("missing package: %s", test.id)
+ continue
+ }
+ if p.Types == nil {
+ t.Errorf("missing types.Package for %s", test.id)
+ }
+ if p.Syntax == nil {
+ t.Errorf("missing ast.Files for %s", test.id)
+ }
+ if !p.IllTyped {
+ t.Errorf("IllTyped was false for %s", test.id)
+ }
+ if errs := errorMessages(p.Errors); !reflect.DeepEqual(errs, test.wantErrs) {
+ t.Errorf("in package %s, got errors %s, want %s", p, errs, test.wantErrs)
+ }
+ }
+}
+
+func TestAbsoluteFilenames(t *testing.T) { packagestest.TestAll(t, testAbsoluteFilenames) }
+func testAbsoluteFilenames(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; const A = 1`,
+ "b/b.go": `package b; import ("golang.org/fake/a"; _ "errors"); var B = a.A`,
+ "b/vendor/a/a.go": `package a; const A = 1`,
+ "c/c.go": `package c; import (_ "golang.org/fake/b"; _ "unsafe")`,
+ "c/c2.go": "// +build ignore\n\n" + `package c; import _ "fmt"`,
+ "subdir/d/d.go": `package d`,
+ "subdir/e/d.go": `package e`,
+ "e/e.go": `package main; import _ "golang.org/fake/b"`,
+ "e/e2.go": `package main; import _ "golang.org/fake/c"`,
+ "f/f.go": `package f`,
+ "f/f.s": ``,
+ }}})
+ defer exported.Cleanup()
+ exported.Config.Dir = filepath.Dir(filepath.Dir(exported.File("golang.org/fake", "a/a.go")))
+
+ checkFile := func(filename string) {
+ if !filepath.IsAbs(filename) {
+ t.Errorf("filename is not absolute: %s", filename)
+ }
+ if _, err := os.Stat(filename); err != nil {
+ t.Errorf("stat error, %s: %v", filename, err)
+ }
+ }
+
+ for _, test := range []struct {
+ pattern string
+ want string
+ }{
+ // Import paths
+ {"golang.org/fake/a", "a.go"},
+ {"golang.org/fake/b/vendor/a", "a.go"},
+ {"golang.org/fake/b", "b.go"},
+ {"golang.org/fake/c", "c.go"},
+ {"golang.org/fake/subdir/d", "d.go"},
+ {"golang.org/fake/subdir/e", "d.go"},
+ {"golang.org/fake/e", "e.go e2.go"},
+ {"golang.org/fake/f", "f.go f.s"},
+ // Relative paths
+ {"./a", "a.go"},
+ {"./b/vendor/a", "a.go"},
+ {"./b", "b.go"},
+ {"./c", "c.go"},
+ {"./subdir/d", "d.go"},
+ {"./subdir/e", "d.go"},
+ {"./e", "e.go e2.go"},
+ {"./f", "f.go f.s"},
+ } {
+ exported.Config.Mode = packages.LoadFiles
+ pkgs, err := packages.Load(exported.Config, test.pattern)
+ if err != nil {
+ t.Errorf("pattern %s: %v", test.pattern, err)
+ continue
+ }
+
+ if got := strings.Join(srcs(pkgs[0]), " "); got != test.want {
+ t.Errorf("in package %s, got %s, want %s", test.pattern, got, test.want)
+ }
+
+ // Test that files in all packages exist and are absolute paths.
+ _, all := importGraph(pkgs)
+ for _, pkg := range all {
+ for _, filename := range pkg.GoFiles {
+ checkFile(filename)
+ }
+ for _, filename := range pkg.OtherFiles {
+ checkFile(filename)
+ }
+ for _, filename := range pkg.IgnoredFiles {
+ checkFile(filename)
+ }
+ }
+ }
+}
+
+func TestContains(t *testing.T) { packagestest.TestAll(t, testContains) }
+func testContains(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; import "golang.org/fake/b"`,
+ "b/b.go": `package b; import "golang.org/fake/c"`,
+ "c/c.go": `package c`,
+ }}})
+ defer exported.Cleanup()
+ bFile := exported.File("golang.org/fake", "b/b.go")
+ exported.Config.Mode = packages.LoadImports
+ initial, err := packages.Load(exported.Config, "file="+bFile)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ graph, _ := importGraph(initial)
+ wantGraph := `
+* golang.org/fake/b
+ golang.org/fake/c
+ golang.org/fake/b -> golang.org/fake/c
+`[1:]
+ if graph != wantGraph {
+ t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
+ }
+}
+
+// This test ensures that the effective GOARCH variable in the
+// application determines the Sizes function used by the type checker.
+// This behavior is a stop-gap until we make the build system's query
+// tool report the correct sizes function for the actual configuration.
+func TestSizes(t *testing.T) { packagestest.TestAll(t, testSizes) }
+func testSizes(t *testing.T, exporter packagestest.Exporter) {
+ // Only run this test on operating systems that have both an amd64 and 386 port.
+ switch runtime.GOOS {
+ case "linux", "windows", "freebsd", "openbsd", "netbsd", "android":
+ default:
+ t.Skipf("skipping test on %s", runtime.GOOS)
+ }
+
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; import "unsafe"; const WordSize = 8*unsafe.Sizeof(int(0))`,
+ }}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.LoadSyntax
+ savedEnv := exported.Config.Env
+ for arch, wantWordSize := range map[string]int64{"386": 32, "amd64": 64} {
+ exported.Config.Env = append(savedEnv, "GOARCH="+arch)
+ initial, err := packages.Load(exported.Config, "golang.org/fake/a")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if packages.PrintErrors(initial) > 0 {
+ t.Fatal("there were errors")
+ }
+ gotWordSize, _ := constantpkg.Int64Val(constant(initial[0], "WordSize").Val())
+ if gotWordSize != wantWordSize {
+ t.Errorf("for GOARCH=%s, got word size %d, want %d", arch, gotWordSize, wantWordSize)
+ }
+ }
+}
+
+// TestContainsFallbackSticks ensures that when there are both contains and non-contains queries
+// the decision whether to fallback to the pre-1.11 go list sticks across both sets of calls to
+// go list.
+func TestContainsFallbackSticks(t *testing.T) { packagestest.TestAll(t, testContainsFallbackSticks) }
+func testContainsFallbackSticks(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; import "golang.org/fake/b"`,
+ "b/b.go": `package b; import "golang.org/fake/c"`,
+ "c/c.go": `package c`,
+ }}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.LoadImports
+ bFile := exported.File("golang.org/fake", "b/b.go")
+ initial, err := packages.Load(exported.Config, "golang.org/fake/a", "file="+bFile)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ graph, _ := importGraph(initial)
+ wantGraph := `
+* golang.org/fake/a
+* golang.org/fake/b
+ golang.org/fake/c
+ golang.org/fake/a -> golang.org/fake/b
+ golang.org/fake/b -> golang.org/fake/c
+`[1:]
+ if graph != wantGraph {
+ t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
+ }
+}
+
+// Test that Load with no patterns is equivalent to loading "." via the golist
+// driver.
+func TestNoPatterns(t *testing.T) { packagestest.TestAll(t, testNoPatterns) }
+func testNoPatterns(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a;`,
+ "a/b/b.go": `package b;`,
+ }}})
+ defer exported.Cleanup()
+
+ aDir := filepath.Dir(exported.File("golang.org/fake", "a/a.go"))
+ exported.Config.Dir = aDir
+
+ initial, err := packages.Load(exported.Config)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(initial) != 1 || initial[0].Name != "a" {
+ t.Fatalf(`Load() = %v, wanted just the package in the current directory`, initial)
+ }
+}
+
+func TestJSON(t *testing.T) { packagestest.TestAll(t, testJSON) }
+func testJSON(t *testing.T, exporter packagestest.Exporter) {
+ //TODO: add in some errors
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; const A = 1`,
+ "b/b.go": `package b; import "golang.org/fake/a"; var B = a.A`,
+ "c/c.go": `package c; import "golang.org/fake/b" ; var C = b.B`,
+ "d/d.go": `package d; import "golang.org/fake/b" ; var D = b.B`,
+ }}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.LoadImports
+ initial, err := packages.Load(exported.Config, "golang.org/fake/c", "golang.org/fake/d")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Visit and print all packages.
+ buf := &bytes.Buffer{}
+ enc := json.NewEncoder(buf)
+ enc.SetIndent("", "\t")
+ packages.Visit(initial, nil, func(pkg *packages.Package) {
+ // trim the source lists for stable results
+ pkg.GoFiles = cleanPaths(pkg.GoFiles)
+ pkg.CompiledGoFiles = cleanPaths(pkg.CompiledGoFiles)
+ pkg.OtherFiles = cleanPaths(pkg.OtherFiles)
+ pkg.IgnoredFiles = cleanPaths(pkg.IgnoredFiles)
+ if err := enc.Encode(pkg); err != nil {
+ t.Fatal(err)
+ }
+ })
+
+ wantJSON := `
+{
+ "ID": "golang.org/fake/a",
+ "Name": "a",
+ "PkgPath": "golang.org/fake/a",
+ "GoFiles": [
+ "a.go"
+ ],
+ "CompiledGoFiles": [
+ "a.go"
+ ]
+}
+{
+ "ID": "golang.org/fake/b",
+ "Name": "b",
+ "PkgPath": "golang.org/fake/b",
+ "GoFiles": [
+ "b.go"
+ ],
+ "CompiledGoFiles": [
+ "b.go"
+ ],
+ "Imports": {
+ "golang.org/fake/a": "golang.org/fake/a"
+ }
+}
+{
+ "ID": "golang.org/fake/c",
+ "Name": "c",
+ "PkgPath": "golang.org/fake/c",
+ "GoFiles": [
+ "c.go"
+ ],
+ "CompiledGoFiles": [
+ "c.go"
+ ],
+ "Imports": {
+ "golang.org/fake/b": "golang.org/fake/b"
+ }
+}
+{
+ "ID": "golang.org/fake/d",
+ "Name": "d",
+ "PkgPath": "golang.org/fake/d",
+ "GoFiles": [
+ "d.go"
+ ],
+ "CompiledGoFiles": [
+ "d.go"
+ ],
+ "Imports": {
+ "golang.org/fake/b": "golang.org/fake/b"
+ }
+}
+`[1:]
+
+ if buf.String() != wantJSON {
+ t.Errorf("wrong JSON: got <<%s>>, want <<%s>>", buf.String(), wantJSON)
+ }
+ // now decode it again
+ var decoded []*packages.Package
+ dec := json.NewDecoder(buf)
+ for dec.More() {
+ p := new(packages.Package)
+ if err := dec.Decode(p); err != nil {
+ t.Fatal(err)
+ }
+ decoded = append(decoded, p)
+ }
+ if len(decoded) != 4 {
+ t.Fatalf("got %d packages, want 4", len(decoded))
+ }
+ for i, want := range []*packages.Package{{
+ ID: "golang.org/fake/a",
+ Name: "a",
+ }, {
+ ID: "golang.org/fake/b",
+ Name: "b",
+ Imports: map[string]*packages.Package{
+ "golang.org/fake/a": {ID: "golang.org/fake/a"},
+ },
+ }, {
+ ID: "golang.org/fake/c",
+ Name: "c",
+ Imports: map[string]*packages.Package{
+ "golang.org/fake/b": {ID: "golang.org/fake/b"},
+ },
+ }, {
+ ID: "golang.org/fake/d",
+ Name: "d",
+ Imports: map[string]*packages.Package{
+ "golang.org/fake/b": {ID: "golang.org/fake/b"},
+ },
+ }} {
+ got := decoded[i]
+ if got.ID != want.ID {
+ t.Errorf("Package %d has ID %q want %q", i, got.ID, want.ID)
+ }
+ if got.Name != want.Name {
+ t.Errorf("Package %q has Name %q want %q", got.ID, got.Name, want.Name)
+ }
+ if len(got.Imports) != len(want.Imports) {
+ t.Errorf("Package %q has %d imports want %d", got.ID, len(got.Imports), len(want.Imports))
+ continue
+ }
+ for path, ipkg := range got.Imports {
+ if want.Imports[path] == nil {
+ t.Errorf("Package %q has unexpected import %q", got.ID, path)
+ continue
+ }
+ if want.Imports[path].ID != ipkg.ID {
+ t.Errorf("Package %q import %q is %q want %q", got.ID, path, ipkg.ID, want.Imports[path].ID)
+ }
+ }
+ }
+}
+
+func TestRejectInvalidQueries(t *testing.T) {
+ queries := []string{"key=", "key=value"}
+ cfg := &packages.Config{
+ Mode: packages.LoadImports,
+ Env: append(os.Environ(), "GO111MODULE=off", "GOPACKAGESDRIVER=off"),
+ }
+ for _, q := range queries {
+ if _, err := packages.Load(cfg, q); err == nil {
+ t.Errorf("packages.Load(%q) succeeded. Expected \"invalid query type\" error", q)
+ } else if !strings.Contains(err.Error(), "invalid query type") {
+ t.Errorf("packages.Load(%q): got error %v, want \"invalid query type\" error", q, err)
+ }
+ }
+}
+
+func TestPatternPassthrough(t *testing.T) { packagestest.TestAll(t, testPatternPassthrough) }
+func testPatternPassthrough(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a;`,
+ }}})
+ defer exported.Cleanup()
+
+ initial, err := packages.Load(exported.Config, "pattern=a")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ graph, _ := importGraph(initial)
+ wantGraph := `
+* a
+`[1:]
+ if graph != wantGraph {
+ t.Errorf("wrong import graph: got <<%s>>, want <<%s>>", graph, wantGraph)
+ }
+
+}
+
+func TestConfigDefaultEnv(t *testing.T) { packagestest.TestAll(t, testConfigDefaultEnv) }
+func testConfigDefaultEnv(t *testing.T, exporter packagestest.Exporter) {
+ const driverJSON = `{
+ "Roots": ["gopackagesdriver"],
+ "Packages": [{"ID": "gopackagesdriver", "Name": "gopackagesdriver"}]
+}`
+ var (
+ pathKey string
+ driverScript packagestest.Writer
+ )
+ switch runtime.GOOS {
+ case "android":
+ t.Skip("doesn't run on android")
+ case "windows":
+ // TODO(jayconrod): write an equivalent batch script for windows.
+ // Hint: "type" can be used to read a file to stdout.
+ t.Skip("test requires sh")
+ case "plan9":
+ pathKey = "path"
+ driverScript = packagestest.Script(`#!/bin/rc
+
+cat <<'EOF'
+` + driverJSON + `
+EOF
+`)
+ default:
+ pathKey = "PATH"
+ driverScript = packagestest.Script(`#!/bin/sh
+
+cat - <<'EOF'
+` + driverJSON + `
+EOF
+`)
+ }
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "bin/gopackagesdriver": driverScript,
+ "golist/golist.go": "package golist",
+ }}})
+ defer exported.Cleanup()
+ driver := exported.File("golang.org/fake", "bin/gopackagesdriver")
+ binDir := filepath.Dir(driver)
+ if err := os.Chmod(driver, 0755); err != nil {
+ t.Fatal(err)
+ }
+
+ path, ok := os.LookupEnv(pathKey)
+ var pathWithDriver string
+ if ok {
+ pathWithDriver = binDir + string(os.PathListSeparator) + path
+ } else {
+ pathWithDriver = binDir
+ }
+ for _, test := range []struct {
+ desc string
+ path string
+ driver string
+ wantIDs string
+ }{
+ {
+ desc: "driver_off",
+ path: pathWithDriver,
+ driver: "off",
+ wantIDs: "[golist]",
+ }, {
+ desc: "driver_unset",
+ path: pathWithDriver,
+ driver: "",
+ wantIDs: "[gopackagesdriver]",
+ }, {
+ desc: "driver_set",
+ path: "",
+ driver: driver,
+ wantIDs: "[gopackagesdriver]",
+ },
+ } {
+ t.Run(test.desc, func(t *testing.T) {
+ oldPath := os.Getenv(pathKey)
+ os.Setenv(pathKey, test.path)
+ defer os.Setenv(pathKey, oldPath)
+ // Clone exported.Config
+ config := exported.Config
+ config.Env = append([]string{}, exported.Config.Env...)
+ config.Env = append(config.Env, "GOPACKAGESDRIVER="+test.driver)
+ pkgs, err := packages.Load(exported.Config, "golist")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ gotIds := make([]string, len(pkgs))
+ for i, pkg := range pkgs {
+ gotIds[i] = pkg.ID
+ }
+ if fmt.Sprint(pkgs) != test.wantIDs {
+ t.Errorf("got %v; want %v", gotIds, test.wantIDs)
+ }
+ })
+ }
+}
+
+// This test that a simple x test package layout loads correctly.
+// There was a bug in go list where it returned multiple copies of the same
+// package (specifically in this case of golang.org/fake/a), and this triggered
+// a bug in go/packages where it would leave an empty entry in the root package
+// list. This would then cause a nil pointer crash.
+// This bug was triggered by the simple package layout below, and thus this
+// test will make sure the bug remains fixed.
+func TestBasicXTest(t *testing.T) { packagestest.TestAll(t, testBasicXTest) }
+func testBasicXTest(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a;`,
+ "a/a_test.go": `package a_test;`,
+ }}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.LoadFiles
+ exported.Config.Tests = true
+ _, err := packages.Load(exported.Config, "golang.org/fake/a")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestErrorMissingFile(t *testing.T) { packagestest.TestAll(t, testErrorMissingFile) }
+func testErrorMissingFile(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a_test.go": `package a;`,
+ }}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.LoadSyntax
+ exported.Config.Tests = false
+ pkgs, err := packages.Load(exported.Config, "missing.go")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(pkgs) == 0 && runtime.GOOS == "windows" {
+ t.Skip("Issue #31344: the ad-hoc command-line-arguments package isn't created on windows")
+ }
+ if len(pkgs) != 1 || (pkgs[0].PkgPath != "command-line-arguments" && pkgs[0].PkgPath != "missing.go") {
+ t.Fatalf("packages.Load: want [command-line-arguments] or [missing.go], got %v", pkgs)
+ }
+ if len(pkgs[0].Errors) == 0 {
+ t.Errorf("result of Load: want package with errors, got none: %+v", pkgs[0])
+ }
+}
+
+func TestReturnErrorWhenUsingNonGoFiles(t *testing.T) {
+ packagestest.TestAll(t, testReturnErrorWhenUsingNonGoFiles)
+}
+func testReturnErrorWhenUsingNonGoFiles(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/gopatha",
+ Files: map[string]interface{}{
+ "a/a.go": `package a`,
+ }}, {
+ Name: "golang.org/gopathb",
+ Files: map[string]interface{}{
+ "b/b.c": `package b`,
+ }}})
+ defer exported.Cleanup()
+ config := packages.Config{Env: append(os.Environ(), "GOPACKAGESDRIVER=off")}
+ pkgs, err := packages.Load(&config, "b/b.c")
+ if err != nil {
+ return
+ }
+ // Go <1.14 calls the package command-line-arguments while Go 1.14+ uses the file names.
+ if len(pkgs) != 1 || (pkgs[0].PkgPath != "command-line-arguments" && pkgs[0].PkgPath != "b/b.c") {
+ t.Fatalf("packages.Load: want [command-line-arguments] or [b/b.c], got %v", pkgs)
+ }
+ if len(pkgs[0].Errors) != 1 {
+ t.Fatalf("result of Load: want package with one error, got: %+v", pkgs[0])
+ }
+}
+
+func TestReturnErrorWhenUsingGoFilesInMultipleDirectories(t *testing.T) {
+ packagestest.TestAll(t, testReturnErrorWhenUsingGoFilesInMultipleDirectories)
+}
+func testReturnErrorWhenUsingGoFilesInMultipleDirectories(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/gopatha",
+ Files: map[string]interface{}{
+ "a/a.go": `package a`,
+ "b/b.go": `package b`,
+ }}})
+ defer exported.Cleanup()
+ want := "named files must all be in one directory"
+ pkgs, err := packages.Load(exported.Config, exported.File("golang.org/gopatha", "a/a.go"), exported.File("golang.org/gopatha", "b/b.go"))
+ if err != nil {
+ // Check if the error returned is the one we expected.
+ if !strings.Contains(err.Error(), want) {
+ t.Fatalf("want error message: %s, got: %s", want, err.Error())
+ }
+ return
+ }
+ if len(pkgs) != 1 || pkgs[0].PkgPath != "command-line-arguments" {
+ t.Fatalf("packages.Load: want [command-line-arguments], got %v", pkgs)
+ }
+ if len(pkgs[0].Errors) != 1 {
+ t.Fatalf("result of Load: want package with one error, got: %+v", pkgs[0])
+ }
+ got := pkgs[0].Errors[0].Error()
+ if !strings.Contains(got, want) {
+ t.Fatalf("want error message: %s, got: %s", want, got)
+ }
+}
+
+func TestReturnErrorForUnexpectedDirectoryLayout(t *testing.T) {
+ packagestest.TestAll(t, testReturnErrorForUnexpectedDirectoryLayout)
+}
+func testReturnErrorForUnexpectedDirectoryLayout(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/gopatha",
+ Files: map[string]interface{}{
+ "a/testdata/a.go": `package a; import _ "b"`,
+ "a/vendor/b/b.go": `package b; import _ "fmt"`,
+ }}})
+ defer exported.Cleanup()
+ want := "unexpected directory layout"
+ // triggering this error requires a relative package path
+ exported.Config.Dir = filepath.Dir(exported.File("golang.org/gopatha", "a/testdata/a.go"))
+ pkgs, err := packages.Load(exported.Config, ".")
+
+ // This error doesn't seem to occur in module mode; so only
+ // complain if we get zero packages while also getting no error.
+ if err == nil {
+ if len(pkgs) == 0 {
+ // TODO(dh): we'll need to expand on the error check if/when Go stops emitting this error
+ t.Fatalf("want error, got nil")
+ }
+ return
+ }
+ // Check if the error returned is the one we expected.
+ if !strings.Contains(err.Error(), want) {
+ t.Fatalf("want error message: %s, got: %s", want, err.Error())
+ }
+}
+
+func TestMissingDependency(t *testing.T) { packagestest.TestAll(t, testMissingDependency) }
+func testMissingDependency(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; import _ "this/package/doesnt/exist"`,
+ }}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.LoadAllSyntax
+ pkgs, err := packages.Load(exported.Config, "golang.org/fake/a")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(pkgs) != 1 && pkgs[0].PkgPath != "golang.org/fake/a" {
+ t.Fatalf("packages.Load: want [golang.org/fake/a], got %v", pkgs)
+ }
+ if len(pkgs[0].Errors) == 0 {
+ t.Errorf("result of Load: want package with errors, got none: %+v", pkgs[0])
+ }
+}
+
+func TestAdHocContains(t *testing.T) { packagestest.TestAll(t, testAdHocContains) }
+func testAdHocContains(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a;`,
+ }}})
+ defer exported.Cleanup()
+
+ tmpfile, err := ioutil.TempFile("", "adhoc*.go")
+ filename := tmpfile.Name()
+ if err != nil {
+ t.Fatal(err)
+ }
+ fmt.Fprint(tmpfile, `package main; import "fmt"; func main() { fmt.Println("time for coffee") }`)
+ if err := tmpfile.Close(); err != nil {
+ t.Fatal(err)
+ }
+
+ defer func() {
+ if err := os.Remove(filename); err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ exported.Config.Mode = packages.NeedImports | packages.NeedFiles
+ pkgs, err := packages.Load(exported.Config, "file="+filename)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(pkgs) != 1 && pkgs[0].PkgPath != "command-line-arguments" {
+ t.Fatalf("packages.Load: want [command-line-arguments], got %v", pkgs)
+ }
+ pkg := pkgs[0]
+ if _, ok := pkg.Imports["fmt"]; !ok || len(pkg.Imports) != 1 {
+ t.Fatalf("Imports of loaded package: want [fmt], got %v", pkg.Imports)
+ }
+ if len(pkg.GoFiles) != 1 || pkg.GoFiles[0] != filename {
+ t.Fatalf("GoFiles of loaded package: want [%s], got %v", filename, pkg.GoFiles)
+ }
+}
+
+func TestCgoNoCcompiler(t *testing.T) { packagestest.TestAll(t, testCgoNoCcompiler) }
+func testCgoNoCcompiler(t *testing.T, exporter packagestest.Exporter) {
+ testenv.NeedsTool(t, "cgo")
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a
+import "net/http"
+const A = http.MethodGet
+`,
+ }}})
+ defer exported.Cleanup()
+
+ // Explicitly enable cgo but configure a nonexistent C compiler.
+ exported.Config.Env = append(exported.Config.Env, "CGO_ENABLED=1", "CC=doesnotexist")
+ exported.Config.Mode = packages.LoadAllSyntax
+ initial, err := packages.Load(exported.Config, "golang.org/fake/a")
+
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Check value of a.A.
+ a := initial[0]
+ aA := constant(a, "A")
+ if aA == nil {
+ t.Fatalf("a.A: got nil")
+ }
+ got := aA.Val().String()
+ if got != "\"GET\"" {
+ t.Errorf("a.A: got %s, want %s", got, "\"GET\"")
+ }
+}
+
+func TestCgoMissingFile(t *testing.T) { packagestest.TestAll(t, testCgoMissingFile) }
+func testCgoMissingFile(t *testing.T, exporter packagestest.Exporter) {
+ testenv.NeedsTool(t, "cgo")
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a
+
+// #include "foo.h"
+import "C"
+
+const A = 4
+`,
+ }}})
+ defer exported.Cleanup()
+
+ // Explicitly enable cgo.
+ exported.Config.Env = append(exported.Config.Env, "CGO_ENABLED=1")
+ exported.Config.Mode = packages.LoadAllSyntax
+ initial, err := packages.Load(exported.Config, "golang.org/fake/a")
+
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Check value of a.A.
+ a := initial[0]
+ aA := constant(a, "A")
+ if aA == nil {
+ t.Fatalf("a.A: got nil")
+ }
+ got := aA.Val().String()
+ if got != "4" {
+ t.Errorf("a.A: got %s, want %s", got, "4")
+ }
+}
+
+func TestLoadImportsC(t *testing.T) {
+ // This test checks that when a package depends on the
+ // test variant of "syscall", "unsafe", or "runtime/cgo", that dependency
+ // is not removed when those packages are added when it imports "C".
+ //
+ // For this test to work, the external test of syscall must have a dependency
+ // on net, and net must import "syscall" and "C".
+ if runtime.GOOS == "windows" {
+ t.Skipf("skipping on windows; packages on windows do not satisfy conditions for test.")
+ }
+ if runtime.GOOS == "plan9" {
+ // See https://golang.org/issue/27100.
+ t.Skip(`skipping on plan9; for some reason "net [syscall.test]" is not loaded`)
+ }
+ testenv.NeedsGoPackages(t)
+
+ cfg := &packages.Config{
+ Context: testCtx,
+ Mode: packages.LoadImports,
+ Tests: true,
+ }
+ initial, err := packages.Load(cfg, "syscall", "net")
+ if err != nil {
+ t.Fatalf("failed to load imports: %v", err)
+ }
+
+ _, all := importGraph(initial)
+
+ for _, test := range []struct {
+ pattern string
+ wantImport string // an import to check for
+ }{
+ {"net", "syscall:syscall"},
+ {"net [syscall.test]", "syscall:syscall [syscall.test]"},
+ {"syscall_test [syscall.test]", "net:net [syscall.test]"},
+ } {
+ // Test the import paths.
+ pkg := all[test.pattern]
+ if pkg == nil {
+ t.Errorf("package %q not loaded", test.pattern)
+ continue
+ }
+ if imports := strings.Join(imports(pkg), " "); !strings.Contains(imports, test.wantImport) {
+ t.Errorf("package %q: got \n%s, \nwant to have %s", test.pattern, imports, test.wantImport)
+ }
+ }
+}
+
+func TestCgoNoSyntax(t *testing.T) {
+ packagestest.TestAll(t, testCgoNoSyntax)
+}
+func testCgoNoSyntax(t *testing.T, exporter packagestest.Exporter) {
+ testenv.NeedsTool(t, "cgo")
+
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "c/c.go": `package c; import "C"`,
+ },
+ }})
+
+ // Explicitly enable cgo.
+ exported.Config.Env = append(exported.Config.Env, "CGO_ENABLED=1")
+
+ modes := []packages.LoadMode{
+ packages.NeedTypes,
+ packages.NeedName | packages.NeedTypes,
+ packages.NeedName | packages.NeedTypes | packages.NeedImports,
+ packages.NeedName | packages.NeedTypes | packages.NeedImports | packages.NeedDeps,
+ packages.NeedName | packages.NeedImports,
+ }
+ for _, mode := range modes {
+ t.Run(fmt.Sprint(mode), func(t *testing.T) {
+ exported.Config.Mode = mode
+ pkgs, err := packages.Load(exported.Config, "golang.org/fake/c")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(pkgs) != 1 {
+ t.Fatalf("Expected 1 package, got %v", pkgs)
+ }
+ pkg := pkgs[0]
+ if len(pkg.Errors) != 0 {
+ t.Fatalf("Expected no errors in package, got %v", pkg.Errors)
+ }
+ })
+ }
+}
+
+func TestCgoBadPkgConfig(t *testing.T) {
+ packagestest.TestAll(t, testCgoBadPkgConfig)
+}
+func testCgoBadPkgConfig(t *testing.T, exporter packagestest.Exporter) {
+ testenv.NeedsTool(t, "cgo")
+
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "c/c.go": `package c
+
+// #cgo pkg-config: --cflags -- foo
+import "C"`,
+ },
+ }})
+
+ dir := buildFakePkgconfig(t, exported.Config.Env)
+ defer os.RemoveAll(dir)
+ env := exported.Config.Env
+ for i, v := range env {
+ if strings.HasPrefix(v, "PATH=") {
+ env[i] = "PATH=" + dir + string(os.PathListSeparator) + v[len("PATH="):]
+ }
+ }
+
+ exported.Config.Env = append(exported.Config.Env, "CGO_ENABLED=1")
+
+ exported.Config.Mode = packages.NeedName | packages.NeedCompiledGoFiles
+ pkgs, err := packages.Load(exported.Config, "golang.org/fake/c")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(pkgs) != 1 {
+ t.Fatalf("Expected 1 package, got %v", pkgs)
+ }
+ if pkgs[0].Name != "c" {
+ t.Fatalf("Expected package to have name \"c\", got %q", pkgs[0].Name)
+ }
+}
+
+func buildFakePkgconfig(t *testing.T, env []string) string {
+ tmpdir, err := ioutil.TempDir("", "fakepkgconfig")
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = ioutil.WriteFile(filepath.Join(tmpdir, "pkg-config.go"), []byte(`
+package main
+
+import "fmt"
+import "os"
+
+func main() {
+ fmt.Fprintln(os.Stderr, "bad")
+ os.Exit(2)
+}
+`), 0644)
+ if err != nil {
+ os.RemoveAll(tmpdir)
+ t.Fatal(err)
+ }
+ cmd := exec.Command("go", "build", "-o", "pkg-config", "pkg-config.go")
+ cmd.Dir = tmpdir
+ cmd.Env = env
+
+ if b, err := cmd.CombinedOutput(); err != nil {
+ os.RemoveAll(tmpdir)
+ fmt.Println(os.Environ())
+ t.Log(string(b))
+ t.Fatal(err)
+ }
+ return tmpdir
+}
+
+func TestIssue32814(t *testing.T) { packagestest.TestAll(t, testIssue32814) }
+func testIssue32814(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{}}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.NeedName | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedTypesSizes
+ pkgs, err := packages.Load(exported.Config, "fmt")
+
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(pkgs) != 1 && pkgs[0].PkgPath != "fmt" {
+ t.Fatalf("packages.Load: want [fmt], got %v", pkgs)
+ }
+ pkg := pkgs[0]
+ if len(pkg.Errors) != 0 {
+ t.Fatalf("Errors for fmt pkg: got %v, want none", pkg.Errors)
+ }
+ if !pkg.Types.Complete() {
+ t.Fatalf("Types.Complete() for fmt pkg: got %v, want true", pkgs[0].Types.Complete())
+
+ }
+}
+
+func TestLoadTypesInfoWithoutNeedDeps(t *testing.T) {
+ packagestest.TestAll(t, testLoadTypesInfoWithoutNeedDeps)
+}
+func testLoadTypesInfoWithoutNeedDeps(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; import _ "golang.org/fake/b"`,
+ "b/b.go": `package b`,
+ }}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.NeedTypes | packages.NeedTypesInfo | packages.NeedImports
+ pkgs, err := packages.Load(exported.Config, "golang.org/fake/a")
+ if err != nil {
+ t.Fatal(err)
+ }
+ pkg := pkgs[0]
+ if pkg.IllTyped {
+ t.Fatal("Loaded package is ill typed")
+ }
+ const expectedImport = "golang.org/fake/b"
+ if _, ok := pkg.Imports[expectedImport]; !ok || len(pkg.Imports) != 1 {
+ t.Fatalf("Imports of loaded package: want [%s], got %v", expectedImport, pkg.Imports)
+ }
+}
+
+func TestLoadWithNeedDeps(t *testing.T) {
+ packagestest.TestAll(t, testLoadWithNeedDeps)
+}
+func testLoadWithNeedDeps(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; import _ "golang.org/fake/b"`,
+ "b/b.go": `package b; import _ "golang.org/fake/c"`,
+ "c/c.go": `package c`,
+ }}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.NeedTypes | packages.NeedTypesInfo | packages.NeedImports | packages.NeedDeps
+ pkgs, err := packages.Load(exported.Config, "golang.org/fake/a")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(pkgs) != 1 {
+ t.Fatalf("Expected 1 package, got %d", len(pkgs))
+ }
+
+ pkgA := pkgs[0]
+ if pkgA.IllTyped {
+ t.Fatal("Loaded package is ill typed")
+ }
+
+ pkgB := pkgA.Imports["golang.org/fake/b"]
+ if pkgB == nil || len(pkgA.Imports) != 1 {
+ t.Fatalf("Imports of loaded package 'a' are invalid: %v", pkgA.Imports)
+ }
+ if pkgB.Types == nil || !pkgB.Types.Complete() || pkgB.TypesInfo == nil {
+ t.Fatalf("Types of package 'b' are nil or incomplete: %v, %v", pkgB.Types, pkgB.TypesInfo)
+ }
+
+ pkgC := pkgB.Imports["golang.org/fake/c"]
+ if pkgC == nil || len(pkgB.Imports) != 1 {
+ t.Fatalf("Imports of loaded package 'c' are invalid: %v", pkgB.Imports)
+ }
+ if pkgC.Types == nil || !pkgC.Types.Complete() || pkgC.TypesInfo == nil {
+ t.Fatalf("Types of package 'b' are nil or incomplete: %v, %v", pkgC.Types, pkgC.TypesInfo)
+ }
+}
+
+func TestImpliedLoadMode(t *testing.T) {
+ packagestest.TestAll(t, testImpliedLoadMode)
+}
+func testImpliedLoadMode(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; import _ "golang.org/fake/b"`,
+ "b/b.go": `package b`,
+ }}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.NeedTypes | packages.NeedTypesInfo
+ pkgs, err := packages.Load(exported.Config, "golang.org/fake/a")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(pkgs) != 1 {
+ t.Fatalf("Expected 1 package, got %d", len(pkgs))
+ }
+
+ pkg := pkgs[0]
+ if pkg.IllTyped {
+ t.Fatalf("Loaded package is ill typed: %v", pkg.Errors)
+ }
+
+ // Check that packages.NeedTypesInfo worked well.
+ if !pkg.Types.Complete() {
+ t.Fatalf("Loaded package types are incomplete")
+ }
+
+ // Check that implied packages.NeedImports by packages.NeedTypesInfo
+ // didn't add Imports.
+ if len(pkg.Imports) != 0 {
+ t.Fatalf("Package imports weren't requested but were returned: %v", pkg.Imports)
+ }
+}
+
+func TestIssue35331(t *testing.T) {
+ packagestest.TestAll(t, testIssue35331)
+}
+func testIssue35331(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ }})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles |
+ packages.NeedImports | packages.NeedDeps | packages.NeedSyntax
+ exported.Config.Tests = false
+ pkgs, err := packages.Load(exported.Config, "strconv")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(pkgs) != 1 {
+ t.Fatalf("Expected 1 package, got %v", pkgs)
+ }
+ packages.Visit(pkgs, func(pkg *packages.Package) bool {
+ if len(pkg.Errors) > 0 {
+ t.Errorf("Expected no errors in package %q, got %v", pkg.ID, pkg.Errors)
+ }
+ if len(pkg.Syntax) == 0 && pkg.ID != "unsafe" {
+ t.Errorf("Expected syntax on package %q, got none.", pkg.ID)
+ }
+ return true
+ }, nil)
+}
+
+func TestMultiplePackageVersionsIssue36188(t *testing.T) {
+ packagestest.TestAll(t, testMultiplePackageVersionsIssue36188)
+}
+
+func testMultiplePackageVersionsIssue36188(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; import _ "golang.org/fake/b"`,
+ "b/b.go": `package main`,
+ }}})
+ pkgs, err := packages.Load(exported.Config, "golang.org/fake/a", "golang.org/fake/b")
+ if err != nil {
+ t.Fatal(err)
+ }
+ sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].ID < pkgs[j].ID })
+ if len(pkgs) != 2 {
+ t.Fatalf("expected two packages, got %v", pkgs)
+ }
+ if pkgs[0].ID != "golang.org/fake/a" && pkgs[1].ID != "golang.org/fake/b" {
+ t.Fatalf(`expected (sorted) IDs "golang.org/fake/a" and "golang.org/fake/b", got %q and %q`,
+ pkgs[0].ID, pkgs[1].ID)
+ }
+ if pkgs[0].Errors == nil {
+ t.Errorf(`expected error on package "golang.org/fake/a", got none`)
+ }
+ if pkgs[1].Errors != nil {
+ t.Errorf(`expected no errors on package "golang.org/fake/b", got %v`, pkgs[1].Errors)
+ }
+ defer exported.Cleanup()
+}
+
+func TestLoadModeStrings(t *testing.T) {
+ testcases := []struct {
+ mode packages.LoadMode
+ expected string
+ }{
+ {
+ packages.LoadMode(0),
+ "LoadMode(0)",
+ },
+ {
+ packages.NeedName,
+ "LoadMode(NeedName)",
+ },
+ {
+ packages.NeedFiles,
+ "LoadMode(NeedFiles)",
+ },
+ {
+ packages.NeedCompiledGoFiles,
+ "LoadMode(NeedCompiledGoFiles)",
+ },
+ {
+ packages.NeedImports,
+ "LoadMode(NeedImports)",
+ },
+ {
+ packages.NeedDeps,
+ "LoadMode(NeedDeps)",
+ },
+ {
+ packages.NeedExportsFile,
+ "LoadMode(NeedExportsFile)",
+ },
+ {
+ packages.NeedTypes,
+ "LoadMode(NeedTypes)",
+ },
+ {
+ packages.NeedSyntax,
+ "LoadMode(NeedSyntax)",
+ },
+ {
+ packages.NeedTypesInfo,
+ "LoadMode(NeedTypesInfo)",
+ },
+ {
+ packages.NeedTypesSizes,
+ "LoadMode(NeedTypesSizes)",
+ },
+ {
+ packages.NeedName | packages.NeedExportsFile,
+ "LoadMode(NeedName|NeedExportsFile)",
+ },
+ {
+ packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedTypesSizes,
+ "LoadMode(NeedName|NeedFiles|NeedCompiledGoFiles|NeedImports|NeedDeps|NeedExportsFile|NeedTypes|NeedSyntax|NeedTypesInfo|NeedTypesSizes)",
+ },
+ {
+ packages.NeedName | 8192,
+ "LoadMode(NeedName|Unknown)",
+ },
+ {
+ 4096,
+ "LoadMode(Unknown)",
+ },
+ }
+
+ for tcInd, tc := range testcases {
+ t.Run(fmt.Sprintf("test-%d", tcInd), func(t *testing.T) {
+ actual := tc.mode.String()
+ if tc.expected != actual {
+ t.Errorf("want %#v, got %#v", tc.expected, actual)
+ }
+ })
+ }
+}
+
+func TestCycleImportStack(t *testing.T) {
+ packagestest.TestAll(t, testCycleImportStack)
+}
+func testCycleImportStack(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; import _ "golang.org/fake/b"`,
+ "b/b.go": `package b; import _ "golang.org/fake/a"`,
+ }}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.NeedName | packages.NeedImports
+ pkgs, err := packages.Load(exported.Config, "golang.org/fake/a")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(pkgs) != 1 {
+ t.Fatalf("Expected 1 package, got %v", pkgs)
+ }
+ pkg := pkgs[0]
+ if len(pkg.Errors) != 1 {
+ t.Fatalf("Expected one error in package, got %v", pkg.Errors)
+ }
+ expected := "import cycle not allowed: import stack: [golang.org/fake/a golang.org/fake/b golang.org/fake/a]"
+ if pkg.Errors[0].Msg != expected {
+ t.Fatalf("Expected error %q, got %q", expected, pkg.Errors[0].Msg)
+ }
+}
+
+func TestForTestField(t *testing.T) {
+ packagestest.TestAll(t, testForTestField)
+}
+func testForTestField(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a; func hello() {};`,
+ "a/a_test.go": `package a; import "testing"; func TestA1(t *testing.T) {};`,
+ "a/x_test.go": `package a_test; import "testing"; func TestA2(t *testing.T) {};`,
+ }}})
+ defer exported.Cleanup()
+
+ // Add overlays to make sure they don't affect anything.
+ exported.Config.Overlay = map[string][]byte{
+ "a/a_test.go": []byte(`package a; import "testing"; func TestA1(t *testing.T) { hello(); };`),
+ "a/x_test.go": []byte(`package a_test; import "testing"; func TestA2(t *testing.T) { hello(); };`),
+ }
+ exported.Config.Tests = true
+ exported.Config.Mode = packages.NeedName | packages.NeedImports
+ forTest := "golang.org/fake/a"
+ pkgs, err := packages.Load(exported.Config, forTest)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(pkgs) != 4 {
+ t.Errorf("expected 4 packages, got %v", len(pkgs))
+ }
+ for _, pkg := range pkgs {
+ var hasTestFile bool
+ for _, f := range pkg.CompiledGoFiles {
+ if strings.Contains(f, "a_test.go") || strings.Contains(f, "x_test.go") {
+ hasTestFile = true
+ break
+ }
+ }
+ if !hasTestFile {
+ continue
+ }
+ got := packagesinternal.GetForTest(pkg)
+ if got != forTest {
+ t.Errorf("expected %q, got %q", forTest, got)
+ }
+ }
+}
+
+func TestIssue37529(t *testing.T) {
+ packagestest.TestAll(t, testIssue37529)
+}
+func testIssue37529(t *testing.T, exporter packagestest.Exporter) {
+ // Tests #37529. When automatic vendoring is triggered, and we try to determine
+ // the module root dir for a new overlay package, we previously would do a go list -m all,
+ // which is incompatible with automatic vendoring.
+
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "c/c2.go": `package c`,
+ "a/a.go": `package a; import "b.com/b"; const A = b.B`,
+ "vendor/b.com/b/b.go": `package b; const B = 4`,
+ }}})
+ rootDir := filepath.Dir(filepath.Dir(exported.File("golang.org/fake", "a/a.go")))
+ exported.Config.Overlay = map[string][]byte{
+ filepath.Join(rootDir, "c/c.go"): []byte(`package c; import "golang.org/fake/a"; const C = a.A`),
+ }
+ exported.Config.Env = append(exported.Config.Env, "GOFLAGS=-mod=vendor")
+ exported.Config.Mode = packages.LoadAllSyntax
+
+ defer exported.Cleanup()
+
+ initial, err := packages.Load(exported.Config, "golang.org/fake/c")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Check value of a.A.
+ a := initial[0]
+ aA := constant(a, "C")
+ if aA == nil {
+ t.Fatalf("a.A: got nil")
+ }
+ got := aA.Val().String()
+ if got != "4" {
+ t.Errorf("a.A: got %s, want %s", got, "4")
+ }
+}
+
+func TestIssue37098(t *testing.T) { packagestest.TestAll(t, testIssue37098) }
+func testIssue37098(t *testing.T, exporter packagestest.Exporter) {
+ // packages.Load should only return Go sources in
+ // (*Package).CompiledGoFiles. This tests #37098, where using SWIG to
+ // causes C++ sources to be inadvertently included in
+ // (*Package).CompiledGoFiles.
+ t.Skip("Issue #37098: SWIG causes generated C++ sources in CompiledGoFiles")
+
+ // Create a fake package with an empty Go source, and a SWIG interface
+ // file.
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ // The "package" statement must be included for SWIG sources to
+ // be generated.
+ "a/a.go": "package a",
+ "a/a.swigcxx": "",
+ }}})
+ defer exported.Cleanup()
+
+ initial, err := packages.Load(exported.Config, "golang.org/fake/a")
+ if err != nil {
+ t.Fatalf("failed to load the package: %v", err)
+ }
+ // Try and parse each of the files
+ for _, pkg := range initial {
+ for _, file := range pkg.CompiledGoFiles {
+
+ // Validate that each file can be parsed as a Go source.
+ fset := token.NewFileSet()
+ _, err := parser.ParseFile(fset, file, nil, parser.ImportsOnly)
+ if err != nil {
+ t.Errorf("Failed to parse file '%s' as a Go source: %v", file, err)
+
+ contents, err := ioutil.ReadFile(file)
+ if err != nil {
+ t.Fatalf("Failed to read the un-parsable file '%s': %v", file, err)
+ }
+
+ // Print out some of the un-parsable file to aid in debugging.
+ n := len(contents)
+
+ // Don't print the whole file if it is too large.
+ const maxBytes = 1000
+ if n > maxBytes {
+ n = maxBytes
+ }
+
+ t.Logf("First %d bytes of un-parsable file: %s", n, contents[:n])
+ }
+ }
+ }
+}
+
+// TestInvalidFilesInXTest checks the fix for golang/go#37971 in Go 1.15.
+func TestInvalidFilesInXTest(t *testing.T) { packagestest.TestAll(t, testInvalidFilesInXTest) }
+func testInvalidFilesInXTest(t *testing.T, exporter packagestest.Exporter) {
+ testenv.NeedsGo1Point(t, 15)
+ exported := packagestest.Export(t, exporter, []packagestest.Module{
+ {
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "d/d.go": `package d; import "net/http"; const d = http.MethodGet; func Get() string { return d; }`,
+ "d/d2.go": ``, // invalid file
+ "d/d_test.go": `package d_test; import "testing"; import "golang.org/fake/d"; func TestD(t *testing.T) { d.Get(); }`,
+ },
+ },
+ })
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.NeedName | packages.NeedFiles
+ exported.Config.Tests = true
+
+ initial, err := packages.Load(exported.Config, "golang.org/fake/d")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(initial) != 3 {
+ t.Errorf("expected 3 packages, got %d", len(initial))
+ }
+}
+
+func TestTypecheckCgo(t *testing.T) { packagestest.TestAll(t, testTypecheckCgo) }
+func testTypecheckCgo(t *testing.T, exporter packagestest.Exporter) {
+ testenv.NeedsGo1Point(t, 15)
+ testenv.NeedsTool(t, "cgo")
+
+ const cgo = `package cgo
+ import "C"
+
+ func Example() {
+ C.CString("hi")
+ }
+ `
+ exported := packagestest.Export(t, exporter, []packagestest.Module{
+ {
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "cgo/cgo.go": cgo,
+ },
+ },
+ })
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.NeedFiles | packages.NeedCompiledGoFiles |
+ packages.NeedSyntax | packages.NeedDeps | packages.NeedTypes |
+ packages.LoadMode(packagesinternal.TypecheckCgo)
+
+ initial, err := packages.Load(exported.Config, "golang.org/fake/cgo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ pkg := initial[0]
+ if len(pkg.Errors) != 0 {
+ t.Fatalf("package has errors: %v", pkg.Errors)
+ }
+
+ expos := pkg.Types.Scope().Lookup("Example").Pos()
+ fname := pkg.Fset.File(expos).Name()
+ if !strings.HasSuffix(fname, "cgo.go") {
+ t.Errorf("position for cgo package was loaded from %v, wanted cgo.go", fname)
+ }
+}
+
+func TestModule(t *testing.T) {
+ packagestest.TestAll(t, testModule)
+}
+func testModule(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{"a/a.go": `package a`}}})
+ exported.Config.Mode = packages.NeedModule
+ rootDir := filepath.Dir(filepath.Dir(exported.File("golang.org/fake", "a/a.go")))
+
+ initial, err := packages.Load(exported.Config, "golang.org/fake/a")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(initial) != 1 {
+ t.Fatal("want exactly one package, got ", initial)
+ }
+ a := initial[0]
+ switch exported.Exporter.Name() {
+ case "GOPATH":
+ if a.Module != nil {
+ t.Fatal("package.Module: want nil, got ", a.Module)
+ }
+ case "Modules":
+ // Make sure Modules field is set, and spot check a few of its fields.
+ if a.Module == nil {
+ t.Fatal("package.Module: want non-nil, got nil")
+ }
+ if a.Module.Path != "golang.org/fake" {
+ t.Fatalf("package.Modile.Path: want \"golang.org/fake\", got %q", a.Module.Path)
+ }
+ if a.Module.GoMod != filepath.Join(rootDir, "go.mod") {
+ t.Fatalf("package.Module.GoMod: want %q, got %q", filepath.Join(rootDir, "go.mod"), a.Module.GoMod)
+ }
+ default:
+ t.Fatalf("Expected exporter to be GOPATH or Modules, got %v", exported.Exporter.Name())
+ }
+}
+
+func TestExternal_NotHandled(t *testing.T) {
+ packagestest.TestAll(t, testExternal_NotHandled)
+}
+func testExternal_NotHandled(t *testing.T, exporter packagestest.Exporter) {
+ testenv.NeedsGoBuild(t)
+
+ tempdir, err := ioutil.TempDir("", "testexternal")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tempdir)
+
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "a/a.go": `package a`,
+ "empty_driver/main.go": `package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+)
+
+func main() {
+ ioutil.ReadAll(os.Stdin)
+ fmt.Println("{}")
+}
+`,
+ "nothandled_driver/main.go": `package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+)
+
+func main() {
+ ioutil.ReadAll(os.Stdin)
+ fmt.Println("{\"NotHandled\": true}")
+}
+`,
+ }}})
+ baseEnv := exported.Config.Env
+
+ // As a control, create a fake driver that always returns an empty response.
+ emptyDriverPath := filepath.Join(tempdir, "empty_driver.exe") // Add .exe because Windows expects it.
+ cmd := exec.Command("go", "build", "-o", emptyDriverPath, "golang.org/fake/empty_driver")
+ cmd.Env = baseEnv
+ cmd.Dir = exported.Config.Dir
+ if b, err := cmd.CombinedOutput(); err != nil {
+ t.Log(string(b))
+ t.Fatal(err)
+ }
+
+ exported.Config.Env = append(append([]string{}, baseEnv...), "GOPACKAGESDRIVER="+emptyDriverPath)
+ initial, err := packages.Load(exported.Config, "golang.org/fake/a")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(initial) != 0 {
+ t.Errorf("package.Load with empty driver: want [], got %v", initial)
+ }
+
+ // Create a fake driver that always returns a NotHandled response.
+ notHandledDriverPath := filepath.Join(tempdir, "nothandled_driver.exe")
+ cmd = exec.Command("go", "build", "-o", notHandledDriverPath, "golang.org/fake/nothandled_driver")
+ cmd.Env = baseEnv
+ cmd.Dir = exported.Config.Dir
+ if b, err := cmd.CombinedOutput(); err != nil {
+ t.Log(string(b))
+ t.Fatal(err)
+ }
+
+ exported.Config.Env = append(append([]string{}, baseEnv...), "GOPACKAGESDRIVER="+notHandledDriverPath)
+ initial, err = packages.Load(exported.Config, "golang.org/fake/a")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(initial) != 1 || initial[0].PkgPath != "golang.org/fake/a" {
+ t.Errorf("package.Load: want [golang.org/fake/a], got %v", initial)
+ }
+}
+
+func TestInvalidPackageName(t *testing.T) {
+ packagestest.TestAll(t, testInvalidPackageName)
+}
+
+func testInvalidPackageName(t *testing.T, exporter packagestest.Exporter) {
+ testenv.NeedsGo1Point(t, 15)
+
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "golang.org/fake",
+ Files: map[string]interface{}{
+ "main.go": `package default
+
+func main() {
+}
+`,
+ },
+ }})
+ defer exported.Cleanup()
+
+ initial, err := packages.Load(exported.Config, "golang.org/fake")
+ if err != nil {
+ t.Fatal(err)
+ }
+ pkg := initial[0]
+ if len(pkg.CompiledGoFiles) != 1 {
+ t.Fatalf("expected 1 Go file in package %s, got %v", pkg.ID, len(pkg.CompiledGoFiles))
+ }
+}
+
+func errorMessages(errors []packages.Error) []string {
+ var msgs []string
+ for _, err := range errors {
+ msgs = append(msgs, err.Msg)
+ }
+ return msgs
+}
+
+func srcs(p *packages.Package) []string {
+ return cleanPaths(append(p.GoFiles[:len(p.GoFiles):len(p.GoFiles)], p.OtherFiles...))
+}
+
+// cleanPaths attempts to reduce path names to stable forms
+func cleanPaths(paths []string) []string {
+ result := make([]string, len(paths))
+ for i, src := range paths {
+ // If the source file doesn't have an extension like .go or .s,
+ // it comes from GOCACHE. The names there aren't predictable.
+ name := filepath.Base(src)
+ if !strings.Contains(name, ".") {
+ result[i] = fmt.Sprintf("%d.go", i) // make cache names predictable
+ } else {
+ result[i] = name
+ }
+ }
+ return result
+}
+
+// importGraph returns the import graph as a user-friendly string,
+// and a map containing all packages keyed by ID.
+func importGraph(initial []*packages.Package) (string, map[string]*packages.Package) {
+ out := new(bytes.Buffer)
+
+ initialSet := make(map[*packages.Package]bool)
+ for _, p := range initial {
+ initialSet[p] = true
+ }
+
+ // We can't use Visit because we need to prune
+ // the traversal of specific edges, not just nodes.
+ var nodes, edges []string
+ res := make(map[string]*packages.Package)
+ seen := make(map[*packages.Package]bool)
+ var visit func(p *packages.Package)
+ visit = func(p *packages.Package) {
+ if !seen[p] {
+ seen[p] = true
+ if res[p.ID] != nil {
+ panic("duplicate ID: " + p.ID)
+ }
+ res[p.ID] = p
+
+ star := ' ' // mark initial packages with a star
+ if initialSet[p] {
+ star = '*'
+ }
+ nodes = append(nodes, fmt.Sprintf("%c %s", star, p.ID))
+
+ // To avoid a lot of noise,
+ // we prune uninteresting dependencies of testmain packages,
+ // which we identify by this import:
+ isTestMain := p.Imports["testing/internal/testdeps"] != nil
+
+ for _, imp := range p.Imports {
+ if isTestMain {
+ switch imp.ID {
+ case "os", "reflect", "testing", "testing/internal/testdeps":
+ continue
+ }
+ }
+ // math/bits took on a dependency on unsafe in 1.12, which breaks some
+ // tests. As a short term hack, prune that edge.
+ // ditto for ("errors", "internal/reflectlite") in 1.13.
+ // TODO(matloob): think of a cleaner solution, or remove math/bits from the test.
+ if p.ID == "math/bits" && imp.ID == "unsafe" {
+ continue
+ }
+ edges = append(edges, fmt.Sprintf("%s -> %s", p, imp))
+ visit(imp)
+ }
+ }
+ }
+ for _, p := range initial {
+ visit(p)
+ }
+
+ // Sort, ignoring leading optional star prefix.
+ sort.Slice(nodes, func(i, j int) bool { return nodes[i][2:] < nodes[j][2:] })
+ for _, node := range nodes {
+ fmt.Fprintf(out, "%s\n", node)
+ }
+
+ sort.Strings(edges)
+ for _, edge := range edges {
+ fmt.Fprintf(out, " %s\n", edge)
+ }
+
+ return out.String(), res
+}
+
+func constant(p *packages.Package, name string) *types.Const {
+ if p == nil || p.Types == nil {
+ return nil
+ }
+ c := p.Types.Scope().Lookup(name)
+ if c == nil {
+ return nil
+ }
+ return c.(*types.Const)
+}
+
+func copyAll(srcPath, dstPath string) error {
+ return filepath.Walk(srcPath, func(path string, info os.FileInfo, _ error) error {
+ if info.IsDir() {
+ return nil
+ }
+ contents, err := ioutil.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ rel, err := filepath.Rel(srcPath, path)
+ if err != nil {
+ return err
+ }
+ dstFilePath := strings.Replace(filepath.Join(dstPath, rel), "definitelynot_go.mod", "go.mod", -1)
+ if err := os.MkdirAll(filepath.Dir(dstFilePath), 0755); err != nil {
+ return err
+ }
+ if err := ioutil.WriteFile(dstFilePath, contents, 0644); err != nil {
+ return err
+ }
+ return nil
+ })
+}