protected override async Task <ExitCode> InvokeInternal(ILogger logger) { var processManager = new MLaunchProcessManager(_arguments.XcodeRoot, _arguments.MlaunchPath); var deviceLoader = new HardwareDeviceLoader(processManager); var simulatorLoader = new SimulatorLoader(processManager); var logs = new Logs(_arguments.OutputDirectory); var cts = new CancellationTokenSource(); cts.CancelAfter(_arguments.Timeout); var exitCode = ExitCode.SUCCESS; foreach (TestTarget target in _arguments.TestTargets) { var tunnelBore = (_arguments.CommunicationChannel == CommunicationChannel.UsbTunnel && !target.IsSimulator()) ? new TunnelBore(processManager) : null; var exitCodeForRun = await RunTest(logger, target, logs, processManager, deviceLoader, simulatorLoader, tunnelBore, cts.Token); if (exitCodeForRun != ExitCode.SUCCESS) { exitCode = exitCodeForRun; } } return(exitCode); }
private async Task <ExitCode> RunTest( ILogger logger, TestTarget target, Logs logs, MLaunchProcessManager processManager, IHardwareDeviceLoader deviceLoader, ISimulatorLoader simulatorLoader, ITunnelBore?tunnelBore, CancellationToken cancellationToken = default) { var isLldbEnabled = IsLldbEnabled(); if (isLldbEnabled && !_arguments.EnableLldb) { // the file is present, but the user did not set it, warn him about it logger.LogWarning("Lldb will be used since '~/.mtouch-launch-with-lldb' was found in the system but it was not created by xharness."); } else if (_arguments.EnableLldb) { if (!File.Exists(_mlaunchLldbConfigFile)) { // create empty file var f = File.Create(_mlaunchLldbConfigFile); f.Dispose(); _createdLldbFile = true; } } logger.LogInformation($"Starting test for {target.AsString()}{ (_arguments.DeviceName != null ? " targeting " + _arguments.DeviceName : null) }.."); string mainLogFile = Path.Join(_arguments.OutputDirectory, $"run-{target}{(_arguments.DeviceName != null ? "-" + _arguments.DeviceName : null)}.log"); IFileBackedLog mainLog = logs.Create(mainLogFile, LogType.ExecutionLog.ToString(), true); int verbosity = GetMlaunchVerbosity(_arguments.Verbosity); string?deviceName = _arguments.DeviceName; var appBundleInformationParser = new AppBundleInformationParser(processManager); AppBundleInformation appBundleInfo; try { appBundleInfo = await appBundleInformationParser.ParseFromAppBundle(_arguments.AppPackagePath, target, mainLog, cancellationToken); } catch (Exception e) { logger.LogError($"Failed to get bundle information: {e.Message}"); return(ExitCode.FAILED_TO_GET_BUNDLE_INFO); } if (!target.IsSimulator()) { logger.LogInformation($"Installing application '{appBundleInfo.AppName}' on " + (deviceName != null ? $" on device '{deviceName}'" : target.AsString())); var appInstaller = new AppInstaller(processManager, deviceLoader, mainLog, verbosity); ProcessExecutionResult result; try { (deviceName, result) = await appInstaller.InstallApp(appBundleInfo, target, cancellationToken : cancellationToken); } catch (NoDeviceFoundException) { logger.LogError($"Failed to find suitable device for target {target.AsString()}"); return(ExitCode.DEVICE_NOT_FOUND); } catch (Exception e) { logger.LogError($"Failed to install the app bundle:{Environment.NewLine}{e}"); return(ExitCode.PACKAGE_INSTALLATION_FAILURE); } if (!result.Succeeded) { // use the knowledge base class to decide if the error is known, if it is, let the user know // the failure reason if (_errorKnowledgeBase.IsKnownInstallIssue(mainLog, out var errorMessage)) { logger.LogError($"Failed to install the app bundle (exit code={result.ExitCode}): {errorMessage}"); } else { logger.LogError($"Failed to install the app bundle (exit code={result.ExitCode})"); } return(ExitCode.PACKAGE_INSTALLATION_FAILURE); } logger.LogInformation($"Application '{appBundleInfo.AppName}' was installed successfully on device '{deviceName}'"); } logger.LogInformation($"Starting application '{appBundleInfo.AppName}' on " + (deviceName != null ? $"device '{deviceName}'" : target.AsString())); // only add the extra callback if we do know that the feature was indeed enabled Action <string>?logCallback = isLldbEnabled ? (l) => NotifyUserLldbCommand(logger, l) : (Action <string>?)null; var appRunner = new AppRunner( processManager, deviceLoader, simulatorLoader, new SimpleListenerFactory(tunnelBore), new CrashSnapshotReporterFactory(processManager), new CaptureLogFactory(), new DeviceLogCapturerFactory(processManager), new TestReporterFactory(processManager), mainLog, logs, new Helpers(), useXmlOutput: true, // the cli ALWAYS will get the output as xml logCallback: logCallback); try { string resultMessage; TestExecutingResult testResult; (deviceName, testResult, resultMessage) = await appRunner.RunApp(appBundleInfo, target, _arguments.Timeout, _arguments.LaunchTimeout, deviceName, verbosity : verbosity, xmlResultJargon : _arguments.XmlResultJargon, cancellationToken : cancellationToken); switch (testResult) { case TestExecutingResult.Succeeded: logger.LogInformation($"Application finished the test run successfully"); logger.LogInformation(resultMessage); return(ExitCode.SUCCESS); case TestExecutingResult.Failed: logger.LogInformation($"Application finished the test run successfully with some failed tests"); logger.LogInformation(resultMessage); return(ExitCode.TESTS_FAILED); case TestExecutingResult.Crashed: if (resultMessage != null) { logger.LogError($"Application run crashed:{Environment.NewLine}" + $"{resultMessage}{Environment.NewLine}{Environment.NewLine}" + $"Check logs for more information."); } else { logger.LogError($"Application run crashed. Check logs for more information"); } return(ExitCode.APP_CRASH); case TestExecutingResult.TimedOut: logger.LogWarning($"Application run timed out"); return(ExitCode.TIMED_OUT); default: if (resultMessage != null) { logger.LogError($"Application run ended in an unexpected way: '{testResult}'{Environment.NewLine}" + $"{resultMessage}{Environment.NewLine}{Environment.NewLine}" + $"Check logs for more information."); } else { logger.LogError($"Application run ended in an unexpected way: '{testResult}'. Check logs for more information"); } return(ExitCode.GENERAL_FAILURE); } } catch (NoDeviceFoundException) { logger.LogError($"Failed to find suitable device for target {target.AsString()}"); return(ExitCode.DEVICE_NOT_FOUND); } catch (Exception e) { if (_errorKnowledgeBase.IsKnownTestIssue(mainLog, out var failureMessage)) { logger.LogError($"Application run failed:{Environment.NewLine}{failureMessage}"); } else { logger.LogError($"Application run failed:{Environment.NewLine}{e}"); } return(ExitCode.APP_CRASH); } finally { if (!target.IsSimulator() && deviceName != null) { logger.LogInformation($"Uninstalling the application '{appBundleInfo.AppName}' from device '{deviceName}'"); var appUninstaller = new AppUninstaller(processManager, mainLog, verbosity); var uninstallResult = await appUninstaller.UninstallApp(deviceName, appBundleInfo.BundleIdentifier, cancellationToken); if (!uninstallResult.Succeeded) { logger.LogError($"Failed to uninstall the app bundle with exit code: {uninstallResult.ExitCode}"); } else { logger.LogInformation($"Application '{appBundleInfo.AppName}' was uninstalled successfully"); } } if (_createdLldbFile) // clean after the setting { File.Delete(_mlaunchLldbConfigFile); } } }
protected override async Task <ExitCode> InvokeInternal(ILogger logger) { var processManager = new MLaunchProcessManager(mlaunchPath: _arguments.MlaunchPath); var deviceLoader = new HardwareDeviceLoader(processManager); var simulatorLoader = new SimulatorLoader(processManager); var log = new MemoryLog(); // do we really want to log this? var mlaunchLog = new MemoryLog { Timestamp = false }; ProcessExecutionResult result; try { result = await processManager.ExecuteCommandAsync(new MlaunchArguments(new MlaunchVersionArgument()), new MemoryLog(), mlaunchLog, new MemoryLog(), TimeSpan.FromSeconds(10)); } catch (Exception e) { logger.LogError($"Failed to get mlaunch version info:{Environment.NewLine}{e}"); return(ExitCode.GENERAL_FAILURE); } if (!result.Succeeded) { logger.LogError($"Failed to get mlaunch version info:{Environment.NewLine}{mlaunchLog}"); return(ExitCode.GENERAL_FAILURE); } // build the required data, then depending on the format print out var info = new SystemInfo( machineName: Environment.MachineName, oSName: "Mac OS X", oSVersion: Darwin.GetVersion() ?? "", oSPlatform: "Darwin", xcodePath: processManager.XcodeRoot, xcodeVersion: processManager.XcodeVersion.ToString(), mlaunchPath: processManager.MlaunchPath, mlaunchVersion: mlaunchLog.ToString().Trim()); try { await simulatorLoader.LoadDevices(log); } catch (Exception e) { logger.LogError($"Failed to load simulators:{Environment.NewLine}{e}"); logger.LogInformation($"Execution log:{Environment.NewLine}{log}"); return(ExitCode.GENERAL_FAILURE); } foreach (var sim in simulatorLoader.AvailableDevices) { info.Simulators.Add(new DeviceInfo( name: sim.Name, uDID: sim.UDID, type: sim.SimDeviceType.Remove(0, SimulatorPrefix.Length).Replace('-', ' '), oSVersion: sim.OSVersion)); } try { await deviceLoader.LoadDevices(log); } catch (Exception e) { logger.LogError($"Failed to load connected devices:{Environment.NewLine}{e}"); logger.LogInformation($"Execution log:{Environment.NewLine}{log}"); return(ExitCode.GENERAL_FAILURE); } foreach (var dev in deviceLoader.ConnectedDevices) { info.Devices.Add(new DeviceInfo( name: dev.Name, uDID: dev.DeviceIdentifier, type: $"{dev.DeviceClass} {dev.DevicePlatform}", oSVersion: dev.OSVersion)); } if (_arguments.UseJson) { await AsJson(info); } else { AsText(info); } return(ExitCode.SUCCESS); }
protected async override Task <ExitCode> InvokeInternal(ILogger logger) { var processManager = new MLaunchProcessManager(); // Validate the presence of Xamarin.iOS var missingXamariniOS = false; if (_arguments.TemplateType == TemplateType.Managed) { var dotnetLog = new MemoryLog() { Timestamp = false }; var process = new Process(); process.StartInfo.FileName = "bash"; process.StartInfo.Arguments = "-c \"" + _arguments.DotnetPath + " --info | grep \\\"Base Path\\\" | cut -d':' -f 2 | tr -d '[:space:]'\""; var result = await processManager.RunAsync(process, new MemoryLog(), dotnetLog, new MemoryLog(), TimeSpan.FromSeconds(5)); if (result.Succeeded) { var sdkPath = dotnetLog.ToString().Trim(); if (Directory.Exists(sdkPath)) { var xamarinIosPath = Path.Combine(sdkPath, "Xamarin", "iOS", "Xamarin.iOS.CSharp.targets"); if (!File.Exists(xamarinIosPath)) { missingXamariniOS = true; logger.LogWarning("Failed to find the Xamarin iOS package which is needed for the Managed template: " + xamarinIosPath); } } } } // create the factory, which will be used to find the diff assemblies var assemblyLocator = new AssemblyLocator(Directory.GetCurrentDirectory()); var assemblyDefinitionFactory = new AssemblyDefinitionFactory(_arguments.TestingFramework, assemblyLocator); ITemplatedProject template = _arguments.TemplateType switch { TemplateType.Managed => new XamariniOSTemplate { AssemblyDefinitionFactory = assemblyDefinitionFactory, AssemblyLocator = assemblyDefinitionFactory.AssemblyLocator, OutputDirectoryPath = _arguments.OutputDirectory, IgnoreFilesRootDirectory = _arguments.IgnoreFilesRootDirectory, ProjectFilter = new ProjectFilter(_arguments.IgnoreFilesRootDirectory, _arguments.TraitsRootDirectory), }, _ => throw new Exception("The 'Native' template is not yet supported. Please use the managed one."), }; var logs = new Logs(_arguments.WorkingDirectory); var runLog = logs.Create("package-log.txt", "Package Log"); var consoleLog = new CallbackLog(s => logger.LogInformation(s)) { Timestamp = false }; var aggregatedLog = Log.CreateReadableAggregatedLog(runLog, consoleLog); aggregatedLog.WriteLine("Generating scaffold app with:"); aggregatedLog.WriteLine($"\tAppname: '{_arguments.AppPackageName}'"); aggregatedLog.WriteLine($"\tAssemblies: '{string.Join(" ", _arguments.Assemblies)}'"); aggregatedLog.WriteLine($"\tExtraArgs: '{_arguments.MtouchExtraArgs}'"); // first step, generate the required info to be passed to the factory var projects = new List <(string Name, string[] Assemblies, string?ExtraArgs, double TimeoutMultiplier)> { // TODO: Timeout multiplier handling (Name : _arguments.AppPackageName, Assemblies : _arguments.Assemblies.ToArray(), ExtraArgs : _arguments.MtouchExtraArgs, TimeoutMultiplier : 1.0), }; // TODO: we are not taking into account all the plaforms, just iOS var allProjects = new GeneratedProjects(); foreach (var p in _arguments.Platforms) { // so wish that mono.options allowed use to use async :/ var testProjects = await template.GenerateTestProjectsAsync(projects, XHarness.iOS.Shared.TestImporter.Platform.iOS); allProjects.AddRange(testProjects); } // We do have all the required projects, time to compile them aggregatedLog.WriteLine("Scaffold app generated."); // First step, nuget restore whatever is needed var projectPath = Path.Combine(_arguments.OutputDirectory, _arguments.AppPackageName + ".csproj"); aggregatedLog.WriteLine($"Project path is {projectPath}"); aggregatedLog.WriteLine($"Performing nuget restore."); using (var dotnetRestore = new Process()) { dotnetRestore.StartInfo.FileName = _arguments.DotnetPath; var args = new List <string> { "restore", projectPath, "--verbosity:detailed" }; dotnetRestore.StartInfo.Arguments = StringUtils.FormatArguments(args); var result = await processManager.RunAsync(dotnetRestore, aggregatedLog, _nugetRestoreTimeout); if (result.TimedOut) { aggregatedLog.WriteLine("nuget restore timedout."); return(ExitCode.PACKAGE_BUNDLING_FAILURE_NUGET_RESTORE); } if (!result.Succeeded) { aggregatedLog.WriteLine($"nuget restore exited with {result.ExitCode}"); return(ExitCode.PACKAGE_BUNDLING_FAILURE_NUGET_RESTORE); } } var finalResult = ExitCode.SUCCESS; // perform the build of the application using (var dotnetBuild = new Process()) { dotnetBuild.StartInfo.FileName = _arguments.DotnetPath; // TODO: Only taking into account one platform // https://github.com/dotnet/xharness/issues/105 if (_arguments.Platforms.Count > 1) { logger.LogWarning($"Multi-platform targetting is not supported yet. Targetting {_arguments.Platforms[0]} only."); } dotnetBuild.StartInfo.Arguments = GetBuildArguments(projectPath, _arguments.Platforms[0].ToString()); aggregatedLog.WriteLine($"Building {_arguments.AppPackageName} ({projectPath})"); var result = await processManager.RunAsync(dotnetBuild, aggregatedLog, _msBuildTimeout); if (result.TimedOut) { aggregatedLog.WriteLine("Build timed out after {0} seconds.", _msBuildTimeout.TotalSeconds); } else if (result.Succeeded) { aggregatedLog.WriteLine("Build was successful."); } else if (!result.Succeeded) { logger.LogError($"Build failed with return code: {result.ExitCode}"); finalResult = ExitCode.PACKAGE_BUNDLING_FAILURE_BUILD; if (missingXamariniOS) { logger.LogWarning("Possible cause of the failure may be missing Xamarin iOS package"); } } } return(finalResult); }