github.com/llvm-mirror/llgo@v0.0.0-20190322182713-bf6f0a60fce1/third_party/gotools/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  	"llvm.org/llgo/third_party/gotools/go/gcimporter"
    24  	"llvm.org/llgo/third_party/gotools/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  
   123  		pkg, err := pkgForPath(lib)
   124  		switch err := err.(type) {
   125  		case nil:
   126  			// ok
   127  		case *build.NoGoError:
   128  			// no Go files - ignore
   129  			continue
   130  		default:
   131  			t.Errorf("typecheck failed: %s", err)
   132  			continue
   133  		}
   134  
   135  		size, gcsize := testExportImport(t, pkg, lib)
   136  		if gcsize == 0 {
   137  			// if gc import didn't happen, assume same size
   138  			// (and avoid division by zero below)
   139  			gcsize = size
   140  		}
   141  
   142  		if testing.Verbose() {
   143  			fmt.Printf("%s\t%d\t%d\t%d%%\n", lib, size, gcsize, int(float64(size)*100/float64(gcsize)))
   144  		}
   145  		totSize += size
   146  		totGcSize += gcsize
   147  	}
   148  
   149  	if testing.Verbose() {
   150  		fmt.Printf("\n%d\t%d\t%d%%\n", totSize, totGcSize, int(float64(totSize)*100/float64(totGcSize)))
   151  	}
   152  
   153  	types.GcCompatibilityMode = false
   154  }
   155  
   156  func testExportImport(t *testing.T, pkg0 *types.Package, path string) (size, gcsize int) {
   157  	data := ExportData(pkg0)
   158  	size = len(data)
   159  
   160  	imports := make(map[string]*types.Package)
   161  	n, pkg1, err := ImportData(imports, data)
   162  	if err != nil {
   163  		t.Errorf("package %s: import failed: %s", pkg0.Name(), err)
   164  		return
   165  	}
   166  	if n != size {
   167  		t.Errorf("package %s: not all input data consumed", pkg0.Name())
   168  		return
   169  	}
   170  
   171  	s0 := pkgString(pkg0)
   172  	s1 := pkgString(pkg1)
   173  	if s1 != s0 {
   174  		t.Errorf("package %s: \nimport got:\n%s\nwant:\n%s\n", pkg0.Name(), s1, s0)
   175  	}
   176  
   177  	// If we have a standard library, compare also against the gcimported package.
   178  	if path == "" {
   179  		return // not std library
   180  	}
   181  
   182  	gcdata, err := gcExportData(path)
   183  	if err != nil {
   184  		if pkg0.Name() == "main" {
   185  			return // no export data present for main package
   186  		}
   187  		t.Errorf("package %s: couldn't get export data: %s", pkg0.Name(), err)
   188  	}
   189  	gcsize = len(gcdata)
   190  
   191  	imports = make(map[string]*types.Package)
   192  	pkg2, err := gcImportData(imports, gcdata, path)
   193  	if err != nil {
   194  		t.Errorf("package %s: gcimport failed: %s", pkg0.Name(), err)
   195  		return
   196  	}
   197  
   198  	s2 := pkgString(pkg2)
   199  	if s2 != s0 {
   200  		t.Errorf("package %s: \ngcimport got:\n%s\nwant:\n%s\n", pkg0.Name(), s2, s0)
   201  	}
   202  
   203  	return
   204  }
   205  
   206  func pkgForSource(src string) (*types.Package, error) {
   207  	f, err := parser.ParseFile(fset, "", src, 0)
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  	return typecheck("import-test", f)
   212  }
   213  
   214  func pkgForPath(path string) (*types.Package, error) {
   215  	// collect filenames
   216  	ctxt := build.Default
   217  	pkginfo, err := ctxt.Import(path, "", 0)
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  	filenames := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
   222  
   223  	// parse files
   224  	files := make([]*ast.File, len(filenames))
   225  	for i, filename := range filenames {
   226  		var err error
   227  		files[i], err = parser.ParseFile(fset, filepath.Join(pkginfo.Dir, filename), nil, 0)
   228  		if err != nil {
   229  			return nil, err
   230  		}
   231  	}
   232  
   233  	return typecheck(path, files...)
   234  }
   235  
   236  var defaultConf = types.Config{
   237  	// we only care about exports and thus can ignore function bodies
   238  	IgnoreFuncBodies: true,
   239  	// work around C imports if possible
   240  	FakeImportC: true,
   241  	// strconv exports IntSize as a constant. The type-checker must
   242  	// use the same word size otherwise the result of the type-checker
   243  	// and gc imports is different. We don't care about alignment
   244  	// since none of the tests have exported constants depending
   245  	// on alignment (see also issue 8366).
   246  	Sizes: &types.StdSizes{WordSize: strconv.IntSize / 8, MaxAlign: 8},
   247  }
   248  
   249  func typecheck(path string, files ...*ast.File) (*types.Package, error) {
   250  	return defaultConf.Check(path, fset, files, nil)
   251  }
   252  
   253  // pkgString returns a string representation of a package's exported interface.
   254  func pkgString(pkg *types.Package) string {
   255  	var buf bytes.Buffer
   256  
   257  	fmt.Fprintf(&buf, "package %s\n", pkg.Name())
   258  
   259  	scope := pkg.Scope()
   260  	for _, name := range scope.Names() {
   261  		if exported(name) {
   262  			obj := scope.Lookup(name)
   263  			buf.WriteString(obj.String())
   264  
   265  			switch obj := obj.(type) {
   266  			case *types.Const:
   267  				// For now only print constant values if they are not float
   268  				// or complex. This permits comparing go/types results with
   269  				// gc-generated gcimported package interfaces.
   270  				info := obj.Type().Underlying().(*types.Basic).Info()
   271  				if info&types.IsFloat == 0 && info&types.IsComplex == 0 {
   272  					fmt.Fprintf(&buf, " = %s", obj.Val())
   273  				}
   274  
   275  			case *types.TypeName:
   276  				// Print associated methods.
   277  				// Basic types (e.g., unsafe.Pointer) have *types.Basic
   278  				// type rather than *types.Named; so we need to check.
   279  				if typ, _ := obj.Type().(*types.Named); typ != nil {
   280  					if n := typ.NumMethods(); n > 0 {
   281  						// Sort methods by name so that we get the
   282  						// same order independent of whether the
   283  						// methods got imported or coming directly
   284  						// for the source.
   285  						// TODO(gri) This should probably be done
   286  						// in go/types.
   287  						list := make([]*types.Func, n)
   288  						for i := 0; i < n; i++ {
   289  							list[i] = typ.Method(i)
   290  						}
   291  						sort.Sort(byName(list))
   292  
   293  						buf.WriteString("\nmethods (\n")
   294  						for _, m := range list {
   295  							fmt.Fprintf(&buf, "\t%s\n", m)
   296  						}
   297  						buf.WriteString(")")
   298  					}
   299  				}
   300  			}
   301  			buf.WriteByte('\n')
   302  		}
   303  	}
   304  
   305  	return buf.String()
   306  }
   307  
   308  var stdLibRoot = filepath.Join(runtime.GOROOT(), "src") + string(filepath.Separator)
   309  
   310  // The following std libraries are excluded from the stdLibs list.
   311  var excluded = map[string]bool{
   312  	"builtin": true, // contains type declarations with cycles
   313  	"unsafe":  true, // contains fake declarations
   314  }
   315  
   316  // stdLibs returns the list of standard library package paths.
   317  func stdLibs() (list []string, err error) {
   318  	err = filepath.Walk(stdLibRoot, func(path string, info os.FileInfo, err error) error {
   319  		if err == nil && info.IsDir() {
   320  			// testdata directories don't contain importable libraries
   321  			if info.Name() == "testdata" {
   322  				return filepath.SkipDir
   323  			}
   324  			pkgPath := path[len(stdLibRoot):] // remove stdLibRoot
   325  			if len(pkgPath) > 0 && !excluded[pkgPath] {
   326  				list = append(list, pkgPath)
   327  			}
   328  		}
   329  		return nil
   330  	})
   331  	return
   332  }
   333  
   334  type byName []*types.Func
   335  
   336  func (a byName) Len() int           { return len(a) }
   337  func (a byName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
   338  func (a byName) Less(i, j int) bool { return a[i].Name() < a[j].Name() }
   339  
   340  // gcExportData returns the gc-generated export data for the given path.
   341  // It is based on a trimmed-down version of gcimporter.Import which does
   342  // not do the actual import, does not handle package unsafe, and assumes
   343  // that path is a correct standard library package path (no canonicalization,
   344  // or handling of local import paths).
   345  func gcExportData(path string) ([]byte, error) {
   346  	filename, id := gcimporter.FindPkg(path, "")
   347  	if filename == "" {
   348  		return nil, fmt.Errorf("can't find import: %s", path)
   349  	}
   350  	if id != path {
   351  		panic("path should be canonicalized")
   352  	}
   353  
   354  	f, err := os.Open(filename)
   355  	if err != nil {
   356  		return nil, err
   357  	}
   358  	defer f.Close()
   359  
   360  	buf := bufio.NewReader(f)
   361  	if err = gcimporter.FindExportData(buf); err != nil {
   362  		return nil, err
   363  	}
   364  
   365  	var data []byte
   366  	for {
   367  		line, err := buf.ReadBytes('\n')
   368  		if err != nil {
   369  			return nil, err
   370  		}
   371  		data = append(data, line...)
   372  		// export data ends in "$$\n"
   373  		if len(line) == 3 && line[0] == '$' && line[1] == '$' {
   374  			return data, nil
   375  		}
   376  	}
   377  }
   378  
   379  func gcImportData(imports map[string]*types.Package, data []byte, path string) (*types.Package, error) {
   380  	filename := fmt.Sprintf("<filename for %s>", path) // so we have a decent error message if necessary
   381  	return gcimporter.ImportData(imports, filename, path, bufio.NewReader(bytes.NewBuffer(data)))
   382  }