github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/internal/gcimporter/iexport_test.go (about) 1 // Copyright 2019 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 // This is a copy of bexport_test.go for iexport.go. 6 7 //go:build go1.11 8 // +build go1.11 9 10 package gcimporter_test 11 12 import ( 13 "bufio" 14 "bytes" 15 "fmt" 16 "go/ast" 17 "go/build" 18 "go/constant" 19 "go/parser" 20 "go/token" 21 "go/types" 22 "io/ioutil" 23 "math/big" 24 "os" 25 "reflect" 26 "runtime" 27 "sort" 28 "strings" 29 "testing" 30 31 "github.com/powerman/golang-tools/go/ast/inspector" 32 "github.com/powerman/golang-tools/go/buildutil" 33 "github.com/powerman/golang-tools/go/internal/gcimporter" 34 "github.com/powerman/golang-tools/go/loader" 35 "github.com/powerman/golang-tools/internal/typeparams/genericfeatures" 36 ) 37 38 func readExportFile(filename string) ([]byte, error) { 39 f, err := os.Open(filename) 40 if err != nil { 41 return nil, err 42 } 43 defer f.Close() 44 45 buf := bufio.NewReader(f) 46 if _, _, err := gcimporter.FindExportData(buf); err != nil { 47 return nil, err 48 } 49 50 if ch, err := buf.ReadByte(); err != nil { 51 return nil, err 52 } else if ch != 'i' { 53 return nil, fmt.Errorf("unexpected byte: %v", ch) 54 } 55 56 return ioutil.ReadAll(buf) 57 } 58 59 func iexport(fset *token.FileSet, version int, pkg *types.Package) ([]byte, error) { 60 var buf bytes.Buffer 61 if err := gcimporter.IExportCommon(&buf, fset, false, version, []*types.Package{pkg}); err != nil { 62 return nil, err 63 } 64 return buf.Bytes(), nil 65 } 66 67 // isUnifiedBuilder reports whether we are executing on a go builder that uses 68 // unified export data. 69 func isUnifiedBuilder() bool { 70 return os.Getenv("GO_BUILDER_NAME") == "linux-amd64-unified" 71 } 72 73 const minStdlibPackages = 248 74 75 func TestIExportData_stdlib(t *testing.T) { 76 if runtime.Compiler == "gccgo" { 77 t.Skip("gccgo standard library is inaccessible") 78 } 79 if runtime.GOOS == "android" { 80 t.Skipf("incomplete std lib on %s", runtime.GOOS) 81 } 82 if isRace { 83 t.Skipf("stdlib tests take too long in race mode and flake on builders") 84 } 85 if testing.Short() { 86 t.Skip("skipping RAM hungry test in -short mode") 87 } 88 89 // Load, parse and type-check the program. 90 ctxt := build.Default // copy 91 ctxt.GOPATH = "" // disable GOPATH 92 conf := loader.Config{ 93 Build: &ctxt, 94 AllowErrors: true, 95 TypeChecker: types.Config{ 96 Sizes: types.SizesFor(ctxt.Compiler, ctxt.GOARCH), 97 Error: func(err error) { t.Log(err) }, 98 }, 99 } 100 for _, path := range buildutil.AllPackages(conf.Build) { 101 conf.Import(path) 102 } 103 104 // Create a package containing type and value errors to ensure 105 // they are properly encoded/decoded. 106 f, err := conf.ParseFile("haserrors/haserrors.go", `package haserrors 107 const UnknownValue = "" + 0 108 type UnknownType undefined 109 `) 110 if err != nil { 111 t.Fatal(err) 112 } 113 conf.CreateFromFiles("haserrors", f) 114 115 prog, err := conf.Load() 116 if err != nil { 117 t.Fatalf("Load failed: %v", err) 118 } 119 120 var sorted []*types.Package 121 isUnified := isUnifiedBuilder() 122 for pkg, info := range prog.AllPackages { 123 // Temporarily skip packages that use generics on the unified builder, to 124 // fix TryBots. 125 // 126 // TODO(#48595): fix this test with GOEXPERIMENT=unified. 127 inspect := inspector.New(info.Files) 128 features := genericfeatures.ForPackage(inspect, &info.Info) 129 if isUnified && features != 0 { 130 t.Logf("skipping package %q which uses generics", pkg.Path()) 131 continue 132 } 133 if info.Files != nil { // non-empty directory 134 sorted = append(sorted, pkg) 135 } 136 } 137 sort.Slice(sorted, func(i, j int) bool { 138 return sorted[i].Path() < sorted[j].Path() 139 }) 140 141 version := gcimporter.IExportVersion 142 numPkgs := len(sorted) 143 if want := minStdlibPackages; numPkgs < want { 144 t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want) 145 } 146 147 for _, pkg := range sorted { 148 if exportdata, err := iexport(conf.Fset, version, pkg); err != nil { 149 t.Error(err) 150 } else { 151 testPkgData(t, conf.Fset, version, pkg, exportdata) 152 } 153 154 if pkg.Name() == "main" || pkg.Name() == "haserrors" { 155 // skip; no export data 156 } else if bp, err := ctxt.Import(pkg.Path(), "", build.FindOnly); err != nil { 157 t.Log("warning:", err) 158 } else if exportdata, err := readExportFile(bp.PkgObj); err != nil { 159 t.Log("warning:", err) 160 } else { 161 testPkgData(t, conf.Fset, version, pkg, exportdata) 162 } 163 } 164 165 var bundle bytes.Buffer 166 if err := gcimporter.IExportBundle(&bundle, conf.Fset, sorted); err != nil { 167 t.Fatal(err) 168 } 169 fset2 := token.NewFileSet() 170 imports := make(map[string]*types.Package) 171 pkgs2, err := gcimporter.IImportBundle(fset2, imports, bundle.Bytes()) 172 if err != nil { 173 t.Fatal(err) 174 } 175 176 for i, pkg := range sorted { 177 testPkg(t, conf.Fset, version, pkg, fset2, pkgs2[i]) 178 } 179 } 180 181 func testPkgData(t *testing.T, fset *token.FileSet, version int, pkg *types.Package, exportdata []byte) { 182 imports := make(map[string]*types.Package) 183 fset2 := token.NewFileSet() 184 _, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg.Path()) 185 if err != nil { 186 t.Errorf("IImportData(%s): %v", pkg.Path(), err) 187 } 188 189 testPkg(t, fset, version, pkg, fset2, pkg2) 190 } 191 192 func testPkg(t *testing.T, fset *token.FileSet, version int, pkg *types.Package, fset2 *token.FileSet, pkg2 *types.Package) { 193 if _, err := iexport(fset2, version, pkg2); err != nil { 194 t.Errorf("reexport %q: %v", pkg.Path(), err) 195 } 196 197 // Compare the packages' corresponding members. 198 for _, name := range pkg.Scope().Names() { 199 if !ast.IsExported(name) { 200 continue 201 } 202 obj1 := pkg.Scope().Lookup(name) 203 obj2 := pkg2.Scope().Lookup(name) 204 if obj2 == nil { 205 t.Errorf("%s.%s not found, want %s", pkg.Path(), name, obj1) 206 continue 207 } 208 209 fl1 := fileLine(fset, obj1) 210 fl2 := fileLine(fset2, obj2) 211 if fl1 != fl2 { 212 t.Errorf("%s.%s: got posn %s, want %s", 213 pkg.Path(), name, fl2, fl1) 214 } 215 216 if err := cmpObj(obj1, obj2); err != nil { 217 t.Errorf("%s.%s: %s\ngot: %s\nwant: %s", 218 pkg.Path(), name, err, obj2, obj1) 219 } 220 } 221 } 222 223 // TestVeryLongFile tests the position of an import object declared in 224 // a very long input file. Line numbers greater than maxlines are 225 // reported as line 1, not garbage or token.NoPos. 226 func TestIExportData_long(t *testing.T) { 227 // parse and typecheck 228 longFile := "package foo" + strings.Repeat("\n", 123456) + "var X int" 229 fset1 := token.NewFileSet() 230 f, err := parser.ParseFile(fset1, "foo.go", longFile, 0) 231 if err != nil { 232 t.Fatal(err) 233 } 234 var conf types.Config 235 pkg, err := conf.Check("foo", fset1, []*ast.File{f}, nil) 236 if err != nil { 237 t.Fatal(err) 238 } 239 240 // export 241 exportdata, err := iexport(fset1, gcimporter.IExportVersion, pkg) 242 if err != nil { 243 t.Fatal(err) 244 } 245 246 // import 247 imports := make(map[string]*types.Package) 248 fset2 := token.NewFileSet() 249 _, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg.Path()) 250 if err != nil { 251 t.Fatalf("IImportData(%s): %v", pkg.Path(), err) 252 } 253 254 // compare 255 posn1 := fset1.Position(pkg.Scope().Lookup("X").Pos()) 256 posn2 := fset2.Position(pkg2.Scope().Lookup("X").Pos()) 257 if want := "foo.go:1:1"; posn2.String() != want { 258 t.Errorf("X position = %s, want %s (orig was %s)", 259 posn2, want, posn1) 260 } 261 } 262 263 func TestIExportData_typealiases(t *testing.T) { 264 // parse and typecheck 265 fset1 := token.NewFileSet() 266 f, err := parser.ParseFile(fset1, "p.go", src, 0) 267 if err != nil { 268 t.Fatal(err) 269 } 270 var conf types.Config 271 pkg1, err := conf.Check("p", fset1, []*ast.File{f}, nil) 272 if err == nil { 273 // foo in undeclared in src; we should see an error 274 t.Fatal("invalid source type-checked without error") 275 } 276 if pkg1 == nil { 277 // despite incorrect src we should see a (partially) type-checked package 278 t.Fatal("nil package returned") 279 } 280 checkPkg(t, pkg1, "export") 281 282 // export 283 // use a nil fileset here to confirm that it doesn't panic 284 exportdata, err := iexport(nil, gcimporter.IExportVersion, pkg1) 285 if err != nil { 286 t.Fatal(err) 287 } 288 289 // import 290 imports := make(map[string]*types.Package) 291 fset2 := token.NewFileSet() 292 _, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg1.Path()) 293 if err != nil { 294 t.Fatalf("IImportData(%s): %v", pkg1.Path(), err) 295 } 296 checkPkg(t, pkg2, "import") 297 } 298 299 // cmpObj reports how x and y differ. They are assumed to belong to different 300 // universes so cannot be compared directly. It is an adapted version of 301 // equalObj in bexport_test.go. 302 func cmpObj(x, y types.Object) error { 303 if reflect.TypeOf(x) != reflect.TypeOf(y) { 304 return fmt.Errorf("%T vs %T", x, y) 305 } 306 xt := x.Type() 307 yt := y.Type() 308 switch x := x.(type) { 309 case *types.Var, *types.Func: 310 // ok 311 case *types.Const: 312 xval := x.Val() 313 yval := y.(*types.Const).Val() 314 equal := constant.Compare(xval, token.EQL, yval) 315 if !equal { 316 // try approx. comparison 317 xkind := xval.Kind() 318 ykind := yval.Kind() 319 if xkind == constant.Complex || ykind == constant.Complex { 320 equal = same(constant.Real(xval), constant.Real(yval)) && 321 same(constant.Imag(xval), constant.Imag(yval)) 322 } else if xkind == constant.Float || ykind == constant.Float { 323 equal = same(xval, yval) 324 } else if xkind == constant.Unknown && ykind == constant.Unknown { 325 equal = true 326 } 327 } 328 if !equal { 329 return fmt.Errorf("unequal constants %s vs %s", xval, yval) 330 } 331 case *types.TypeName: 332 if xalias, yalias := x.IsAlias(), y.(*types.TypeName).IsAlias(); xalias != yalias { 333 return fmt.Errorf("mismatching IsAlias(): %s vs %s", x, y) 334 } 335 // equalType does not recurse into the underlying types of named types, so 336 // we must pass the underlying type explicitly here. However, in doing this 337 // we may skip checking the features of the named types themselves, in 338 // situations where the type name is not referenced by the underlying or 339 // any other top-level declarations. Therefore, we must explicitly compare 340 // named types here, before passing their underlying types into equalType. 341 xn, _ := xt.(*types.Named) 342 yn, _ := yt.(*types.Named) 343 if (xn == nil) != (yn == nil) { 344 return fmt.Errorf("mismatching types: %T vs %T", xt, yt) 345 } 346 if xn != nil { 347 if err := cmpNamed(xn, yn); err != nil { 348 return err 349 } 350 } 351 xt = xt.Underlying() 352 yt = yt.Underlying() 353 default: 354 return fmt.Errorf("unexpected %T", x) 355 } 356 return equalType(xt, yt) 357 } 358 359 // Use the same floating-point precision (512) as cmd/compile 360 // (see Mpprec in cmd/compile/internal/gc/mpfloat.go). 361 const mpprec = 512 362 363 // same compares non-complex numeric values and reports if they are approximately equal. 364 func same(x, y constant.Value) bool { 365 xf := constantToFloat(x) 366 yf := constantToFloat(y) 367 d := new(big.Float).Sub(xf, yf) 368 d.Abs(d) 369 eps := big.NewFloat(1.0 / (1 << (mpprec - 1))) // allow for 1 bit of error 370 return d.Cmp(eps) < 0 371 } 372 373 // copy of the function with the same name in iexport.go. 374 func constantToFloat(x constant.Value) *big.Float { 375 var f big.Float 376 f.SetPrec(mpprec) 377 if v, exact := constant.Float64Val(x); exact { 378 // float64 379 f.SetFloat64(v) 380 } else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int { 381 // TODO(gri): add big.Rat accessor to constant.Value. 382 n := valueToRat(num) 383 d := valueToRat(denom) 384 f.SetRat(n.Quo(n, d)) 385 } else { 386 // Value too large to represent as a fraction => inaccessible. 387 // TODO(gri): add big.Float accessor to constant.Value. 388 _, ok := f.SetString(x.ExactString()) 389 if !ok { 390 panic("should not reach here") 391 } 392 } 393 return &f 394 } 395 396 // copy of the function with the same name in iexport.go. 397 func valueToRat(x constant.Value) *big.Rat { 398 // Convert little-endian to big-endian. 399 // I can't believe this is necessary. 400 bytes := constant.Bytes(x) 401 for i := 0; i < len(bytes)/2; i++ { 402 bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i] 403 } 404 return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes)) 405 }