github.com/SpiderOak/mobile@v0.0.0-20221129182558-6f541b59af45/cmd/gomobile/bind_test.go (about)

     1  // Copyright 2015 The Go Authors.  All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"io/ioutil"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"runtime"
    14  	"strings"
    15  	"testing"
    16  	"text/template"
    17  
    18  	"github.com/SpiderOak/mobile/internal/sdkpath"
    19  )
    20  
    21  func TestBindAndroid(t *testing.T) {
    22  	platform, err := sdkpath.AndroidAPIPath(minAndroidAPI)
    23  	if err != nil {
    24  		t.Skip("No compatible Android API platform found, skipping bind")
    25  	}
    26  	// platform is a path like "/path/to/Android/sdk/platforms/android-32"
    27  	components := strings.Split(platform, string(filepath.Separator))
    28  	if len(components) < 2 {
    29  		t.Fatalf("API path is too short: %s", platform)
    30  	}
    31  	components = components[len(components)-2:]
    32  	platformRel := filepath.Join("$ANDROID_HOME", components[0], components[1])
    33  
    34  	defer func() {
    35  		xout = os.Stderr
    36  		buildN = false
    37  		buildX = false
    38  		buildO = ""
    39  		buildTarget = ""
    40  		bindJavaPkg = ""
    41  	}()
    42  	buildN = true
    43  	buildX = true
    44  	buildO = "asset.aar"
    45  	buildTarget = "android/arm"
    46  
    47  	tests := []struct {
    48  		javaPkg string
    49  	}{
    50  		{
    51  			// Empty javaPkg
    52  		},
    53  		{
    54  			javaPkg: "com.example.foo",
    55  		},
    56  	}
    57  	for _, tc := range tests {
    58  		bindJavaPkg = tc.javaPkg
    59  
    60  		buf := new(bytes.Buffer)
    61  		xout = buf
    62  		gopath = filepath.SplitList(goEnv("GOPATH"))[0]
    63  		if goos == "windows" {
    64  			os.Setenv("HOMEDRIVE", "C:")
    65  		}
    66  		cmdBind.flag.Parse([]string{"github.com/SpiderOak/mobile/asset"})
    67  		err := runBind(cmdBind)
    68  		if err != nil {
    69  			t.Log(buf.String())
    70  			t.Fatal(err)
    71  		}
    72  		got := filepath.ToSlash(buf.String())
    73  
    74  		output, err := defaultOutputData("")
    75  		if err != nil {
    76  			t.Fatal(err)
    77  		}
    78  		data := struct {
    79  			outputData
    80  			AndroidPlatform string
    81  			JavaPkg         string
    82  		}{
    83  			outputData:      output,
    84  			AndroidPlatform: platformRel,
    85  			JavaPkg:         tc.javaPkg,
    86  		}
    87  
    88  		wantBuf := new(bytes.Buffer)
    89  		if err := bindAndroidTmpl.Execute(wantBuf, data); err != nil {
    90  			t.Errorf("%+v: computing diff failed: %v", tc, err)
    91  			continue
    92  		}
    93  
    94  		diff, err := diff(got, wantBuf.String())
    95  		if err != nil {
    96  			t.Errorf("%+v: computing diff failed: %v", tc, err)
    97  			continue
    98  		}
    99  		if diff != "" {
   100  			t.Errorf("%+v: unexpected output:\n%s", tc, diff)
   101  		}
   102  	}
   103  }
   104  
   105  func TestBindApple(t *testing.T) {
   106  	if !xcodeAvailable() {
   107  		t.Skip("Xcode is missing")
   108  	}
   109  	defer func() {
   110  		xout = os.Stderr
   111  		buildN = false
   112  		buildX = false
   113  		buildO = ""
   114  		buildTarget = ""
   115  		bindPrefix = ""
   116  	}()
   117  	buildN = true
   118  	buildX = true
   119  	buildO = "Asset.xcframework"
   120  	buildTarget = "ios/arm64"
   121  
   122  	tests := []struct {
   123  		prefix string
   124  		out    string
   125  	}{
   126  		{
   127  			// empty prefix
   128  		},
   129  		{
   130  			prefix: "Foo",
   131  		},
   132  		{
   133  			out: "Abcde.xcframework",
   134  		},
   135  	}
   136  	for _, tc := range tests {
   137  		bindPrefix = tc.prefix
   138  		if tc.out != "" {
   139  			buildO = tc.out
   140  		}
   141  
   142  		buf := new(bytes.Buffer)
   143  		xout = buf
   144  		gopath = filepath.SplitList(goEnv("GOPATH"))[0]
   145  		if goos == "windows" {
   146  			os.Setenv("HOMEDRIVE", "C:")
   147  		}
   148  		cmdBind.flag.Parse([]string{"github.com/SpiderOak/mobile/asset"})
   149  		if err := runBind(cmdBind); err != nil {
   150  			t.Log(buf.String())
   151  			t.Fatal(err)
   152  		}
   153  		got := filepath.ToSlash(buf.String())
   154  
   155  		output, err := defaultOutputData("")
   156  		if err != nil {
   157  			t.Fatal(err)
   158  		}
   159  
   160  		data := struct {
   161  			outputData
   162  			Output string
   163  			Prefix string
   164  		}{
   165  			outputData: output,
   166  			Output:     buildO[:len(buildO)-len(".xcframework")],
   167  			Prefix:     tc.prefix,
   168  		}
   169  
   170  		wantBuf := new(bytes.Buffer)
   171  		if err := bindAppleTmpl.Execute(wantBuf, data); err != nil {
   172  			t.Errorf("%+v: computing diff failed: %v", tc, err)
   173  			continue
   174  		}
   175  
   176  		diff, err := diff(got, wantBuf.String())
   177  		if err != nil {
   178  			t.Errorf("%+v: computing diff failed: %v", tc, err)
   179  			continue
   180  		}
   181  		if diff != "" {
   182  			t.Errorf("%+v: unexpected output:\n%s", tc, diff)
   183  		}
   184  	}
   185  }
   186  
   187  var bindAndroidTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
   188  WORK=$WORK
   189  GOOS=android CGO_ENABLED=1 gobind -lang=go,java -outdir=$WORK{{if .JavaPkg}} -javapkg={{.JavaPkg}}{{end}} github.com/SpiderOak/mobile/asset
   190  mkdir -p $WORK/src-android-arm
   191  PWD=$WORK/src-android-arm GOMODCACHE=$GOPATH/pkg/mod GOOS=android GOARCH=arm CC=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang CXX=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang++ CGO_ENABLED=1 GOARM=7 GOPATH=$WORK:$GOPATH go mod tidy
   192  PWD=$WORK/src-android-arm GOMODCACHE=$GOPATH/pkg/mod GOOS=android GOARCH=arm CC=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang CXX=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang++ CGO_ENABLED=1 GOARM=7 GOPATH=$WORK:$GOPATH go build -x -buildmode=c-shared -o=$WORK/android/src/main/jniLibs/armeabi-v7a/libgojni.so ./gobind
   193  PWD=$WORK/java javac -d $WORK/javac-output -source 1.7 -target 1.7 -bootclasspath {{.AndroidPlatform}}/android.jar *.java
   194  jar c -C $WORK/javac-output .
   195  `))
   196  
   197  var bindAppleTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
   198  WORK=$WORK
   199  rm -r -f "{{.Output}}.xcframework"
   200  GOOS=ios CGO_ENABLED=1 gobind -lang=go,objc -outdir=$WORK/ios -tags=ios{{if .Prefix}} -prefix={{.Prefix}}{{end}} github.com/SpiderOak/mobile/asset
   201  mkdir -p $WORK/ios/src-arm64
   202  PWD=$WORK/ios/src-arm64 GOMODCACHE=$GOPATH/pkg/mod GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphoneos GOPATH=$WORK/ios:$GOPATH go mod tidy
   203  PWD=$WORK/ios/src-arm64 GOMODCACHE=$GOPATH/pkg/mod GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphoneos GOPATH=$WORK/ios:$GOPATH go build -x -buildmode=c-archive -o $WORK/{{.Output}}-ios-arm64.a ./gobind
   204  mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
   205  ln -s A $WORK/ios/iphoneos/{{.Output}}.framework/Versions/Current
   206  ln -s Versions/Current/Headers $WORK/ios/iphoneos/{{.Output}}.framework/Headers
   207  ln -s Versions/Current/{{.Output}} $WORK/ios/iphoneos/{{.Output}}.framework/{{.Output}}
   208  xcrun lipo $WORK/{{.Output}}-ios-arm64.a -create -o $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/{{.Output}}
   209  cp $WORK/ios/src/gobind/{{.Prefix}}Asset.objc.h $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers/{{.Prefix}}Asset.objc.h
   210  mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
   211  cp $WORK/ios/src/gobind/Universe.objc.h $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers/Universe.objc.h
   212  mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
   213  cp $WORK/ios/src/gobind/ref.h $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers/ref.h
   214  mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
   215  mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
   216  mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Resources
   217  ln -s Versions/Current/Resources $WORK/ios/iphoneos/{{.Output}}.framework/Resources
   218  mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Resources
   219  mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Modules
   220  ln -s Versions/Current/Modules $WORK/ios/iphoneos/{{.Output}}.framework/Modules
   221  xcodebuild -create-xcframework -framework $WORK/ios/iphoneos/{{.Output}}.framework -output {{.Output}}.xcframework
   222  `))
   223  
   224  func TestBindAppleAll(t *testing.T) {
   225  	if !xcodeAvailable() {
   226  		t.Skip("Xcode is missing")
   227  	}
   228  	defer func() {
   229  		xout = os.Stderr
   230  		buildN = false
   231  		buildX = false
   232  		buildO = ""
   233  		buildTarget = ""
   234  		bindPrefix = ""
   235  	}()
   236  	buildN = true
   237  	buildX = true
   238  	buildO = "Asset.xcframework"
   239  	buildTarget = "ios"
   240  
   241  	buf := new(bytes.Buffer)
   242  	xout = buf
   243  	gopath = filepath.SplitList(goEnv("GOPATH"))[0]
   244  	if goos == "windows" {
   245  		os.Setenv("HOMEDRIVE", "C:")
   246  	}
   247  	cmdBind.flag.Parse([]string{"github.com/SpiderOak/mobile/asset"})
   248  	if err := runBind(cmdBind); err != nil {
   249  		t.Log(buf.String())
   250  		t.Fatal(err)
   251  	}
   252  }
   253  
   254  func TestBindWithGoModules(t *testing.T) {
   255  	if runtime.GOOS == "android" || runtime.GOOS == "ios" {
   256  		t.Skipf("gomobile and gobind are not available on %s", runtime.GOOS)
   257  	}
   258  
   259  	dir, err := ioutil.TempDir("", "gomobile-test")
   260  	if err != nil {
   261  		t.Fatal(err)
   262  	}
   263  	defer os.RemoveAll(dir)
   264  
   265  	if out, err := exec.Command("go", "build", "-o="+dir, "github.com/SpiderOak/mobile/cmd/gobind").CombinedOutput(); err != nil {
   266  		t.Fatalf("%v: %s", err, string(out))
   267  	}
   268  	if out, err := exec.Command("go", "build", "-o="+dir, "github.com/SpiderOak/mobile/cmd/gomobile").CombinedOutput(); err != nil {
   269  		t.Fatalf("%v: %s", err, string(out))
   270  	}
   271  	path := dir
   272  	if p := os.Getenv("PATH"); p != "" {
   273  		path += string(filepath.ListSeparator) + p
   274  	}
   275  
   276  	for _, target := range []string{"android", "ios"} {
   277  		t.Run(target, func(t *testing.T) {
   278  			switch target {
   279  			case "android":
   280  				if _, err := sdkpath.AndroidAPIPath(minAndroidAPI); err != nil {
   281  					t.Skip("No compatible Android API platform found, skipping bind")
   282  				}
   283  			case "ios":
   284  				if !xcodeAvailable() {
   285  					t.Skip("Xcode is missing")
   286  				}
   287  			}
   288  
   289  			var out string
   290  			switch target {
   291  			case "android":
   292  				out = filepath.Join(dir, "cgopkg.aar")
   293  			case "ios":
   294  				out = filepath.Join(dir, "Cgopkg.xcframework")
   295  			}
   296  
   297  			tests := []struct {
   298  				Name string
   299  				Path string
   300  				Dir  string
   301  			}{
   302  				{
   303  					Name: "Absolute Path",
   304  					Path: "github.com/SpiderOak/mobile/bind/testdata/cgopkg",
   305  				},
   306  				{
   307  					Name: "Relative Path",
   308  					Path: "./bind/testdata/cgopkg",
   309  					Dir:  filepath.Join("..", ".."),
   310  				},
   311  			}
   312  
   313  			for _, tc := range tests {
   314  				tc := tc
   315  				t.Run(tc.Name, func(t *testing.T) {
   316  					cmd := exec.Command(filepath.Join(dir, "gomobile"), "bind", "-target="+target, "-o="+out, tc.Path)
   317  					cmd.Env = append(os.Environ(), "PATH="+path, "GO111MODULE=on")
   318  					cmd.Dir = tc.Dir
   319  					if out, err := cmd.CombinedOutput(); err != nil {
   320  						t.Errorf("gomobile bind failed: %v\n%s", err, string(out))
   321  					}
   322  				})
   323  			}
   324  		})
   325  	}
   326  }