1// Copyright 2017 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 5package srcimporter 6 7import ( 8 "flag" 9 "go/build" 10 "go/token" 11 "go/types" 12 "internal/testenv" 13 "os" 14 "path" 15 "path/filepath" 16 "runtime" 17 "strings" 18 "testing" 19 "time" 20) 21 22func TestMain(m *testing.M) { 23 flag.Parse() 24 if goTool, err := testenv.GoTool(); err == nil { 25 os.Setenv("PATH", filepath.Dir(goTool)+string(os.PathListSeparator)+os.Getenv("PATH")) 26 } 27 os.Exit(m.Run()) 28} 29 30const maxTime = 2 * time.Second 31 32var importer = New(&build.Default, token.NewFileSet(), make(map[string]*types.Package)) 33 34func doImport(t *testing.T, path, srcDir string) { 35 t0 := time.Now() 36 if _, err := importer.ImportFrom(path, srcDir, 0); err != nil { 37 // don't report an error if there's no buildable Go files 38 if _, nogo := err.(*build.NoGoError); !nogo { 39 t.Errorf("import %q failed (%v)", path, err) 40 } 41 return 42 } 43 t.Logf("import %q: %v", path, time.Since(t0)) 44} 45 46// walkDir imports the all the packages with the given path 47// prefix recursively. It returns the number of packages 48// imported and whether importing was aborted because time 49// has passed endTime. 50func walkDir(t *testing.T, path string, endTime time.Time) (int, bool) { 51 if time.Now().After(endTime) { 52 t.Log("testing time used up") 53 return 0, true 54 } 55 56 // ignore fake packages and testdata directories 57 if path == "builtin" || path == "unsafe" || strings.HasSuffix(path, "testdata") { 58 return 0, false 59 } 60 61 list, err := os.ReadDir(filepath.Join(runtime.GOROOT(), "src", path)) 62 if err != nil { 63 t.Fatalf("walkDir %s failed (%v)", path, err) 64 } 65 66 nimports := 0 67 hasGoFiles := false 68 for _, f := range list { 69 if f.IsDir() { 70 n, abort := walkDir(t, filepath.Join(path, f.Name()), endTime) 71 nimports += n 72 if abort { 73 return nimports, true 74 } 75 } else if strings.HasSuffix(f.Name(), ".go") { 76 hasGoFiles = true 77 } 78 } 79 80 if hasGoFiles { 81 doImport(t, path, "") 82 nimports++ 83 } 84 85 return nimports, false 86} 87 88func TestImportStdLib(t *testing.T) { 89 if !testenv.HasSrc() { 90 t.Skip("no source code available") 91 } 92 93 if testing.Short() && testenv.Builder() == "" { 94 t.Skip("skipping in -short mode") 95 } 96 dt := maxTime 97 nimports, _ := walkDir(t, "", time.Now().Add(dt)) // installed packages 98 t.Logf("tested %d imports", nimports) 99} 100 101var importedObjectTests = []struct { 102 name string 103 want string 104}{ 105 {"flag.Bool", "func Bool(name string, value bool, usage string) *bool"}, 106 {"io.Reader", "type Reader interface{Read(p []byte) (n int, err error)}"}, 107 {"io.ReadWriter", "type ReadWriter interface{Reader; Writer}"}, // go/types.gcCompatibilityMode is off => interface not flattened 108 {"math.Pi", "const Pi untyped float"}, 109 {"math.Sin", "func Sin(x float64) float64"}, 110 {"math/big.Int", "type Int struct{neg bool; abs nat}"}, 111 {"golang.org/x/text/unicode/norm.MaxSegmentSize", "const MaxSegmentSize untyped int"}, 112} 113 114func TestImportedTypes(t *testing.T) { 115 if !testenv.HasSrc() { 116 t.Skip("no source code available") 117 } 118 119 for _, test := range importedObjectTests { 120 i := strings.LastIndex(test.name, ".") 121 if i < 0 { 122 t.Fatal("invalid test data format") 123 } 124 importPath := test.name[:i] 125 objName := test.name[i+1:] 126 127 pkg, err := importer.ImportFrom(importPath, ".", 0) 128 if err != nil { 129 t.Error(err) 130 continue 131 } 132 133 obj := pkg.Scope().Lookup(objName) 134 if obj == nil { 135 t.Errorf("%s: object not found", test.name) 136 continue 137 } 138 139 got := types.ObjectString(obj, types.RelativeTo(pkg)) 140 if got != test.want { 141 t.Errorf("%s: got %q; want %q", test.name, got, test.want) 142 } 143 144 if named, _ := obj.Type().(*types.Named); named != nil { 145 verifyInterfaceMethodRecvs(t, named, 0) 146 } 147 } 148} 149 150// verifyInterfaceMethodRecvs verifies that method receiver types 151// are named if the methods belong to a named interface type. 152func verifyInterfaceMethodRecvs(t *testing.T, named *types.Named, level int) { 153 // avoid endless recursion in case of an embedding bug that lead to a cycle 154 if level > 10 { 155 t.Errorf("%s: embeds itself", named) 156 return 157 } 158 159 iface, _ := named.Underlying().(*types.Interface) 160 if iface == nil { 161 return // not an interface 162 } 163 164 // check explicitly declared methods 165 for i := 0; i < iface.NumExplicitMethods(); i++ { 166 m := iface.ExplicitMethod(i) 167 recv := m.Type().(*types.Signature).Recv() 168 if recv == nil { 169 t.Errorf("%s: missing receiver type", m) 170 continue 171 } 172 if recv.Type() != named { 173 t.Errorf("%s: got recv type %s; want %s", m, recv.Type(), named) 174 } 175 } 176 177 // check embedded interfaces (they are named, too) 178 for i := 0; i < iface.NumEmbeddeds(); i++ { 179 // embedding of interfaces cannot have cycles; recursion will terminate 180 verifyInterfaceMethodRecvs(t, iface.Embedded(i), level+1) 181 } 182} 183 184func TestReimport(t *testing.T) { 185 if !testenv.HasSrc() { 186 t.Skip("no source code available") 187 } 188 189 // Reimporting a partially imported (incomplete) package is not supported (see issue #19337). 190 // Make sure we recognize the situation and report an error. 191 192 mathPkg := types.NewPackage("math", "math") // incomplete package 193 importer := New(&build.Default, token.NewFileSet(), map[string]*types.Package{mathPkg.Path(): mathPkg}) 194 _, err := importer.ImportFrom("math", ".", 0) 195 if err == nil || !strings.HasPrefix(err.Error(), "reimport") { 196 t.Errorf("got %v; want reimport error", err) 197 } 198} 199 200func TestIssue20855(t *testing.T) { 201 if !testenv.HasSrc() { 202 t.Skip("no source code available") 203 } 204 205 pkg, err := importer.ImportFrom("go/internal/srcimporter/testdata/issue20855", ".", 0) 206 if err == nil || !strings.Contains(err.Error(), "missing function body") { 207 t.Fatalf("got unexpected or no error: %v", err) 208 } 209 if pkg == nil { 210 t.Error("got no package despite no hard errors") 211 } 212} 213 214func testImportPath(t *testing.T, pkgPath string) { 215 if !testenv.HasSrc() { 216 t.Skip("no source code available") 217 } 218 219 pkgName := path.Base(pkgPath) 220 221 pkg, err := importer.Import(pkgPath) 222 if err != nil { 223 t.Fatal(err) 224 } 225 226 if pkg.Name() != pkgName { 227 t.Errorf("got %q; want %q", pkg.Name(), pkgName) 228 } 229 230 if pkg.Path() != pkgPath { 231 t.Errorf("got %q; want %q", pkg.Path(), pkgPath) 232 } 233} 234 235// TestIssue23092 tests relative imports. 236func TestIssue23092(t *testing.T) { 237 testImportPath(t, "./testdata/issue23092") 238} 239 240// TestIssue24392 tests imports against a path containing 'testdata'. 241func TestIssue24392(t *testing.T) { 242 testImportPath(t, "go/internal/srcimporter/testdata/issue24392") 243} 244 245func TestCgo(t *testing.T) { 246 testenv.MustHaveGoBuild(t) 247 testenv.MustHaveCGO(t) 248 249 importer := New(&build.Default, token.NewFileSet(), make(map[string]*types.Package)) 250 _, err := importer.ImportFrom("./misc/cgo/test", runtime.GOROOT(), 0) 251 if err != nil { 252 t.Fatalf("Import failed: %v", err) 253 } 254} 255