diff options
Diffstat (limited to 'vendor/golang.org/x/tools/go/loader/loader_test.go')
-rw-r--r-- | vendor/golang.org/x/tools/go/loader/loader_test.go | 816 |
1 files changed, 816 insertions, 0 deletions
diff --git a/vendor/golang.org/x/tools/go/loader/loader_test.go b/vendor/golang.org/x/tools/go/loader/loader_test.go new file mode 100644 index 0000000..02630eb --- /dev/null +++ b/vendor/golang.org/x/tools/go/loader/loader_test.go @@ -0,0 +1,816 @@ +// Copyright 2013 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. + +// No testdata on Android. + +// +build !android + +package loader_test + +import ( + "fmt" + "go/build" + "go/constant" + "go/types" + "path/filepath" + "reflect" + "sort" + "strings" + "sync" + "testing" + + "golang.org/x/tools/go/buildutil" + "golang.org/x/tools/go/loader" +) + +// TestFromArgs checks that conf.FromArgs populates conf correctly. +// It does no I/O. +func TestFromArgs(t *testing.T) { + type result struct { + Err string + Rest []string + ImportPkgs map[string]bool + CreatePkgs []loader.PkgSpec + } + for _, test := range []struct { + args []string + tests bool + want result + }{ + // Mix of existing and non-existent packages. + { + args: []string{"nosuchpkg", "errors"}, + want: result{ + ImportPkgs: map[string]bool{"errors": false, "nosuchpkg": false}, + }, + }, + // Same, with -test flag. + { + args: []string{"nosuchpkg", "errors"}, + tests: true, + want: result{ + ImportPkgs: map[string]bool{"errors": true, "nosuchpkg": true}, + }, + }, + // Surplus arguments. + { + args: []string{"fmt", "errors", "--", "surplus"}, + want: result{ + Rest: []string{"surplus"}, + ImportPkgs: map[string]bool{"errors": false, "fmt": false}, + }, + }, + // Ad hoc package specified as *.go files. + { + args: []string{"foo.go", "bar.go"}, + want: result{CreatePkgs: []loader.PkgSpec{{ + Filenames: []string{"foo.go", "bar.go"}, + }}}, + }, + // Mixture of *.go and import paths. + { + args: []string{"foo.go", "fmt"}, + want: result{ + Err: "named files must be .go files: fmt", + }, + }, + } { + var conf loader.Config + rest, err := conf.FromArgs(test.args, test.tests) + got := result{ + Rest: rest, + ImportPkgs: conf.ImportPkgs, + CreatePkgs: conf.CreatePkgs, + } + if err != nil { + got.Err = err.Error() + } + if !reflect.DeepEqual(got, test.want) { + t.Errorf("FromArgs(%q) = %+v, want %+v", test.args, got, test.want) + } + } +} + +func TestLoad_NoInitialPackages(t *testing.T) { + var conf loader.Config + + const wantErr = "no initial packages were loaded" + + prog, err := conf.Load() + if err == nil { + t.Errorf("Load succeeded unexpectedly, want %q", wantErr) + } else if err.Error() != wantErr { + t.Errorf("Load failed with wrong error %q, want %q", err, wantErr) + } + if prog != nil { + t.Errorf("Load unexpectedly returned a Program") + } +} + +func TestLoad_MissingInitialPackage(t *testing.T) { + var conf loader.Config + conf.Import("nosuchpkg") + conf.Import("errors") + + const wantErr = "couldn't load packages due to errors: nosuchpkg" + + prog, err := conf.Load() + if err == nil { + t.Errorf("Load succeeded unexpectedly, want %q", wantErr) + } else if err.Error() != wantErr { + t.Errorf("Load failed with wrong error %q, want %q", err, wantErr) + } + if prog != nil { + t.Errorf("Load unexpectedly returned a Program") + } +} + +func TestLoad_MissingInitialPackage_AllowErrors(t *testing.T) { + var conf loader.Config + conf.AllowErrors = true + conf.Import("nosuchpkg") + conf.ImportWithTests("errors") + + prog, err := conf.Load() + if err != nil { + t.Errorf("Load failed unexpectedly: %v", err) + } + if prog == nil { + t.Fatalf("Load returned a nil Program") + } + if got, want := created(prog), "errors_test"; got != want { + t.Errorf("Created = %s, want %s", got, want) + } + if got, want := imported(prog), "errors"; got != want { + t.Errorf("Imported = %s, want %s", got, want) + } +} + +func TestCreateUnnamedPackage(t *testing.T) { + var conf loader.Config + conf.CreateFromFilenames("") + prog, err := conf.Load() + if err != nil { + t.Fatalf("Load failed: %v", err) + } + if got, want := fmt.Sprint(prog.InitialPackages()), "[(unnamed)]"; got != want { + t.Errorf("InitialPackages = %s, want %s", got, want) + } +} + +func TestLoad_MissingFileInCreatedPackage(t *testing.T) { + var conf loader.Config + conf.CreateFromFilenames("", "missing.go") + + const wantErr = "couldn't load packages due to errors: (unnamed)" + + prog, err := conf.Load() + if prog != nil { + t.Errorf("Load unexpectedly returned a Program") + } + if err == nil { + t.Fatalf("Load succeeded unexpectedly, want %q", wantErr) + } + if err.Error() != wantErr { + t.Fatalf("Load failed with wrong error %q, want %q", err, wantErr) + } +} + +func TestLoad_MissingFileInCreatedPackage_AllowErrors(t *testing.T) { + conf := loader.Config{AllowErrors: true} + conf.CreateFromFilenames("", "missing.go") + + prog, err := conf.Load() + if err != nil { + t.Errorf("Load failed: %v", err) + } + if got, want := fmt.Sprint(prog.InitialPackages()), "[(unnamed)]"; got != want { + t.Fatalf("InitialPackages = %s, want %s", got, want) + } +} + +func TestLoad_ParseError(t *testing.T) { + var conf loader.Config + conf.CreateFromFilenames("badpkg", "testdata/badpkgdecl.go") + + const wantErr = "couldn't load packages due to errors: badpkg" + + prog, err := conf.Load() + if prog != nil { + t.Errorf("Load unexpectedly returned a Program") + } + if err == nil { + t.Fatalf("Load succeeded unexpectedly, want %q", wantErr) + } + if err.Error() != wantErr { + t.Fatalf("Load failed with wrong error %q, want %q", err, wantErr) + } +} + +func TestLoad_ParseError_AllowErrors(t *testing.T) { + var conf loader.Config + conf.AllowErrors = true + conf.CreateFromFilenames("badpkg", "testdata/badpkgdecl.go") + + prog, err := conf.Load() + if err != nil { + t.Errorf("Load failed unexpectedly: %v", err) + } + if prog == nil { + t.Fatalf("Load returned a nil Program") + } + if got, want := created(prog), "badpkg"; got != want { + t.Errorf("Created = %s, want %s", got, want) + } + + badpkg := prog.Created[0] + if len(badpkg.Files) != 1 { + t.Errorf("badpkg has %d files, want 1", len(badpkg.Files)) + } + wantErr := filepath.Join("testdata", "badpkgdecl.go") + ":1:34: expected 'package', found 'EOF'" + if !hasError(badpkg.Errors, wantErr) { + t.Errorf("badpkg.Errors = %v, want %s", badpkg.Errors, wantErr) + } +} + +func TestLoad_FromSource_Success(t *testing.T) { + var conf loader.Config + conf.CreateFromFilenames("P", "testdata/a.go", "testdata/b.go") + + prog, err := conf.Load() + if err != nil { + t.Errorf("Load failed unexpectedly: %v", err) + } + if prog == nil { + t.Fatalf("Load returned a nil Program") + } + if got, want := created(prog), "P"; got != want { + t.Errorf("Created = %s, want %s", got, want) + } +} + +func TestLoad_FromImports_Success(t *testing.T) { + var conf loader.Config + conf.ImportWithTests("fmt") + conf.ImportWithTests("errors") + + prog, err := conf.Load() + if err != nil { + t.Errorf("Load failed unexpectedly: %v", err) + } + if prog == nil { + t.Fatalf("Load returned a nil Program") + } + if got, want := created(prog), "errors_test fmt_test"; got != want { + t.Errorf("Created = %q, want %s", got, want) + } + if got, want := imported(prog), "errors fmt"; got != want { + t.Errorf("Imported = %s, want %s", got, want) + } + // Check set of transitive packages. + // There are >30 and the set may grow over time, so only check a few. + want := map[string]bool{ + "strings": true, + "time": true, + "runtime": true, + "testing": true, + "unicode": true, + } + for _, path := range all(prog) { + delete(want, path) + } + if len(want) > 0 { + t.Errorf("AllPackages is missing these keys: %q", keys(want)) + } +} + +func TestLoad_MissingIndirectImport(t *testing.T) { + pkgs := map[string]string{ + "a": `package a; import _ "b"`, + "b": `package b; import _ "c"`, + } + conf := loader.Config{Build: fakeContext(pkgs)} + conf.Import("a") + + const wantErr = "couldn't load packages due to errors: b" + + prog, err := conf.Load() + if err == nil { + t.Errorf("Load succeeded unexpectedly, want %q", wantErr) + } else if err.Error() != wantErr { + t.Errorf("Load failed with wrong error %q, want %q", err, wantErr) + } + if prog != nil { + t.Errorf("Load unexpectedly returned a Program") + } +} + +func TestLoad_BadDependency_AllowErrors(t *testing.T) { + for _, test := range []struct { + descr string + pkgs map[string]string + wantPkgs string + }{ + + { + descr: "missing dependency", + pkgs: map[string]string{ + "a": `package a; import _ "b"`, + "b": `package b; import _ "c"`, + }, + wantPkgs: "a b", + }, + { + descr: "bad package decl in dependency", + pkgs: map[string]string{ + "a": `package a; import _ "b"`, + "b": `package b; import _ "c"`, + "c": `package`, + }, + wantPkgs: "a b", + }, + { + descr: "parse error in dependency", + pkgs: map[string]string{ + "a": `package a; import _ "b"`, + "b": `package b; import _ "c"`, + "c": `package c; var x = `, + }, + wantPkgs: "a b c", + }, + } { + conf := loader.Config{ + AllowErrors: true, + Build: fakeContext(test.pkgs), + } + conf.Import("a") + + prog, err := conf.Load() + if err != nil { + t.Errorf("%s: Load failed unexpectedly: %v", test.descr, err) + } + if prog == nil { + t.Fatalf("%s: Load returned a nil Program", test.descr) + } + + if got, want := imported(prog), "a"; got != want { + t.Errorf("%s: Imported = %s, want %s", test.descr, got, want) + } + if got := all(prog); strings.Join(got, " ") != test.wantPkgs { + t.Errorf("%s: AllPackages = %s, want %s", test.descr, got, test.wantPkgs) + } + } +} + +func TestCwd(t *testing.T) { + ctxt := fakeContext(map[string]string{"one/two/three": `package three`}) + for _, test := range []struct { + cwd, arg, want string + }{ + {cwd: "/go/src/one", arg: "./two/three", want: "one/two/three"}, + {cwd: "/go/src/one", arg: "../one/two/three", want: "one/two/three"}, + {cwd: "/go/src/one", arg: "one/two/three", want: "one/two/three"}, + {cwd: "/go/src/one/two/three", arg: ".", want: "one/two/three"}, + {cwd: "/go/src/one", arg: "two/three", want: ""}, + } { + conf := loader.Config{ + Cwd: test.cwd, + Build: ctxt, + } + conf.Import(test.arg) + + var got string + prog, err := conf.Load() + if prog != nil { + got = imported(prog) + } + if got != test.want { + t.Errorf("Load(%s) from %s: Imported = %s, want %s", + test.arg, test.cwd, got, test.want) + if err != nil { + t.Errorf("Load failed: %v", err) + } + } + } +} + +func TestLoad_vendor(t *testing.T) { + pkgs := map[string]string{ + "a": `package a; import _ "x"`, + "a/vendor": ``, // mkdir a/vendor + "a/vendor/x": `package xa`, + "b": `package b; import _ "x"`, + "b/vendor": ``, // mkdir b/vendor + "b/vendor/x": `package xb`, + "c": `package c; import _ "x"`, + "x": `package xc`, + } + conf := loader.Config{Build: fakeContext(pkgs)} + conf.Import("a") + conf.Import("b") + conf.Import("c") + + prog, err := conf.Load() + if err != nil { + t.Fatal(err) + } + + // Check that a, b, and c see different versions of x. + for _, r := range "abc" { + name := string(r) + got := prog.Package(name).Pkg.Imports()[0] + want := "x" + name + if got.Name() != want { + t.Errorf("package %s import %q = %s, want %s", + name, "x", got.Name(), want) + } + } +} + +func TestVendorCwd(t *testing.T) { + // Test the interaction of cwd and vendor directories. + ctxt := fakeContext(map[string]string{ + "net": ``, // mkdir net + "net/http": `package http; import _ "hpack"`, + "vendor": ``, // mkdir vendor + "vendor/hpack": `package vendorhpack`, + "hpack": `package hpack`, + }) + for i, test := range []struct { + cwd, arg, want string + }{ + {cwd: "/go/src/net", arg: "http"}, // not found + {cwd: "/go/src/net", arg: "./http", want: "net/http vendor/hpack"}, + {cwd: "/go/src/net", arg: "hpack", want: "vendor/hpack"}, + {cwd: "/go/src/vendor", arg: "hpack", want: "vendor/hpack"}, + {cwd: "/go/src/vendor", arg: "./hpack", want: "vendor/hpack"}, + } { + conf := loader.Config{ + Cwd: test.cwd, + Build: ctxt, + } + conf.Import(test.arg) + + var got string + prog, err := conf.Load() + if prog != nil { + got = strings.Join(all(prog), " ") + } + if got != test.want { + t.Errorf("#%d: Load(%s) from %s: got %s, want %s", + i, test.arg, test.cwd, got, test.want) + if err != nil { + t.Errorf("Load failed: %v", err) + } + } + } +} + +func TestVendorCwdIssue16580(t *testing.T) { + // Regression test for Go issue 16580. + // Import decls in "created" packages were vendor-resolved + // w.r.t. cwd, not the parent directory of the package's files. + ctxt := fakeContext(map[string]string{ + "a": ``, // mkdir a + "a/vendor": ``, // mkdir a/vendor + "a/vendor/b": `package b; const X = true`, + "b": `package b; const X = false`, + }) + for _, test := range []struct { + filename, cwd string + want bool // expected value of b.X; depends on filename, not on cwd + }{ + {filename: "c.go", cwd: "/go/src", want: false}, + {filename: "c.go", cwd: "/go/src/a", want: false}, + {filename: "c.go", cwd: "/go/src/a/b", want: false}, + {filename: "c.go", cwd: "/go/src/a/vendor/b", want: false}, + + {filename: "/go/src/a/c.go", cwd: "/go/src", want: true}, + {filename: "/go/src/a/c.go", cwd: "/go/src/a", want: true}, + {filename: "/go/src/a/c.go", cwd: "/go/src/a/b", want: true}, + {filename: "/go/src/a/c.go", cwd: "/go/src/a/vendor/b", want: true}, + + {filename: "/go/src/c/c.go", cwd: "/go/src", want: false}, + {filename: "/go/src/c/c.go", cwd: "/go/src/a", want: false}, + {filename: "/go/src/c/c.go", cwd: "/go/src/a/b", want: false}, + {filename: "/go/src/c/c.go", cwd: "/go/src/a/vendor/b", want: false}, + } { + conf := loader.Config{ + Cwd: test.cwd, + Build: ctxt, + } + f, err := conf.ParseFile(test.filename, `package dummy; import "b"; const X = b.X`) + if err != nil { + t.Fatal(f) + } + conf.CreateFromFiles("dummy", f) + + prog, err := conf.Load() + if err != nil { + t.Errorf("%+v: Load failed: %v", test, err) + continue + } + + x := constant.BoolVal(prog.Created[0].Pkg.Scope().Lookup("X").(*types.Const).Val()) + if x != test.want { + t.Errorf("%+v: b.X = %t", test, x) + } + } + + // TODO(adonovan): also test imports within XTestGoFiles. +} + +// TODO(adonovan): more Load tests: +// +// failures: +// - to parse package decl of *_test.go files +// - to parse package decl of external *_test.go files +// - to parse whole of *_test.go files +// - to parse whole of external *_test.go files +// - to open a *.go file during import scanning +// - to import from binary + +// features: +// - InitialPackages +// - PackageCreated hook +// - TypeCheckFuncBodies hook + +func TestTransitivelyErrorFreeFlag(t *testing.T) { + // Create an minimal custom build.Context + // that fakes the following packages: + // + // a --> b --> c! c has an error + // \ d and e are transitively error-free. + // e --> d + // + // Each package [a-e] consists of one file, x.go. + pkgs := map[string]string{ + "a": `package a; import (_ "b"; _ "e")`, + "b": `package b; import _ "c"`, + "c": `package c; func f() { _ = int(false) }`, // type error within function body + "d": `package d;`, + "e": `package e; import _ "d"`, + } + conf := loader.Config{ + AllowErrors: true, + Build: fakeContext(pkgs), + } + conf.Import("a") + + prog, err := conf.Load() + if err != nil { + t.Errorf("Load failed: %s", err) + } + if prog == nil { + t.Fatalf("Load returned nil *Program") + } + + for pkg, info := range prog.AllPackages { + var wantErr, wantTEF bool + switch pkg.Path() { + case "a", "b": + case "c": + wantErr = true + case "d", "e": + wantTEF = true + default: + t.Errorf("unexpected package: %q", pkg.Path()) + continue + } + + if (info.Errors != nil) != wantErr { + if wantErr { + t.Errorf("Package %q.Error = nil, want error", pkg.Path()) + } else { + t.Errorf("Package %q has unexpected Errors: %v", + pkg.Path(), info.Errors) + } + } + + if info.TransitivelyErrorFree != wantTEF { + t.Errorf("Package %q.TransitivelyErrorFree=%t, want %t", + pkg.Path(), info.TransitivelyErrorFree, wantTEF) + } + } +} + +// Test that syntax (scan/parse), type, and loader errors are recorded +// (in PackageInfo.Errors) and reported (via Config.TypeChecker.Error). +func TestErrorReporting(t *testing.T) { + pkgs := map[string]string{ + "a": `package a; import (_ "b"; _ "c"); var x int = false`, + "b": `package b; 'syntax error!`, + } + conf := loader.Config{ + AllowErrors: true, + Build: fakeContext(pkgs), + } + var mu sync.Mutex + var allErrors []error + conf.TypeChecker.Error = func(err error) { + mu.Lock() + allErrors = append(allErrors, err) + mu.Unlock() + } + conf.Import("a") + + prog, err := conf.Load() + if err != nil { + t.Errorf("Load failed: %s", err) + } + if prog == nil { + t.Fatalf("Load returned nil *Program") + } + + // TODO(adonovan): test keys of ImportMap. + + // Check errors recorded in each PackageInfo. + for pkg, info := range prog.AllPackages { + switch pkg.Path() { + case "a": + if !hasError(info.Errors, "cannot convert false") { + t.Errorf("a.Errors = %v, want bool conversion (type) error", info.Errors) + } + if !hasError(info.Errors, "could not import c") { + t.Errorf("a.Errors = %v, want import (loader) error", info.Errors) + } + case "b": + if !hasError(info.Errors, "rune literal not terminated") { + t.Errorf("b.Errors = %v, want unterminated literal (syntax) error", info.Errors) + } + } + } + + // Check errors reported via error handler. + if !hasError(allErrors, "cannot convert false") || + !hasError(allErrors, "rune literal not terminated") || + !hasError(allErrors, "could not import c") { + t.Errorf("allErrors = %v, want syntax, type and loader errors", allErrors) + } +} + +func TestCycles(t *testing.T) { + for _, test := range []struct { + descr string + ctxt *build.Context + wantErr string + }{ + { + "self-cycle", + fakeContext(map[string]string{ + "main": `package main; import _ "selfcycle"`, + "selfcycle": `package selfcycle; import _ "selfcycle"`, + }), + `import cycle: selfcycle -> selfcycle`, + }, + { + "three-package cycle", + fakeContext(map[string]string{ + "main": `package main; import _ "a"`, + "a": `package a; import _ "b"`, + "b": `package b; import _ "c"`, + "c": `package c; import _ "a"`, + }), + `import cycle: c -> a -> b -> c`, + }, + { + "self-cycle in dependency of test file", + buildutil.FakeContext(map[string]map[string]string{ + "main": { + "main.go": `package main`, + "main_test.go": `package main; import _ "a"`, + }, + "a": { + "a.go": `package a; import _ "a"`, + }, + }), + `import cycle: a -> a`, + }, + // TODO(adonovan): fix: these fail + // { + // "two-package cycle in dependency of test file", + // buildutil.FakeContext(map[string]map[string]string{ + // "main": { + // "main.go": `package main`, + // "main_test.go": `package main; import _ "a"`, + // }, + // "a": { + // "a.go": `package a; import _ "main"`, + // }, + // }), + // `import cycle: main -> a -> main`, + // }, + // { + // "self-cycle in augmented package", + // buildutil.FakeContext(map[string]map[string]string{ + // "main": { + // "main.go": `package main`, + // "main_test.go": `package main; import _ "main"`, + // }, + // }), + // `import cycle: main -> main`, + // }, + } { + conf := loader.Config{ + AllowErrors: true, + Build: test.ctxt, + } + var mu sync.Mutex + var allErrors []error + conf.TypeChecker.Error = func(err error) { + mu.Lock() + allErrors = append(allErrors, err) + mu.Unlock() + } + conf.ImportWithTests("main") + + prog, err := conf.Load() + if err != nil { + t.Errorf("%s: Load failed: %s", test.descr, err) + } + if prog == nil { + t.Fatalf("%s: Load returned nil *Program", test.descr) + } + + if !hasError(allErrors, test.wantErr) { + t.Errorf("%s: Load() errors = %q, want %q", + test.descr, allErrors, test.wantErr) + } + } + + // TODO(adonovan): + // - Test that in a legal test cycle, none of the symbols + // defined by augmentation are visible via import. +} + +// ---- utilities ---- + +// Simplifying wrapper around buildutil.FakeContext for single-file packages. +func fakeContext(pkgs map[string]string) *build.Context { + pkgs2 := make(map[string]map[string]string) + for path, content := range pkgs { + pkgs2[path] = map[string]string{"x.go": content} + } + return buildutil.FakeContext(pkgs2) +} + +func hasError(errors []error, substr string) bool { + for _, err := range errors { + if strings.Contains(err.Error(), substr) { + return true + } + } + return false +} + +func keys(m map[string]bool) (keys []string) { + for key := range m { + keys = append(keys, key) + } + sort.Strings(keys) + return +} + +// Returns all loaded packages. +func all(prog *loader.Program) []string { + var pkgs []string + for _, info := range prog.AllPackages { + pkgs = append(pkgs, info.Pkg.Path()) + } + sort.Strings(pkgs) + return pkgs +} + +// Returns initially imported packages, as a string. +func imported(prog *loader.Program) string { + var pkgs []string + for _, info := range prog.Imported { + pkgs = append(pkgs, info.Pkg.Path()) + } + sort.Strings(pkgs) + return strings.Join(pkgs, " ") +} + +// Returns initially created packages, as a string. +func created(prog *loader.Program) string { + var pkgs []string + for _, info := range prog.Created { + pkgs = append(pkgs, info.Pkg.Path()) + } + return strings.Join(pkgs, " ") +} + +// Load package "io" twice in parallel. +// When run with -race, this is a regression test for Go issue 20718, in +// which the global "unsafe" package was modified concurrently. +func TestLoad1(t *testing.T) { loadIO(t) } +func TestLoad2(t *testing.T) { loadIO(t) } + +func loadIO(t *testing.T) { + t.Parallel() + conf := &loader.Config{ImportPkgs: map[string]bool{"io": false}} + if _, err := conf.Load(); err != nil { + t.Fatal(err) + } +} |