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 }