| // 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. | |
| package loader_test | |
| // This file enumerates all packages beneath $GOROOT, loads them, plus | |
| // their external tests if any, runs the type checker on them, and | |
| // prints some summary information. | |
| import ( | |
| "bytes" | |
| "fmt" | |
| "go/ast" | |
| "go/build" | |
| "go/token" | |
| "go/types" | |
| "os" | |
| "path/filepath" | |
| "runtime" | |
| "strings" | |
| "testing" | |
| "time" | |
| "golang.org/x/tools/go/buildutil" | |
| "golang.org/x/tools/go/loader" | |
| "golang.org/x/tools/internal/testenv" | |
| ) | |
| func TestStdlib(t *testing.T) { | |
| if runtime.GOOS == "android" { | |
| t.Skipf("incomplete std lib on %s", runtime.GOOS) | |
| } | |
| if testing.Short() { | |
| t.Skip("skipping in short mode; uses tons of memory (https://golang.org/issue/14113)") | |
| } | |
| testenv.NeedsTool(t, "go") | |
| runtime.GC() | |
| t0 := time.Now() | |
| var memstats runtime.MemStats | |
| runtime.ReadMemStats(&memstats) | |
| alloc := memstats.Alloc | |
| // Load, parse and type-check the program. | |
| ctxt := build.Default // copy | |
| ctxt.GOPATH = "" // disable GOPATH | |
| conf := loader.Config{Build: &ctxt} | |
| for _, path := range buildutil.AllPackages(conf.Build) { | |
| conf.ImportWithTests(path) | |
| } | |
| prog, err := conf.Load() | |
| if err != nil { | |
| t.Fatalf("Load failed: %v", err) | |
| } | |
| t1 := time.Now() | |
| runtime.GC() | |
| runtime.ReadMemStats(&memstats) | |
| numPkgs := len(prog.AllPackages) | |
| if want := 205; numPkgs < want { | |
| t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want) | |
| } | |
| // Dump package members. | |
| if false { | |
| for pkg := range prog.AllPackages { | |
| fmt.Printf("Package %s:\n", pkg.Path()) | |
| scope := pkg.Scope() | |
| qualifier := types.RelativeTo(pkg) | |
| for _, name := range scope.Names() { | |
| if ast.IsExported(name) { | |
| fmt.Printf("\t%s\n", types.ObjectString(scope.Lookup(name), qualifier)) | |
| } | |
| } | |
| fmt.Println() | |
| } | |
| } | |
| // Check that Test functions for regexp and compress/bzip2 are | |
| // simultaneously present. The apparent cycle formed when augmenting | |
| // these packages by their tests (together with io/ioutil's test, which is now | |
| // an xtest) was the original motivation or reporting golang.org/issue/7114. | |
| // | |
| // compress/bzip2.TestBitReader in bzip2_test.go imports io/ioutil | |
| // io/ioutil.TestTempFile in tempfile_test.go imports regexp (no longer exists) | |
| // regexp.TestRE2Search in exec_test.go imports compress/bzip2 | |
| for _, test := range []struct{ pkg, fn string }{ | |
| {"regexp", "TestRE2Search"}, | |
| {"compress/bzip2", "TestBitReader"}, | |
| } { | |
| info := prog.Imported[test.pkg] | |
| if info == nil { | |
| t.Errorf("failed to load package %q", test.pkg) | |
| continue | |
| } | |
| obj, _ := info.Pkg.Scope().Lookup(test.fn).(*types.Func) | |
| if obj == nil { | |
| t.Errorf("package %q has no func %q", test.pkg, test.fn) | |
| continue | |
| } | |
| } | |
| // Dump some statistics. | |
| // determine line count | |
| var lineCount int | |
| prog.Fset.Iterate(func(f *token.File) bool { | |
| lineCount += f.LineCount() | |
| return true | |
| }) | |
| t.Log("GOMAXPROCS: ", runtime.GOMAXPROCS(0)) | |
| t.Log("#Source lines: ", lineCount) | |
| t.Log("Load/parse/typecheck: ", t1.Sub(t0)) | |
| t.Log("#MB: ", int64(memstats.Alloc-alloc)/1000000) | |
| } | |
| func TestCgoOption(t *testing.T) { | |
| if testing.Short() { | |
| t.Skip("skipping in short mode; uses tons of memory (https://golang.org/issue/14113)") | |
| } | |
| switch runtime.GOOS { | |
| // On these systems, the net and os/user packages don't use cgo | |
| // or the std library is incomplete (Android). | |
| case "android", "plan9", "solaris", "windows": | |
| t.Skipf("no cgo or incomplete std lib on %s", runtime.GOOS) | |
| case "darwin": | |
| t.Skipf("golang/go#58493: file locations in this test are stale on darwin") | |
| } | |
| testenv.NeedsTool(t, "go") | |
| // In nocgo builds (e.g. linux-amd64-nocgo), | |
| // there is no "runtime/cgo" package, | |
| // so cgo-generated Go files will have a failing import. | |
| testenv.NeedsTool(t, "cgo") | |
| // Test that we can load cgo-using packages with | |
| // CGO_ENABLED=[01], which causes go/build to select pure | |
| // Go/native implementations, respectively, based on build | |
| // tags. | |
| // | |
| // Each entry specifies a package-level object and the generic | |
| // file expected to define it when cgo is disabled. | |
| // When cgo is enabled, the exact file is not specified (since | |
| // it varies by platform), but must differ from the generic one. | |
| // | |
| // The test also loads the actual file to verify that the | |
| // object is indeed defined at that location. | |
| for _, test := range []struct { | |
| pkg, name, genericFile string | |
| }{ | |
| {"net", "cgoLookupHost", "cgo_stub.go"}, | |
| {"os/user", "current", "lookup_stubs.go"}, | |
| } { | |
| ctxt := build.Default | |
| for _, ctxt.CgoEnabled = range []bool{false, true} { | |
| conf := loader.Config{Build: &ctxt} | |
| conf.Import(test.pkg) | |
| prog, err := conf.Load() | |
| if err != nil { | |
| t.Errorf("Load failed: %v", err) | |
| continue | |
| } | |
| info := prog.Imported[test.pkg] | |
| if info == nil { | |
| t.Errorf("package %s not found", test.pkg) | |
| continue | |
| } | |
| obj := info.Pkg.Scope().Lookup(test.name) | |
| if obj == nil { | |
| t.Errorf("no object %s.%s", test.pkg, test.name) | |
| continue | |
| } | |
| posn := prog.Fset.Position(obj.Pos()) | |
| t.Logf("%s: %s (CgoEnabled=%t)", posn, obj, ctxt.CgoEnabled) | |
| gotFile := filepath.Base(posn.Filename) | |
| filesMatch := gotFile == test.genericFile | |
| if ctxt.CgoEnabled && filesMatch { | |
| t.Errorf("CGO_ENABLED=1: %s found in %s, want native file", | |
| obj, gotFile) | |
| } else if !ctxt.CgoEnabled && !filesMatch { | |
| t.Errorf("CGO_ENABLED=0: %s found in %s, want %s", | |
| obj, gotFile, test.genericFile) | |
| } | |
| // Load the file and check the object is declared at the right place. | |
| b, err := os.ReadFile(posn.Filename) | |
| if err != nil { | |
| t.Errorf("can't read %s: %s", posn.Filename, err) | |
| continue | |
| } | |
| line := string(bytes.Split(b, []byte("\n"))[posn.Line-1]) | |
| ident := line[posn.Column-1:] | |
| if !strings.HasPrefix(ident, test.name) { | |
| t.Errorf("%s: %s not declared here (looking at %q)", posn, obj, ident) | |
| } | |
| } | |
| } | |
| } |