| 1 | // Copyright 2013 The Go Authors. All rights reserved. |
|---|---|
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | package loader_test |
| 6 | |
| 7 | // This file enumerates all packages beneath $GOROOT, loads them, plus |
| 8 | // their external tests if any, runs the type checker on them, and |
| 9 | // prints some summary information. |
| 10 | |
| 11 | import ( |
| 12 | "bytes" |
| 13 | "fmt" |
| 14 | "go/ast" |
| 15 | "go/build" |
| 16 | "go/token" |
| 17 | "go/types" |
| 18 | "io/ioutil" |
| 19 | "path/filepath" |
| 20 | "runtime" |
| 21 | "strings" |
| 22 | "testing" |
| 23 | "time" |
| 24 | |
| 25 | "golang.org/x/tools/go/buildutil" |
| 26 | "golang.org/x/tools/go/loader" |
| 27 | "golang.org/x/tools/internal/testenv" |
| 28 | ) |
| 29 | |
| 30 | func TestStdlib(t *testing.T) { |
| 31 | if runtime.GOOS == "android" { |
| 32 | t.Skipf("incomplete std lib on %s", runtime.GOOS) |
| 33 | } |
| 34 | if testing.Short() { |
| 35 | t.Skip("skipping in short mode; uses tons of memory (https://golang.org/issue/14113)") |
| 36 | } |
| 37 | testenv.NeedsTool(t, "go") |
| 38 | |
| 39 | runtime.GC() |
| 40 | t0 := time.Now() |
| 41 | var memstats runtime.MemStats |
| 42 | runtime.ReadMemStats(&memstats) |
| 43 | alloc := memstats.Alloc |
| 44 | |
| 45 | // Load, parse and type-check the program. |
| 46 | ctxt := build.Default // copy |
| 47 | ctxt.GOPATH = "" // disable GOPATH |
| 48 | conf := loader.Config{Build: &ctxt} |
| 49 | for _, path := range buildutil.AllPackages(conf.Build) { |
| 50 | conf.ImportWithTests(path) |
| 51 | } |
| 52 | |
| 53 | prog, err := conf.Load() |
| 54 | if err != nil { |
| 55 | t.Fatalf("Load failed: %v", err) |
| 56 | } |
| 57 | |
| 58 | t1 := time.Now() |
| 59 | runtime.GC() |
| 60 | runtime.ReadMemStats(&memstats) |
| 61 | |
| 62 | numPkgs := len(prog.AllPackages) |
| 63 | if want := 205; numPkgs < want { |
| 64 | t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want) |
| 65 | } |
| 66 | |
| 67 | // Dump package members. |
| 68 | if false { |
| 69 | for pkg := range prog.AllPackages { |
| 70 | fmt.Printf("Package %s:\n", pkg.Path()) |
| 71 | scope := pkg.Scope() |
| 72 | qualifier := types.RelativeTo(pkg) |
| 73 | for _, name := range scope.Names() { |
| 74 | if ast.IsExported(name) { |
| 75 | fmt.Printf("\t%s\n", types.ObjectString(scope.Lookup(name), qualifier)) |
| 76 | } |
| 77 | } |
| 78 | fmt.Println() |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | // Check that Test functions for regexp and compress/bzip2 are |
| 83 | // simultaneously present. The apparent cycle formed when augmenting |
| 84 | // these packages by their tests (together with io/ioutil's test, which is now |
| 85 | // an xtest) was the original motivation or reporting golang.org/issue/7114. |
| 86 | // |
| 87 | // compress/bzip2.TestBitReader in bzip2_test.go imports io/ioutil |
| 88 | // io/ioutil.TestTempFile in tempfile_test.go imports regexp (no longer exists) |
| 89 | // regexp.TestRE2Search in exec_test.go imports compress/bzip2 |
| 90 | for _, test := range []struct{ pkg, fn string }{ |
| 91 | {"regexp", "TestRE2Search"}, |
| 92 | {"compress/bzip2", "TestBitReader"}, |
| 93 | } { |
| 94 | info := prog.Imported[test.pkg] |
| 95 | if info == nil { |
| 96 | t.Errorf("failed to load package %q", test.pkg) |
| 97 | continue |
| 98 | } |
| 99 | obj, _ := info.Pkg.Scope().Lookup(test.fn).(*types.Func) |
| 100 | if obj == nil { |
| 101 | t.Errorf("package %q has no func %q", test.pkg, test.fn) |
| 102 | continue |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | // Dump some statistics. |
| 107 | |
| 108 | // determine line count |
| 109 | var lineCount int |
| 110 | prog.Fset.Iterate(func(f *token.File) bool { |
| 111 | lineCount += f.LineCount() |
| 112 | return true |
| 113 | }) |
| 114 | |
| 115 | t.Log("GOMAXPROCS: ", runtime.GOMAXPROCS(0)) |
| 116 | t.Log("#Source lines: ", lineCount) |
| 117 | t.Log("Load/parse/typecheck: ", t1.Sub(t0)) |
| 118 | t.Log("#MB: ", int64(memstats.Alloc-alloc)/1000000) |
| 119 | } |
| 120 | |
| 121 | func TestCgoOption(t *testing.T) { |
| 122 | if testing.Short() { |
| 123 | t.Skip("skipping in short mode; uses tons of memory (https://golang.org/issue/14113)") |
| 124 | } |
| 125 | switch runtime.GOOS { |
| 126 | // On these systems, the net and os/user packages don't use cgo |
| 127 | // or the std library is incomplete (Android). |
| 128 | case "android", "plan9", "solaris", "windows": |
| 129 | t.Skipf("no cgo or incomplete std lib on %s", runtime.GOOS) |
| 130 | } |
| 131 | // In nocgo builds (e.g. linux-amd64-nocgo), |
| 132 | // there is no "runtime/cgo" package, |
| 133 | // so cgo-generated Go files will have a failing import. |
| 134 | if !build.Default.CgoEnabled { |
| 135 | return |
| 136 | } |
| 137 | testenv.NeedsTool(t, "go") |
| 138 | |
| 139 | // Test that we can load cgo-using packages with |
| 140 | // CGO_ENABLED=[01], which causes go/build to select pure |
| 141 | // Go/native implementations, respectively, based on build |
| 142 | // tags. |
| 143 | // |
| 144 | // Each entry specifies a package-level object and the generic |
| 145 | // file expected to define it when cgo is disabled. |
| 146 | // When cgo is enabled, the exact file is not specified (since |
| 147 | // it varies by platform), but must differ from the generic one. |
| 148 | // |
| 149 | // The test also loads the actual file to verify that the |
| 150 | // object is indeed defined at that location. |
| 151 | for _, test := range []struct { |
| 152 | pkg, name, genericFile string |
| 153 | }{ |
| 154 | {"net", "cgoLookupHost", "cgo_stub.go"}, |
| 155 | {"os/user", "current", "lookup_stubs.go"}, |
| 156 | } { |
| 157 | ctxt := build.Default |
| 158 | for _, ctxt.CgoEnabled = range []bool{false, true} { |
| 159 | conf := loader.Config{Build: &ctxt} |
| 160 | conf.Import(test.pkg) |
| 161 | prog, err := conf.Load() |
| 162 | if err != nil { |
| 163 | t.Errorf("Load failed: %v", err) |
| 164 | continue |
| 165 | } |
| 166 | info := prog.Imported[test.pkg] |
| 167 | if info == nil { |
| 168 | t.Errorf("package %s not found", test.pkg) |
| 169 | continue |
| 170 | } |
| 171 | obj := info.Pkg.Scope().Lookup(test.name) |
| 172 | if obj == nil { |
| 173 | t.Errorf("no object %s.%s", test.pkg, test.name) |
| 174 | continue |
| 175 | } |
| 176 | posn := prog.Fset.Position(obj.Pos()) |
| 177 | t.Logf("%s: %s (CgoEnabled=%t)", posn, obj, ctxt.CgoEnabled) |
| 178 | |
| 179 | gotFile := filepath.Base(posn.Filename) |
| 180 | filesMatch := gotFile == test.genericFile |
| 181 | |
| 182 | if ctxt.CgoEnabled && filesMatch { |
| 183 | t.Errorf("CGO_ENABLED=1: %s found in %s, want native file", |
| 184 | obj, gotFile) |
| 185 | } else if !ctxt.CgoEnabled && !filesMatch { |
| 186 | t.Errorf("CGO_ENABLED=0: %s found in %s, want %s", |
| 187 | obj, gotFile, test.genericFile) |
| 188 | } |
| 189 | |
| 190 | // Load the file and check the object is declared at the right place. |
| 191 | b, err := ioutil.ReadFile(posn.Filename) |
| 192 | if err != nil { |
| 193 | t.Errorf("can't read %s: %s", posn.Filename, err) |
| 194 | continue |
| 195 | } |
| 196 | line := string(bytes.Split(b, []byte("\n"))[posn.Line-1]) |
| 197 | ident := line[posn.Column-1:] |
| 198 | if !strings.HasPrefix(ident, test.name) { |
| 199 | t.Errorf("%s: %s not declared here (looking at %q)", posn, obj, ident) |
| 200 | } |
| 201 | } |
| 202 | } |
| 203 | } |
| 204 |
Members