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