github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/types/objectpath/objectpath_test.go (about)

     1  // Copyright 2018 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 objectpath_test
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"go/ast"
    11  	"go/importer"
    12  	"go/parser"
    13  	"go/token"
    14  	"go/types"
    15  	"strings"
    16  	"testing"
    17  
    18  	"golang.org/x/tools/go/buildutil"
    19  	"golang.org/x/tools/go/gcexportdata"
    20  	"golang.org/x/tools/go/loader"
    21  	"golang.org/x/tools/go/types/objectpath"
    22  )
    23  
    24  func TestPaths(t *testing.T) {
    25  	pkgs := map[string]map[string]string{
    26  		"b": {"b.go": `
    27  package b
    28  
    29  import "a"
    30  
    31  const C = a.Int(0)
    32  
    33  func F(a, b, c int, d a.T)
    34  
    35  type T struct{ A int; b int; a.T }
    36  
    37  func (T) M() *interface{ f() }
    38  
    39  type U T
    40  
    41  type A = struct{ x int }
    42  
    43  var V []*a.T
    44  
    45  type M map[struct{x int}]struct{y int}
    46  
    47  func unexportedFunc()
    48  type unexportedType struct{}
    49  
    50  type S struct{t struct{x int}}
    51  type R []struct{y int}
    52  type Q [2]struct{z int}
    53  `},
    54  		"a": {"a.go": `
    55  package a
    56  
    57  type Int int
    58  
    59  type T struct{x, y int}
    60  
    61  `},
    62  	}
    63  	paths := []pathTest{
    64  		// Good paths
    65  		{"b", "C", "const b.C a.Int", ""},
    66  		{"b", "F", "func b.F(a int, b int, c int, d a.T)", ""},
    67  		{"b", "F.PA0", "var a int", ""},
    68  		{"b", "F.PA1", "var b int", ""},
    69  		{"b", "F.PA2", "var c int", ""},
    70  		{"b", "F.PA3", "var d a.T", ""},
    71  		{"b", "T", "type b.T struct{A int; b int; a.T}", ""},
    72  		{"b", "T.O", "type b.T struct{A int; b int; a.T}", ""},
    73  		{"b", "T.UF0", "field A int", ""},
    74  		{"b", "T.UF1", "field b int", ""},
    75  		{"b", "T.UF2", "field T a.T", ""},
    76  		{"b", "U.UF2", "field T a.T", ""}, // U.U... are aliases for T.U...
    77  		{"b", "A", "type b.A = struct{x int}", ""},
    78  		{"b", "A.F0", "field x int", ""},
    79  		{"b", "V", "var b.V []*a.T", ""},
    80  		{"b", "M", "type b.M map[struct{x int}]struct{y int}", ""},
    81  		{"b", "M.UKF0", "field x int", ""},
    82  		{"b", "M.UEF0", "field y int", ""},
    83  		{"b", "T.M0", "func (b.T).M() *interface{f()}", ""}, // concrete method
    84  		{"b", "T.M0.RA0", "var  *interface{f()}", ""},       // parameter
    85  		{"b", "T.M0.RA0.EM0", "func (interface).f()", ""},   // interface method
    86  		{"b", "unexportedType", "type b.unexportedType struct{}", ""},
    87  		{"b", "S.UF0.F0", "field x int", ""},
    88  		{"b", "R.UEF0", "field y int", ""},
    89  		{"b", "Q.UEF0", "field z int", ""},
    90  		{"a", "T", "type a.T struct{x int; y int}", ""},
    91  		{"a", "T.UF0", "field x int", ""},
    92  
    93  		// Bad paths
    94  		{"b", "", "", "empty path"},
    95  		{"b", "missing", "", `package b does not contain "missing"`},
    96  		{"b", "F.U", "", "invalid path: ends with 'U', want [AFMO]"},
    97  		{"b", "F.PA3.O", "", "path denotes type a.T struct{x int; y int}, which belongs to a different package"},
    98  		{"b", "F.PA!", "", `invalid path: bad numeric operand "" for code 'A'`},
    99  		{"b", "F.PA3.UF0", "", "path denotes field x int, which belongs to a different package"},
   100  		{"b", "F.PA3.UF5", "", "field index 5 out of range [0-2)"},
   101  		{"b", "V.EE", "", "invalid path: ends with 'E', want [AFMO]"},
   102  		{"b", "F..O", "", "invalid path: unexpected '.' in type context"},
   103  		{"b", "T.OO", "", "invalid path: code 'O' in object context"},
   104  		{"b", "T.EO", "", "cannot apply 'E' to b.T (got *types.Named, want pointer, slice, array, chan or map)"},
   105  		{"b", "A.O", "", "cannot apply 'O' to struct{x int} (got *types.Struct, want named or type param)"},
   106  		{"b", "A.UF0", "", "cannot apply 'U' to struct{x int} (got *types.Struct, want named)"},
   107  		{"b", "M.UPO", "", "cannot apply 'P' to map[struct{x int}]struct{y int} (got *types.Map, want signature)"},
   108  		{"b", "C.O", "", "path denotes type a.Int int, which belongs to a different package"},
   109  		{"b", "T.M9", "", "method index 9 out of range [0-1)"},
   110  		{"b", "M.UF0", "", "cannot apply 'F' to map[struct{x int}]struct{y int} (got *types.Map, want struct)"},
   111  		{"b", "V.KO", "", "cannot apply 'K' to []*a.T (got *types.Slice, want map)"},
   112  		{"b", "V.A4", "", "cannot apply 'A' to []*a.T (got *types.Slice, want tuple)"},
   113  		{"b", "V.RA0", "", "cannot apply 'R' to []*a.T (got *types.Slice, want signature)"},
   114  		{"b", "F.PA4", "", "tuple index 4 out of range [0-4)"},
   115  		{"b", "F.XO", "", "invalid path: unknown code 'X'"},
   116  	}
   117  	conf := loader.Config{Build: buildutil.FakeContext(pkgs)}
   118  	conf.Import("a")
   119  	conf.Import("b")
   120  	prog, err := conf.Load()
   121  	if err != nil {
   122  		t.Fatal(err)
   123  	}
   124  
   125  	for _, test := range paths {
   126  		if err := testPath(prog, test); err != nil {
   127  			t.Error(err)
   128  		}
   129  	}
   130  
   131  	// bad objects
   132  	bInfo := prog.Imported["b"]
   133  	for _, test := range []struct {
   134  		obj     types.Object
   135  		wantErr string
   136  	}{
   137  		{types.Universe.Lookup("nil"), "predeclared nil has no path"},
   138  		{types.Universe.Lookup("len"), "predeclared builtin len has no path"},
   139  		{types.Universe.Lookup("int"), "predeclared type int has no path"},
   140  		{bInfo.Implicits[bInfo.Files[0].Imports[0]], "no path for package a"}, // import "a"
   141  		{bInfo.Pkg.Scope().Lookup("unexportedFunc"), "no path for non-exported func b.unexportedFunc()"},
   142  	} {
   143  		path, err := objectpath.For(test.obj)
   144  		if err == nil {
   145  			t.Errorf("Object(%s) = %q, want error", test.obj, path)
   146  			continue
   147  		}
   148  		if err.Error() != test.wantErr {
   149  			t.Errorf("Object(%s) error was %q, want %q", test.obj, err, test.wantErr)
   150  			continue
   151  		}
   152  	}
   153  }
   154  
   155  type pathTest struct {
   156  	pkg     string
   157  	path    objectpath.Path
   158  	wantobj string
   159  	wantErr string
   160  }
   161  
   162  func testPath(prog *loader.Program, test pathTest) error {
   163  	// We test objectpath by enumerating a set of paths
   164  	// and ensuring that Path(pkg, Object(pkg, path)) == path.
   165  	//
   166  	// It might seem more natural to invert the test:
   167  	// identify a set of objects and for each one,
   168  	// ensure that Object(pkg, Path(pkg, obj)) == obj.
   169  	// However, for most interesting test cases there is no
   170  	// easy way to identify the object short of applying
   171  	// a series of destructuring operations to pkg---which
   172  	// is essentially what objectpath.Object does.
   173  	// (We do a little of that when testing bad paths, below.)
   174  	//
   175  	// The downside is that the test depends on the path encoding.
   176  	// The upside is that the test exercises the encoding.
   177  
   178  	pkg := prog.Imported[test.pkg].Pkg
   179  	// check path -> object
   180  	obj, err := objectpath.Object(pkg, test.path)
   181  	if (test.wantErr != "") != (err != nil) {
   182  		return fmt.Errorf("Object(%s, %q) returned error %q, want %q", pkg.Path(), test.path, err, test.wantErr)
   183  	}
   184  	if test.wantErr != "" {
   185  		if got := err.Error(); got != test.wantErr {
   186  			return fmt.Errorf("Object(%s, %q) error was %q, want %q",
   187  				pkg.Path(), test.path, got, test.wantErr)
   188  		}
   189  		return nil
   190  	}
   191  	// Inv: err == nil
   192  
   193  	if objString := obj.String(); objString != test.wantobj {
   194  		return fmt.Errorf("Object(%s, %q) = %s, want %s", pkg.Path(), test.path, objString, test.wantobj)
   195  	}
   196  	if obj.Pkg() != pkg {
   197  		return fmt.Errorf("Object(%s, %q) = %v, which belongs to package %s",
   198  			pkg.Path(), test.path, obj, obj.Pkg().Path())
   199  	}
   200  
   201  	// check object -> path
   202  	path2, err := objectpath.For(obj)
   203  	if err != nil {
   204  		return fmt.Errorf("For(%v) failed: %v, want %q", obj, err, test.path)
   205  	}
   206  	// We do not require that test.path == path2. Aliases are legal.
   207  	// But we do require that Object(path2) finds the same object.
   208  	obj2, err := objectpath.Object(pkg, path2)
   209  	if err != nil {
   210  		return fmt.Errorf("Object(%s, %q) failed: %v (roundtrip from %q)", pkg.Path(), path2, err, test.path)
   211  	}
   212  	if obj2 != obj {
   213  		return fmt.Errorf("Object(%s, For(obj)) != obj: got %s, obj is %s (path1=%q, path2=%q)", pkg.Path(), obj2, obj, test.path, path2)
   214  	}
   215  	return nil
   216  }
   217  
   218  // TestSourceAndExportData uses objectpath to compute a correspondence
   219  // of objects between two versions of the same package, one loaded from
   220  // source, the other from export data.
   221  func TestSourceAndExportData(t *testing.T) {
   222  	const src = `
   223  package p
   224  
   225  type I int
   226  
   227  func (I) F() *struct{ X, Y int } {
   228  	return nil
   229  }
   230  
   231  type Foo interface {
   232  	Method() (string, func(int) struct{ X int })
   233  }
   234  
   235  var X chan struct{ Z int }
   236  var Z map[string]struct{ A int }
   237  `
   238  
   239  	// Parse source file and type-check it as a package, "src".
   240  	fset := token.NewFileSet()
   241  	f, err := parser.ParseFile(fset, "src.go", src, 0)
   242  	if err != nil {
   243  		t.Fatal(err)
   244  	}
   245  	conf := types.Config{Importer: importer.For("source", nil)}
   246  	info := &types.Info{
   247  		Defs: make(map[*ast.Ident]types.Object),
   248  	}
   249  	srcpkg, err := conf.Check("src/p", fset, []*ast.File{f}, info)
   250  	if err != nil {
   251  		t.Fatal(err)
   252  	}
   253  
   254  	// Export binary export data then reload it as a new package, "bin".
   255  	var buf bytes.Buffer
   256  	if err := gcexportdata.Write(&buf, fset, srcpkg); err != nil {
   257  		t.Fatal(err)
   258  	}
   259  
   260  	imports := make(map[string]*types.Package)
   261  	binpkg, err := gcexportdata.Read(&buf, fset, imports, "bin/p")
   262  	if err != nil {
   263  		t.Fatal(err)
   264  	}
   265  
   266  	// Now find the correspondences between them.
   267  	for _, srcobj := range info.Defs {
   268  		if srcobj == nil {
   269  			continue // e.g. package declaration
   270  		}
   271  		if _, ok := srcobj.(*types.PkgName); ok {
   272  			continue // PkgName has no objectpath
   273  		}
   274  
   275  		path, err := objectpath.For(srcobj)
   276  		if err != nil {
   277  			t.Errorf("For(%v): %v", srcobj, err)
   278  			continue
   279  		}
   280  		binobj, err := objectpath.Object(binpkg, path)
   281  		if err != nil {
   282  			t.Errorf("Object(%s, %q): %v", binpkg.Path(), path, err)
   283  			continue
   284  		}
   285  
   286  		// Check the object strings match.
   287  		// (We can't check that types are identical because the
   288  		// objects belong to different type-checker realms.)
   289  		srcstr := objectString(srcobj)
   290  		binstr := objectString(binobj)
   291  		if srcstr != binstr {
   292  			t.Errorf("ObjectStrings do not match: Object(For(%q)) = %s, want %s",
   293  				path, srcstr, binstr)
   294  			continue
   295  		}
   296  	}
   297  }
   298  
   299  func objectString(obj types.Object) string {
   300  	s := types.ObjectString(obj, (*types.Package).Name)
   301  
   302  	// The printing of interface methods changed in go1.11.
   303  	// This work-around makes the specific test pass with earlier versions.
   304  	s = strings.Replace(s, "func (interface).Method", "func (p.Foo).Method", -1)
   305  
   306  	return s
   307  }
   308  
   309  // TestOrdering uses objectpath over two Named types with the same method
   310  // names but in a different source order and checks that objectpath is the
   311  // same for methods with the same name.
   312  func TestOrdering(t *testing.T) {
   313  	pkgs := map[string]map[string]string{
   314  		"p": {"p.go": `
   315  package p
   316  
   317  type T struct{ A int }
   318  
   319  func (T) M() { }
   320  func (T) N() { }
   321  func (T) X() { }
   322  func (T) Y() { }
   323  `},
   324  		"q": {"q.go": `
   325  package q
   326  
   327  type T struct{ A int }
   328  
   329  func (T) N() { }
   330  func (T) M() { }
   331  func (T) Y() { }
   332  func (T) X() { }
   333  `}}
   334  	conf := loader.Config{Build: buildutil.FakeContext(pkgs)}
   335  	conf.Import("p")
   336  	conf.Import("q")
   337  	prog, err := conf.Load()
   338  	if err != nil {
   339  		t.Fatal(err)
   340  	}
   341  	p := prog.Imported["p"].Pkg
   342  	q := prog.Imported["q"].Pkg
   343  
   344  	// From here, the objectpaths generated for p and q should be the
   345  	// same. If they are not, then we are generating an ordering that is
   346  	// dependent on the declaration of the types within the file.
   347  	for _, test := range []struct {
   348  		path objectpath.Path
   349  	}{
   350  		{"T.M0"},
   351  		{"T.M1"},
   352  		{"T.M2"},
   353  		{"T.M3"},
   354  	} {
   355  		pobj, err := objectpath.Object(p, test.path)
   356  		if err != nil {
   357  			t.Errorf("Object(%s) failed in a1: %v", test.path, err)
   358  			continue
   359  		}
   360  		qobj, err := objectpath.Object(q, test.path)
   361  		if err != nil {
   362  			t.Errorf("Object(%s) failed in a2: %v", test.path, err)
   363  			continue
   364  		}
   365  		if pobj.Name() != pobj.Name() {
   366  			t.Errorf("Objects(%s) not equal, got a1 = %v, a2 = %v", test.path, pobj.Name(), qobj.Name())
   367  		}
   368  	}
   369  }