1
2
3
4
5 package gcimporter_test
6
7 import (
8 "bytes"
9 "fmt"
10 "internal/goexperiment"
11 "internal/testenv"
12 "os"
13 "os/exec"
14 "path/filepath"
15 "runtime"
16 "strings"
17 "testing"
18 "time"
19
20 "go/ast"
21 "go/importer"
22 "go/parser"
23 "go/token"
24 "go/types"
25
26 . "go/internal/gcimporter"
27 )
28
29
30
31
32 func skipSpecialPlatforms(t *testing.T) {
33 switch platform := runtime.GOOS + "-" + runtime.GOARCH; platform {
34 case "darwin-arm64":
35 t.Skipf("no compiled packages available for import on %s", platform)
36 }
37 }
38
39
40
41 func compile(t *testing.T, dirname, filename, outdirname string) string {
42
43 if !strings.HasSuffix(filename, ".go") {
44 t.Fatalf("filename doesn't end in .go: %s", filename)
45 }
46 basename := filepath.Base(filename)
47 outname := filepath.Join(outdirname, basename[:len(basename)-2]+"o")
48 cmd := exec.Command(testenv.GoToolPath(t), "tool", "compile", "-o", outname, filename)
49 cmd.Dir = dirname
50 out, err := cmd.CombinedOutput()
51 if err != nil {
52 t.Logf("%s", out)
53 t.Fatalf("go tool compile %s failed: %s", filename, err)
54 }
55 return outname
56 }
57
58 func testPath(t *testing.T, path, srcDir string) *types.Package {
59 t0 := time.Now()
60 fset := token.NewFileSet()
61 pkg, err := Import(fset, make(map[string]*types.Package), path, srcDir, nil)
62 if err != nil {
63 t.Errorf("testPath(%s): %s", path, err)
64 return nil
65 }
66 t.Logf("testPath(%s): %v", path, time.Since(t0))
67 return pkg
68 }
69
70 const maxTime = 30 * time.Second
71
72 var pkgExts = [...]string{".a", ".o"}
73
74 func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) {
75 dirname := filepath.Join(runtime.GOROOT(), "pkg", runtime.GOOS+"_"+runtime.GOARCH, dir)
76 list, err := os.ReadDir(dirname)
77 if err != nil {
78 t.Fatalf("testDir(%s): %s", dirname, err)
79 }
80 for _, f := range list {
81 if time.Now().After(endTime) {
82 t.Log("testing time used up")
83 return
84 }
85 switch {
86 case !f.IsDir():
87
88 for _, ext := range pkgExts {
89 if strings.HasSuffix(f.Name(), ext) {
90 name := f.Name()[0 : len(f.Name())-len(ext)]
91 if testPath(t, filepath.Join(dir, name), dir) != nil {
92 nimports++
93 }
94 }
95 }
96 case f.IsDir():
97 nimports += testDir(t, filepath.Join(dir, f.Name()), endTime)
98 }
99 }
100 return
101 }
102
103 func mktmpdir(t *testing.T) string {
104 tmpdir, err := os.MkdirTemp("", "gcimporter_test")
105 if err != nil {
106 t.Fatal("mktmpdir:", err)
107 }
108 if err := os.Mkdir(filepath.Join(tmpdir, "testdata"), 0700); err != nil {
109 os.RemoveAll(tmpdir)
110 t.Fatal("mktmpdir:", err)
111 }
112 return tmpdir
113 }
114
115 func TestImportTestdata(t *testing.T) {
116
117 if runtime.Compiler != "gc" {
118 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
119 }
120
121 testfiles := map[string][]string{
122 "exports.go": {"go/ast", "go/token"},
123 }
124 if !goexperiment.Unified {
125 testfiles["generics.go"] = nil
126 }
127
128 for testfile, wantImports := range testfiles {
129 tmpdir := mktmpdir(t)
130 defer os.RemoveAll(tmpdir)
131
132 compile(t, "testdata", testfile, filepath.Join(tmpdir, "testdata"))
133 path := "./testdata/" + strings.TrimSuffix(testfile, ".go")
134
135 if pkg := testPath(t, path, tmpdir); pkg != nil {
136
137
138
139 got := fmt.Sprint(pkg.Imports())
140 for _, want := range wantImports {
141 if !strings.Contains(got, want) {
142 t.Errorf(`Package("exports").Imports() = %s, does not contain %s`, got, want)
143 }
144 }
145 }
146 }
147 }
148
149 func TestImportTypeparamTests(t *testing.T) {
150
151 if goexperiment.Unified {
152 t.Skip("unified export data format is currently unsupported")
153 }
154
155
156 if runtime.Compiler != "gc" {
157 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
158 }
159
160 tmpdir := mktmpdir(t)
161 defer os.RemoveAll(tmpdir)
162
163
164
165 rootDir := filepath.Join(runtime.GOROOT(), "test", "typeparam")
166 list, err := os.ReadDir(rootDir)
167 if err != nil {
168 t.Fatal(err)
169 }
170
171 skip := map[string]string{
172 "equal.go": "inconsistent embedded sorting",
173 "nested.go": "fails to compile",
174 "issue50417.go": "inconsistent interface member sorting",
175 }
176
177 for _, entry := range list {
178 if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") {
179
180 continue
181 }
182
183 t.Run(entry.Name(), func(t *testing.T) {
184 if reason, ok := skip[entry.Name()]; ok {
185 t.Skip(reason)
186 }
187
188 filename := filepath.Join(rootDir, entry.Name())
189 src, err := os.ReadFile(filename)
190 if err != nil {
191 t.Fatal(err)
192 }
193 if !bytes.HasPrefix(src, []byte("// run")) && !bytes.HasPrefix(src, []byte("// compile")) {
194
195
196
197 t.Skipf("not detected as a run test")
198 }
199
200
201
202 compile(t, rootDir, entry.Name(), filepath.Join(tmpdir, "testdata"))
203 pkgName := strings.TrimSuffix(entry.Name(), ".go")
204 imported := importPkg(t, "./testdata/"+pkgName, tmpdir)
205 checked := checkFile(t, filename, src)
206
207 seen := make(map[string]bool)
208 for _, name := range imported.Scope().Names() {
209 if !token.IsExported(name) {
210 continue
211 }
212 seen[name] = true
213
214 importedObj := imported.Scope().Lookup(name)
215 got := types.ObjectString(importedObj, types.RelativeTo(imported))
216 got = sanitizeObjectString(got)
217
218 checkedObj := checked.Scope().Lookup(name)
219 if checkedObj == nil {
220 t.Fatalf("imported object %q was not type-checked", name)
221 }
222 want := types.ObjectString(checkedObj, types.RelativeTo(checked))
223 want = sanitizeObjectString(want)
224
225 if got != want {
226 t.Errorf("imported %q as %q, want %q", name, got, want)
227 }
228 }
229
230 for _, name := range checked.Scope().Names() {
231 if !token.IsExported(name) || seen[name] {
232 continue
233 }
234 t.Errorf("did not import object %q", name)
235 }
236 })
237 }
238 }
239
240
241
242
243 func sanitizeObjectString(s string) string {
244 var runes []rune
245 for _, r := range s {
246 if '₀' <= r && r < '₀'+10 {
247 continue
248 }
249 runes = append(runes, r)
250 }
251 return string(runes)
252 }
253
254 func checkFile(t *testing.T, filename string, src []byte) *types.Package {
255 fset := token.NewFileSet()
256 f, err := parser.ParseFile(fset, filename, src, 0)
257 if err != nil {
258 t.Fatal(err)
259 }
260 config := types.Config{
261 Importer: importer.Default(),
262 }
263 pkg, err := config.Check("", fset, []*ast.File{f}, nil)
264 if err != nil {
265 t.Fatal(err)
266 }
267 return pkg
268 }
269
270 func TestVersionHandling(t *testing.T) {
271 skipSpecialPlatforms(t)
272
273
274 if runtime.Compiler != "gc" {
275 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
276 }
277
278 const dir = "./testdata/versions"
279 list, err := os.ReadDir(dir)
280 if err != nil {
281 t.Fatal(err)
282 }
283
284 tmpdir := mktmpdir(t)
285 defer os.RemoveAll(tmpdir)
286 corruptdir := filepath.Join(tmpdir, "testdata", "versions")
287 if err := os.Mkdir(corruptdir, 0700); err != nil {
288 t.Fatal(err)
289 }
290
291 fset := token.NewFileSet()
292
293 for _, f := range list {
294 name := f.Name()
295 if !strings.HasSuffix(name, ".a") {
296 continue
297 }
298 if strings.Contains(name, "corrupted") {
299 continue
300 }
301 pkgpath := "./" + name[:len(name)-2]
302
303 if testing.Verbose() {
304 t.Logf("importing %s", name)
305 }
306
307
308 _, err := Import(fset, make(map[string]*types.Package), pkgpath, dir, nil)
309 if err != nil {
310
311 if strings.Contains(err.Error(), "no longer supported") {
312 switch name {
313 case "test_go1.7_0.a", "test_go1.7_1.a",
314 "test_go1.8_4.a", "test_go1.8_5.a",
315 "test_go1.11_6b.a", "test_go1.11_999b.a":
316 continue
317 }
318
319 }
320
321 if strings.Contains(err.Error(), "newer version") {
322 switch name {
323 case "test_go1.11_999i.a":
324 continue
325 }
326
327 }
328 t.Errorf("import %q failed: %v", pkgpath, err)
329 continue
330 }
331
332
333
334 data, err := os.ReadFile(filepath.Join(dir, name))
335 if err != nil {
336 t.Fatal(err)
337 }
338
339 i := bytes.Index(data, []byte("\n$$B\n")) + 5
340 j := bytes.Index(data[i:], []byte("\n$$\n")) + i
341 if i < 0 || j < 0 || i > j {
342 t.Fatalf("export data section not found (i = %d, j = %d)", i, j)
343 }
344
345 for k := j - 13; k >= i; k -= 7 {
346 data[k]++
347 }
348
349 pkgpath += "_corrupted"
350 filename := filepath.Join(corruptdir, pkgpath) + ".a"
351 os.WriteFile(filename, data, 0666)
352
353
354 _, err = Import(fset, make(map[string]*types.Package), pkgpath, corruptdir, nil)
355 if err == nil {
356 t.Errorf("import corrupted %q succeeded", pkgpath)
357 } else if msg := err.Error(); !strings.Contains(msg, "version skew") {
358 t.Errorf("import %q error incorrect (%s)", pkgpath, msg)
359 }
360 }
361 }
362
363 func TestImportStdLib(t *testing.T) {
364 skipSpecialPlatforms(t)
365
366
367 if runtime.Compiler != "gc" {
368 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
369 }
370
371 dt := maxTime
372 if testing.Short() && testenv.Builder() == "" {
373 dt = 10 * time.Millisecond
374 }
375 nimports := testDir(t, "", time.Now().Add(dt))
376 t.Logf("tested %d imports", nimports)
377 }
378
379 var importedObjectTests = []struct {
380 name string
381 want string
382 }{
383
384 {"crypto.Hash", "type Hash uint"},
385 {"go/ast.ObjKind", "type ObjKind int"},
386 {"go/types.Qualifier", "type Qualifier func(*Package) string"},
387 {"go/types.Comparable", "func Comparable(T Type) bool"},
388 {"math.Pi", "const Pi untyped float"},
389 {"math.Sin", "func Sin(x float64) float64"},
390 {"go/ast.NotNilFilter", "func NotNilFilter(_ string, v reflect.Value) bool"},
391 {"go/internal/gcimporter.FindPkg", "func FindPkg(path string, srcDir string) (filename string, id string)"},
392
393
394 {"context.Context", "type Context interface{Deadline() (deadline time.Time, ok bool); Done() <-chan struct{}; Err() error; Value(key any) any}"},
395 {"crypto.Decrypter", "type Decrypter interface{Decrypt(rand io.Reader, msg []byte, opts DecrypterOpts) (plaintext []byte, err error); Public() PublicKey}"},
396 {"encoding.BinaryMarshaler", "type BinaryMarshaler interface{MarshalBinary() (data []byte, err error)}"},
397 {"io.Reader", "type Reader interface{Read(p []byte) (n int, err error)}"},
398 {"io.ReadWriter", "type ReadWriter interface{Reader; Writer}"},
399 {"go/ast.Node", "type Node interface{End() go/token.Pos; Pos() go/token.Pos}"},
400 {"go/types.Type", "type Type interface{String() string; Underlying() Type}"},
401 }
402
403 func TestImportedTypes(t *testing.T) {
404 skipSpecialPlatforms(t)
405
406
407 if runtime.Compiler != "gc" {
408 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
409 }
410
411 fset := token.NewFileSet()
412 for _, test := range importedObjectTests {
413 s := strings.Split(test.name, ".")
414 if len(s) != 2 {
415 t.Fatal("inconsistent test data")
416 }
417 importPath := s[0]
418 objName := s[1]
419
420 pkg, err := Import(fset, make(map[string]*types.Package), importPath, ".", nil)
421 if err != nil {
422 t.Error(err)
423 continue
424 }
425
426 obj := pkg.Scope().Lookup(objName)
427 if obj == nil {
428 t.Errorf("%s: object not found", test.name)
429 continue
430 }
431
432 got := types.ObjectString(obj, types.RelativeTo(pkg))
433 if got != test.want {
434 t.Errorf("%s: got %q; want %q", test.name, got, test.want)
435 }
436
437 if named, _ := obj.Type().(*types.Named); named != nil {
438 verifyInterfaceMethodRecvs(t, named, 0)
439 }
440 }
441 }
442
443
444
445 func verifyInterfaceMethodRecvs(t *testing.T, named *types.Named, level int) {
446
447 if level > 10 {
448 t.Errorf("%s: embeds itself", named)
449 return
450 }
451
452 iface, _ := named.Underlying().(*types.Interface)
453 if iface == nil {
454 return
455 }
456
457
458 for i := 0; i < iface.NumExplicitMethods(); i++ {
459 m := iface.ExplicitMethod(i)
460 recv := m.Type().(*types.Signature).Recv()
461 if recv == nil {
462 t.Errorf("%s: missing receiver type", m)
463 continue
464 }
465 if recv.Type() != named {
466 t.Errorf("%s: got recv type %s; want %s", m, recv.Type(), named)
467 }
468 }
469
470
471 for i := 0; i < iface.NumEmbeddeds(); i++ {
472
473 if etype, _ := iface.EmbeddedType(i).(*types.Named); etype != nil {
474 verifyInterfaceMethodRecvs(t, etype, level+1)
475 }
476 }
477 }
478
479 func TestIssue5815(t *testing.T) {
480 skipSpecialPlatforms(t)
481
482
483 if runtime.Compiler != "gc" {
484 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
485 }
486
487 pkg := importPkg(t, "strings", ".")
488
489 scope := pkg.Scope()
490 for _, name := range scope.Names() {
491 obj := scope.Lookup(name)
492 if obj.Pkg() == nil {
493 t.Errorf("no pkg for %s", obj)
494 }
495 if tname, _ := obj.(*types.TypeName); tname != nil {
496 named := tname.Type().(*types.Named)
497 for i := 0; i < named.NumMethods(); i++ {
498 m := named.Method(i)
499 if m.Pkg() == nil {
500 t.Errorf("no pkg for %s", m)
501 }
502 }
503 }
504 }
505 }
506
507
508 func TestCorrectMethodPackage(t *testing.T) {
509 skipSpecialPlatforms(t)
510
511
512 if runtime.Compiler != "gc" {
513 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
514 }
515
516 imports := make(map[string]*types.Package)
517 fset := token.NewFileSet()
518 _, err := Import(fset, imports, "net/http", ".", nil)
519 if err != nil {
520 t.Fatal(err)
521 }
522
523 mutex := imports["sync"].Scope().Lookup("Mutex").(*types.TypeName).Type()
524 mset := types.NewMethodSet(types.NewPointer(mutex))
525 sel := mset.Lookup(nil, "Lock")
526 lock := sel.Obj().(*types.Func)
527 if got, want := lock.Pkg().Path(), "sync"; got != want {
528 t.Errorf("got package path %q; want %q", got, want)
529 }
530 }
531
532 func TestIssue13566(t *testing.T) {
533 skipSpecialPlatforms(t)
534
535
536 if runtime.Compiler != "gc" {
537 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
538 }
539
540
541
542 if runtime.GOOS == "windows" {
543 t.Skip("avoid dealing with relative paths/drive letters on windows")
544 }
545
546 tmpdir := mktmpdir(t)
547 defer os.RemoveAll(tmpdir)
548 testoutdir := filepath.Join(tmpdir, "testdata")
549
550
551
552
553 bpath, err := filepath.Abs(filepath.Join("testdata", "b.go"))
554 if err != nil {
555 t.Fatal(err)
556 }
557 compile(t, "testdata", "a.go", testoutdir)
558 compile(t, testoutdir, bpath, testoutdir)
559
560
561 pkg := importPkg(t, "./testdata/b", tmpdir)
562
563
564 for _, imp := range pkg.Imports() {
565 if imp.Name() == "" {
566 t.Errorf("no name for %s package", imp.Path())
567 }
568 }
569 }
570
571 func TestIssue13898(t *testing.T) {
572 skipSpecialPlatforms(t)
573
574
575 if runtime.Compiler != "gc" {
576 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
577 }
578
579
580 fset := token.NewFileSet()
581 imports := make(map[string]*types.Package)
582 _, err := Import(fset, imports, "go/internal/gcimporter", ".", nil)
583 if err != nil {
584 t.Fatal(err)
585 }
586
587
588 var goTypesPkg *types.Package
589 for path, pkg := range imports {
590 if path == "go/types" {
591 goTypesPkg = pkg
592 break
593 }
594 }
595 if goTypesPkg == nil {
596 t.Fatal("go/types not found")
597 }
598
599
600 obj := lookupObj(t, goTypesPkg.Scope(), "Object")
601 typ, ok := obj.Type().(*types.Named)
602 if !ok {
603 t.Fatalf("go/types.Object type is %v; wanted named type", typ)
604 }
605
606
607 m, index, indirect := types.LookupFieldOrMethod(typ, false, nil, "Pkg")
608 if m == nil {
609 t.Fatalf("go/types.Object.Pkg not found (index = %v, indirect = %v)", index, indirect)
610 }
611
612
613 if m.Pkg().Path() != "go/types" {
614 t.Fatalf("found %v; want go/types", m.Pkg())
615 }
616 }
617
618 func TestIssue15517(t *testing.T) {
619 skipSpecialPlatforms(t)
620
621
622 if runtime.Compiler != "gc" {
623 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
624 }
625
626
627
628 if runtime.GOOS == "windows" {
629 t.Skip("avoid dealing with relative paths/drive letters on windows")
630 }
631
632 tmpdir := mktmpdir(t)
633 defer os.RemoveAll(tmpdir)
634
635 compile(t, "testdata", "p.go", filepath.Join(tmpdir, "testdata"))
636
637
638
639
640
641
642
643
644
645
646
647
648
649 imports := make(map[string]*types.Package)
650 fset := token.NewFileSet()
651 for i := 0; i < 3; i++ {
652 if _, err := Import(fset, imports, "./././testdata/p", tmpdir, nil); err != nil {
653 t.Fatal(err)
654 }
655 }
656 }
657
658 func TestIssue15920(t *testing.T) {
659 skipSpecialPlatforms(t)
660
661
662 if runtime.Compiler != "gc" {
663 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
664 }
665
666
667
668 if runtime.GOOS == "windows" {
669 t.Skip("avoid dealing with relative paths/drive letters on windows")
670 }
671
672 compileAndImportPkg(t, "issue15920")
673 }
674
675 func TestIssue20046(t *testing.T) {
676 skipSpecialPlatforms(t)
677
678
679 if runtime.Compiler != "gc" {
680 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
681 }
682
683
684
685 if runtime.GOOS == "windows" {
686 t.Skip("avoid dealing with relative paths/drive letters on windows")
687 }
688
689
690 pkg := compileAndImportPkg(t, "issue20046")
691 obj := lookupObj(t, pkg.Scope(), "V")
692 if m, index, indirect := types.LookupFieldOrMethod(obj.Type(), false, nil, "M"); m == nil {
693 t.Fatalf("V.M not found (index = %v, indirect = %v)", index, indirect)
694 }
695 }
696 func TestIssue25301(t *testing.T) {
697 skipSpecialPlatforms(t)
698
699
700 if runtime.Compiler != "gc" {
701 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
702 }
703
704
705
706 if runtime.GOOS == "windows" {
707 t.Skip("avoid dealing with relative paths/drive letters on windows")
708 }
709
710 compileAndImportPkg(t, "issue25301")
711 }
712
713 func TestIssue25596(t *testing.T) {
714 skipSpecialPlatforms(t)
715
716
717 if runtime.Compiler != "gc" {
718 t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
719 }
720
721
722
723 if runtime.GOOS == "windows" {
724 t.Skip("avoid dealing with relative paths/drive letters on windows")
725 }
726
727 compileAndImportPkg(t, "issue25596")
728 }
729
730 func importPkg(t *testing.T, path, srcDir string) *types.Package {
731 fset := token.NewFileSet()
732 pkg, err := Import(fset, make(map[string]*types.Package), path, srcDir, nil)
733 if err != nil {
734 t.Fatal(err)
735 }
736 return pkg
737 }
738
739 func compileAndImportPkg(t *testing.T, name string) *types.Package {
740 tmpdir := mktmpdir(t)
741 defer os.RemoveAll(tmpdir)
742 compile(t, "testdata", name+".go", filepath.Join(tmpdir, "testdata"))
743 return importPkg(t, "./testdata/"+name, tmpdir)
744 }
745
746 func lookupObj(t *testing.T, scope *types.Scope, name string) types.Object {
747 if obj := scope.Lookup(name); obj != nil {
748 return obj
749 }
750 t.Fatalf("%s not found", name)
751 return nil
752 }
753
View as plain text