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  }