| // Copyright 2016 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "bytes" |
| "fmt" |
| "os" |
| "os/exec" |
| "runtime" |
| "strings" |
| "testing" |
| |
| "golang.org/x/tools/go/packages/packagestest" |
| ) |
| |
| func TestMain(m *testing.M) { |
| // To avoid recompiling the gobind command (and to support compiler options |
| // like -race and -coverage), allow the test binary itself to re-exec itself |
| // as the gobind command by setting an environment variable. |
| if os.Getenv("GOBIND_TEST_IS_GOBIND") != "" { |
| main() |
| os.Exit(0) |
| } |
| os.Setenv("GOBIND_TEST_IS_GOBIND", "1") |
| |
| os.Exit(m.Run()) |
| } |
| |
| var tests = []struct { |
| name string |
| lang string |
| pkg string |
| goos string |
| // reverse is true if the test needs to generate reverse bindings using |
| // external tools such as javap. |
| reverse bool |
| }{ |
| { |
| name: "ObjC-Testpkg", |
| lang: "objc", |
| pkg: "golang.org/x/mobile/bind/testdata/testpkg", |
| }, |
| { |
| name: "Java-Testpkg", |
| lang: "java", |
| pkg: "golang.org/x/mobile/bind/testdata/testpkg", |
| }, |
| { |
| name: "Go-Testpkg", |
| lang: "go", |
| pkg: "golang.org/x/mobile/bind/testdata/testpkg", |
| }, |
| { |
| name: "Java-Javapkg", |
| lang: "java", |
| pkg: "golang.org/x/mobile/bind/testdata/testpkg/javapkg", |
| goos: "android", |
| reverse: true, |
| }, |
| { |
| name: "Go-Javapkg", |
| lang: "go", |
| pkg: "golang.org/x/mobile/bind/testdata/testpkg/javapkg", |
| goos: "android", |
| reverse: true, |
| }, |
| { |
| name: "Go-Cgopkg", |
| lang: "go,java,objc", |
| pkg: "golang.org/x/mobile/bind/testdata/cgopkg", |
| goos: "android", |
| }, |
| } |
| |
| func mustHaveBindTestdata(t testing.TB) { |
| switch runtime.GOOS { |
| case "android", "ios": |
| t.Skipf("skipping: test cannot access ../../bind/testdata on %s/%s", runtime.GOOS, runtime.GOARCH) |
| } |
| } |
| |
| func gobindBin(t testing.TB) string { |
| switch runtime.GOOS { |
| case "js", "ios": |
| t.Skipf("skipping: cannot exec subprocess on %s/%s", runtime.GOOS, runtime.GOARCH) |
| } |
| |
| p, err := os.Executable() |
| if err != nil { |
| t.Fatal(err) |
| } |
| return p |
| } |
| |
| func runGobind(t testing.TB, lang, pkg, goos string, exported *packagestest.Exported) error { |
| cmd := exec.Command(gobindBin(t), "-lang", lang, pkg) |
| cmd.Dir = exported.Config.Dir |
| cmd.Env = exported.Config.Env |
| if goos != "" { |
| // Add CGO_ENABLED=1 explicitly since Cgo is disabled when GOOS is different from host OS. |
| cmd.Env = append(cmd.Env, "GOOS="+goos, "CGO_ENABLED=1") |
| } |
| stderr := new(strings.Builder) |
| cmd.Stderr = stderr |
| stdout := new(strings.Builder) |
| cmd.Stdout = stdout |
| err := cmd.Run() |
| if testing.Verbose() && stdout.Len() > 0 { |
| t.Logf("stdout (%v):\n%s", cmd, stderr) |
| } |
| if stderr.Len() > 0 { |
| t.Logf("stderr (%v):\n%s", cmd, stderr) |
| } |
| if err != nil { |
| return fmt.Errorf("%v: %w", cmd, err) |
| } |
| return nil |
| } |
| |
| func TestGobind(t *testing.T) { packagestest.TestAll(t, testGobind) } |
| func testGobind(t *testing.T, exporter packagestest.Exporter) { |
| mustHaveBindTestdata(t) |
| |
| _, javapErr := exec.LookPath("javap") |
| exported := packagestest.Export(t, exporter, []packagestest.Module{{ |
| Name: "golang.org/x/mobile", |
| Files: packagestest.MustCopyFileTree("../.."), |
| }}) |
| defer exported.Cleanup() |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| if exporter == packagestest.Modules && test.reverse { |
| t.Skip("reverse binding does't work with Go modules") |
| } |
| if test.reverse && javapErr != nil { |
| t.Skip("reverse bind test requires javap which is not available") |
| } |
| if err := runGobind(t, test.lang, test.pkg, test.goos, exported); err != nil { |
| t.Error(err) |
| } |
| }) |
| } |
| } |
| |
| func TestDocs(t *testing.T) { packagestest.TestAll(t, testDocs) } |
| func testDocs(t *testing.T, exporter packagestest.Exporter) { |
| mustHaveBindTestdata(t) |
| |
| const docsrc = ` |
| package doctest |
| |
| // This is a comment. |
| type Struct struct{ |
| }` |
| |
| exported := packagestest.Export(t, exporter, []packagestest.Module{ |
| { |
| Name: "example.com/doctest", |
| Files: map[string]interface{}{ |
| "doc.go": docsrc, |
| }, |
| }, |
| { |
| // gobind requires golang.org/x/mobile to generate code for reverse bindings. |
| Name: "golang.org/x/mobile", |
| Files: packagestest.MustCopyFileTree("../.."), |
| }, |
| }) |
| defer exported.Cleanup() |
| |
| const comment = "This is a comment." |
| for _, lang := range []string{"java", "objc"} { |
| cmd := exec.Command(gobindBin(t), "-lang", lang, "example.com/doctest") |
| cmd.Dir = exported.Config.Dir |
| cmd.Env = exported.Config.Env |
| out, err := cmd.CombinedOutput() |
| if err != nil { |
| t.Errorf("gobind -lang %s failed: %v: %s", lang, err, out) |
| continue |
| } |
| if bytes.Index(out, []byte(comment)) == -1 { |
| t.Errorf("gobind output for language %s did not contain the comment %q", lang, comment) |
| } |
| } |
| } |
| |
| func BenchmarkGobind(b *testing.B) { |
| packagestest.BenchmarkAll(b, benchmarkGobind) |
| } |
| |
| func benchmarkGobind(b *testing.B, exporter packagestest.Exporter) { |
| _, javapErr := exec.LookPath("javap") |
| exported := packagestest.Export(b, exporter, []packagestest.Module{{ |
| Name: "golang.org/x/mobile", |
| Files: packagestest.MustCopyFileTree("../.."), |
| }}) |
| defer exported.Cleanup() |
| |
| for _, test := range tests { |
| b.Run(test.name, func(b *testing.B) { |
| if exporter == packagestest.Modules && test.reverse { |
| b.Skip("reverse binding does't work with Go modules") |
| } |
| if test.reverse && javapErr != nil { |
| b.Skip("reverse bind test requires javap which is not available") |
| } |
| for i := 0; i < b.N; i++ { |
| if err := runGobind(b, test.lang, test.pkg, test.goos, exported); err != nil { |
| b.Error(err) |
| } |
| } |
| }) |
| } |
| } |