github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/go/importer/import_test.go (about)

     1  // Copyright 2013 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  package importer
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"fmt"
    11  	"go/ast"
    12  	"go/build"
    13  	"go/parser"
    14  	"go/token"
    15  	"os"
    16  	"path/filepath"
    17  	"runtime"
    18  	"sort"
    19  	"strconv"
    20  	"testing"
    21  	"time"
    22  
    23  	"golang.org/x/tools/go/gcimporter"
    24  	"golang.org/x/tools/go/types"
    25  )
    26  
    27  var fset = token.NewFileSet()
    28  
    29  var tests = []string{
    30  	`package p`,
    31  
    32  	// consts
    33  	`package p; const X = true`,
    34  	`package p; const X, y, Z = true, false, 0 != 0`,
    35  	`package p; const ( A float32 = 1<<iota; B; C; D)`,
    36  	`package p; const X = "foo"`,
    37  	`package p; const X string = "foo"`,
    38  	`package p; const X = 0`,
    39  	`package p; const X = -42`,
    40  	`package p; const X = 3.14159265`,
    41  	`package p; const X = -1e-10`,
    42  	`package p; const X = 1.2 + 2.3i`,
    43  	`package p; const X = -1i`,
    44  	`package p; import "math"; const Pi = math.Pi`,
    45  	`package p; import m "math"; const Pi = m.Pi`,
    46  
    47  	// types
    48  	`package p; type T int`,
    49  	`package p; type T [10]int`,
    50  	`package p; type T []int`,
    51  	`package p; type T struct{}`,
    52  	`package p; type T struct{x int}`,
    53  	`package p; type T *int`,
    54  	`package p; type T func()`,
    55  	`package p; type T *T`,
    56  	`package p; type T interface{}`,
    57  	`package p; type T interface{ foo() }`,
    58  	`package p; type T interface{ m() T }`,
    59  	// TODO(gri) disabled for now - import/export works but
    60  	// types.Type.String() used in the test cannot handle cases
    61  	// like this yet
    62  	// `package p; type T interface{ m() interface{T} }`,
    63  	`package p; type T map[string]bool`,
    64  	`package p; type T chan int`,
    65  	`package p; type T <-chan complex64`,
    66  	`package p; type T chan<- map[int]string`,
    67  	// test case for issue 8177
    68  	`package p; type T1 interface { F(T2) }; type T2 interface { T1 }`,
    69  
    70  	// vars
    71  	`package p; var X int`,
    72  	`package p; var X, Y, Z struct{f int "tag"}`,
    73  
    74  	// funcs
    75  	`package p; func F()`,
    76  	`package p; func F(x int, y struct{}) bool`,
    77  	`package p; type T int; func (*T) F(x int, y struct{}) T`,
    78  
    79  	// selected special cases
    80  	`package p; type T int`,
    81  	`package p; type T uint8`,
    82  	`package p; type T byte`,
    83  	`package p; type T error`,
    84  	`package p; import "net/http"; type T http.Client`,
    85  	`package p; import "net/http"; type ( T1 http.Client; T2 struct { http.Client } )`,
    86  	`package p; import "unsafe"; type ( T1 unsafe.Pointer; T2 unsafe.Pointer )`,
    87  	`package p; import "unsafe"; type T struct { p unsafe.Pointer }`,
    88  }
    89  
    90  func TestImportSrc(t *testing.T) {
    91  	for _, src := range tests {
    92  		pkg, err := pkgForSource(src)
    93  		if err != nil {
    94  			t.Errorf("typecheck failed: %s", err)
    95  			continue
    96  		}
    97  		testExportImport(t, pkg, "")
    98  	}
    99  }
   100  
   101  func TestImportStdLib(t *testing.T) {
   102  	start := time.Now()
   103  
   104  	libs, err := stdLibs()
   105  	if err != nil {
   106  		t.Fatalf("could not compute list of std libraries: %s", err)
   107  	}
   108  	if len(libs) < 100 {
   109  		t.Fatalf("only %d std libraries found - something's not right", len(libs))
   110  	}
   111  
   112  	// make sure printed go/types types and gc-imported types
   113  	// can be compared reasonably well
   114  	types.GcCompatibilityMode = true
   115  
   116  	var totSize, totGcSize int
   117  	for _, lib := range libs {
   118  		// limit run time for short tests
   119  		if testing.Short() && time.Since(start) >= 750*time.Millisecond {
   120  			return
   121  		}
   122  		if lib == "cmd/internal/objfile" || lib == "net/http" {
   123  			// gcimporter doesn't support vendored imports.
   124  			// TODO(gri): fix.
   125  			continue
   126  		}
   127  
   128  		pkg, err := pkgForPath(lib)
   129  		switch err := err.(type) {
   130  		case nil:
   131  			// ok
   132  		case *build.NoGoError:
   133  			// no Go files - ignore
   134  			continue
   135  		default:
   136  			t.Errorf("typecheck failed: %s", err)
   137  			continue
   138  		}
   139  
   140  		size, gcsize := testExportImport(t, pkg, lib)
   141  		if gcsize == 0 {
   142  			// if gc import didn't happen, assume same size
   143  			// (and avoid division by zero below)
   144  			gcsize = size
   145  		}
   146  
   147  		if testing.Verbose() {
   148  			fmt.Printf("%s\t%d\t%d\t%d%%\n", lib, size, gcsize, int(float64(size)*100/float64(gcsize)))
   149  		}
   150  		totSize += size
   151  		totGcSize += gcsize
   152  	}
   153  
   154  	if testing.Verbose() {
   155  		fmt.Printf("\n%d\t%d\t%d%%\n", totSize, totGcSize, int(float64(totSize)*100/float64(totGcSize)))
   156  	}
   157  
   158  	types.GcCompatibilityMode = false
   159  }
   160  
   161  func testExportImport(t *testing.T, pkg0 *types.Package, path string) (size, gcsize int) {
   162  	data := ExportData(pkg0)
   163  	size = len(data)
   164  
   165  	imports := make(map[string]*types.Package)
   166  	n, pkg1, err := ImportData(imports, data)
   167  	if err != nil {
   168  		t.Errorf("package %s: import failed: %s", pkg0.Name(), err)
   169  		return
   170  	}
   171  	if n != size {
   172  		t.Errorf("package %s: not all input data consumed", pkg0.Name())
   173  		return
   174  	}
   175  
   176  	s0 := pkgString(pkg0)
   177  	s1 := pkgString(pkg1)
   178  	if s1 != s0 {
   179  		t.Errorf("package %s: \nimport got:\n%s\nwant:\n%s\n", pkg0.Name(), s1, s0)
   180  	}
   181  
   182  	// If we have a standard library, compare also against the gcimported package.
   183  	if path == "" {
   184  		return // not std library
   185  	}
   186  
   187  	gcdata, err := gcExportData(path)
   188  	if err != nil {
   189  		if pkg0.Name() == "main" {
   190  			return // no export data present for main package
   191  		}
   192  		t.Errorf("package %s: couldn't get export data: %s", pkg0.Name(), err)
   193  	}
   194  	gcsize = len(gcdata)
   195  
   196  	imports = make(map[string]*types.Package)
   197  	pkg2, err := gcImportData(imports, gcdata, path)
   198  	if err != nil {
   199  		t.Errorf("package %s: gcimport failed: %s", pkg0.Name(), err)
   200  		return
   201  	}
   202  
   203  	s2 := pkgString(pkg2)
   204  	if s2 != s0 {
   205  		t.Errorf("package %s: \ngcimport got:\n%s\nwant:\n%s\n", pkg0.Name(), s2, s0)
   206  	}
   207  
   208  	return
   209  }
   210  
   211  func pkgForSource(src string) (*types.Package, error) {
   212  	f, err := parser.ParseFile(fset, "", src, 0)
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  	return typecheck("import-test", f)
   217  }
   218  
   219  func pkgForPath(path string) (*types.Package, error) {
   220  	// collect filenames
   221  	ctxt := build.Default
   222  	pkginfo, err := ctxt.Import(path, "", 0)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  	filenames := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
   227  
   228  	// parse files
   229  	files := make([]*ast.File, len(filenames))
   230  	for i, filename := range filenames {
   231  		var err error
   232  		files[i], err = parser.ParseFile(fset, filepath.Join(pkginfo.Dir, filename), nil, 0)
   233  		if err != nil {
   234  			return nil, err
   235  		}
   236  	}
   237  
   238  	return typecheck(path, files...)
   239  }
   240  
   241  var defaultConf = types.Config{
   242  	// we only care about exports and thus can ignore function bodies
   243  	IgnoreFuncBodies: true,
   244  	// work around C imports if possible
   245  	FakeImportC: true,
   246  	// strconv exports IntSize as a constant. The type-checker must
   247  	// use the same word size otherwise the result of the type-checker
   248  	// and gc imports is different. We don't care about alignment
   249  	// since none of the tests have exported constants depending
   250  	// on alignment (see also issue 8366).
   251  	Sizes: &types.StdSizes{WordSize: strconv.IntSize / 8, MaxAlign: 8},
   252  }
   253  
   254  func typecheck(path string, files ...*ast.File) (*types.Package, error) {
   255  	return defaultConf.Check(path, fset, files, nil)
   256  }
   257  
   258  // pkgString returns a string representation of a package's exported interface.
   259  func pkgString(pkg *types.Package) string {
   260  	var buf bytes.Buffer
   261  
   262  	fmt.Fprintf(&buf, "package %s\n", pkg.Name())
   263  
   264  	scope := pkg.Scope()
   265  	for _, name := range scope.Names() {
   266  		if exported(name) {
   267  			obj := scope.Lookup(name)
   268  			buf.WriteString(obj.String())
   269  
   270  			switch obj := obj.(type) {
   271  			case *types.Const:
   272  				// For now only print constant values if they are not float
   273  				// or complex. This permits comparing go/types results with
   274  				// gc-generated gcimported package interfaces.
   275  				info := obj.Type().Underlying().(*types.Basic).Info()
   276  				if info&types.IsFloat == 0 && info&types.IsComplex == 0 {
   277  					fmt.Fprintf(&buf, " = %s", obj.Val())
   278  				}
   279  
   280  			case *types.TypeName:
   281  				// Print associated methods.
   282  				// Basic types (e.g., unsafe.Pointer) have *types.Basic
   283  				// type rather than *types.Named; so we need to check.
   284  				if typ, _ := obj.Type().(*types.Named); typ != nil {
   285  					if n := typ.NumMethods(); n > 0 {
   286  						// Sort methods by name so that we get the
   287  						// same order independent of whether the
   288  						// methods got imported or coming directly
   289  						// for the source.
   290  						// TODO(gri) This should probably be done
   291  						// in go/types.
   292  						list := make([]*types.Func, n)
   293  						for i := 0; i < n; i++ {
   294  							list[i] = typ.Method(i)
   295  						}
   296  						sort.Sort(byName(list))
   297  
   298  						buf.WriteString("\nmethods (\n")
   299  						for _, m := range list {
   300  							fmt.Fprintf(&buf, "\t%s\n", m)
   301  						}
   302  						buf.WriteString(")")
   303  					}
   304  				}
   305  			}
   306  			buf.WriteByte('\n')
   307  		}
   308  	}
   309  
   310  	return buf.String()
   311  }
   312  
   313  var stdLibRoot = filepath.Join(runtime.GOROOT(), "src") + string(filepath.Separator)
   314  
   315  // The following std libraries are excluded from the stdLibs list.
   316  var excluded = map[string]bool{
   317  	"builtin": true, // contains type declarations with cycles
   318  	"unsafe":  true, // contains fake declarations
   319  }
   320  
   321  // stdLibs returns the list of standard library package paths.
   322  func stdLibs() (list []string, err error) {
   323  	err = filepath.Walk(stdLibRoot, func(path string, info os.FileInfo, err error) error {
   324  		if err == nil && info.IsDir() {
   325  			// testdata directories don't contain importable libraries
   326  			if info.Name() == "testdata" {
   327  				return filepath.SkipDir
   328  			}
   329  			pkgPath := path[len(stdLibRoot):] // remove stdLibRoot
   330  			if len(pkgPath) > 0 && !excluded[pkgPath] {
   331  				list = append(list, pkgPath)
   332  			}
   333  		}
   334  		return nil
   335  	})
   336  	return
   337  }
   338  
   339  type byName []*types.Func
   340  
   341  func (a byName) Len() int           { return len(a) }
   342  func (a byName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
   343  func (a byName) Less(i, j int) bool { return a[i].Name() < a[j].Name() }
   344  
   345  // gcExportData returns the gc-generated export data for the given path.
   346  // It is based on a trimmed-down version of gcimporter.Import which does
   347  // not do the actual import, does not handle package unsafe, and assumes
   348  // that path is a correct standard library package path (no canonicalization,
   349  // or handling of local import paths).
   350  func gcExportData(path string) ([]byte, error) {
   351  	filename, id := gcimporter.FindPkg(path, "")
   352  	if filename == "" {
   353  		return nil, fmt.Errorf("can't find import: %s", path)
   354  	}
   355  	if id != path {
   356  		panic("path should be canonicalized")
   357  	}
   358  
   359  	f, err := os.Open(filename)
   360  	if err != nil {
   361  		return nil, err
   362  	}
   363  	defer f.Close()
   364  
   365  	buf := bufio.NewReader(f)
   366  	if err = gcimporter.FindExportData(buf); err != nil {
   367  		return nil, err
   368  	}
   369  
   370  	var data []byte
   371  	for {
   372  		line, err := buf.ReadBytes('\n')
   373  		if err != nil {
   374  			return nil, err
   375  		}
   376  		data = append(data, line...)
   377  		// export data ends in "$$\n"
   378  		if len(line) == 3 && line[0] == '$' && line[1] == '$' {
   379  			return data, nil
   380  		}
   381  	}
   382  }
   383  
   384  func gcImportData(imports map[string]*types.Package, data []byte, path string) (*types.Package, error) {
   385  	filename := fmt.Sprintf("<filename for %s>", path) // so we have a decent error message if necessary
   386  	return gcimporter.ImportData(imports, filename, path, bufio.NewReader(bytes.NewBuffer(data)))
   387  }