<commit_before>// 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/jiri"
	"v.io/jiri/tool"
)

var (
	checkExportedSymbols = []string{"swift_io_v_v23_V_nativeInitGlobal", "swift_io_v_v23_context_VContext_nativeWithCancel"}
	checkSharedTypes     = []string{"SwiftByteArray", "SwiftByteArrayArray", "GoContextHandle"}
)

func resetVars() {
	buildCgo = false
	buildFramework = false

	if sh != nil {
		sh.Cleanup()
	}
	sh = newShell()

	flagBuildMode = buildModeArchive
	flagBuildDirCgo = ""
	flagOutDirSwift = sh.MakeTempDir()
	flagReleaseMode = false
	flagTargetArch = targetArchAll

	targetArchs = []string{} // gets set by parseBuildFlags()
	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-v23-profile", "install", "-target="+target, "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 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{"build-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{"build-cgo", "build-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{"build-cgo", "build-framework"}); err == nil {
		t.Error("Expected error building framework for 1 architecture")
		return
	}
}

func TestCgoBuildForSimulator64(t *testing.T) {
	jirix := initForTest(t)
	if err := testCgoBuildForArch(jirix, targetArchAmd64, buildModeArchive); err != nil {
		t.Error(err)
	}
	if err := testCgoBuildForArch(jirix, targetArchAmd64, buildModeShared); err != nil {
		t.Error(err)
	}
}

func TestCgoBuildForArm(t *testing.T) {
	jirix := initForTest(t)
	// Expect error for ARM currently as of Go 1.5
	if err := testCgoBuildForArch(jirix, targetArchArm, buildModeArchive); err == nil {
		t.Error("Expected error for building unsupported 32-bit arm")
	}
}

func TestCgoBuildForArm64(t *testing.T) {
	jirix := initForTest(t)
	if err := testCgoBuildForArch(jirix, targetArchArm64, buildModeArchive); err != nil {
		t.Error(err)
	}
}

func TestCgoBuildForAll(t *testing.T) {
	jirix := initForTest(t)
	if err := testCgoBuildForArch(jirix, targetArchAll, buildModeArchive); err != nil {
		t.Error(err)
	}
}

func testCgoBuildForArch(jirix *jiri.X, arch string, buildMode string) error {
	resetVars()
	buildCgo = true
	flagBuildMode = buildMode
	flagTargetArch = arch
	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", frameworkBinaryName, 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 checkExportedSymbols {
		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 checkSharedTypes {
		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 checkExportedSymbols {
		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)
	}
	return nil
}

func TestUniversalFrameworkBuilds(t *testing.T) {
	jirix := initForTest(t)
	flagTargetArch = targetArchAll
	if err := parseBuildFlags(); err != nil {
		t.Error(err)
		return
	}
	// Make sure v23 exports exist<commit_msg>jiri-swift: Set TMPDIR before running swift tests.<commit_after>// 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/jiri"
	"v.io/jiri/tool"
)

var (
	checkExportedSymbols = []string{"swift_io_v_v23_V_nativeInitGlobal", "swift_io_v_v23_context_VContext_nativeWithCancel"}
	checkSharedTypes     = []string{"SwiftByteArray", "SwiftByteArrayArray", "GoContextHandle"}
)

func resetVars() {
	buildCgo = false
	buildFramework = false

	if sh != nil {
		sh.Cleanup()
	}
	sh = newShell()

	flagBuildMode = buildModeArchive
	flagBuildDirCgo = ""
	flagOutDirSwift = sh.MakeTempDir()
	flagReleaseMode = false
	flagTargetArch = targetArchAll

	targetArchs = []string{} // gets set by parseBuildFlags()
	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-v23-profile", "install", "-target="+target, "base").Run()
	}
}

func initForTest(t *testing.T) (*jiri.X, func()) {
	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)
	}

	// Set TMPDIR to something unique, so the iOS builds do not clobber any
	// other builds running concurrently.
	oldTempDir := os.Getenv("TMPDIR")
	tempDir, err := ioutil.TempDir("", "jiri-swift-test-")
	if err != nil {
		t.Fatal(err)
	}
	if err := os.Setenv("TMPDIR", tempDir); err != nil {
		t.Fatal(err)
	}
	cleanup := func() {
		if err := os.Setenv("TMPDIR", oldTempDir); err != nil {
			t.Fatal(err)
		}
		if err := os.RemoveAll(tempDir); err != nil {
			t.Fatal(err)
		}
	}

	verboseFlag := true
	jirix := &jiri.X{Context: tool.NewContext(tool.ContextOpts{Verbose: &verboseFlag}), Root: root}
	// Clean before testing
	runClean(jirix, []string{})
	return jirix, cleanup
}

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, cleanup := initForTest(t)
	defer cleanup()
	// 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{"build-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{"build-cgo", "build-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{"build-cgo", "build-framework"}); err == nil {
		t.Error("Expected error building framework for 1 architecture")
		return
	}
}

func TestCgoBuildForSimulator64(t *testing.T) {
	jirix, cleanup := initForTest(t)
	defer cleanup()
	if err := testCgoBuildForArch(jirix, targetArchAmd64, buildModeArchive); err != nil {
		t.Error(err)
	}
	if err := testCgoBuildForArch(jirix, targetArchAmd64, buildModeShared); err != nil {
		t.Error(err)
	}
}

func TestCgoBuildForArm(t *testing.T) {
	jirix, cleanup := initForTest(t)
	defer cleanup()
	// Expect error for ARM currently as of Go 1.5
	if err := testCgoBuildForArch(jirix, targetArchArm, buildModeArchive); err == nil {
		t.Error("Expected error for building unsupported 32-bit arm")
	}
}

func TestCgoBuildForArm64(t *testing.T) {
	jirix, cleanup := initForTest(t)
	defer cleanup()
	if err := testCgoBuildForArch(jirix, targetArchArm64, buildModeArchive); err != nil {
		t.Error(err)
	}
}

func TestCgoBuildForAll(t *testing.T) {
	jirix, cleanup := initForTest(t)
	defer cleanup()
	if err := testCgoBuildForArch(jirix, targetArchAll, buildModeArchive); err != nil {
		t.Error(err)
	}
}

func testCgoBuildForArch(jirix *jiri.X, arch string, buildMode string) error {
	resetVars()
	buildCgo = true
	flagBuildMode = buildMode
	flagTargetArch = arch
	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", frameworkBinaryName, 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 checkExportedSymbols {
		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 checkSharedTypes {
		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 checkExportedSymbols {
		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)
	}
	return nil
}

func TestUniversalFrameworkBuilds(t *testing.T) {
	jirix, cleanup := initForTest(t)
	defer cleanup()

	flagTargetArch = targetArchAll
	if err := parseBuildFlags(); err != nil {
		t.Error(err)
		return
	}
	// Make sure v23 exports exist
{'fraction_non_alphanumeric': 0.1266327649815535, 'fraction_numerical': 0.007079469538338818, 'mean_word_length': 4.294008973343891, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '628320', 'n_tokens_mistral': 7292, 'n_tokens_neox': 6926, 'n_words': 1908}