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 }