github.com/SkycoinProject/gomobile@v0.0.0-20190312151609-d3739f865fa6/bind/bind_test.go (about)

     1  package bind
     2  
     3  import (
     4  	"bytes"
     5  	"flag"
     6  	"go/ast"
     7  	"go/build"
     8  	"go/importer"
     9  	"go/parser"
    10  	"go/token"
    11  	"go/types"
    12  	"io"
    13  	"io/ioutil"
    14  	"log"
    15  	"os"
    16  	"os/exec"
    17  	"path"
    18  	"path/filepath"
    19  	"runtime"
    20  	"strings"
    21  	"testing"
    22  
    23  	"golang.org/x/mobile/internal/importers"
    24  	"golang.org/x/mobile/internal/importers/java"
    25  	"golang.org/x/mobile/internal/importers/objc"
    26  )
    27  
    28  func init() {
    29  	log.SetFlags(log.Lshortfile)
    30  }
    31  
    32  var updateFlag = flag.Bool("update", false, "Update the golden files.")
    33  
    34  var tests = []string{
    35  	"", // The universe package with the error type.
    36  	"testdata/basictypes.go",
    37  	"testdata/structs.go",
    38  	"testdata/interfaces.go",
    39  	"testdata/issue10788.go",
    40  	"testdata/issue12328.go",
    41  	"testdata/issue12403.go",
    42  	"testdata/issue29559.go",
    43  	"testdata/keywords.go",
    44  	"testdata/try.go",
    45  	"testdata/vars.go",
    46  	"testdata/ignore.go",
    47  	"testdata/doc.go",
    48  	"testdata/underscores.go",
    49  }
    50  
    51  var javaTests = []string{
    52  	"testdata/java.go",
    53  	"testdata/classes.go",
    54  }
    55  
    56  var objcTests = []string{
    57  	"testdata/objc.go",
    58  	"testdata/objcw.go",
    59  }
    60  
    61  var fset = token.NewFileSet()
    62  
    63  func fileRefs(t *testing.T, filename string, pkgPrefix string) *importers.References {
    64  	f, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
    65  	if err != nil {
    66  		t.Fatalf("%s: %v", filename, err)
    67  	}
    68  	refs, err := importers.AnalyzeFile(f, pkgPrefix)
    69  	if err != nil {
    70  		t.Fatalf("%s: %v", filename, err)
    71  	}
    72  	fakePath := path.Dir(filename)
    73  	for i := range refs.Embedders {
    74  		refs.Embedders[i].PkgPath = fakePath
    75  	}
    76  	return refs
    77  }
    78  
    79  func typeCheck(t *testing.T, filename string, gopath string) (*types.Package, *ast.File) {
    80  	f, err := parser.ParseFile(fset, filename, nil, parser.AllErrors|parser.ParseComments)
    81  	if err != nil {
    82  		t.Fatalf("%s: %v", filename, err)
    83  	}
    84  
    85  	pkgName := filepath.Base(filename)
    86  	pkgName = strings.TrimSuffix(pkgName, ".go")
    87  
    88  	// typecheck and collect typechecker errors
    89  	var conf types.Config
    90  	conf.Error = func(err error) {
    91  		t.Error(err)
    92  	}
    93  	if gopath != "" {
    94  		conf.Importer = importer.Default()
    95  		oldDefault := build.Default
    96  		defer func() { build.Default = oldDefault }()
    97  		build.Default.GOPATH = gopath
    98  	}
    99  	pkg, err := conf.Check(pkgName, fset, []*ast.File{f}, nil)
   100  	if err != nil {
   101  		t.Fatal(err)
   102  	}
   103  	return pkg, f
   104  }
   105  
   106  // diff runs the command "diff a b" and returns its output
   107  func diff(a, b string) string {
   108  	var buf bytes.Buffer
   109  	var cmd *exec.Cmd
   110  	switch runtime.GOOS {
   111  	case "plan9":
   112  		cmd = exec.Command("/bin/diff", "-c", a, b)
   113  	default:
   114  		cmd = exec.Command("/usr/bin/diff", "-u", a, b)
   115  	}
   116  	cmd.Stdout = &buf
   117  	cmd.Stderr = &buf
   118  	cmd.Run()
   119  	return buf.String()
   120  }
   121  
   122  func writeTempFile(t *testing.T, name string, contents []byte) string {
   123  	f, err := ioutil.TempFile("", name)
   124  	if err != nil {
   125  		t.Fatal(err)
   126  	}
   127  	if _, err := f.Write(contents); err != nil {
   128  		t.Fatal(err)
   129  	}
   130  	if err := f.Close(); err != nil {
   131  		t.Fatal(err)
   132  	}
   133  	return f.Name()
   134  }
   135  
   136  func TestGenObjc(t *testing.T) {
   137  	for _, filename := range tests {
   138  		var pkg *types.Package
   139  		var file *ast.File
   140  		if filename != "" {
   141  			pkg, file = typeCheck(t, filename, "")
   142  		}
   143  
   144  		var buf bytes.Buffer
   145  		g := &ObjcGen{
   146  			Generator: &Generator{
   147  				Printer: &Printer{Buf: &buf, IndentEach: []byte("\t")},
   148  				Fset:    fset,
   149  				Files:   []*ast.File{file},
   150  				Pkg:     pkg,
   151  			},
   152  		}
   153  		if pkg != nil {
   154  			g.AllPkg = []*types.Package{pkg}
   155  		}
   156  		g.Init(nil)
   157  
   158  		testcases := []struct {
   159  			suffix string
   160  			gen    func() error
   161  		}{
   162  			{
   163  				".objc.h.golden",
   164  				g.GenH,
   165  			},
   166  			{
   167  				".objc.m.golden",
   168  				g.GenM,
   169  			},
   170  			{
   171  				".objc.go.h.golden",
   172  				g.GenGoH,
   173  			},
   174  		}
   175  		for _, tc := range testcases {
   176  			buf.Reset()
   177  			if err := tc.gen(); err != nil {
   178  				t.Errorf("%s: %v", filename, err)
   179  				continue
   180  			}
   181  			out := writeTempFile(t, "generated"+tc.suffix, buf.Bytes())
   182  			defer os.Remove(out)
   183  			var golden string
   184  			if filename != "" {
   185  				golden = filename[:len(filename)-len(".go")]
   186  			} else {
   187  				golden = "testdata/universe"
   188  			}
   189  			golden += tc.suffix
   190  			if diffstr := diff(golden, out); diffstr != "" {
   191  				t.Errorf("%s: does not match Objective-C golden:\n%s", filename, diffstr)
   192  				if *updateFlag {
   193  					t.Logf("Updating %s...", golden)
   194  					err := exec.Command("/bin/cp", out, golden).Run()
   195  					if err != nil {
   196  						t.Errorf("Update failed: %s", err)
   197  					}
   198  				}
   199  			}
   200  		}
   201  	}
   202  }
   203  
   204  func genObjcPackages(t *testing.T, dir string, cg *ObjcWrapper) {
   205  	pkgBase := filepath.Join(dir, "src", "ObjC")
   206  	if err := os.MkdirAll(pkgBase, 0700); err != nil {
   207  		t.Fatal(err)
   208  	}
   209  	for i, jpkg := range cg.Packages() {
   210  		pkgDir := filepath.Join(pkgBase, jpkg)
   211  		if err := os.MkdirAll(pkgDir, 0700); err != nil {
   212  			t.Fatal(err)
   213  		}
   214  		pkgFile := filepath.Join(pkgDir, "package.go")
   215  		cg.Buf.Reset()
   216  		cg.GenPackage(i)
   217  		if err := ioutil.WriteFile(pkgFile, cg.Buf.Bytes(), 0600); err != nil {
   218  			t.Fatal(err)
   219  		}
   220  	}
   221  	cg.Buf.Reset()
   222  	cg.GenInterfaces()
   223  	clsFile := filepath.Join(pkgBase, "interfaces.go")
   224  	if err := ioutil.WriteFile(clsFile, cg.Buf.Bytes(), 0600); err != nil {
   225  		t.Fatal(err)
   226  	}
   227  
   228  	cmd := exec.Command(
   229  		"go",
   230  		"install",
   231  		"-pkgdir="+filepath.Join(dir, "pkg", build.Default.GOOS+"_"+build.Default.GOARCH),
   232  		"ObjC/...",
   233  	)
   234  	cmd.Env = append(os.Environ(), "GOPATH="+dir)
   235  	if out, err := cmd.CombinedOutput(); err != nil {
   236  		t.Fatalf("failed to go install the generated ObjC wrappers: %v: %s", err, string(out))
   237  	}
   238  }
   239  
   240  func genJavaPackages(t *testing.T, dir string, cg *ClassGen) {
   241  	buf := cg.Buf
   242  	cg.Buf = new(bytes.Buffer)
   243  	pkgBase := filepath.Join(dir, "src", "Java")
   244  	if err := os.MkdirAll(pkgBase, 0700); err != nil {
   245  		t.Fatal(err)
   246  	}
   247  	for i, jpkg := range cg.Packages() {
   248  		pkgDir := filepath.Join(pkgBase, jpkg)
   249  		if err := os.MkdirAll(pkgDir, 0700); err != nil {
   250  			t.Fatal(err)
   251  		}
   252  		pkgFile := filepath.Join(pkgDir, "package.go")
   253  		cg.Buf.Reset()
   254  		cg.GenPackage(i)
   255  		if err := ioutil.WriteFile(pkgFile, cg.Buf.Bytes(), 0600); err != nil {
   256  			t.Fatal(err)
   257  		}
   258  		io.Copy(buf, cg.Buf)
   259  	}
   260  	cg.Buf.Reset()
   261  	cg.GenInterfaces()
   262  	clsFile := filepath.Join(pkgBase, "interfaces.go")
   263  	if err := ioutil.WriteFile(clsFile, cg.Buf.Bytes(), 0600); err != nil {
   264  		t.Fatal(err)
   265  	}
   266  	io.Copy(buf, cg.Buf)
   267  	cg.Buf = buf
   268  
   269  	cmd := exec.Command(
   270  		"go",
   271  		"install",
   272  		"-pkgdir="+filepath.Join(dir, "pkg", build.Default.GOOS+"_"+build.Default.GOARCH),
   273  		"Java/...",
   274  	)
   275  	cmd.Env = append(os.Environ(), "GOPATH="+dir)
   276  	if out, err := cmd.CombinedOutput(); err != nil {
   277  		t.Fatalf("failed to go install the generated Java wrappers: %v: %s", err, string(out))
   278  	}
   279  }
   280  
   281  func TestGenJava(t *testing.T) {
   282  	allTests := tests
   283  	if java.IsAvailable() {
   284  		allTests = append(append([]string{}, allTests...), javaTests...)
   285  	}
   286  	for _, filename := range allTests {
   287  		var pkg *types.Package
   288  		var file *ast.File
   289  		var buf bytes.Buffer
   290  		var cg *ClassGen
   291  		var classes []*java.Class
   292  		if filename != "" {
   293  			refs := fileRefs(t, filename, "Java/")
   294  			imp := &java.Importer{}
   295  			var err error
   296  			classes, err = imp.Import(refs)
   297  			if err != nil {
   298  				t.Fatal(err)
   299  			}
   300  			tmpGopath := ""
   301  			if len(classes) > 0 {
   302  				tmpGopath, err = ioutil.TempDir(os.TempDir(), "gomobile-bind-test-")
   303  				if err != nil {
   304  					t.Fatal(err)
   305  				}
   306  				defer os.RemoveAll(tmpGopath)
   307  				cg = &ClassGen{
   308  					Printer: &Printer{
   309  						IndentEach: []byte("\t"),
   310  						Buf:        new(bytes.Buffer),
   311  					},
   312  				}
   313  				cg.Init(classes, refs.Embedders)
   314  				genJavaPackages(t, tmpGopath, cg)
   315  				cg.Buf = &buf
   316  			}
   317  			pkg, file = typeCheck(t, filename, tmpGopath)
   318  		}
   319  		g := &JavaGen{
   320  			Generator: &Generator{
   321  				Printer: &Printer{Buf: &buf, IndentEach: []byte("    ")},
   322  				Fset:    fset,
   323  				Files:   []*ast.File{file},
   324  				Pkg:     pkg,
   325  			},
   326  		}
   327  		if pkg != nil {
   328  			g.AllPkg = []*types.Package{pkg}
   329  		}
   330  		g.Init(classes)
   331  		testCases := []struct {
   332  			suffix string
   333  			gen    func() error
   334  		}{
   335  			{
   336  				".java.golden",
   337  				func() error {
   338  					for i := range g.ClassNames() {
   339  						if err := g.GenClass(i); err != nil {
   340  							return err
   341  						}
   342  					}
   343  					return g.GenJava()
   344  				},
   345  			},
   346  			{
   347  				".java.c.golden",
   348  				func() error {
   349  					if cg != nil {
   350  						cg.GenC()
   351  					}
   352  					return g.GenC()
   353  				},
   354  			},
   355  			{
   356  				".java.h.golden",
   357  				func() error {
   358  					if cg != nil {
   359  						cg.GenH()
   360  					}
   361  					return g.GenH()
   362  				},
   363  			},
   364  		}
   365  
   366  		for _, tc := range testCases {
   367  			buf.Reset()
   368  			if err := tc.gen(); err != nil {
   369  				t.Errorf("%s: %v", filename, err)
   370  				continue
   371  			}
   372  			out := writeTempFile(t, "generated"+tc.suffix, buf.Bytes())
   373  			defer os.Remove(out)
   374  			var golden string
   375  			if filename != "" {
   376  				golden = filename[:len(filename)-len(".go")]
   377  			} else {
   378  				golden = "testdata/universe"
   379  			}
   380  			golden += tc.suffix
   381  			if diffstr := diff(golden, out); diffstr != "" {
   382  				t.Errorf("%s: does not match Java golden:\n%s", filename, diffstr)
   383  
   384  				if *updateFlag {
   385  					t.Logf("Updating %s...", golden)
   386  					if err := exec.Command("/bin/cp", out, golden).Run(); err != nil {
   387  						t.Errorf("Update failed: %s", err)
   388  					}
   389  				}
   390  
   391  			}
   392  		}
   393  	}
   394  }
   395  
   396  func TestGenGo(t *testing.T) {
   397  	for _, filename := range tests {
   398  		var buf bytes.Buffer
   399  		var pkg *types.Package
   400  		if filename != "" {
   401  			pkg, _ = typeCheck(t, filename, "")
   402  		}
   403  		testGenGo(t, filename, &buf, pkg)
   404  	}
   405  }
   406  
   407  func TestGenGoJavaWrappers(t *testing.T) {
   408  	if !java.IsAvailable() {
   409  		t.Skipf("java is not available")
   410  	}
   411  	for _, filename := range javaTests {
   412  		var buf bytes.Buffer
   413  		refs := fileRefs(t, filename, "Java/")
   414  		imp := &java.Importer{}
   415  		classes, err := imp.Import(refs)
   416  		if err != nil {
   417  			t.Fatal(err)
   418  		}
   419  		tmpGopath, err := ioutil.TempDir(os.TempDir(), "gomobile-bind-test-")
   420  		if err != nil {
   421  			t.Fatal(err)
   422  		}
   423  		defer os.RemoveAll(tmpGopath)
   424  		cg := &ClassGen{
   425  			Printer: &Printer{
   426  				IndentEach: []byte("\t"),
   427  				Buf:        &buf,
   428  			},
   429  		}
   430  		cg.Init(classes, refs.Embedders)
   431  		genJavaPackages(t, tmpGopath, cg)
   432  		pkg, _ := typeCheck(t, filename, tmpGopath)
   433  		cg.GenGo()
   434  		testGenGo(t, filename, &buf, pkg)
   435  	}
   436  }
   437  
   438  func TestGenGoObjcWrappers(t *testing.T) {
   439  	if runtime.GOOS != "darwin" {
   440  		t.Skipf("can only generate objc wrappers on darwin")
   441  	}
   442  	for _, filename := range objcTests {
   443  		var buf bytes.Buffer
   444  		refs := fileRefs(t, filename, "ObjC/")
   445  		types, err := objc.Import(refs)
   446  		if err != nil {
   447  			t.Fatal(err)
   448  		}
   449  		tmpGopath, err := ioutil.TempDir(os.TempDir(), "gomobile-bind-test-")
   450  		if err != nil {
   451  			t.Fatal(err)
   452  		}
   453  		defer os.RemoveAll(tmpGopath)
   454  		cg := &ObjcWrapper{
   455  			Printer: &Printer{
   456  				IndentEach: []byte("\t"),
   457  				Buf:        &buf,
   458  			},
   459  		}
   460  		var genNames []string
   461  		for _, emb := range refs.Embedders {
   462  			genNames = append(genNames, emb.Name)
   463  		}
   464  		cg.Init(types, genNames)
   465  		genObjcPackages(t, tmpGopath, cg)
   466  		pkg, _ := typeCheck(t, filename, tmpGopath)
   467  		cg.GenGo()
   468  		testGenGo(t, filename, &buf, pkg)
   469  	}
   470  }
   471  
   472  func testGenGo(t *testing.T, filename string, buf *bytes.Buffer, pkg *types.Package) {
   473  	conf := &GeneratorConfig{
   474  		Writer: buf,
   475  		Fset:   fset,
   476  		Pkg:    pkg,
   477  	}
   478  	if pkg != nil {
   479  		conf.AllPkg = []*types.Package{pkg}
   480  	}
   481  	if err := GenGo(conf); err != nil {
   482  		t.Errorf("%s: %v", filename, err)
   483  		return
   484  	}
   485  	out := writeTempFile(t, "go", buf.Bytes())
   486  	defer os.Remove(out)
   487  	golden := filename
   488  	if golden == "" {
   489  		golden = "testdata/universe"
   490  	}
   491  	golden += ".golden"
   492  	if diffstr := diff(golden, out); diffstr != "" {
   493  		t.Errorf("%s: does not match Go golden:\n%s", filename, diffstr)
   494  
   495  		if *updateFlag {
   496  			t.Logf("Updating %s...", golden)
   497  			if err := exec.Command("/bin/cp", out, golden).Run(); err != nil {
   498  				t.Errorf("Update failed: %s", err)
   499  			}
   500  		}
   501  	}
   502  }
   503  
   504  func TestCustomPrefix(t *testing.T) {
   505  	const datafile = "testdata/customprefix.go"
   506  	pkg, file := typeCheck(t, datafile, "")
   507  
   508  	type testCase struct {
   509  		golden string
   510  		gen    func(w io.Writer) error
   511  	}
   512  	var buf bytes.Buffer
   513  	jg := &JavaGen{
   514  		JavaPkg: "com.example",
   515  		Generator: &Generator{
   516  			Printer: &Printer{Buf: &buf, IndentEach: []byte("    ")},
   517  			Fset:    fset,
   518  			AllPkg:  []*types.Package{pkg},
   519  			Files:   []*ast.File{file},
   520  			Pkg:     pkg,
   521  		},
   522  	}
   523  	jg.Init(nil)
   524  	testCases := []testCase{
   525  		{
   526  			"testdata/customprefix.java.golden",
   527  			func(w io.Writer) error {
   528  				buf.Reset()
   529  				for i := range jg.ClassNames() {
   530  					if err := jg.GenClass(i); err != nil {
   531  						return err
   532  					}
   533  				}
   534  				if err := jg.GenJava(); err != nil {
   535  					return err
   536  				}
   537  				_, err := io.Copy(w, &buf)
   538  				return err
   539  			},
   540  		},
   541  		{
   542  			"testdata/customprefix.java.h.golden",
   543  			func(w io.Writer) error {
   544  				buf.Reset()
   545  				if err := jg.GenH(); err != nil {
   546  					return err
   547  				}
   548  				_, err := io.Copy(w, &buf)
   549  				return err
   550  			},
   551  		},
   552  		{
   553  			"testdata/customprefix.java.c.golden",
   554  			func(w io.Writer) error {
   555  				buf.Reset()
   556  				if err := jg.GenC(); err != nil {
   557  					return err
   558  				}
   559  				_, err := io.Copy(w, &buf)
   560  				return err
   561  			},
   562  		},
   563  	}
   564  	for _, pref := range []string{"EX", ""} {
   565  		og := &ObjcGen{
   566  			Prefix: pref,
   567  			Generator: &Generator{
   568  				Printer: &Printer{Buf: &buf, IndentEach: []byte("    ")},
   569  				Fset:    fset,
   570  				AllPkg:  []*types.Package{pkg},
   571  				Pkg:     pkg,
   572  			},
   573  		}
   574  		og.Init(nil)
   575  		testCases = append(testCases, []testCase{
   576  			{
   577  				"testdata/customprefix" + pref + ".objc.go.h.golden",
   578  				func(w io.Writer) error {
   579  					buf.Reset()
   580  					if err := og.GenGoH(); err != nil {
   581  						return err
   582  					}
   583  					_, err := io.Copy(w, &buf)
   584  					return err
   585  				},
   586  			},
   587  			{
   588  				"testdata/customprefix" + pref + ".objc.h.golden",
   589  				func(w io.Writer) error {
   590  					buf.Reset()
   591  					if err := og.GenH(); err != nil {
   592  						return err
   593  					}
   594  					_, err := io.Copy(w, &buf)
   595  					return err
   596  				},
   597  			},
   598  			{
   599  				"testdata/customprefix" + pref + ".objc.m.golden",
   600  				func(w io.Writer) error {
   601  					buf.Reset()
   602  					if err := og.GenM(); err != nil {
   603  						return err
   604  					}
   605  					_, err := io.Copy(w, &buf)
   606  					return err
   607  				},
   608  			},
   609  		}...)
   610  	}
   611  
   612  	for _, tc := range testCases {
   613  		var buf bytes.Buffer
   614  		if err := tc.gen(&buf); err != nil {
   615  			t.Errorf("generating %s: %v", tc.golden, err)
   616  			continue
   617  		}
   618  		out := writeTempFile(t, "generated", buf.Bytes())
   619  		defer os.Remove(out)
   620  		if diffstr := diff(tc.golden, out); diffstr != "" {
   621  			t.Errorf("%s: generated file does not match:\b%s", tc.golden, diffstr)
   622  			if *updateFlag {
   623  				t.Logf("Updating %s...", tc.golden)
   624  				err := exec.Command("/bin/cp", out, tc.golden).Run()
   625  				if err != nil {
   626  					t.Errorf("Update failed: %s", err)
   627  				}
   628  			}
   629  		}
   630  	}
   631  }
   632  
   633  func TestLowerFirst(t *testing.T) {
   634  	testCases := []struct {
   635  		in, want string
   636  	}{
   637  		{"", ""},
   638  		{"Hello", "hello"},
   639  		{"HelloGopher", "helloGopher"},
   640  		{"hello", "hello"},
   641  		{"ID", "id"},
   642  		{"IDOrName", "idOrName"},
   643  		{"ΓειαΣας", "γειαΣας"},
   644  	}
   645  
   646  	for _, tc := range testCases {
   647  		if got := lowerFirst(tc.in); got != tc.want {
   648  			t.Errorf("lowerFirst(%q) = %q; want %q", tc.in, got, tc.want)
   649  		}
   650  	}
   651  }
   652  
   653  // Test that typeName work for anonymous qualified fields.
   654  func TestSelectorExprTypeName(t *testing.T) {
   655  	e, err := parser.ParseExprFrom(fset, "", "struct { bytes.Buffer }", 0)
   656  	if err != nil {
   657  		t.Fatal(err)
   658  	}
   659  	ft := e.(*ast.StructType).Fields.List[0].Type
   660  	if got, want := typeName(ft), "Buffer"; got != want {
   661  		t.Errorf("got: %q; want %q", got, want)
   662  	}
   663  }