GoPLS Viewer

Home|gopls/internal/gcimporter/iexport_test.go
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/ast/inspector"
32    "golang.org/x/tools/go/buildutil"
33    "golang.org/x/tools/go/gcexportdata"
34    "golang.org/x/tools/go/loader"
35    "golang.org/x/tools/internal/gcimporter"
36    "golang.org/x/tools/internal/typeparams/genericfeatures"
37)
38
39func readExportFile(filename string) ([]byteerror) {
40    ferr := os.Open(filename)
41    if err != nil {
42        return nilerr
43    }
44    defer f.Close()
45
46    buf := bufio.NewReader(f)
47    if __err := gcimporter.FindExportData(buf); err != nil {
48        return nilerr
49    }
50
51    if cherr := buf.ReadByte(); err != nil {
52        return nilerr
53    } else if ch != 'i' {
54        return nilfmt.Errorf("unexpected byte: %v"ch)
55    }
56
57    return ioutil.ReadAll(buf)
58}
59
60func iexport(fset *token.FileSetversion intpkg *types.Package) ([]byteerror) {
61    var buf bytes.Buffer
62    const bundleshallow = falsefalse
63    if err := gcimporter.IExportCommon(&buffsetbundleshallowversion, []*types.Package{pkg}); err != nil {
64        return nilerr
65    }
66    return buf.Bytes(), nil
67}
68
69// isUnifiedBuilder reports whether we are executing on a go builder that uses
70// unified export data.
71func isUnifiedBuilder() bool {
72    return os.Getenv("GO_BUILDER_NAME") == "linux-amd64-unified"
73}
74
75const minStdlibPackages = 248
76
77func TestIExportData_stdlib(t *testing.T) {
78    if runtime.Compiler == "gccgo" {
79        t.Skip("gccgo standard library is inaccessible")
80    }
81    if runtime.GOOS == "android" {
82        t.Skipf("incomplete std lib on %s"runtime.GOOS)
83    }
84    if isRace {
85        t.Skipf("stdlib tests take too long in race mode and flake on builders")
86    }
87    if testing.Short() {
88        t.Skip("skipping RAM hungry test in -short mode")
89    }
90
91    // Load, parse and type-check the program.
92    ctxt := build.Default // copy
93    ctxt.GOPATH = ""      // disable GOPATH
94    conf := loader.Config{
95        Build:       &ctxt,
96        AllowErrorstrue,
97        TypeCheckertypes.Config{
98            Sizestypes.SizesFor(ctxt.Compilerctxt.GOARCH),
99            Error: func(err error) { t.Log(err) },
100        },
101    }
102    for _path := range buildutil.AllPackages(conf.Build) {
103        conf.Import(path)
104    }
105
106    // Create a package containing type and value errors to ensure
107    // they are properly encoded/decoded.
108    ferr := conf.ParseFile("haserrors/haserrors.go"`package haserrors
109const UnknownValue = "" + 0
110type UnknownType undefined
111`)
112    if err != nil {
113        t.Fatal(err)
114    }
115    conf.CreateFromFiles("haserrors"f)
116
117    progerr := conf.Load()
118    if err != nil {
119        t.Fatalf("Load failed: %v"err)
120    }
121
122    var sorted []*types.Package
123    isUnified := isUnifiedBuilder()
124    for pkginfo := range prog.AllPackages {
125        // Temporarily skip packages that use generics on the unified builder, to
126        // fix TryBots.
127        //
128        // TODO(#48595): fix this test with GOEXPERIMENT=unified.
129        inspect := inspector.New(info.Files)
130        features := genericfeatures.ForPackage(inspect, &info.Info)
131        if isUnified && features != 0 {
132            t.Logf("skipping package %q which uses generics"pkg.Path())
133            continue
134        }
135        if info.Files != nil { // non-empty directory
136            sorted = append(sortedpkg)
137        }
138    }
139    sort.Slice(sorted, func(ij intbool {
140        return sorted[i].Path() < sorted[j].Path()
141    })
142
143    version := gcimporter.IExportVersion
144    numPkgs := len(sorted)
145    if want := minStdlibPackagesnumPkgs < want {
146        t.Errorf("Loaded only %d packages, want at least %d"numPkgswant)
147    }
148
149    for _pkg := range sorted {
150        if exportdataerr := iexport(conf.Fsetversionpkg); err != nil {
151            t.Error(err)
152        } else {
153            testPkgData(tconf.Fsetversionpkgexportdata)
154        }
155
156        if pkg.Name() == "main" || pkg.Name() == "haserrors" {
157            // skip; no export data
158        } else if bperr := ctxt.Import(pkg.Path(), ""build.FindOnly); err != nil {
159            t.Log("warning:"err)
160        } else if exportdataerr := readExportFile(bp.PkgObj); err != nil {
161            t.Log("warning:"err)
162        } else {
163            testPkgData(tconf.Fsetversionpkgexportdata)
164        }
165    }
166
167    var bundle bytes.Buffer
168    if err := gcimporter.IExportBundle(&bundleconf.Fsetsorted); err != nil {
169        t.Fatal(err)
170    }
171    fset2 := token.NewFileSet()
172    imports := make(map[string]*types.Package)
173    pkgs2err := gcimporter.IImportBundle(fset2importsbundle.Bytes())
174    if err != nil {
175        t.Fatal(err)
176    }
177
178    for ipkg := range sorted {
179        testPkg(tconf.Fsetversionpkgfset2pkgs2[i])
180    }
181}
182
183func testPkgData(t *testing.Tfset *token.FileSetversion intpkg *types.Packageexportdata []byte) {
184    imports := make(map[string]*types.Package)
185    fset2 := token.NewFileSet()
186    _pkg2err := gcimporter.IImportData(fset2importsexportdatapkg.Path())
187    if err != nil {
188        t.Errorf("IImportData(%s): %v"pkg.Path(), err)
189    }
190
191    testPkg(tfsetversionpkgfset2pkg2)
192}
193
194func testPkg(t *testing.Tfset *token.FileSetversion intpkg *types.Packagefset2 *token.FileSetpkg2 *types.Package) {
195    if _err := iexport(fset2versionpkg2); err != nil {
196        t.Errorf("reexport %q: %v"pkg.Path(), err)
197    }
198
199    // Compare the packages' corresponding members.
200    for _name := range pkg.Scope().Names() {
201        if !token.IsExported(name) {
202            continue
203        }
204        obj1 := pkg.Scope().Lookup(name)
205        obj2 := pkg2.Scope().Lookup(name)
206        if obj2 == nil {
207            t.Errorf("%s.%s not found, want %s"pkg.Path(), nameobj1)
208            continue
209        }
210
211        fl1 := fileLine(fsetobj1)
212        fl2 := fileLine(fset2obj2)
213        if fl1 != fl2 {
214            t.Errorf("%s.%s: got posn %s, want %s",
215                pkg.Path(), namefl2fl1)
216        }
217
218        if err := cmpObj(obj1obj2); err != nil {
219            t.Errorf("%s.%s: %s\ngot:  %s\nwant: %s",
220                pkg.Path(), nameerrobj2obj1)
221        }
222    }
223}
224
225// TestVeryLongFile tests the position of an import object declared in
226// a very long input file.  Line numbers greater than maxlines are
227// reported as line 1, not garbage or token.NoPos.
228func TestIExportData_long(t *testing.T) {
229    // parse and typecheck
230    longFile := "package foo" + strings.Repeat("\n"123456) + "var X int"
231    fset1 := token.NewFileSet()
232    ferr := parser.ParseFile(fset1"foo.go"longFile0)
233    if err != nil {
234        t.Fatal(err)
235    }
236    var conf types.Config
237    pkgerr := conf.Check("foo"fset1, []*ast.File{f}, nil)
238    if err != nil {
239        t.Fatal(err)
240    }
241
242    // export
243    exportdataerr := iexport(fset1gcimporter.IExportVersionpkg)
244    if err != nil {
245        t.Fatal(err)
246    }
247
248    // import
249    imports := make(map[string]*types.Package)
250    fset2 := token.NewFileSet()
251    _pkg2err := gcimporter.IImportData(fset2importsexportdatapkg.Path())
252    if err != nil {
253        t.Fatalf("IImportData(%s): %v"pkg.Path(), err)
254    }
255
256    // compare
257    posn1 := fset1.Position(pkg.Scope().Lookup("X").Pos())
258    posn2 := fset2.Position(pkg2.Scope().Lookup("X").Pos())
259    if want := "foo.go:1:1"posn2.String() != want {
260        t.Errorf("X position = %s, want %s (orig was %s)",
261            posn2wantposn1)
262    }
263}
264
265func TestIExportData_typealiases(t *testing.T) {
266    // parse and typecheck
267    fset1 := token.NewFileSet()
268    ferr := parser.ParseFile(fset1"p.go"src0)
269    if err != nil {
270        t.Fatal(err)
271    }
272    var conf types.Config
273    pkg1err := conf.Check("p"fset1, []*ast.File{f}, nil)
274    if err == nil {
275        // foo in undeclared in src; we should see an error
276        t.Fatal("invalid source type-checked without error")
277    }
278    if pkg1 == nil {
279        // despite incorrect src we should see a (partially) type-checked package
280        t.Fatal("nil package returned")
281    }
282    checkPkg(tpkg1"export")
283
284    // export
285    // use a nil fileset here to confirm that it doesn't panic
286    exportdataerr := iexport(nilgcimporter.IExportVersionpkg1)
287    if err != nil {
288        t.Fatal(err)
289    }
290
291    // import
292    imports := make(map[string]*types.Package)
293    fset2 := token.NewFileSet()
294    _pkg2err := gcimporter.IImportData(fset2importsexportdatapkg1.Path())
295    if err != nil {
296        t.Fatalf("IImportData(%s): %v"pkg1.Path(), err)
297    }
298    checkPkg(tpkg2"import")
299}
300
301// cmpObj reports how x and y differ. They are assumed to belong to different
302// universes so cannot be compared directly. It is an adapted version of
303// equalObj in bexport_test.go.
304func cmpObj(xy types.Objecterror {
305    if reflect.TypeOf(x) != reflect.TypeOf(y) {
306        return fmt.Errorf("%T vs %T"xy)
307    }
308    xt := x.Type()
309    yt := y.Type()
310    switch x := x.(type) {
311    case *types.Var, *types.Func:
312        // ok
313    case *types.Const:
314        xval := x.Val()
315        yval := y.(*types.Const).Val()
316        equal := constant.Compare(xvaltoken.EQLyval)
317        if !equal {
318            // try approx. comparison
319            xkind := xval.Kind()
320            ykind := yval.Kind()
321            if xkind == constant.Complex || ykind == constant.Complex {
322                equal = same(constant.Real(xval), constant.Real(yval)) &&
323                    same(constant.Imag(xval), constant.Imag(yval))
324            } else if xkind == constant.Float || ykind == constant.Float {
325                equal = same(xvalyval)
326            } else if xkind == constant.Unknown && ykind == constant.Unknown {
327                equal = true
328            }
329        }
330        if !equal {
331            return fmt.Errorf("unequal constants %s vs %s"xvalyval)
332        }
333    case *types.TypeName:
334        if xaliasyalias := x.IsAlias(), y.(*types.TypeName).IsAlias(); xalias != yalias {
335            return fmt.Errorf("mismatching IsAlias(): %s vs %s"xy)
336        }
337        // equalType does not recurse into the underlying types of named types, so
338        // we must pass the underlying type explicitly here. However, in doing this
339        // we may skip checking the features of the named types themselves, in
340        // situations where the type name is not referenced by the underlying or
341        // any other top-level declarations. Therefore, we must explicitly compare
342        // named types here, before passing their underlying types into equalType.
343        xn_ := xt.(*types.Named)
344        yn_ := yt.(*types.Named)
345        if (xn == nil) != (yn == nil) {
346            return fmt.Errorf("mismatching types: %T vs %T"xtyt)
347        }
348        if xn != nil {
349            if err := cmpNamed(xnyn); err != nil {
350                return err
351            }
352        }
353        xt = xt.Underlying()
354        yt = yt.Underlying()
355    default:
356        return fmt.Errorf("unexpected %T"x)
357    }
358    return equalType(xtyt)
359}
360
361// Use the same floating-point precision (512) as cmd/compile
362// (see Mpprec in cmd/compile/internal/gc/mpfloat.go).
363const mpprec = 512
364
365// same compares non-complex numeric values and reports if they are approximately equal.
366func same(xy constant.Valuebool {
367    xf := constantToFloat(x)
368    yf := constantToFloat(y)
369    d := new(big.Float).Sub(xfyf)
370    d.Abs(d)
371    eps := big.NewFloat(1.0 / (1 << (mpprec - 1))) // allow for 1 bit of error
372    return d.Cmp(eps) < 0
373}
374
375// copy of the function with the same name in iexport.go.
376func constantToFloat(x constant.Value) *big.Float {
377    var f big.Float
378    f.SetPrec(mpprec)
379    if vexact := constant.Float64Val(x); exact {
380        // float64
381        f.SetFloat64(v)
382    } else if numdenom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int {
383        // TODO(gri): add big.Rat accessor to constant.Value.
384        n := valueToRat(num)
385        d := valueToRat(denom)
386        f.SetRat(n.Quo(nd))
387    } else {
388        // Value too large to represent as a fraction => inaccessible.
389        // TODO(gri): add big.Float accessor to constant.Value.
390        _ok := f.SetString(x.ExactString())
391        if !ok {
392            panic("should not reach here")
393        }
394    }
395    return &f
396}
397
398// copy of the function with the same name in iexport.go.
399func valueToRat(x constant.Value) *big.Rat {
400    // Convert little-endian to big-endian.
401    // I can't believe this is necessary.
402    bytes := constant.Bytes(x)
403    for i := 0i < len(bytes)/2i++ {
404        bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i]
405    }
406    return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes))
407}
408
409// This is a regression test for a bug in iexport of types.Struct:
410// unexported fields were losing their implicit package qualifier.
411func TestUnexportedStructFields(t *testing.T) {
412    fset := token.NewFileSet()
413    export := make(map[string][]byte)
414
415    // process parses and type-checks a single-file
416    // package and saves its export data.
417    process := func(pathcontent string) {
418        syntaxerr := parser.ParseFile(fsetpath+"/x.go"content0)
419        if err != nil {
420            t.Fatal(err)
421        }
422        packages := make(map[string]*types.Package// keys are package paths
423        cfg := &types.Config{
424            ImporterimporterFunc(func(path string) (*types.Packageerror) {
425                dataok := export[path]
426                if !ok {
427                    return nilfmt.Errorf("missing export data for %s"path)
428                }
429                return gcexportdata.Read(bytes.NewReader(data), fsetpackagespath)
430            }),
431        }
432        pkg := types.NewPackage(pathsyntax.Name.Name)
433        check := types.NewChecker(cfgfsetpkgnil)
434        if err := check.Files([]*ast.File{syntax}); err != nil {
435            t.Fatal(err)
436        }
437        var out bytes.Buffer
438        if err := gcexportdata.Write(&outfsetpkg); err != nil {
439            t.Fatal(err)
440        }
441        export[path] = out.Bytes()
442    }
443
444    // Historically this led to a spurious error:
445    // "cannot convert a.M (variable of type a.MyTime) to type time.Time"
446    // because the private fields of Time and MyTime were not identical.
447    process("time"`package time; type Time struct { x, y int }`)
448    process("a"`package a; import "time"; type MyTime time.Time; var M MyTime`)
449    process("b"`package b; import ("a"; "time"); var _ = time.Time(a.M)`)
450}
451
452type importerFunc func(path string) (*types.Packageerror)
453
454func (f importerFuncImport(path string) (*types.Packageerror) { return f(path) }
455
MembersX
testPkg.version
TestIExportData_typealiases.t
TestUnexportedStructFields.BlockStmt.cfg
minStdlibPackages
cmpObj.y
TestUnexportedStructFields.t
TestIExportData_stdlib.conf
testPkgData.t
cmpObj.BlockStmt.xval
iexport.fset
iexport.version
testPkg
TestIExportData_typealiases.fset1
same.x
importerFunc.Import
TestIExportData_stdlib.f
testPkgData.fset2
same
same.y
cmpObj.BlockStmt.BlockStmt.ykind
TestIExportData_stdlib.RangeStmt_3692.BlockStmt.err
testPkgData.pkg2
testPkg.err
TestIExportData_long.imports
TestIExportData_typealiases.f
TestIExportData_typealiases.exportdata
cmpObj.BlockStmt.yval
testPkgData
testPkgData.exportdata
cmpObj.BlockStmt.equal
importerFunc.Import.f
testPkgData.fset
testPkgData.err
TestUnexportedStructFields.export
TestIExportData_stdlib.t
TestIExportData_stdlib.RangeStmt_2910.pkg
TestIExportData_stdlib.RangeStmt_3692.pkg
TestIExportData_stdlib.RangeStmt_3692.BlockStmt.exportdata
TestIExportData_stdlib.fset2
TestIExportData_stdlib.RangeStmt_4544.i
TestIExportData_typealiases.pkg2
cmpObj.xt
same.eps
TestIExportData_stdlib.bundle
TestIExportData_typealiases.imports
TestIExportData_stdlib.sorted
testPkg.fset2
TestIExportData_long.want
TestIExportData_typealiases
TestIExportData_typealiases.fset2
readExportFile.filename
testPkg._
TestIExportData_long.conf
mpprec
TestUnexportedStructFields.BlockStmt.pkg
TestIExportData_stdlib.isUnified
testPkg.RangeStmt_5298.BlockStmt.fl2
TestIExportData_long.pkg2
cmpObj.yt
same.yf
cmpObj.BlockStmt.yalias
iexport.pkg
isUnifiedBuilder
TestIExportData_stdlib
TestIExportData_stdlib.RangeStmt_2910.BlockStmt.features
testPkgData.pkg
TestIExportData_long
TestIExportData_long._
TestIExportData_stdlib.err
TestIExportData_stdlib.want
TestIExportData_typealiases.pkg1
TestIExportData_stdlib.RangeStmt_2910.info
testPkg.fset
TestIExportData_long.posn1
TestIExportData_stdlib.ctxt
TestIExportData_stdlib.pkgs2
TestIExportData_long.fset2
TestUnexportedStructFields.fset
iexport.bundle
TestIExportData_stdlib.RangeStmt_2910.BlockStmt.inspect
TestIExportData_stdlib.RangeStmt_4544.pkg
testPkgData._
TestIExportData_long.err
TestIExportData_long.pkg
TestUnexportedStructFields
importerFunc.Import.path
TestIExportData_long.fset1
TestIExportData_typealiases.err
TestUnexportedStructFields.BlockStmt.packages
readExportFile.f
readExportFile.ch
TestIExportData_stdlib.imports
testPkg.t
testPkg.pkg
TestIExportData_typealiases.conf
cmpObj.BlockStmt.BlockStmt.xkind
readExportFile.buf
testPkg.RangeStmt_5298.name
TestIExportData_typealiases._
cmpObj.BlockStmt.BlockStmt.err
gcexportdata
iexport.buf
testPkg.RangeStmt_5298.BlockStmt.err
TestIExportData_long.exportdata
readExportFile._
TestIExportData_stdlib.prog
TestIExportData_stdlib.version
TestIExportData_long.t
cmpObj.x
TestUnexportedStructFields.BlockStmt.err
readExportFile
iexport
testPkg.RangeStmt_5298.BlockStmt.obj2
same.xf
testPkgData.imports
cmpObj
importerFunc
TestIExportData_stdlib.numPkgs
TestIExportData_stdlib.RangeStmt_3692.BlockStmt.bp
testPkg.pkg2
TestUnexportedStructFields.BlockStmt.syntax
TestUnexportedStructFields.BlockStmt.out
iexport.shallow
TestIExportData_stdlib.RangeStmt_2378.path
testPkg.RangeStmt_5298.BlockStmt.fl1
TestIExportData_long.posn2
TestUnexportedStructFields.BlockStmt.check
iexport.err
testPkgData.version
TestIExportData_long.f
readExportFile.err
testPkg.RangeStmt_5298.BlockStmt.obj1
cmpObj.BlockStmt.xalias
same.d
Members
X