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