| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700 |
- package bind
- import (
- "bytes"
- "flag"
- "go/ast"
- "go/build"
- "go/format"
- "go/importer"
- "go/parser"
- "go/token"
- "go/types"
- "io"
- "io/ioutil"
- "log"
- "os"
- "os/exec"
- "path"
- "path/filepath"
- "runtime"
- "strings"
- "testing"
- "golang.org/x/mobile/internal/importers"
- "golang.org/x/mobile/internal/importers/java"
- "golang.org/x/mobile/internal/importers/objc"
- )
- func init() {
- log.SetFlags(log.Lshortfile)
- }
- var updateFlag = flag.Bool("update", false, "Update the golden files.")
- var tests = []string{
- "", // The universe package with the error type.
- "testdata/basictypes.go",
- "testdata/structs.go",
- "testdata/interfaces.go",
- "testdata/issue10788.go",
- "testdata/issue12328.go",
- "testdata/issue12403.go",
- "testdata/issue29559.go",
- "testdata/keywords.go",
- "testdata/try.go",
- "testdata/vars.go",
- "testdata/ignore.go",
- "testdata/doc.go",
- "testdata/underscores.go",
- }
- var javaTests = []string{
- "testdata/java.go",
- "testdata/classes.go",
- }
- var objcTests = []string{
- "testdata/objc.go",
- "testdata/objcw.go",
- }
- var fset = token.NewFileSet()
- func fileRefs(t *testing.T, filename string, pkgPrefix string) *importers.References {
- f, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
- if err != nil {
- t.Fatalf("%s: %v", filename, err)
- }
- refs, err := importers.AnalyzeFile(f, pkgPrefix)
- if err != nil {
- t.Fatalf("%s: %v", filename, err)
- }
- fakePath := path.Dir(filename)
- for i := range refs.Embedders {
- refs.Embedders[i].PkgPath = fakePath
- }
- return refs
- }
- func typeCheck(t *testing.T, filename string, gopath string) (*types.Package, *ast.File) {
- f, err := parser.ParseFile(fset, filename, nil, parser.AllErrors|parser.ParseComments)
- if err != nil {
- t.Fatalf("%s: %v", filename, err)
- }
- pkgName := filepath.Base(filename)
- pkgName = strings.TrimSuffix(pkgName, ".go")
- // typecheck and collect typechecker errors
- var conf types.Config
- conf.Error = func(err error) {
- t.Error(err)
- }
- if gopath != "" {
- conf.Importer = importer.Default()
- oldDefault := build.Default
- defer func() { build.Default = oldDefault }()
- build.Default.GOPATH = gopath
- }
- pkg, err := conf.Check(pkgName, fset, []*ast.File{f}, nil)
- if err != nil {
- t.Fatal(err)
- }
- return pkg, f
- }
- // diff runs the command "diff a b" and returns its output
- func diff(a, b string) string {
- var buf bytes.Buffer
- var cmd *exec.Cmd
- switch runtime.GOOS {
- case "plan9":
- cmd = exec.Command("/bin/diff", "-c", a, b)
- default:
- cmd = exec.Command("/usr/bin/diff", "-u", a, b)
- }
- cmd.Stdout = &buf
- cmd.Stderr = &buf
- cmd.Run()
- return buf.String()
- }
- func writeTempFile(t *testing.T, name string, contents []byte) string {
- f, err := ioutil.TempFile("", name)
- if err != nil {
- t.Fatal(err)
- }
- if _, err := f.Write(contents); err != nil {
- t.Fatal(err)
- }
- if err := f.Close(); err != nil {
- t.Fatal(err)
- }
- return f.Name()
- }
- func TestGenObjc(t *testing.T) {
- for _, filename := range tests {
- var pkg *types.Package
- var file *ast.File
- if filename != "" {
- pkg, file = typeCheck(t, filename, "")
- }
- var buf bytes.Buffer
- g := &ObjcGen{
- Generator: &Generator{
- Printer: &Printer{Buf: &buf, IndentEach: []byte("\t")},
- Fset: fset,
- Files: []*ast.File{file},
- Pkg: pkg,
- },
- }
- if pkg != nil {
- g.AllPkg = []*types.Package{pkg}
- }
- g.Init(nil)
- testcases := []struct {
- suffix string
- gen func() error
- }{
- {
- ".objc.h.golden",
- g.GenH,
- },
- {
- ".objc.m.golden",
- g.GenM,
- },
- {
- ".objc.go.h.golden",
- g.GenGoH,
- },
- }
- for _, tc := range testcases {
- buf.Reset()
- if err := tc.gen(); err != nil {
- t.Errorf("%s: %v", filename, err)
- continue
- }
- out := writeTempFile(t, "generated"+tc.suffix, buf.Bytes())
- defer os.Remove(out)
- var golden string
- if filename != "" {
- golden = filename[:len(filename)-len(".go")]
- } else {
- golden = "testdata/universe"
- }
- golden += tc.suffix
- if diffstr := diff(golden, out); diffstr != "" {
- t.Errorf("%s: does not match Objective-C golden:\n%s", filename, diffstr)
- if *updateFlag {
- t.Logf("Updating %s...", golden)
- err := exec.Command("/bin/cp", out, golden).Run()
- if err != nil {
- t.Errorf("Update failed: %s", err)
- }
- }
- }
- }
- }
- }
- func genObjcPackages(t *testing.T, dir string, cg *ObjcWrapper) {
- pkgBase := filepath.Join(dir, "src", "ObjC")
- if err := os.MkdirAll(pkgBase, 0700); err != nil {
- t.Fatal(err)
- }
- for i, jpkg := range cg.Packages() {
- pkgDir := filepath.Join(pkgBase, jpkg)
- if err := os.MkdirAll(pkgDir, 0700); err != nil {
- t.Fatal(err)
- }
- pkgFile := filepath.Join(pkgDir, "package.go")
- cg.Buf.Reset()
- cg.GenPackage(i)
- if err := ioutil.WriteFile(pkgFile, cg.Buf.Bytes(), 0600); err != nil {
- t.Fatal(err)
- }
- }
- cg.Buf.Reset()
- cg.GenInterfaces()
- clsFile := filepath.Join(pkgBase, "interfaces.go")
- if err := ioutil.WriteFile(clsFile, cg.Buf.Bytes(), 0600); err != nil {
- t.Fatal(err)
- }
- gocmd := filepath.Join(runtime.GOROOT(), "bin", "go")
- cmd := exec.Command(
- gocmd,
- "install",
- "-pkgdir="+filepath.Join(dir, "pkg", build.Default.GOOS+"_"+build.Default.GOARCH),
- "ObjC/...",
- )
- cmd.Env = append(os.Environ(), "GOPATH="+dir, "GO111MODULE=off")
- if out, err := cmd.CombinedOutput(); err != nil {
- t.Fatalf("failed to go install the generated ObjC wrappers: %v: %s", err, string(out))
- }
- }
- func genJavaPackages(t *testing.T, dir string, cg *ClassGen) {
- buf := cg.Buf
- cg.Buf = new(bytes.Buffer)
- pkgBase := filepath.Join(dir, "src", "Java")
- if err := os.MkdirAll(pkgBase, 0700); err != nil {
- t.Fatal(err)
- }
- for i, jpkg := range cg.Packages() {
- pkgDir := filepath.Join(pkgBase, jpkg)
- if err := os.MkdirAll(pkgDir, 0700); err != nil {
- t.Fatal(err)
- }
- pkgFile := filepath.Join(pkgDir, "package.go")
- cg.Buf.Reset()
- cg.GenPackage(i)
- if err := ioutil.WriteFile(pkgFile, cg.Buf.Bytes(), 0600); err != nil {
- t.Fatal(err)
- }
- io.Copy(buf, cg.Buf)
- }
- cg.Buf.Reset()
- cg.GenInterfaces()
- clsFile := filepath.Join(pkgBase, "interfaces.go")
- if err := ioutil.WriteFile(clsFile, cg.Buf.Bytes(), 0600); err != nil {
- t.Fatal(err)
- }
- io.Copy(buf, cg.Buf)
- cg.Buf = buf
- gocmd := filepath.Join(runtime.GOROOT(), "bin", "go")
- cmd := exec.Command(
- gocmd,
- "install",
- "-pkgdir="+filepath.Join(dir, "pkg", build.Default.GOOS+"_"+build.Default.GOARCH),
- "Java/...",
- )
- cmd.Env = append(os.Environ(), "GOPATH="+dir, "GO111MODULE=off")
- if out, err := cmd.CombinedOutput(); err != nil {
- t.Fatalf("failed to go install the generated Java wrappers: %v: %s", err, string(out))
- }
- }
- func TestGenJava(t *testing.T) {
- allTests := tests
- if java.IsAvailable() {
- allTests = append(append([]string{}, allTests...), javaTests...)
- }
- for _, filename := range allTests {
- var pkg *types.Package
- var file *ast.File
- var buf bytes.Buffer
- var cg *ClassGen
- var classes []*java.Class
- if filename != "" {
- refs := fileRefs(t, filename, "Java/")
- imp := &java.Importer{}
- var err error
- classes, err = imp.Import(refs)
- if err != nil {
- t.Fatal(err)
- }
- tmpGopath := ""
- if len(classes) > 0 {
- tmpGopath, err = ioutil.TempDir(os.TempDir(), "gomobile-bind-test-")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpGopath)
- cg = &ClassGen{
- Printer: &Printer{
- IndentEach: []byte("\t"),
- Buf: new(bytes.Buffer),
- },
- }
- cg.Init(classes, refs.Embedders)
- genJavaPackages(t, tmpGopath, cg)
- cg.Buf = &buf
- }
- pkg, file = typeCheck(t, filename, tmpGopath)
- }
- g := &JavaGen{
- Generator: &Generator{
- Printer: &Printer{Buf: &buf, IndentEach: []byte(" ")},
- Fset: fset,
- Files: []*ast.File{file},
- Pkg: pkg,
- },
- }
- if pkg != nil {
- g.AllPkg = []*types.Package{pkg}
- }
- g.Init(classes)
- testCases := []struct {
- suffix string
- gen func() error
- }{
- {
- ".java.golden",
- func() error {
- for i := range g.ClassNames() {
- if err := g.GenClass(i); err != nil {
- return err
- }
- }
- return g.GenJava()
- },
- },
- {
- ".java.c.golden",
- func() error {
- if cg != nil {
- cg.GenC()
- }
- return g.GenC()
- },
- },
- {
- ".java.h.golden",
- func() error {
- if cg != nil {
- cg.GenH()
- }
- return g.GenH()
- },
- },
- }
- for _, tc := range testCases {
- buf.Reset()
- if err := tc.gen(); err != nil {
- t.Errorf("%s: %v", filename, err)
- continue
- }
- out := writeTempFile(t, "generated"+tc.suffix, buf.Bytes())
- defer os.Remove(out)
- var golden string
- if filename != "" {
- golden = filename[:len(filename)-len(".go")]
- } else {
- golden = "testdata/universe"
- }
- golden += tc.suffix
- if diffstr := diff(golden, out); diffstr != "" {
- t.Errorf("%s: does not match Java golden:\n%s", filename, diffstr)
- if *updateFlag {
- t.Logf("Updating %s...", golden)
- if err := exec.Command("/bin/cp", out, golden).Run(); err != nil {
- t.Errorf("Update failed: %s", err)
- }
- }
- }
- }
- }
- }
- func TestGenGo(t *testing.T) {
- for _, filename := range tests {
- var buf bytes.Buffer
- var pkg *types.Package
- if filename != "" {
- pkg, _ = typeCheck(t, filename, "")
- }
- testGenGo(t, filename, &buf, pkg)
- }
- }
- func TestGenGoJavaWrappers(t *testing.T) {
- if !java.IsAvailable() {
- t.Skipf("java is not available")
- }
- for _, filename := range javaTests {
- var buf bytes.Buffer
- refs := fileRefs(t, filename, "Java/")
- imp := &java.Importer{}
- classes, err := imp.Import(refs)
- if err != nil {
- t.Fatal(err)
- }
- tmpGopath, err := ioutil.TempDir(os.TempDir(), "gomobile-bind-test-")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpGopath)
- cg := &ClassGen{
- Printer: &Printer{
- IndentEach: []byte("\t"),
- Buf: &buf,
- },
- }
- cg.Init(classes, refs.Embedders)
- genJavaPackages(t, tmpGopath, cg)
- pkg, _ := typeCheck(t, filename, tmpGopath)
- cg.GenGo()
- testGenGo(t, filename, &buf, pkg)
- }
- }
- func TestGenGoObjcWrappers(t *testing.T) {
- if runtime.GOOS != "darwin" {
- t.Skipf("can only generate objc wrappers on darwin")
- }
- for _, filename := range objcTests {
- var buf bytes.Buffer
- refs := fileRefs(t, filename, "ObjC/")
- types, err := objc.Import(refs)
- if err != nil {
- t.Fatal(err)
- }
- tmpGopath, err := ioutil.TempDir(os.TempDir(), "gomobile-bind-test-")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmpGopath)
- cg := &ObjcWrapper{
- Printer: &Printer{
- IndentEach: []byte("\t"),
- Buf: &buf,
- },
- }
- var genNames []string
- for _, emb := range refs.Embedders {
- genNames = append(genNames, emb.Name)
- }
- cg.Init(types, genNames)
- genObjcPackages(t, tmpGopath, cg)
- pkg, _ := typeCheck(t, filename, tmpGopath)
- cg.GenGo()
- testGenGo(t, filename, &buf, pkg)
- }
- }
- func testGenGo(t *testing.T, filename string, buf *bytes.Buffer, pkg *types.Package) {
- conf := &GeneratorConfig{
- Writer: buf,
- Fset: fset,
- Pkg: pkg,
- }
- if pkg != nil {
- conf.AllPkg = []*types.Package{pkg}
- }
- if err := GenGo(conf); err != nil {
- t.Errorf("%s: %v", filename, err)
- return
- }
- // TODO(hyangah): let GenGo format the generated go files.
- out := writeTempFile(t, "go", gofmt(t, buf.Bytes()))
- defer os.Remove(out)
- golden := filename
- if golden == "" {
- golden = "testdata/universe"
- }
- golden += ".golden"
- goldenContents, err := ioutil.ReadFile(golden)
- if err != nil {
- t.Fatalf("failed to read golden file: %v", err)
- }
- // format golden file using the current go version's formatting rule.
- formattedGolden := writeTempFile(t, "go", gofmt(t, goldenContents))
- defer os.Remove(formattedGolden)
- if diffstr := diff(formattedGolden, out); diffstr != "" {
- t.Errorf("%s: does not match Go golden:\n%s", filename, diffstr)
- if *updateFlag {
- t.Logf("Updating %s...", golden)
- if err := exec.Command("/bin/cp", out, golden).Run(); err != nil {
- t.Errorf("Update failed: %s", err)
- }
- }
- }
- }
- // gofmt formats the collection of Go source files auto-generated by gobind.
- func gofmt(t *testing.T, src []byte) []byte {
- t.Helper()
- buf := &bytes.Buffer{}
- mark := []byte(gobindPreamble)
- for i, c := range bytes.Split(src, mark) {
- if i == 0 {
- buf.Write(c)
- continue
- }
- tmp := append(mark, c...)
- out, err := format.Source(tmp)
- if err != nil {
- t.Fatalf("failed to format Go file: error=%v\n----\n%s\n----", err, tmp)
- }
- if _, err := buf.Write(out); err != nil {
- t.Fatalf("failed to write formatted file to buffer: %v", err)
- }
- }
- return buf.Bytes()
- }
- func TestCustomPrefix(t *testing.T) {
- const datafile = "testdata/customprefix.go"
- pkg, file := typeCheck(t, datafile, "")
- type testCase struct {
- golden string
- gen func(w io.Writer) error
- }
- var buf bytes.Buffer
- jg := &JavaGen{
- JavaPkg: "com.example",
- Generator: &Generator{
- Printer: &Printer{Buf: &buf, IndentEach: []byte(" ")},
- Fset: fset,
- AllPkg: []*types.Package{pkg},
- Files: []*ast.File{file},
- Pkg: pkg,
- },
- }
- jg.Init(nil)
- testCases := []testCase{
- {
- "testdata/customprefix.java.golden",
- func(w io.Writer) error {
- buf.Reset()
- for i := range jg.ClassNames() {
- if err := jg.GenClass(i); err != nil {
- return err
- }
- }
- if err := jg.GenJava(); err != nil {
- return err
- }
- _, err := io.Copy(w, &buf)
- return err
- },
- },
- {
- "testdata/customprefix.java.h.golden",
- func(w io.Writer) error {
- buf.Reset()
- if err := jg.GenH(); err != nil {
- return err
- }
- _, err := io.Copy(w, &buf)
- return err
- },
- },
- {
- "testdata/customprefix.java.c.golden",
- func(w io.Writer) error {
- buf.Reset()
- if err := jg.GenC(); err != nil {
- return err
- }
- _, err := io.Copy(w, &buf)
- return err
- },
- },
- }
- for _, pref := range []string{"EX", ""} {
- og := &ObjcGen{
- Prefix: pref,
- Generator: &Generator{
- Printer: &Printer{Buf: &buf, IndentEach: []byte(" ")},
- Fset: fset,
- AllPkg: []*types.Package{pkg},
- Pkg: pkg,
- },
- }
- og.Init(nil)
- testCases = append(testCases, []testCase{
- {
- "testdata/customprefix" + pref + ".objc.go.h.golden",
- func(w io.Writer) error {
- buf.Reset()
- if err := og.GenGoH(); err != nil {
- return err
- }
- _, err := io.Copy(w, &buf)
- return err
- },
- },
- {
- "testdata/customprefix" + pref + ".objc.h.golden",
- func(w io.Writer) error {
- buf.Reset()
- if err := og.GenH(); err != nil {
- return err
- }
- _, err := io.Copy(w, &buf)
- return err
- },
- },
- {
- "testdata/customprefix" + pref + ".objc.m.golden",
- func(w io.Writer) error {
- buf.Reset()
- if err := og.GenM(); err != nil {
- return err
- }
- _, err := io.Copy(w, &buf)
- return err
- },
- },
- }...)
- }
- for _, tc := range testCases {
- var buf bytes.Buffer
- if err := tc.gen(&buf); err != nil {
- t.Errorf("generating %s: %v", tc.golden, err)
- continue
- }
- out := writeTempFile(t, "generated", buf.Bytes())
- defer os.Remove(out)
- if diffstr := diff(tc.golden, out); diffstr != "" {
- t.Errorf("%s: generated file does not match:\b%s", tc.golden, diffstr)
- if *updateFlag {
- t.Logf("Updating %s...", tc.golden)
- err := exec.Command("/bin/cp", out, tc.golden).Run()
- if err != nil {
- t.Errorf("Update failed: %s", err)
- }
- }
- }
- }
- }
- func TestLowerFirst(t *testing.T) {
- testCases := []struct {
- in, want string
- }{
- {"", ""},
- {"Hello", "hello"},
- {"HelloGopher", "helloGopher"},
- {"hello", "hello"},
- {"ID", "id"},
- {"IDOrName", "idOrName"},
- {"ΓειαΣας", "γειαΣας"},
- }
- for _, tc := range testCases {
- if got := lowerFirst(tc.in); got != tc.want {
- t.Errorf("lowerFirst(%q) = %q; want %q", tc.in, got, tc.want)
- }
- }
- }
- // Test that typeName work for anonymous qualified fields.
- func TestSelectorExprTypeName(t *testing.T) {
- e, err := parser.ParseExprFrom(fset, "", "struct { bytes.Buffer }", 0)
- if err != nil {
- t.Fatal(err)
- }
- ft := e.(*ast.StructType).Fields.List[0].Type
- if got, want := typeName(ft), "Buffer"; got != want {
- t.Errorf("got: %q; want %q", got, want)
- }
- }
|