1// Copyright 2016 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 gcimporter_test 6 7import ( 8 "fmt" 9 "go/ast" 10 "go/build" 11 "go/constant" 12 "go/parser" 13 "go/token" 14 "go/types" 15 "reflect" 16 "runtime" 17 "strings" 18 "testing" 19 20 "golang.org/x/tools/go/buildutil" 21 "golang.org/x/tools/go/internal/gcimporter" 22 "golang.org/x/tools/go/loader" 23) 24 25var isRace = false 26 27func TestBExportData_stdlib(t *testing.T) { 28 if runtime.Compiler == "gccgo" { 29 t.Skip("gccgo standard library is inaccessible") 30 } 31 if runtime.GOOS == "android" { 32 t.Skipf("incomplete std lib on %s", runtime.GOOS) 33 } 34 if isRace { 35 t.Skipf("stdlib tests take too long in race mode and flake on builders") 36 } 37 38 // Load, parse and type-check the program. 39 ctxt := build.Default // copy 40 ctxt.GOPATH = "" // disable GOPATH 41 conf := loader.Config{ 42 Build: &ctxt, 43 AllowErrors: true, 44 } 45 for _, path := range buildutil.AllPackages(conf.Build) { 46 conf.Import(path) 47 } 48 49 // Create a package containing type and value errors to ensure 50 // they are properly encoded/decoded. 51 f, err := conf.ParseFile("haserrors/haserrors.go", `package haserrors 52const UnknownValue = "" + 0 53type UnknownType undefined 54`) 55 if err != nil { 56 t.Fatal(err) 57 } 58 conf.CreateFromFiles("haserrors", f) 59 60 prog, err := conf.Load() 61 if err != nil { 62 t.Fatalf("Load failed: %v", err) 63 } 64 65 numPkgs := len(prog.AllPackages) 66 if want := 248; numPkgs < want { 67 t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want) 68 } 69 70 for pkg, info := range prog.AllPackages { 71 if info.Files == nil { 72 continue // empty directory 73 } 74 exportdata, err := gcimporter.BExportData(conf.Fset, pkg) 75 if err != nil { 76 t.Fatal(err) 77 } 78 79 imports := make(map[string]*types.Package) 80 fset2 := token.NewFileSet() 81 n, pkg2, err := gcimporter.BImportData(fset2, imports, exportdata, pkg.Path()) 82 if err != nil { 83 t.Errorf("BImportData(%s): %v", pkg.Path(), err) 84 continue 85 } 86 if n != len(exportdata) { 87 t.Errorf("BImportData(%s) decoded %d bytes, want %d", 88 pkg.Path(), n, len(exportdata)) 89 } 90 91 // Compare the packages' corresponding members. 92 for _, name := range pkg.Scope().Names() { 93 if !ast.IsExported(name) { 94 continue 95 } 96 obj1 := pkg.Scope().Lookup(name) 97 obj2 := pkg2.Scope().Lookup(name) 98 if obj2 == nil { 99 t.Errorf("%s.%s not found, want %s", pkg.Path(), name, obj1) 100 continue 101 } 102 103 fl1 := fileLine(conf.Fset, obj1) 104 fl2 := fileLine(fset2, obj2) 105 if fl1 != fl2 { 106 t.Errorf("%s.%s: got posn %s, want %s", 107 pkg.Path(), name, fl2, fl1) 108 } 109 110 if err := equalObj(obj1, obj2); err != nil { 111 t.Errorf("%s.%s: %s\ngot: %s\nwant: %s", 112 pkg.Path(), name, err, obj2, obj1) 113 } 114 } 115 } 116} 117 118func fileLine(fset *token.FileSet, obj types.Object) string { 119 posn := fset.Position(obj.Pos()) 120 return fmt.Sprintf("%s:%d", posn.Filename, posn.Line) 121} 122 123// equalObj reports how x and y differ. They are assumed to belong to 124// different universes so cannot be compared directly. 125func equalObj(x, y types.Object) error { 126 if reflect.TypeOf(x) != reflect.TypeOf(y) { 127 return fmt.Errorf("%T vs %T", x, y) 128 } 129 xt := x.Type() 130 yt := y.Type() 131 switch x.(type) { 132 case *types.Var, *types.Func: 133 // ok 134 case *types.Const: 135 xval := x.(*types.Const).Val() 136 yval := y.(*types.Const).Val() 137 // Use string comparison for floating-point values since rounding is permitted. 138 if constant.Compare(xval, token.NEQ, yval) && 139 !(xval.Kind() == constant.Float && xval.String() == yval.String()) { 140 return fmt.Errorf("unequal constants %s vs %s", xval, yval) 141 } 142 case *types.TypeName: 143 xt = xt.Underlying() 144 yt = yt.Underlying() 145 default: 146 return fmt.Errorf("unexpected %T", x) 147 } 148 return equalType(xt, yt) 149} 150 151func equalType(x, y types.Type) error { 152 if reflect.TypeOf(x) != reflect.TypeOf(y) { 153 return fmt.Errorf("unequal kinds: %T vs %T", x, y) 154 } 155 switch x := x.(type) { 156 case *types.Interface: 157 y := y.(*types.Interface) 158 // TODO(gri): enable separate emission of Embedded interfaces 159 // and ExplicitMethods then use this logic. 160 // if x.NumEmbeddeds() != y.NumEmbeddeds() { 161 // return fmt.Errorf("unequal number of embedded interfaces: %d vs %d", 162 // x.NumEmbeddeds(), y.NumEmbeddeds()) 163 // } 164 // for i := 0; i < x.NumEmbeddeds(); i++ { 165 // xi := x.Embedded(i) 166 // yi := y.Embedded(i) 167 // if xi.String() != yi.String() { 168 // return fmt.Errorf("mismatched %th embedded interface: %s vs %s", 169 // i, xi, yi) 170 // } 171 // } 172 // if x.NumExplicitMethods() != y.NumExplicitMethods() { 173 // return fmt.Errorf("unequal methods: %d vs %d", 174 // x.NumExplicitMethods(), y.NumExplicitMethods()) 175 // } 176 // for i := 0; i < x.NumExplicitMethods(); i++ { 177 // xm := x.ExplicitMethod(i) 178 // ym := y.ExplicitMethod(i) 179 // if xm.Name() != ym.Name() { 180 // return fmt.Errorf("mismatched %th method: %s vs %s", i, xm, ym) 181 // } 182 // if err := equalType(xm.Type(), ym.Type()); err != nil { 183 // return fmt.Errorf("mismatched %s method: %s", xm.Name(), err) 184 // } 185 // } 186 if x.NumMethods() != y.NumMethods() { 187 return fmt.Errorf("unequal methods: %d vs %d", 188 x.NumMethods(), y.NumMethods()) 189 } 190 for i := 0; i < x.NumMethods(); i++ { 191 xm := x.Method(i) 192 ym := y.Method(i) 193 if xm.Name() != ym.Name() { 194 return fmt.Errorf("mismatched %dth method: %s vs %s", i, xm, ym) 195 } 196 if err := equalType(xm.Type(), ym.Type()); err != nil { 197 return fmt.Errorf("mismatched %s method: %s", xm.Name(), err) 198 } 199 } 200 case *types.Array: 201 y := y.(*types.Array) 202 if x.Len() != y.Len() { 203 return fmt.Errorf("unequal array lengths: %d vs %d", x.Len(), y.Len()) 204 } 205 if err := equalType(x.Elem(), y.Elem()); err != nil { 206 return fmt.Errorf("array elements: %s", err) 207 } 208 case *types.Basic: 209 y := y.(*types.Basic) 210 if x.Kind() != y.Kind() { 211 return fmt.Errorf("unequal basic types: %s vs %s", x, y) 212 } 213 case *types.Chan: 214 y := y.(*types.Chan) 215 if x.Dir() != y.Dir() { 216 return fmt.Errorf("unequal channel directions: %d vs %d", x.Dir(), y.Dir()) 217 } 218 if err := equalType(x.Elem(), y.Elem()); err != nil { 219 return fmt.Errorf("channel elements: %s", err) 220 } 221 case *types.Map: 222 y := y.(*types.Map) 223 if err := equalType(x.Key(), y.Key()); err != nil { 224 return fmt.Errorf("map keys: %s", err) 225 } 226 if err := equalType(x.Elem(), y.Elem()); err != nil { 227 return fmt.Errorf("map values: %s", err) 228 } 229 case *types.Named: 230 y := y.(*types.Named) 231 if x.String() != y.String() { 232 return fmt.Errorf("unequal named types: %s vs %s", x, y) 233 } 234 case *types.Pointer: 235 y := y.(*types.Pointer) 236 if err := equalType(x.Elem(), y.Elem()); err != nil { 237 return fmt.Errorf("pointer elements: %s", err) 238 } 239 case *types.Signature: 240 y := y.(*types.Signature) 241 if err := equalType(x.Params(), y.Params()); err != nil { 242 return fmt.Errorf("parameters: %s", err) 243 } 244 if err := equalType(x.Results(), y.Results()); err != nil { 245 return fmt.Errorf("results: %s", err) 246 } 247 if x.Variadic() != y.Variadic() { 248 return fmt.Errorf("unequal variadicity: %t vs %t", 249 x.Variadic(), y.Variadic()) 250 } 251 if (x.Recv() != nil) != (y.Recv() != nil) { 252 return fmt.Errorf("unequal receivers: %s vs %s", x.Recv(), y.Recv()) 253 } 254 if x.Recv() != nil { 255 // TODO(adonovan): fix: this assertion fires for interface methods. 256 // The type of the receiver of an interface method is a named type 257 // if the Package was loaded from export data, or an unnamed (interface) 258 // type if the Package was produced by type-checking ASTs. 259 // if err := equalType(x.Recv().Type(), y.Recv().Type()); err != nil { 260 // return fmt.Errorf("receiver: %s", err) 261 // } 262 } 263 case *types.Slice: 264 y := y.(*types.Slice) 265 if err := equalType(x.Elem(), y.Elem()); err != nil { 266 return fmt.Errorf("slice elements: %s", err) 267 } 268 case *types.Struct: 269 y := y.(*types.Struct) 270 if x.NumFields() != y.NumFields() { 271 return fmt.Errorf("unequal struct fields: %d vs %d", 272 x.NumFields(), y.NumFields()) 273 } 274 for i := 0; i < x.NumFields(); i++ { 275 xf := x.Field(i) 276 yf := y.Field(i) 277 if xf.Name() != yf.Name() { 278 return fmt.Errorf("mismatched fields: %s vs %s", xf, yf) 279 } 280 if err := equalType(xf.Type(), yf.Type()); err != nil { 281 return fmt.Errorf("struct field %s: %s", xf.Name(), err) 282 } 283 if x.Tag(i) != y.Tag(i) { 284 return fmt.Errorf("struct field %s has unequal tags: %q vs %q", 285 xf.Name(), x.Tag(i), y.Tag(i)) 286 } 287 } 288 case *types.Tuple: 289 y := y.(*types.Tuple) 290 if x.Len() != y.Len() { 291 return fmt.Errorf("unequal tuple lengths: %d vs %d", x.Len(), y.Len()) 292 } 293 for i := 0; i < x.Len(); i++ { 294 if err := equalType(x.At(i).Type(), y.At(i).Type()); err != nil { 295 return fmt.Errorf("tuple element %d: %s", i, err) 296 } 297 } 298 } 299 return nil 300} 301 302// TestVeryLongFile tests the position of an import object declared in 303// a very long input file. Line numbers greater than maxlines are 304// reported as line 1, not garbage or token.NoPos. 305func TestVeryLongFile(t *testing.T) { 306 // parse and typecheck 307 longFile := "package foo" + strings.Repeat("\n", 123456) + "var X int" 308 fset1 := token.NewFileSet() 309 f, err := parser.ParseFile(fset1, "foo.go", longFile, 0) 310 if err != nil { 311 t.Fatal(err) 312 } 313 var conf types.Config 314 pkg, err := conf.Check("foo", fset1, []*ast.File{f}, nil) 315 if err != nil { 316 t.Fatal(err) 317 } 318 319 // export 320 exportdata, err := gcimporter.BExportData(fset1, pkg) 321 if err != nil { 322 t.Fatal(err) 323 } 324 325 // import 326 imports := make(map[string]*types.Package) 327 fset2 := token.NewFileSet() 328 _, pkg2, err := gcimporter.BImportData(fset2, imports, exportdata, pkg.Path()) 329 if err != nil { 330 t.Fatalf("BImportData(%s): %v", pkg.Path(), err) 331 } 332 333 // compare 334 posn1 := fset1.Position(pkg.Scope().Lookup("X").Pos()) 335 posn2 := fset2.Position(pkg2.Scope().Lookup("X").Pos()) 336 if want := "foo.go:1:1"; posn2.String() != want { 337 t.Errorf("X position = %s, want %s (orig was %s)", 338 posn2, want, posn1) 339 } 340} 341 342const src = ` 343package p 344 345type ( 346 T0 = int32 347 T1 = struct{} 348 T2 = struct{ T1 } 349 Invalid = foo // foo is undeclared 350) 351` 352 353func checkPkg(t *testing.T, pkg *types.Package, label string) { 354 T1 := types.NewStruct(nil, nil) 355 T2 := types.NewStruct([]*types.Var{types.NewField(0, pkg, "T1", T1, true)}, nil) 356 357 for _, test := range []struct { 358 name string 359 typ types.Type 360 }{ 361 {"T0", types.Typ[types.Int32]}, 362 {"T1", T1}, 363 {"T2", T2}, 364 {"Invalid", types.Typ[types.Invalid]}, 365 } { 366 obj := pkg.Scope().Lookup(test.name) 367 if obj == nil { 368 t.Errorf("%s: %s not found", label, test.name) 369 continue 370 } 371 tname, _ := obj.(*types.TypeName) 372 if tname == nil { 373 t.Errorf("%s: %v not a type name", label, obj) 374 continue 375 } 376 if !tname.IsAlias() { 377 t.Errorf("%s: %v: not marked as alias", label, tname) 378 continue 379 } 380 if got := tname.Type(); !types.Identical(got, test.typ) { 381 t.Errorf("%s: %v: got %v; want %v", label, tname, got, test.typ) 382 } 383 } 384} 385 386func TestTypeAliases(t *testing.T) { 387 // parse and typecheck 388 fset1 := token.NewFileSet() 389 f, err := parser.ParseFile(fset1, "p.go", src, 0) 390 if err != nil { 391 t.Fatal(err) 392 } 393 var conf types.Config 394 pkg1, err := conf.Check("p", fset1, []*ast.File{f}, nil) 395 if err == nil { 396 // foo in undeclared in src; we should see an error 397 t.Fatal("invalid source type-checked without error") 398 } 399 if pkg1 == nil { 400 // despite incorrect src we should see a (partially) type-checked package 401 t.Fatal("nil package returned") 402 } 403 checkPkg(t, pkg1, "export") 404 405 // export 406 exportdata, err := gcimporter.BExportData(fset1, pkg1) 407 if err != nil { 408 t.Fatal(err) 409 } 410 411 // import 412 imports := make(map[string]*types.Package) 413 fset2 := token.NewFileSet() 414 _, pkg2, err := gcimporter.BImportData(fset2, imports, exportdata, pkg1.Path()) 415 if err != nil { 416 t.Fatalf("BImportData(%s): %v", pkg1.Path(), err) 417 } 418 checkPkg(t, pkg2, "import") 419} 420