func registerFail(format string, v ...interface{}) {
	log.Error(format, v...)

	if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
		log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
	}

	os.Exit(1)
}
예제 #2
0
func main() {
	configs := createConfigsModelFromEnvs()

	fmt.Println()
	configs.print()

	if explanation, err := configs.validate(); err != nil {
		fmt.Println()
		log.Error("Issue with input: %s", err)
		fmt.Println()

		if explanation != "" {
			fmt.Println(explanation)
			fmt.Println()
		}

		os.Exit(1)
	}

	err := os.Chmod(configs.GradlewPath, 0770)
	if err != nil {
		log.Error("Failed to add executable permission on gradlew file (%s), error: %s", configs.GradlewPath, err)
		os.Exit(1)
	}

	fmt.Println()
	log.Info("Running gradle task...")
	if err := runGradleTask(configs.GradlewPath, configs.GradleFile, configs.UnitTestTasks, configs.UnitTestFlags); err != nil {
		log.Error("Gradle task failed, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_GRADLE_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_GRADLE_TEST_RESULT", err)
		}

		os.Exit(1)
	}

	if err := exportEnvironmentWithEnvman("BITRISE_GRADLE_TEST_RESULT", "succeeded"); err != nil {
		log.Warn("Failed to export environment: %s, error: %s", "BITRISE_GRADLE_TEST_RESULT", err)
	}
}
예제 #3
0
func main() {
	configs := createConfigsModelFromEnvs()

	fmt.Println()
	configs.print()

	if err := configs.validate(); err != nil {
		log.Error("Issue with input: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}

	// Custom Options
	resultLogPth := filepath.Join(configs.DeployDir, "TestResult.xml")
	customOptions := []string{"--result", resultLogPth}
	if configs.CustomOptions != "" {
		options, err := shellquote.Split(configs.CustomOptions)
		if err != nil {
			log.Error("Failed to split params (%s), error: %s", configs.CustomOptions, err)

			if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
				log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
			}

			os.Exit(1)
		}

		customOptions = append(customOptions, options...)
	}
	// ---

	//
	// build
	fmt.Println()
	log.Info("Runing all nunit test projects in solution: %s", configs.XamarinSolution)

	builder, err := builder.New(configs.XamarinSolution, []constants.ProjectType{}, false)
	if err != nil {
		log.Error("Failed to create xamarin builder, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}

	prepareCallback := func(solutionName string, projectName string, projectType constants.ProjectType, command *tools.Editable) {
		if projectType == constants.ProjectTypeNunitTest {
			(*command).SetCustomOptions(customOptions...)
		}
	}

	callback := func(solutionName string, projectName string, projectType constants.ProjectType, commandStr string, alreadyPerformed bool) {
		fmt.Println()
		if projectName == "" {
			log.Info("Building solution: %s", solutionName)
		} else {
			if projectType == constants.ProjectTypeNunitTest {
				log.Info("Building test project: %s", projectName)
			} else {
				log.Info("Building project: %s", projectName)
			}
		}

		log.Done("$ %s", commandStr)

		if alreadyPerformed {
			log.Warn("build command already performed, skipping...")
		}

		fmt.Println()
	}

	warnings, err := builder.BuildAllNunitTestProjects(configs.XamarinConfiguration, configs.XamarinPlatform, prepareCallback, callback)
	resultLog, logErr := testResultLogContent(resultLogPth)
	if logErr != nil {
		log.Warn("Failed to read test result, error: %s", logErr)
	}

	for _, warning := range warnings {
		log.Warn(warning)
	}

	if err != nil {
		log.Error("Test run failed, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		if resultLog != "" {
			if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", resultLog); err != nil {
				log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", err)
			}
		}

		os.Exit(1)
	}

	if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "succeeded"); err != nil {
		log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
	}

	if resultLog != "" {
		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", resultLog); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", err)
		}
	}
}
func main() {
	configs := createConfigsModelFromEnvs()

	fmt.Println()
	configs.print()

	if err := configs.validate(); err != nil {
		log.Error("Issue with input: %s", err)
		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}
		os.Exit(1)
	}

	//
	// build
	fmt.Println()
	log.Info("Building all iOS Xamarin UITest and Referred Projects in solution: %s", configs.XamarinSolution)

	builder, err := builder.New(configs.XamarinSolution, []constants.ProjectType{constants.ProjectTypeIOS}, false)
	if err != nil {
		log.Error("Failed to create xamarin builder, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}

	callback := func(solutionName string, projectName string, projectType constants.ProjectType, commandStr string, alreadyPerformed bool) {
		fmt.Println()
		if projectType == constants.ProjectTypeXamarinUITest {
			log.Info("Building test project: %s", projectName)
		} else {
			log.Info("Building project: %s", projectName)
		}

		log.Done("$ %s", commandStr)

		if alreadyPerformed {
			log.Warn("build command already performed, skipping...")
		}

		fmt.Println()
	}

	warnings, err := builder.BuildAllXamarinUITestAndReferredProjects(configs.XamarinConfiguration, configs.XamarinPlatform, nil, callback)
	for _, warning := range warnings {
		log.Warn(warning)
	}
	if err != nil {
		log.Error("Build failed, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}

	projectOutputMap, err := builder.CollectProjectOutputs(configs.XamarinConfiguration, configs.XamarinPlatform)
	if err != nil {
		log.Error("Failed to collect project outputs, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}

	testProjectOutputMap, warnings, err := builder.CollectXamarinUITestProjectOutputs(configs.XamarinConfiguration, configs.XamarinPlatform)
	for _, warning := range warnings {
		log.Warn(warning)
	}
	if err != nil {
		log.Error("Failed to collect test project output, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}
	// ---

	//
	// Test Cloud submit
	solutionDir := filepath.Dir(configs.XamarinSolution)
	pattern := filepath.Join(solutionDir, "packages/Xamarin.UITest.*/tools/test-cloud.exe")
	testClouds, err := filepath.Glob(pattern)
	if err != nil {
		log.Error("Failed to find test-cloud.exe path with pattern (%s), error: %s", pattern, err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}
	if len(testClouds) == 0 {
		if err != nil {
			log.Error("No test-cloud.exe found path with pattern (%s)", pattern)

			if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
				log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
			}

			os.Exit(1)
		}
	}

	testCloud, err := testcloud.NewModel(testClouds[0])
	if err != nil {
		log.Error("Failed to create test cloud model, error: %s", err)
		os.Exit(1)
	}

	testCloud.SetAPIKey(configs.APIKey)
	testCloud.SetUser(configs.User)
	testCloud.SetDevices(configs.Devices)
	testCloud.SetIsAsyncJSON(configs.IsAsync == "yes")
	testCloud.SetSeries(configs.Series)

	// If test cloud runs in asnyc mode test result will not be saved into file
	resultLogPth := filepath.Join(configs.DeployDir, "TestResult.xml")
	if configs.IsAsync != "yes" {
		testCloud.SetNunitXMLPth(resultLogPth)
	}

	// Parallelization
	if configs.Parallelization != "none" {
		parallelization, err := testcloud.ParseParallelization(configs.Parallelization)
		if err != nil {
			log.Error("Failed to parse parallelization, error: %s", err)

			if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
				log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
			}

			os.Exit(1)
		}

		testCloud.SetParallelization(parallelization)
	}
	// ---

	// Custom Options
	if configs.CustomOptions != "" {
		options, err := shellquote.Split(configs.CustomOptions)
		if err != nil {
			log.Error("Failed to split params (%s), error: %s", configs.CustomOptions, err)

			if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
				log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
			}

			os.Exit(1)
		}

		testCloud.SetCustomOptions(options...)
	}
	// ---

	// Artifacts
	resultLog := ""

	for testProjectName, testProjectOutput := range testProjectOutputMap {
		if len(testProjectOutput.ReferredProjectNames) == 0 {
			log.Warn("Test project (%s) does not refers to any project, skipping...", testProjectName)
			continue
		}

		for _, projectName := range testProjectOutput.ReferredProjectNames {
			projectOutput, ok := projectOutputMap[projectName]
			if !ok {
				continue
			}

			ipaPth := ""
			dsymPth := ""
			for _, output := range projectOutput.Outputs {
				if output.OutputType == constants.OutputTypeIPA {
					ipaPth = output.Pth
				}

				if output.OutputType == constants.OutputTypeDSYM {
					dsymPth = output.Pth
				}
			}

			if ipaPth == "" {
				log.Warn("No ipa generated for project: %s", projectName)
			}
			if dsymPth == "" {
				log.Warn("No dsym generated for project: %s", projectName)
			}

			// Submit
			fmt.Println()
			log.Info("Testing (%s) against (%s)", testProjectName, projectName)
			log.Detail("test dll: %s", testProjectOutput.Output.Pth)
			log.Detail("ipa: %s", ipaPth)
			log.Detail("dsym: %s", dsymPth)

			testCloud.SetAssemblyDir(filepath.Dir(testProjectOutput.Output.Pth))
			testCloud.SetIPAPth(ipaPth)
			testCloud.SetDSYMPth(dsymPth)

			fmt.Println()
			log.Info("Submitting:")
			log.Done("$ %s", testCloud.PrintableCommand())

			lines := []string{}
			callback := func(line string) {
				log.Detail(line)

				lines = append(lines, line)
			}

			err := testCloud.Submit(callback)

			// If test cloud runs in asnyc mode test result will not be saved into file
			if configs.IsAsync != "yes" {
				testLog, logErr := testResultLogContent(resultLogPth)
				if logErr != nil {
					log.Warn("Failed to read test result, error: %s", logErr)
				}
				resultLog = testLog
			}

			if err != nil {
				log.Error("Submit failed, error: %s", err)

				if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
					log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
				}

				if resultLog != "" {
					if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", resultLog); err != nil {
						log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", err)
					}
				}

				os.Exit(1)
			}
			// ---

			if configs.IsAsync == "yes" {
				fmt.Println()
				log.Info("Preocessing json result:")

				jsonLine := ""
				for _, line := range lines {
					if strings.HasPrefix(line, "{") && strings.HasSuffix(line, "}") {
						jsonLine = line
					}
				}

				if jsonLine != "" {
					var result JSONResultModel
					if err := json.Unmarshal([]byte(jsonLine), &result); err != nil {
						log.Error("Failed to unmarshal result, error: %s", err)
					} else {
						for _, errorMsg := range result.ErrorMessages {
							log.Error(errorMsg)
						}

						if len(result.ErrorMessages) > 0 {
							if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
								log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
							}

							if resultLog != "" {
								if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", resultLog); err != nil {
									log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", err)
								}
							}

							os.Exit(1)
						}

						if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_TO_RUN_ID", result.TestRunID); err != nil {
							log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_TO_RUN_ID", err)
						}

						log.Done("TestRunId (%s) is available in (%s) environment variable", result.TestRunID, "BITRISE_XAMARIN_TEST_TO_RUN_ID")
					}
				}
			}
		}
	}
	// ---

	if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "succeeded"); err != nil {
		log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
	}

	if resultLog != "" {
		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", resultLog); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", err)
		}
	}
}
예제 #5
0
func main() {
	configs := createConfigsModelFromEnvs()
	configs.print()
	if err := configs.validate(); err != nil {
		log.Error("Issue with input: %s", err)
		os.Exit(1)
	}

	fmt.Println()
	log.Info("Other Configs:")

	cleanBuild := (configs.IsCleanBuild == "yes")
	generateCodeCoverage := (configs.GenerateCodeCoverageFiles == "yes")
	exportUITestArtifacts := (configs.ExportUITestArtifacts == "true")
	singleBuild := (configs.IsSingleBuild == "true")
	buildBeforeTest := (configs.ShouldBuildBeforeTest == "yes")
	retryOnFail := (configs.ShouldRetryTestOnFail == "yes")

	// Project-or-Workspace flag
	action := ""
	if strings.HasSuffix(configs.ProjectPath, ".xcodeproj") {
		action = "-project"
	} else if strings.HasSuffix(configs.ProjectPath, ".xcworkspace") {
		action = "-workspace"
	} else {
		log.Error("Iinvalid project file (%s), extension should be (.xcodeproj/.xcworkspace)", configs.ProjectPath)
		if err := cmd.ExportEnvironmentWithEnvman("BITRISE_XCODE_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export: BITRISE_XCODE_TEST_RESULT, error: %s", err)
		}
		os.Exit(1)
	}

	log.Detail("* action: %s", action)

	// Device Destination
	deviceDestination := fmt.Sprintf("platform=%s,name=%s,OS=%s", configs.SimulatorPlatform, configs.SimulatorDevice, configs.SimulatorOsVersion)

	log.Detail("* device_destination: %s", deviceDestination)

	// Output tools versions
	xcodebuildVersion, err := xcodeutil.GetXcodeVersion()
	if err != nil {
		log.Error("Failed to get the version of xcodebuild! Error: %s", err)
		if err := cmd.ExportEnvironmentWithEnvman("BITRISE_XCODE_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export: BITRISE_XCODE_TEST_RESULT, error: %s", err)
		}
		os.Exit(1)
	}

	log.Detail("* xcodebuild_version: %s (%s)", xcodebuildVersion.Version, xcodebuildVersion.BuildVersion)

	xcprettyVersion, err := cmd.GetXcprettyVersion()
	if err != nil {
		log.Warn("Failed to get the xcpretty version! Error: %s", err)
	} else {
		log.Detail("* xcpretty_version: %s", xcprettyVersion)
	}

	// Simulator infos
	simulator, err := xcodeutil.GetSimulator(configs.SimulatorPlatform, configs.SimulatorDevice, configs.SimulatorOsVersion)
	if err != nil {
		log.Error(fmt.Sprintf("failed to get simulator udid, error: %s", err))
		if err := cmd.ExportEnvironmentWithEnvman("BITRISE_XCODE_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export: BITRISE_XCODE_TEST_RESULT, error: %s", err)
		}
		os.Exit(1)
	}

	log.Detail("* simulator_name: %s, UDID: %s, status: %s", simulator.Name, simulator.SimID, simulator.Status)
	fmt.Println()

	buildParams := models.XcodeBuildParamsModel{
		Action:            action,
		ProjectPath:       configs.ProjectPath,
		Scheme:            configs.Scheme,
		DeviceDestination: deviceDestination,
		CleanBuild:        cleanBuild,
	}

	buildTestParams := models.XcodeBuildTestParamsModel{
		BuildParams: buildParams,

		BuildBeforeTest:      buildBeforeTest,
		AdditionalOptions:    configs.TestOptions,
		GenerateCodeCoverage: generateCodeCoverage,
	}

	if singleBuild {
		buildTestParams.CleanBuild = cleanBuild
	}

	//
	// Start simulator
	if simulator.Status == "Shutdown" {
		log.Info("Booting simulator (%s)...", simulator.SimID)

		if err := xcodeutil.BootSimulator(simulator, xcodebuildVersion); err != nil {
			log.Error(fmt.Sprintf("failed to boot simulator, error: %s", err))
			if err := cmd.ExportEnvironmentWithEnvman("BITRISE_XCODE_TEST_RESULT", "failed"); err != nil {
				log.Warn("Failed to export: BITRISE_XCODE_TEST_RESULT, error: %s", err)
			}
			os.Exit(1)
		}

		if configs.WaitForSimulatorBoot == "yes" {
			log.Detail("Waiting for simulator boot")
			progress.SimpleProgress(".", 1*time.Second, func() {
				time.Sleep(60 * time.Second)
			})
		}

		fmt.Println()
	}

	//
	// Run build
	if !singleBuild {
		if rawXcodebuildOutput, exitCode, buildErr := runBuild(buildParams, configs.OutputTool); buildErr != nil {
			if err := saveRawOutputToLogFile(rawXcodebuildOutput, false); err != nil {
				log.Warn("Failed to save the Raw Output, err: %s", err)
			}

			log.Warn("xcode build exit code: %d", exitCode)
			log.Warn("xcode build log:\n%s", rawXcodebuildOutput)
			log.Error("xcode build failed with error: %s", buildErr)
			if err := cmd.ExportEnvironmentWithEnvman("BITRISE_XCODE_TEST_RESULT", "failed"); err != nil {
				log.Warn("Failed to export: BITRISE_XCODE_TEST_RESULT, error: %s", err)
			}
			os.Exit(1)
		}
	}

	//
	// Run test
	rawXcodebuildOutput, exitCode, testErr := runTest(buildTestParams, configs.OutputTool, configs.XcprettyTestOptions, true, retryOnFail)

	if err := saveRawOutputToLogFile(rawXcodebuildOutput, (testErr == nil)); err != nil {
		log.Warn("Failed to save the Raw Output, error %s", err)
	}

	if exportUITestArtifacts {
		if err := saveAttachements(configs.ProjectPath, configs.Scheme); err != nil {
			log.Warn("Failed to export UI test artifacts, error %s", err)
		}
	}

	if testErr != nil {
		log.Warn("xcode test exit code: %d", exitCode)
		log.Error("xcode test failed, error: %s", testErr)
		hint := `If you can't find the reason of the error in the log, please check the raw-xcodebuild-output.log
The log file is stored in $BITRISE_DEPLOY_DIR, and its full path
is available in the $BITRISE_XCODE_RAW_TEST_RESULT_TEXT_PATH environment variable`
		log.Warn(hint)
		if err := cmd.ExportEnvironmentWithEnvman("BITRISE_XCODE_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export: BITRISE_XCODE_TEST_RESULT, error: %s", err)
		}
		os.Exit(1)
	}

	if err := cmd.ExportEnvironmentWithEnvman("BITRISE_XCODE_TEST_RESULT", "succeeded"); err != nil {
		log.Warn("Failed to export: BITRISE_XCODE_TEST_RESULT, error: %s", err)
	}
}
예제 #6
0
func runTest(buildTestParams models.XcodeBuildTestParamsModel, outputTool, xcprettyOptions string, isAutomaticRetryOnReason, isRetryOnFail bool) (string, int, error) {
	handleTestError := func(fullOutputStr string, exitCode int, testError error) (string, int, error) {
		//
		// Automatic retry
		for _, retryReasonPattern := range automaticRetryReasonPatterns {
			if isStringFoundInOutput(retryReasonPattern, fullOutputStr) {
				log.Warn("Automatic retry reason found in log: %s", retryReasonPattern)
				if isAutomaticRetryOnReason {
					log.Detail("isAutomaticRetryOnReason=true - retrying...")
					return runTest(buildTestParams, outputTool, xcprettyOptions, false, false)
				}
				log.Error("isAutomaticRetryOnReason=false, no more retry, stopping the test!")
				return fullOutputStr, exitCode, testError
			}
		}

		//
		// Retry on fail
		if isRetryOnFail {
			log.Warn("Test run failed")
			log.Detail("isRetryOnFail=true - retrying...")
			return runTest(buildTestParams, outputTool, xcprettyOptions, false, false)
		}

		return fullOutputStr, exitCode, testError
	}

	buildParams := buildTestParams.BuildParams

	xcodebuildArgs := []string{buildParams.Action, buildParams.ProjectPath, "-scheme", buildParams.Scheme}
	if buildTestParams.CleanBuild {
		xcodebuildArgs = append(xcodebuildArgs, "clean")
	}
	// the 'build' argument is required *before* the 'test' arg, to prevent
	//  the Xcode bug described in the README, which causes:
	// 'iPhoneSimulator: Timed out waiting 120 seconds for simulator to boot, current state is 1.'
	//  in case the compilation takes a long time.
	// Related Radar link: https://openradar.appspot.com/22413115
	// Demonstration project: https://github.com/bitrise-io/simulator-launch-timeout-includes-build-time

	// for builds < 120 seconds or fixed Xcode versions, one should
	// have the possibility of opting out, because the explicit build arg
	// leads the project to be compiled twice and increase the duration
	// Related issue link: https://github.com/bitrise-io/steps-xcode-test/issues/55
	if buildTestParams.BuildBeforeTest {
		xcodebuildArgs = append(xcodebuildArgs, "build")
	}
	xcodebuildArgs = append(xcodebuildArgs, "test", "-destination", buildParams.DeviceDestination)

	if buildTestParams.GenerateCodeCoverage {
		xcodebuildArgs = append(xcodebuildArgs, "GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES")
		xcodebuildArgs = append(xcodebuildArgs, "GCC_GENERATE_TEST_COVERAGE_FILES=YES")
	}

	if buildTestParams.AdditionalOptions != "" {
		options, err := shellquote.Split(buildTestParams.AdditionalOptions)
		if err != nil {
			return "", 1, fmt.Errorf("failed to parse additional options (%s), error: %s", buildTestParams.AdditionalOptions, err)
		}
		xcodebuildArgs = append(xcodebuildArgs, options...)
	}

	xcprettyArgs := []string{}
	if xcprettyOptions != "" {
		options, err := shellquote.Split(xcprettyOptions)
		if err != nil {
			return "", 1, fmt.Errorf("failed to parse additional options (%s), error: %s", xcprettyOptions, err)
		}
		// get and delete the xcpretty output file, if exists
		xcprettyOutputFilePath := ""
		isNextOptOutputPth := false
		for _, aOpt := range options {
			if isNextOptOutputPth {
				xcprettyOutputFilePath = aOpt
				break
			}
			if aOpt == "--output" {
				isNextOptOutputPth = true
				continue
			}
		}
		if xcprettyOutputFilePath != "" {
			if isExist, err := pathutil.IsPathExists(xcprettyOutputFilePath); err != nil {
				log.Error("Failed to check xcpretty output file status (path: %s), error: %s", xcprettyOutputFilePath, err)
			} else if isExist {
				log.Warn("=> Deleting existing xcpretty output: %s", xcprettyOutputFilePath)
				if err := os.Remove(xcprettyOutputFilePath); err != nil {
					log.Error("Failed to delete xcpretty output file (path: %s), error: %s", xcprettyOutputFilePath, err)
				}
			}
		}
		//
		xcprettyArgs = append(xcprettyArgs, options...)
	}

	log.Info("Running the tests...")

	var rawOutput string
	var err error
	var exit int
	if outputTool == "xcpretty" {
		rawOutput, exit, err = runPrettyXcodeBuildCmd(true, xcprettyArgs, xcodebuildArgs)
	} else {
		rawOutput, exit, err = runXcodeBuildCmd(true, xcodebuildArgs...)
	}

	if err != nil {
		return handleTestError(rawOutput, exit, err)
	}
	return rawOutput, exit, nil
}
func main() {
	configs := createConfigsModelFromEnvs()

	fmt.Println()
	configs.print()

	if err := configs.validate(); err != nil {
		log.Error("Issue with input: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}

	// Get Simulator Infos
	fmt.Println()
	log.Info("Collecting simulator info...")
	simulatorInfo, err := getSimulatorInfo(configs.SimulatorOsVersion, configs.SimulatorDevice)
	if err != nil {
		log.Error("Failed to get simulator infos, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}
	log.Done("Simulator (%s), id: (%s), status: %s", simulatorInfo.Name, simulatorInfo.ID, simulatorInfo.Status)
	// ---

	// Nunit Console path
	nunitConsolePth, err := nunit.SystemNunit3ConsolePath()
	if err != nil {
		log.Error("Failed to get system insatlled nunit3-console.exe path, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}
	// ---

	//
	// build
	fmt.Println()
	log.Info("Building all iOS Xamarin UITest and Referred Projects in solution: %s", configs.XamarinSolution)

	builder, err := builder.New(configs.XamarinSolution, []constants.ProjectType{constants.ProjectTypeIOS}, false)
	if err != nil {
		log.Error("Failed to create xamarin builder, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}

	callback := func(solutionName string, projectName string, projectType constants.ProjectType, commandStr string, alreadyPerformed bool) {
		fmt.Println()
		if projectType == constants.ProjectTypeXamarinUITest {
			log.Info("Building test project: %s", projectName)
		} else {
			log.Info("Building project: %s", projectName)
		}

		log.Done("$ %s", commandStr)

		if alreadyPerformed {
			log.Warn("build command already performed, skipping...")
		}

		fmt.Println()
	}

	warnings, err := builder.BuildAllXamarinUITestAndReferredProjects(configs.XamarinConfiguration, configs.XamarinPlatform, nil, callback)
	for _, warning := range warnings {
		log.Warn(warning)
	}
	if err != nil {
		log.Error("Build failed, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}

	projectOutputMap, err := builder.CollectProjectOutputs(configs.XamarinConfiguration, configs.XamarinPlatform)
	if err != nil {
		log.Error("Failed to collect project outputs, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}

	testProjectOutputMap, warnings, err := builder.CollectXamarinUITestProjectOutputs(configs.XamarinConfiguration, configs.XamarinPlatform)
	for _, warning := range warnings {
		log.Warn(warning)
	}
	if err != nil {
		log.Error("Failed to collect test project output, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}
	// ---

	//
	// Run nunit tests
	nunitConsole, err := nunit.New(nunitConsolePth)
	if err != nil {
		log.Error("Failed to create nunit console model, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}

	resultLogPth := filepath.Join(configs.DeployDir, "TestResult.xml")
	nunitConsole.SetResultLogPth(resultLogPth)

	// Artifacts
	resultLog := ""

	for testProjectName, testProjectOutput := range testProjectOutputMap {
		if len(testProjectOutput.ReferredProjectNames) == 0 {
			log.Warn("Test project (%s) does not refers to any project, skipping...", testProjectName)
			continue
		}

		for _, projectName := range testProjectOutput.ReferredProjectNames {
			projectOutput, ok := projectOutputMap[projectName]
			if !ok {
				continue
			}

			appPth := ""
			for _, output := range projectOutput.Outputs {
				if output.OutputType == constants.OutputTypeAPP {
					appPth = output.Pth
				}
			}

			if appPth == "" {
				log.Error("No app generated for project: %s", projectName)

				if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
					log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
				}

				os.Exit(1)
			}

			// Set APP_BUNDLE_PATH env to let the test know which .app file should be tested
			// This env is used in the Xamarin.UITest project to refer to the .app path
			if err := os.Setenv("APP_BUNDLE_PATH", appPth); err != nil {
				log.Error("Failed to set APP_BUNDLE_PATH environment, without this env test will fail, error: %s", err)

				if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
					log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
				}

				os.Exit(1)
			}

			// Run test
			fmt.Println()
			log.Info("Testing (%s) against (%s)", testProjectName, projectName)
			log.Detail("test dll: %s", testProjectOutput.Output.Pth)
			log.Detail("app: %s", appPth)

			nunitConsole.SetDLLPth(testProjectOutput.Output.Pth)
			nunitConsole.SetTestToRun(configs.TestToRun)

			fmt.Println()
			log.Info("Running Xamarin UITest")
			log.Done("$ %s", nunitConsole.PrintableCommand())
			fmt.Println()

			err := nunitConsole.Run()
			testLog, readErr := testResultLogContent(resultLogPth)
			if readErr != nil {
				log.Warn("Failed to read test result, error: %s", readErr)
			}
			resultLog = testLog

			if err != nil {
				log.Error("Test failed, error: %s", err)

				if errorMsg, err := parseErrorFromResultLog(resultLog); err != nil {
					log.Warn("Failed to parse error message from result log, error: %s", err)
				} else if errorMsg != "" {
					log.Error("%s", errorMsg)
				}

				if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
					log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
				}

				if resultLog != "" {
					if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", resultLog); err != nil {
						log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", err)
					}
				}

				os.Exit(1)
			}
		}
	}

	if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "succeeded"); err != nil {
		log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
	}

	if resultLog != "" {
		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", resultLog); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", err)
		}
	}
}
func main() {
	configs := createConfigsModelFromEnvs()

	fmt.Println()
	configs.print()

	if err := configs.validate(); err != nil {
		fmt.Println()
		log.Error("Issue with input: %s", err)

		os.Exit(1)
	}

	//
	// Create client
	fmt.Println()
	log.Info("Authenticateing")

	jwtConfig := new(jwt.Config)

	if configs.JSONKeyPath != "" {
		jsonKeyPth := ""

		if strings.HasPrefix(configs.JSONKeyPath, "file://") {
			jsonKeyPth = strings.TrimPrefix(configs.JSONKeyPath, "file://")
		} else {
			tmpDir, err := pathutil.NormalizedOSTempDirPath("__google-play-deploy__")
			if err != nil {
				log.Error("Failed to create tmp dir, error: %s", err)
				os.Exit(1)
			}

			jsonKeyPth = filepath.Join(tmpDir, "key.json")

			if err := downloadFile(configs.JSONKeyPath, jsonKeyPth); err != nil {
				log.Error("Failed to download json key file, error: %s", err)
				os.Exit(1)
			}
		}

		authConfig, err := jwtConfigFromJSONKeyFile(jsonKeyPth)
		if err != nil {
			log.Error("Failed to create auth config from json key file, error: %s", err)
			os.Exit(1)
		}
		jwtConfig = authConfig
	} else {
		p12KeyPath := ""

		if strings.HasPrefix(configs.P12KeyPath, "file://") {
			p12KeyPath = strings.TrimPrefix(configs.P12KeyPath, "file://")
		} else {
			tmpDir, err := pathutil.NormalizedOSTempDirPath("__google-play-deploy__")
			if err != nil {
				log.Error("Failed to create tmp dir, error: %s", err)
				os.Exit(1)
			}

			p12KeyPath = filepath.Join(tmpDir, "key.p12")

			if err := downloadFile(configs.P12KeyPath, p12KeyPath); err != nil {
				log.Error("Failed to download p12 key file, error: %s", err)
				os.Exit(1)
			}
		}

		authConfig, err := jwtConfigFromP12KeyFile(p12KeyPath, configs.ServiceAccountEmail)
		if err != nil {
			log.Error("Failed to create auth config from p12 key file, error: %s", err)
			os.Exit(1)
		}
		jwtConfig = authConfig
	}

	client := jwtConfig.Client(oauth2.NoContext)
	service, err := androidpublisher.New(client)
	if err != nil {
		log.Error("Failed to create publisher service, error: %s", err)
		os.Exit(1)
	}

	log.Done("Authenticated client created")
	// ---

	//
	// Create insert edit
	fmt.Println()
	log.Info("Create new edit")

	editsService := androidpublisher.NewEditsService(service)

	editsInsertCall := editsService.Insert(configs.PackageName, nil)

	appEdit, err := editsInsertCall.Do()
	if err != nil {
		log.Error("Failed to perform edit insert call, error: %s", err)
		os.Exit(1)
	}

	log.Detail(" editID: %s", appEdit.Id)
	// ---

	//
	// Upload APKs
	fmt.Println()
	log.Info("Upload apks")

	versionCodes := []int64{}
	apkPaths := strings.Split(configs.ApkPath, "|")
	for _, apkPath := range apkPaths {
		apkFile, err := os.Open(apkPath)
		if err != nil {
			log.Error("Failed to read apk (%s), error: %s", apkPath, err)
			os.Exit(1)
		}

		editsApksService := androidpublisher.NewEditsApksService(service)

		editsApksUloadCall := editsApksService.Upload(configs.PackageName, appEdit.Id)
		editsApksUloadCall.Media(apkFile, googleapi.ContentType("application/vnd.android.package-archive"))

		apk, err := editsApksUloadCall.Do()
		if err != nil {
			log.Error("Failed to upload apk, error: %s", err)
			os.Exit(1)
		}

		log.Detail(" uploaded apk version: %d", apk.VersionCode)
		versionCodes = append(versionCodes, apk.VersionCode)
	}
	// ---

	//
	// Update track
	fmt.Println()
	log.Info("Update track")

	editsTracksService := androidpublisher.NewEditsTracksService(service)

	newTrack := androidpublisher.Track{
		Track:        configs.Track,
		VersionCodes: versionCodes,
	}

	if configs.Track == "rollout" {
		userFraction, err := strconv.ParseFloat(configs.UserFraction, 64)
		if err != nil {
			log.Error("Failed to parse user fraction, error: %s", err)
			os.Exit(1)
		}
		newTrack.UserFraction = userFraction
	}

	editsTracksUpdateCall := editsTracksService.Update(configs.PackageName, appEdit.Id, configs.Track, &newTrack)
	track, err := editsTracksUpdateCall.Do()
	if err != nil {
		log.Error("Failed to update track, error: %s", err)
		os.Exit(1)
	}

	log.Detail(" updated track: %s", track.Track)
	log.Detail(" assigned apk versions: %v", track.VersionCodes)
	// ---

	//
	// Update listing
	if configs.WhatsnewsDir != "" {
		fmt.Println()
		log.Info("Update listing")

		recentChangesMap, err := readLocalisedRecentChanges(configs.WhatsnewsDir)
		if err != nil {
			log.Error("Failed to read whatsnews, error: %s", err)
			os.Exit(1)
		}

		editsApklistingsService := androidpublisher.NewEditsApklistingsService(service)

		for _, versionCode := range versionCodes {
			log.Detail(" updating recent changes for version: %d", versionCode)

			for language, recentChanges := range recentChangesMap {
				newApkListing := androidpublisher.ApkListing{
					Language:      language,
					RecentChanges: recentChanges,
				}

				editsApkListingsCall := editsApklistingsService.Update(configs.PackageName, appEdit.Id, versionCode, language, &newApkListing)
				apkListing, err := editsApkListingsCall.Do()
				if err != nil {
					log.Error("Failed to update listing, error: %s", err)
					os.Exit(1)
				}

				log.Detail(" - language: %s", apkListing.Language)
			}
		}
	}
	// ---

	//
	// Validate edit
	fmt.Println()
	log.Info("Validating edit")

	editsValidateCall := editsService.Validate(configs.PackageName, appEdit.Id)
	if _, err := editsValidateCall.Do(); err != nil {
		log.Error("Failed to validate edit, error: %s", err)
		os.Exit(1)
	}

	log.Done("Edit is valid")
	// ---

	//
	// Commit edit
	fmt.Println()
	log.Info("Committing edit")

	editsCommitCall := editsService.Commit(configs.PackageName, appEdit.Id)
	if _, err := editsCommitCall.Do(); err != nil {
		log.Error("Failed to commit edit, error: %s", err)
		os.Exit(1)
	}

	log.Done("Edit committed")
	// ---
}
예제 #9
0
func main() {
	configs := createConfigsModelFromEnvs()

	fmt.Println()
	configs.print()

	if err := configs.validate(); err != nil {
		log.Error("Issue with input: %s", err)
		os.Exit(1)
	}

	nugetPth := "/Library/Frameworks/Mono.framework/Versions/Current/bin/nuget"
	nugetRestoreCmdArgs := []string{nugetPth}

	if configs.NugetVersion == "latest" {
		fmt.Println()
		log.Info("Updating Nuget to latest version...")
		// "sudo $nuget update -self"
		cmdArgs := []string{"sudo", nugetPth, "update", "-self"}
		cmd, err := cmdex.NewCommandFromSlice(cmdArgs)
		if err != nil {
			log.Error("Failed to create command from args (%v), error: %s", cmdArgs, err)
			os.Exit(1)
		}

		cmd.SetStdout(os.Stdout)
		cmd.SetStderr(os.Stderr)

		log.Done("$ %s", cmdex.PrintableCommandArgs(false, cmdArgs))

		if err := cmd.Run(); err != nil {
			log.Error("Failed to update nuget, error: %s", err)
			os.Exit(1)
		}
	} else if configs.NugetVersion != "" {
		fmt.Println()
		log.Info("Downloading Nuget %s version...", configs.NugetVersion)
		tmpDir, err := pathutil.NormalizedOSTempDirPath("__nuget__")
		if err != nil {
			log.Error("Failed to create tmp dir, error: %s", err)
			os.Exit(1)
		}

		downloadPth := filepath.Join(tmpDir, "nuget.exe")

		// https://dist.nuget.org/win-x86-commandline/v3.3.0/nuget.exe
		nugetURL := fmt.Sprintf("https://dist.nuget.org/win-x86-commandline/v%s/nuget.exe", configs.NugetVersion)

		log.Detail("Download URL: %s", nugetURL)

		if err := DownloadFile(nugetURL, downloadPth); err != nil {
			log.Warn("Download failed, error: %s", err)

			// https://dist.nuget.org/win-x86-commandline/v3.4.4/NuGet.exe
			nugetURL = fmt.Sprintf("https://dist.nuget.org/win-x86-commandline/v%s/NuGet.exe", configs.NugetVersion)

			log.Detail("Retry download URl: %s", nugetURL)

			if err := DownloadFile(nugetURL, downloadPth); err != nil {
				log.Error("Failed to download nuget, error: %s", err)
				os.Exit(1)
			}
		}

		nugetRestoreCmdArgs = []string{constants.MonoPath, downloadPth}
	}

	fmt.Println()
	log.Info("Restoring Nuget packages...")

	nugetRestoreCmdArgs = append(nugetRestoreCmdArgs, "restore", configs.XamarinSolution)

	if err := retry.Times(1).Try(func(attempt uint) error {
		if attempt > 0 {
			log.Warn("Attempt %d failed, retrying...", attempt)
		}

		log.Done("$ %s", cmdex.PrintableCommandArgs(false, nugetRestoreCmdArgs))

		cmd, err := cmdex.NewCommandFromSlice(nugetRestoreCmdArgs)
		if err != nil {
			log.Error("Failed to create Nuget command, error: %s", err)
			os.Exit(1)
		}

		cmd.SetStdout(os.Stdout)
		cmd.SetStderr(os.Stderr)

		if err := cmd.Run(); err != nil {
			log.Error("Restore failed, error: %s", err)
			return err
		}
		return nil
	}); err != nil {
		log.Error("Nuget restore failed, error: %s", err)
		os.Exit(1)
	}
}
예제 #10
0
func fail(format string, v ...interface{}) {
	log.Error(format, v...)
	os.Exit(1)
}
func main() {
	configs := createConfigsModelFromEnvs()
	configs.print()
	if err := configs.validate(); err != nil {
		log.Error("Issue with input: %s", err)
		os.Exit(1)
	}
	fmt.Println()

	// Validate Certificates
	certificateURLPassphraseMap := map[string]string{}

	if configs.CertificateURL != "" {
		certificateURLs := strings.Split(configs.CertificateURL, "|")
		certificatePassphrases := strings.Split(configs.CertificatePassphrase, "|")

		if len(certificateURLs) != len(certificatePassphrases) {
			log.Error("Certificate url count: (%d), not equals to Certificate Passphrase count: (%d)", len(certificateURLs), len(certificatePassphrases))
			os.Exit(1)
		}

		for i := 0; i < len(certificateURLs); i++ {
			certificateURL := certificateURLs[i]
			certificatePassphrase := certificatePassphrases[i]

			certificateURLPassphraseMap[certificateURL] = certificatePassphrase
		}
	}

	if configs.DefaultCertificateURL != "" {
		log.Detail("Default Certificate given")
		certificateURLPassphraseMap[configs.DefaultCertificateURL] = configs.DefaultCertificatePassphrase
	}

	certificateCount := len(certificateURLPassphraseMap)
	log.Detail("Provided Certificate count: %d", certificateCount)

	if certificateCount == 0 {
		log.Error("No Certificate provided")
		os.Exit(1)
	}

	// Validate Provisioning Profiles
	provisioningProfileURLs := strings.Split(configs.ProvisioningProfileURL, "|")

	if configs.DefaultProvisioningProfileURL != "" {
		log.Detail("Default Provisioning Profile given")
		provisioningProfileURLs = append(provisioningProfileURLs, configs.DefaultProvisioningProfileURL)
	}

	profileCount := len(provisioningProfileURLs)
	log.Detail("Provided Provisioning Profile count: %d", profileCount)

	if profileCount == 0 {
		log.Error("No Provisioning Profile provided")
		os.Exit(1)
	}

	//
	// Init
	homeDir := os.Getenv("HOME")
	provisioningProfileDir := path.Join(homeDir, "Library/MobileDevice/Provisioning Profiles")
	if exist, err := pathutil.IsPathExists(provisioningProfileDir); err != nil {
		log.Error("Failed to check path (%s), err: %s", provisioningProfileDir, err)
		os.Exit(1)
	} else if !exist {
		if err := os.MkdirAll(provisioningProfileDir, 0777); err != nil {
			log.Error("Failed to create path (%s), err: %s", provisioningProfileDir, err)
			os.Exit(1)
		}
	}

	tempDir, err := pathutil.NormalizedOSTempDirPath("bitrise-cert-tmp")
	if err != nil {
		log.Error("Failed to create tmp directory, err: %s", err)
		os.Exit(1)
	}

	if exist, err := pathutil.IsPathExists(configs.KeychainPath); err != nil {
		log.Error("Failed to check path (%s), err: %s", configs.KeychainPath, err)
		os.Exit(1)
	} else if !exist {
		fmt.Println()
		log.Warn("Keychain (%s) does not exist", configs.KeychainPath)

		keychainPth := fmt.Sprintf("%s-db", configs.KeychainPath)

		log.Detail(" Checking (%s)", keychainPth)

		if exist, err := pathutil.IsPathExists(keychainPth); err != nil {
			log.Error("Failed to check path (%s), err: %s", keychainPth, err)
			os.Exit(1)
		} else if !exist {
			log.Info("Creating keychain: %s", configs.KeychainPath)

			if out, err := runCommandAndReturnCombinedStdoutAndStderr("security", "-v", "create-keychain", "-p", configs.KeychainPassword, configs.KeychainPath); err != nil {
				log.Error("Failed to create keychain, output: %s", out)
				log.Error("Failed to create keychain, err: %s", err)
				os.Exit(1)
			}
		}
	} else {
		log.Detail("Keychain already exists, using it: %s", configs.KeychainPath)
	}

	//
	// Download certificate
	fmt.Println()
	log.Info("Downloading & installing Certificate(s)")

	certificatePassphraseMap := map[string]string{}
	idx := 0
	for certURL, pass := range certificateURLPassphraseMap {
		fmt.Println()
		log.Detail("=> Downloading certificate: %d/%d", idx+1, certificateCount)

		certPath := path.Join(tempDir, fmt.Sprintf("Certificate-%d.p12", idx))
		if err := downloadFile(certPath, certURL); err != nil {
			log.Error("Download failed, err: %s", err)
			os.Exit(1)
		}
		certificatePassphraseMap[certPath] = pass

		idx++
	}

	//
	// Install certificate
	fmt.Println()
	log.Detail("=> Installing downloaded certificate")

	for cert, pass := range certificatePassphraseMap {
		// Import items into a keychain.
		importOut, err := runCommandAndReturnCombinedStdoutAndStderr("security", "import", cert, "-k", configs.KeychainPath, "-P", pass, "-A")
		if err != nil {
			log.Error("Command failed, output: %s", importOut)
			log.Error("Command failed, err: %s", err)
			os.Exit(1)
		}
	}

	// This is new behavior in Sierra, [openradar](https://openradar.appspot.com/28524119)
	// You need to use "security set-key-partition-list -S apple-tool:,apple: -k keychainPass keychainName" after importing the item and before attempting to use it via codesign.
	osVersionCmd := cmdex.NewCommand("sw_vers", "-productVersion")
	out, err := osVersionCmd.RunAndReturnTrimmedCombinedOutput()
	if err != nil {
		log.Error("Failed to get os version, error: %s", err)
		os.Exit(1)
	}

	osVersion, err := version.NewVersion(out)
	if err != nil {
		log.Error("Failed to parse os version (%s), error: %s", out, err)
		os.Exit(1)
	}

	sierraVersionStr := "10.12.0"
	sierraVersion, err := version.NewVersion(sierraVersionStr)
	if err != nil {
		log.Error("Failed to parse os version (%s), error: %s", sierraVersionStr, err)
		os.Exit(1)
	}

	if !osVersion.LessThan(sierraVersion) {
		cmd := cmdex.NewCommand("security", "set-key-partition-list", "-S", "apple-tool:,apple:", "-k", configs.KeychainPassword, configs.KeychainPath)
		if err := cmd.Run(); err != nil {
			log.Error("Failed, err: %s", err)
			os.Exit(1)
		}
	}
	// ---

	// Set keychain settings: Lock keychain when the system sleeps, Lock keychain after timeout interval, Timeout in seconds
	settingsOut, err := runCommandAndReturnCombinedStdoutAndStderr("security", "-v", "set-keychain-settings", "-lut", "72000", configs.KeychainPath)
	if err != nil {
		log.Error("Command failed, output: %s", settingsOut)
		log.Error("Command failed, err: %s", err)
		os.Exit(1)
	}

	// List keychains
	listKeychainsOut, err := runCommandAndReturnCombinedStdoutAndStderr("security", "list-keychains")
	if err != nil {
		log.Error("Command failed, output: %s", listKeychainsOut)
		log.Error("Command failed, err: %s", err)
		os.Exit(1)
	}

	keychainList := strings.Split(listKeychainsOut, "\n")
	strippedKeychainList := []string{}

	for _, keychain := range keychainList {
		strippedKeychain := strip(keychain)
		strippedKeychainList = append(strippedKeychainList, strippedKeychain)
	}

	strippedKeychainList = addKeyChainToList(strippedKeychainList, configs.KeychainPath)

	// Set keychain search path
	args := []string{"-v", "list-keychains", "-s"}
	args = append(args, strippedKeychainList...)

	listKeychainsOut, err = runCommandAndReturnCombinedStdoutAndStderr("security", args...)
	if err != nil {
		log.Error("Command failed, output: %s", listKeychainsOut)
		log.Error("Command failed, err: %s", err)
		os.Exit(1)
	}

	// Set the default keychain
	defaultKeychainOut, err := runCommandAndReturnCombinedStdoutAndStderr("security", "-v", "default-keychain", "-s", configs.KeychainPath)
	if err != nil {
		log.Error("Command failed, output: %s", defaultKeychainOut)
		log.Error("Command failed, err: %s", err)
		os.Exit(1)
	}

	// Unlock the specified keychain
	unlockOut, err := runCommandAndReturnCombinedStdoutAndStderr("security", "-v", "unlock-keychain", "-p", configs.KeychainPassword, configs.KeychainPath)
	if err != nil {
		log.Error("Command failed, output: %s", unlockOut)
		log.Error("Command failed, err: %s", err)
		os.Exit(1)
	}

	for cert, pass := range certificatePassphraseMap {
		certificateIdentity, err := certificateFriendlyName(cert, pass)
		if err != nil {
			log.Error("Failed to get cert identity, output: %s", certificateIdentity)
			log.Error("Failed to get cert identity, err: %s", err)
			os.Exit(1)
		}
		if certificateIdentity == "" {
			log.Error("Failed to get cert identity")
			os.Exit(1)
		}

		log.Done("   Installed certificate: %s", certificateIdentity)
	}

	certs, err := availableCertificates(configs.KeychainPath)
	if err != nil {
		log.Error("Failed to get certificate list, err:%s", err)
		os.Exit(1)
	}
	if len(certs) == 0 {
		log.Error("Failed to import certificate, no certificates found")
		os.Exit(1)
	}

	fmt.Println()
	log.Info("Available certificates:")
	fmt.Println("-----------------------")
	for _, cert := range certs {
		log.Detail(" * %s", cert)
	}

	//
	// Install provisioning profiles
	// NOTE: the URL can be a pipe (|) separated list of Provisioning Profile URLs
	fmt.Println()
	log.Info("Downloading & installing Provisioning Profile(s)")

	for idx, profileURL := range provisioningProfileURLs {
		fmt.Println()
		log.Detail("=> Downloading provisioning profile: %d/%d", idx+1, profileCount)

		provisioningProfileExt := "provisionprofile"
		if !strings.Contains(profileURL, "."+provisioningProfileExt) {
			provisioningProfileExt = "mobileprovision"
		}

		profileTmpPth := path.Join(tempDir, fmt.Sprintf("profile-%d.%s", idx, provisioningProfileExt))
		if err := downloadFile(profileTmpPth, profileURL); err != nil {
			log.Error("Download failed, err: %s", err)
			os.Exit(1)
		}

		fmt.Println()
		fmt.Println("=> Installing provisioning profile")
		out, err := runCommandAndReturnCombinedStdoutAndStderr("/usr/bin/security", "cms", "-D", "-i", profileTmpPth)
		if err != nil {
			log.Error("Command failed, output: %s", out)
			log.Error("Command failed, err: %s", err)
			os.Exit(1)
		}

		outSplit := strings.Split(out, "\n")
		if len(outSplit) > 0 {
			if strings.Contains(outSplit[0], notValidParameterErrorMessage) {
				fixedOutSplit := outSplit[1:len(outSplit)]
				out = strings.Join(fixedOutSplit, "\n")
			}
		}

		tmpProvProfilePth := path.Join(tempDir, "prov")
		if err := writeBytesToFileWithPermission(tmpProvProfilePth, []byte(out), 0); err != nil {
			log.Error("Failed to write profile to file, error: %s", err)
			os.Exit(1)
		}

		profileInfos, err := printableProfileInfos(out)
		if err != nil {
			log.Error("Failed to read profile infos, err: %s", err)
			os.Exit(1)
		}

		fmt.Println()
		log.Info("Profile Infos:")
		log.Detail("%s", profileInfos)
		fmt.Println()

		profileUUID, err := runCommandAndReturnCombinedStdoutAndStderr("/usr/libexec/PlistBuddy", "-c", "Print UUID", tmpProvProfilePth)
		if err != nil {
			log.Error("Command failed, output: %s", profileUUID)
			log.Error("Command failed, err: %s", err)
			os.Exit(1)
		}

		log.Done("   Installed Profile UUID: %s", profileUUID)
		profileFinalPth := path.Join(provisioningProfileDir, profileUUID+"."+provisioningProfileExt)

		log.Detail("   Moving it to: %s", profileFinalPth)

		if out, err := runCommandAndReturnCombinedStdoutAndStderr("cp", profileTmpPth, profileFinalPth); err != nil {
			log.Error("Command failed, output: %s", out)
			log.Error("Command failed, err: %s", err)
			os.Exit(1)
		}
	}
}
func downloadFile(destionationPath, URL string) error {
	url, err := url.Parse(URL)
	if err != nil {
		return err
	}

	scheme := url.Scheme

	tmpDstFilePath := ""
	if scheme != "file" {
		log.Detail("   Downloading (%s) to (%s)", secureInput(URL), destionationPath)

		tmpDir, err := pathutil.NormalizedOSTempDirPath("download")
		if err != nil {
			return err
		}

		tmpDst := path.Join(tmpDir, "tmp_file")
		tmpDstFile, err := os.Create(tmpDst)
		if err != nil {
			return err
		}
		defer func() {
			if err := tmpDstFile.Close(); err != nil {
				log.Error("Failed to close file (%s), error: %s", tmpDst, err)
			}
		}()

		success := false
		var response *http.Response
		for i := 0; i < 3 && !success; i++ {
			if i > 0 {
				fmt.Println("-> Retrying...")
				time.Sleep(3 * time.Second)
			}

			response, err = http.Get(URL)
			if err != nil {
				log.Error(err.Error())
			} else {
				success = true
			}

			if response != nil {
				defer func() {
					if err := response.Body.Close(); err != nil {
						log.Error("Failed to close response body, error: %s", err)
					}
				}()
			}
		}
		if !success {
			return err
		}

		_, err = io.Copy(tmpDstFile, response.Body)
		if err != nil {
			return err
		}

		tmpDstFilePath = tmpDstFile.Name()
	} else {
		log.Detail("   Moving (%s) to (%s)", secureInput(URL), destionationPath)
		tmpDstFilePath = strings.Replace(URL, scheme+"://", "", -1)
	}

	if out, err := runCommandAndReturnCombinedStdoutAndStderr("cp", tmpDstFilePath, destionationPath); err != nil {
		log.Detail("Move out: %s", out)
		return err
	}

	return nil
}