blob: 4a105ca67b2098d0fe9979c795c053c857eee86c [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin
package main
import (
"flag"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"testing"
"v.io/jiri"
"v.io/jiri/tool"
)
func resetVars() {
buildCgo = false
buildFramework = false
if sh != nil {
sh.Cleanup()
}
sh = newShell()
flagBuildMode = buildModeArchive
flagBuildDirCgo = ""
flagOutDirSwift = sh.MakeTempDir()
flagProject = ""
flagReleaseMode = false
flagTargetArch = targetArchAll
targetArchs = []string{} // gets set by parseBuildFlags()
selectedProject = nil
parseBuildFlags()
}
func TestMain(m *testing.M) {
flag.Parse()
resetVars()
// Ensure we have our necessary go profiles installed
installProfiles()
ret := m.Run()
if sh != nil {
sh.Cleanup()
}
os.Exit(ret)
}
func installProfiles() {
targets := []string{"arm64-ios", "amd64-ios"}
for _, target := range targets {
sh.Cmd("jiri", "profile", "install", "-target="+target, "v23:base").Run()
}
}
func initForTest(t *testing.T) *jiri.X {
resetVars()
// Capture JIRI_ROOT using a relative path. We need the real JIRI_ROOT as
// the real of jiri-swift needs the proper profiles installed and itself calls
// out to jiri via GOSH.
root, err := filepath.Abs(filepath.Join("..", "..", "..", "..", "..", "..", ".."))
if err != nil {
t.Fatal(err)
}
// Sanity check that's correct
if _, err := os.Stat(filepath.Join(root, "release", "swift")); err != nil {
t.Fatal("Real JIRI_ROOT was not properly set: ", root)
}
verboseFlag := true
jirix := &jiri.X{Context: tool.NewContext(tool.ContextOpts{Verbose: &verboseFlag}), Root: root}
// Clean before testing
runClean(jirix, []string{})
return jirix
}
func TestParseProjectFlags(t *testing.T) {
resetVars()
if err := parseProjectFlag(); err == nil {
t.Errorf("Expected error when no project is set")
}
flagProject = "VanadiumCore"
if err := parseProjectFlag(); err != nil {
t.Errorf("Expected no error for VandiumCore")
}
flagProject = "vanadiumcore"
if err := parseProjectFlag(); err != nil {
t.Errorf("Expected no error for vanadiumcore")
}
flagProject = "SyncbaseCore"
if err := parseProjectFlag(); err != nil {
t.Errorf("Expected no error for SyncbaseCore")
}
flagProject = "syncbasecore"
if err := parseProjectFlag(); err != nil {
t.Errorf("Expected no error for syncbasecore")
}
flagProject = "Something else"
if err := parseProjectFlag(); err == nil {
t.Errorf("Expected error non-project name")
}
}
func TestParseBuildFlags(t *testing.T) {
resetVars()
// Test shared only working on amd64
flagBuildMode = buildModeShared // should fail on all
flagTargetArch = targetArchAll
if err := parseBuildFlags(); err == nil {
t.Errorf("Expected error in building all in shared mode")
}
flagTargetArch = targetArchArm
if err := parseBuildFlags(); err == nil {
t.Errorf("Expected error in building arm in shared mode")
}
flagTargetArch = targetArchArm64
if err := parseBuildFlags(); err == nil {
t.Errorf("Expected error in building arm64 in shared mode")
}
flagTargetArch = targetArchAmd64
if err := parseBuildFlags(); err != nil {
t.Errorf("Unexpected error setting to build amd64 in shared mode: %v", err)
}
flagBuildMode = buildModeArchive // should work on all
flagTargetArch = targetArchAll
if err := parseBuildFlags(); err != nil {
t.Errorf("Unexpected error setting to build all in archive mode: %v", err)
}
flagTargetArch = targetArchArm
if err := parseBuildFlags(); err == nil {
t.Errorf("Expected 32-bit arm to fail as unsupported in archive mode: %v", err)
}
flagTargetArch = targetArchArm64
if err := parseBuildFlags(); err != nil {
t.Errorf("Unexpected error setting to build arm64 in archive mode: %v", err)
}
flagTargetArch = targetArchAmd64
if err := parseBuildFlags(); err != nil {
t.Errorf("Unexpected error setting to build amd64 in archive mode: %v", err)
}
}
func TestParseBuildArgs(t *testing.T) {
jirix := initForTest(t)
// Default case -- no args
if err := parseBuildArgs(jirix, []string{}); err != nil {
t.Error(err)
return
}
if !buildCgo || !buildFramework {
t.Error("Default no args case didn't result in building cgo & the framework")
return
}
// Cgo binary
resetVars()
if err := parseBuildArgs(jirix, []string{"cgo"}); err != nil {
t.Error(err)
return
}
if !buildCgo || buildFramework {
t.Error("Should only build the cgo binary")
return
}
// Cgo binary + framework
resetVars()
if err := parseBuildArgs(jirix, []string{"cgo", "framework"}); err != nil {
t.Error(err)
return
}
if !buildCgo || !buildFramework {
t.Error("Should the cgo binary & framework")
return
}
// Framework requires universal
resetVars()
flagTargetArch = targetArchAmd64
if err := parseBuildArgs(jirix, []string{"cgo", "framework"}); err == nil {
t.Error("Expected error building framework for 1 architecture")
return
}
}
func TestAllCgoBuildForArmFails(t *testing.T) {
for _, p := range projects {
jirix := initForTest(t)
// Expect error for ARM currently as of Go 1.5
if err := testCgoBuildForArch(jirix, p, targetArchArm, buildModeArchive); err == nil {
t.Error("Expected error for building unsupported 32-bit arm")
}
}
}
func TestVanadiumCoreCgoBuildForArm64(t *testing.T) {
jirix := initForTest(t)
if err := testCgoBuildForArch(jirix, projectVanadiumCore, targetArchArm64, buildModeArchive); err != nil {
t.Error(err)
}
// Currently disabled as we're not using shared libraries and the unit test can take a while.
// Turn back on if we move to shared libraries to get around bitcode restrictions.
//if err := testCgoBuildForArch(jirix, projectVanadiumCore, targetArchArm64, buildModeShared); err != nil {
// t.Error(err)
//}
}
// We don't test amd64 as that gets tested in the universal builds below (which also tests the arm64 build but
// doesn't inspect the results as closely).
func TestSyncbaseCoreCgoBuildForArm64(t *testing.T) {
jirix := initForTest(t)
if err := testCgoBuildForArch(jirix, projectSyncbaseCore, targetArchArm64, buildModeArchive); err != nil {
t.Error(err)
}
// Currently disabled as we're not using shared libraries and the unit test can take a while.
// Turn back on if we move to shared libraries to get around bitcode restrictions.
//if err := testCgoBuildForArch(jirix, projectSyncbaseCore, targetArchArm64, buildModeShared); err != nil {
// t.Error(err)
//}
}
func testCgoBuildForArch(jirix *jiri.X, p *project, arch string, buildMode string) error {
resetVars()
buildCgo = true
flagBuildMode = buildMode
flagTargetArch = arch
flagProject = p.name
if err := parseProjectFlag(); err != nil {
return err
}
if err := parseBuildFlags(); err != nil {
return err
}
if err := runBuildCgo(jirix); err != nil {
return err
}
if err := verifyCgoBuild(jirix); err != nil {
return err
}
return nil
}
func verifyCgoBuild(jirix *jiri.X) error {
// Verify library exists
for _, targetArch := range targetArchs {
binaryPath, err := cgoBinaryPath(jirix, targetArch, flagBuildMode)
if err != nil {
return err
}
if !pathExists(binaryPath) {
return fmt.Errorf("Could not find binary at %v", binaryPath)
} else {
// Verify library is built for iPhone only
if err := verifyCgoBinaryForIOS(binaryPath); err != nil {
return err
}
// Verify exported symbols are present
if err := verifyCgoBinaryExports(binaryPath); err != nil {
return err
}
// Verify target architecture
verifyCgoBinaryArchOrPanic(binaryPath, targetArch)
}
}
// Verify shared header exists
if err := verifyCgoSharedHeaders(jirix); err != nil {
return err
}
// Verify generated header (simple sanity check)
if err := verifyCgoGeneratedHeader(jirix); err != nil {
return err
}
return nil
}
func cgoBinaryPath(jirix *jiri.X, arch string, buildMode string) (string, error) {
binaryPath := path.Join(getSwiftTargetDir(jirix), fmt.Sprintf("%v_%v", selectedProject.libraryBinaryName, arch))
switch buildMode {
case buildModeArchive:
binaryPath = binaryPath + ".a"
case buildModeShared:
binaryPath = binaryPath + ".dylib"
default:
return "", fmt.Errorf("Unsupported build mode %v", buildMode)
}
return binaryPath, nil
}
func verifyCgoBinaryForIOS(binaryPath string) error {
stdout := sh.Cmd("otool", "-l", binaryPath).Stdout()
if strings.Contains(stdout, "LC_VERSION_MIN_MACOSX") {
return fmt.Errorf("Binary contains LC_VERSION_MIN_MACOSX indicating an OS-X build binary")
}
if !strings.Contains(stdout, "LC_VERSION_MIN_IPHONEOS") {
return fmt.Errorf("Binary is missing LC_VERSION_MIN_IPHONEOS so it's not clear that it's built for the iOS platform")
}
return nil
}
// verifyCgoBinaryExports looks at the symbols in the library to look for cgo-wrapper exported functions
func verifyCgoBinaryExports(binaryPath string) error {
stdout := sh.Cmd("otool", "-l", binaryPath).Stdout()
// Test a couple of key functions to make sure we're getting our exports
for _, symbol := range selectedProject.testCheckExportedSymbols {
if !strings.Contains(stdout, symbol) {
fmt.Errorf("Missing %v in %v export table", symbol, binaryPath)
}
}
return nil
}
func verifyCgoSharedHeaders(jirix *jiri.X) error {
goTypesPath := path.Join(getSwiftTargetDir(jirix), "go_types.h")
if !pathExists(goTypesPath) {
return fmt.Errorf("Missing go_types.h at %v", goTypesPath)
}
bytes, err := ioutil.ReadFile(goTypesPath)
if err != nil {
return err
}
goTypes := string(bytes)
for _, typedef := range selectedProject.testCheckSharedTypes {
if !strings.Contains(goTypes, typedef) {
return fmt.Errorf("Missing shared typedef of %v in %v", typedef, goTypesPath)
}
}
return nil
}
func verifyCgoGeneratedHeader(jirix *jiri.X) error {
cgoExportsPath := path.Join(getSwiftTargetDir(jirix), "cgo_exports.h")
if !pathExists(cgoExportsPath) {
return fmt.Errorf("Missing cgo_exports.h at %v", cgoExportsPath)
}
bytes, err := ioutil.ReadFile(cgoExportsPath)
if err != nil {
return err
}
cgoExports := string(bytes)
for _, symbol := range selectedProject.testCheckExportedSymbols {
if !strings.Contains(cgoExports, symbol) {
return fmt.Errorf("Missing symbol %v in %v", symbol, cgoExportsPath)
}
}
if !strings.Contains(cgoExports, "#ifdef __LP64__") {
return fmt.Errorf("Missing __LP64__ guard for 32/64-bit cleaness in %v", cgoExportsPath)
}
if strings.Count(cgoExports, "_check_for_32_bit_pointer_matching_GoInt") != 1 {
return fmt.Errorf("32-bit check should only occur once %v", cgoExportsPath)
}
if strings.Count(cgoExports, "_check_for_64_bit_pointer_matching_GoInt") != 1 {
return fmt.Errorf("64-bit check should only occur once %v", cgoExportsPath)
}
s, e := strings.Index(cgoExports, "/* Start of preamble"), strings.Index(cgoExports, "/* End of preamble")
if s == -1 || e == -1 {
return fmt.Errorf("Missing preamble section")
}
for _, line := range strings.Split(cgoExports[s:e], "\n") {
switch {
case strings.TrimSpace(line) == "":
continue
case strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t"):
return fmt.Errorf("Looks like indented code in preamble")
}
}
return nil
}
func TestVanadiumCoreUniversalFrameworkBuilds(t *testing.T) {
testUniversalFrameworkBuild(t, projectVanadiumCore)
}
func TestSyncbaseCoreUniversalFrameworkBuilds(t *testing.T) {
testUniversalFrameworkBuild(t, projectSyncbaseCore)
}
func testUniversalFrameworkBuild(t *testing.T, p *project) {
jirix := initForTest(t)
flagTargetArch = targetArchAll
flagProject = p.name
if err := parseProjectFlag(); err != nil {
t.Error(err)
return
}
if err := parseBuildFlags(); err != nil {
t.Error(err)
return
}
// Make sure VanadiumCore exports exist
if err := runBuildCgo(jirix); err != nil {
t.Error(err)
return
}
if err := runBuildFramework(jirix); err != nil {
t.Error(err)
return
}
binaryPath := filepath.Join(flagOutDirSwift, selectedProject.frameworkName, selectedProject.frameworkBinaryName)
if err := verifyCgoBinaryForIOS(binaryPath); err != nil {
t.Error(err)
return
}
for _, targetArch := range targetArchs {
appleArch, _ := appleArchFromGoArch(targetArch)
sh.Cmd("lipo", binaryPath, "-verify_arch", appleArch).Run()
if !pathExists(filepath.Join(flagOutDirSwift, selectedProject.frameworkName, "Modules", selectedProject.frameworkBinaryName+".swiftmodule", appleArch+".swiftdoc")) {
t.Errorf("Missing swift moduledoc for architecture %v", targetArch)
}
if !pathExists(filepath.Join(flagOutDirSwift, selectedProject.frameworkName, "Modules", selectedProject.frameworkBinaryName+".swiftmodule", appleArch+".swiftmodule")) {
t.Errorf("Missing swift module for architecture %v", targetArch)
}
}
}