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