github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/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  	"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  
    39  func readExportFile(filename string) ([]byte, error) {
    40  	f, err := os.Open(filename)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  	defer f.Close()
    45  
    46  	buf := bufio.NewReader(f)
    47  	if _, _, err := gcimporter.FindExportData(buf); err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	if ch, err := buf.ReadByte(); err != nil {
    52  		return nil, err
    53  	} else if ch != 'i' {
    54  		return nil, fmt.Errorf("unexpected byte: %v", ch)
    55  	}
    56  
    57  	return ioutil.ReadAll(buf)
    58  }
    59  
    60  func iexport(fset *token.FileSet, version int, pkg *types.Package) ([]byte, error) {
    61  	var buf bytes.Buffer
    62  	const bundle, shallow = false, false
    63  	if err := gcimporter.IExportCommon(&buf, fset, bundle, shallow, version, []*types.Package{pkg}); err != nil {
    64  		return nil, err
    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.
    71  func isUnifiedBuilder() bool {
    72  	return os.Getenv("GO_BUILDER_NAME") == "linux-amd64-unified"
    73  }
    74  
    75  const minStdlibPackages = 248
    76  
    77  func 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  		AllowErrors: true,
    97  		TypeChecker: types.Config{
    98  			Sizes: types.SizesFor(ctxt.Compiler, ctxt.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  	f, err := conf.ParseFile("haserrors/haserrors.go", `package haserrors
   109  const UnknownValue = "" + 0
   110  type UnknownType undefined
   111  `)
   112  	if err != nil {
   113  		t.Fatal(err)
   114  	}
   115  	conf.CreateFromFiles("haserrors", f)
   116  
   117  	prog, err := 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 pkg, info := 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(sorted, pkg)
   137  		}
   138  	}
   139  	sort.Slice(sorted, func(i, j int) bool {
   140  		return sorted[i].Path() < sorted[j].Path()
   141  	})
   142  
   143  	version := gcimporter.IExportVersion
   144  	numPkgs := len(sorted)
   145  	if want := minStdlibPackages; numPkgs < want {
   146  		t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want)
   147  	}
   148  
   149  	for _, pkg := range sorted {
   150  		if exportdata, err := iexport(conf.Fset, version, pkg); err != nil {
   151  			t.Error(err)
   152  		} else {
   153  			testPkgData(t, conf.Fset, version, pkg, exportdata)
   154  		}
   155  
   156  		if pkg.Name() == "main" || pkg.Name() == "haserrors" {
   157  			// skip; no export data
   158  		} else if bp, err := ctxt.Import(pkg.Path(), "", build.FindOnly); err != nil {
   159  			t.Log("warning:", err)
   160  		} else if exportdata, err := readExportFile(bp.PkgObj); err != nil {
   161  			t.Log("warning:", err)
   162  		} else {
   163  			testPkgData(t, conf.Fset, version, pkg, exportdata)
   164  		}
   165  	}
   166  
   167  	var bundle bytes.Buffer
   168  	if err := gcimporter.IExportBundle(&bundle, conf.Fset, sorted); err != nil {
   169  		t.Fatal(err)
   170  	}
   171  	fset2 := token.NewFileSet()
   172  	imports := make(map[string]*types.Package)
   173  	pkgs2, err := gcimporter.IImportBundle(fset2, imports, bundle.Bytes())
   174  	if err != nil {
   175  		t.Fatal(err)
   176  	}
   177  
   178  	for i, pkg := range sorted {
   179  		testPkg(t, conf.Fset, version, pkg, fset2, pkgs2[i])
   180  	}
   181  }
   182  
   183  func testPkgData(t *testing.T, fset *token.FileSet, version int, pkg *types.Package, exportdata []byte) {
   184  	imports := make(map[string]*types.Package)
   185  	fset2 := token.NewFileSet()
   186  	_, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg.Path())
   187  	if err != nil {
   188  		t.Errorf("IImportData(%s): %v", pkg.Path(), err)
   189  	}
   190  
   191  	testPkg(t, fset, version, pkg, fset2, pkg2)
   192  }
   193  
   194  func testPkg(t *testing.T, fset *token.FileSet, version int, pkg *types.Package, fset2 *token.FileSet, pkg2 *types.Package) {
   195  	if _, err := iexport(fset2, version, pkg2); 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(), name, obj1)
   208  			continue
   209  		}
   210  
   211  		fl1 := fileLine(fset, obj1)
   212  		fl2 := fileLine(fset2, obj2)
   213  		if fl1 != fl2 {
   214  			t.Errorf("%s.%s: got posn %s, want %s",
   215  				pkg.Path(), name, fl2, fl1)
   216  		}
   217  
   218  		if err := cmpObj(obj1, obj2); err != nil {
   219  			t.Errorf("%s.%s: %s\ngot:  %s\nwant: %s",
   220  				pkg.Path(), name, err, obj2, obj1)
   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.
   228  func 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  	f, err := parser.ParseFile(fset1, "foo.go", longFile, 0)
   233  	if err != nil {
   234  		t.Fatal(err)
   235  	}
   236  	var conf types.Config
   237  	pkg, err := conf.Check("foo", fset1, []*ast.File{f}, nil)
   238  	if err != nil {
   239  		t.Fatal(err)
   240  	}
   241  
   242  	// export
   243  	exportdata, err := iexport(fset1, gcimporter.IExportVersion, pkg)
   244  	if err != nil {
   245  		t.Fatal(err)
   246  	}
   247  
   248  	// import
   249  	imports := make(map[string]*types.Package)
   250  	fset2 := token.NewFileSet()
   251  	_, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg.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  			posn2, want, posn1)
   262  	}
   263  }
   264  
   265  func TestIExportData_typealiases(t *testing.T) {
   266  	// parse and typecheck
   267  	fset1 := token.NewFileSet()
   268  	f, err := parser.ParseFile(fset1, "p.go", src, 0)
   269  	if err != nil {
   270  		t.Fatal(err)
   271  	}
   272  	var conf types.Config
   273  	pkg1, err := 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(t, pkg1, "export")
   283  
   284  	// export
   285  	// use a nil fileset here to confirm that it doesn't panic
   286  	exportdata, err := iexport(nil, gcimporter.IExportVersion, pkg1)
   287  	if err != nil {
   288  		t.Fatal(err)
   289  	}
   290  
   291  	// import
   292  	imports := make(map[string]*types.Package)
   293  	fset2 := token.NewFileSet()
   294  	_, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg1.Path())
   295  	if err != nil {
   296  		t.Fatalf("IImportData(%s): %v", pkg1.Path(), err)
   297  	}
   298  	checkPkg(t, pkg2, "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.
   304  func cmpObj(x, y types.Object) error {
   305  	if reflect.TypeOf(x) != reflect.TypeOf(y) {
   306  		return fmt.Errorf("%T vs %T", x, y)
   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(xval, token.EQL, yval)
   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(xval, yval)
   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", xval, yval)
   332  		}
   333  	case *types.TypeName:
   334  		if xalias, yalias := x.IsAlias(), y.(*types.TypeName).IsAlias(); xalias != yalias {
   335  			return fmt.Errorf("mismatching IsAlias(): %s vs %s", x, y)
   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", xt, yt)
   347  		}
   348  		if xn != nil {
   349  			if err := cmpNamed(xn, yn); 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(xt, yt)
   359  }
   360  
   361  // Use the same floating-point precision (512) as cmd/compile
   362  // (see Mpprec in cmd/compile/internal/gc/mpfloat.go).
   363  const mpprec = 512
   364  
   365  // same compares non-complex numeric values and reports if they are approximately equal.
   366  func same(x, y constant.Value) bool {
   367  	xf := constantToFloat(x)
   368  	yf := constantToFloat(y)
   369  	d := new(big.Float).Sub(xf, yf)
   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.
   376  func constantToFloat(x constant.Value) *big.Float {
   377  	var f big.Float
   378  	f.SetPrec(mpprec)
   379  	if v, exact := constant.Float64Val(x); exact {
   380  		// float64
   381  		f.SetFloat64(v)
   382  	} else if num, denom := 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(n, d))
   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.
   399  func 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 := 0; i < len(bytes)/2; i++ {
   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.
   411  func 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(path, content string) {
   418  		syntax, err := parser.ParseFile(fset, path+"/x.go", content, 0)
   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  			Importer: importerFunc(func(path string) (*types.Package, error) {
   425  				data, ok := export[path]
   426  				if !ok {
   427  					return nil, fmt.Errorf("missing export data for %s", path)
   428  				}
   429  				return gcexportdata.Read(bytes.NewReader(data), fset, packages, path)
   430  			}),
   431  		}
   432  		pkg := types.NewPackage(path, syntax.Name.Name)
   433  		check := types.NewChecker(cfg, fset, pkg, nil)
   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(&out, fset, pkg); 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  
   452  type importerFunc func(path string) (*types.Package, error)
   453  
   454  func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }