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 }