|  | // 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") | 
|  | } | 
|  | // In nocgo builds (e.g. linux-amd64-nocgo), | 
|  | // there is no "runtime/cgo" package, | 
|  | // so cgo-generated Go files will have a failing import. | 
|  | if !build.Default.CgoEnabled { | 
|  | return | 
|  | } | 
|  | testenv.NeedsTool(t, "go") | 
|  |  | 
|  | // 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) | 
|  | } | 
|  | } | 
|  | } | 
|  | } |