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