Ejemplo n.º 1
0
        private async Task <IFileBackedLog> ProcessCrash(string crashFile)
        {
            var name            = Path.GetFileName(crashFile);
            var crashReportFile = _logs.Create(name, $"Crash report: {name}", timestamp: false);
            var args            = new MlaunchArguments(
                new DownloadCrashReportArgument(crashFile),
                new DownloadCrashReportToArgument(crashReportFile.FullPath));

            if (!string.IsNullOrEmpty(_deviceName))
            {
                args.Add(new DeviceNameArgument(_deviceName));
            }

            var result = await _processManager.ExecuteCommandAsync(args, _log, TimeSpan.FromMinutes(1));

            if (result.Succeeded)
            {
                _log.WriteLine("Downloaded crash report {0} to {1}", crashFile, crashReportFile.FullPath);
                return(await GetSymbolicateCrashReportAsync(crashReportFile));
            }
            else
            {
                _log.WriteLine("Could not download crash report {0}", crashFile);
                return(null);
            }
        }
Ejemplo n.º 2
0
        public JenkinsDeviceLoader(ISimulatorLoader simulators, IHardwareDeviceLoader devices, ILogs logs)
        {
            if (logs == null)
            {
                throw new ArgumentNullException(nameof(logs));
            }

            this.simulators  = simulators ?? throw new ArgumentNullException(nameof(simulators));
            this.devices     = devices ?? throw new ArgumentNullException(nameof(devices));
            SimulatorLoadLog = logs.Create($"simulator-list-{Harness.Helpers.Timestamp}.log", $"Simulator Listing");
            DeviceLoadLog    = logs.Create($"device-list-{Harness.Helpers.Timestamp}.log", $"Device Listing");
        }
Ejemplo n.º 3
0
        public async Task <ILogFile> SymbolicateCrashReportAsync(ILogs logs, ILog log, ILogFile report)
        {
            var symbolicatecrash = Path.Combine(XcodeRoot, "Contents/SharedFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash");

            if (!File.Exists(symbolicatecrash))
            {
                symbolicatecrash = Path.Combine(XcodeRoot, "Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash");
            }

            if (!File.Exists(symbolicatecrash))
            {
                log.WriteLine("Can't symbolicate {0} because the symbolicatecrash script {1} does not exist", report.Path, symbolicatecrash);
                return(report);
            }

            var name         = Path.GetFileName(report.Path);
            var symbolicated = logs.Create(Path.ChangeExtension(name, ".symbolicated.log"), $"Symbolicated crash report: {name}", timestamp: false);
            var environment  = new Dictionary <string, string> {
                { "DEVELOPER_DIR", Path.Combine(XcodeRoot, "Contents", "Developer") }
            };
            var rv = await ProcessManager.ExecuteCommandAsync(symbolicatecrash, new [] { report.Path }, symbolicated, TimeSpan.FromMinutes(1), environment);

            if (rv.Succeeded)
            {
                ;
                log.WriteLine("Symbolicated {0} successfully.", report.Path);
                return(symbolicated);
            }
            else
            {
                log.WriteLine("Failed to symbolicate {0}.", report.Path);
                return(report);
            }
        }
Ejemplo n.º 4
0
        private async Task <ProcessExecutionResult> RunDeviceApp(
            MlaunchArguments mlaunchArguments,
            ICrashSnapshotReporter crashReporter,
            string deviceName,
            TimeSpan timeout,
            CancellationToken cancellationToken)
        {
            var deviceSystemLog   = _logs.Create($"device-{deviceName}-{_helpers.Timestamp}.log", LogType.SystemLog.ToString());
            var deviceLogCapturer = _deviceLogCapturerFactory.Create(_mainLog, deviceSystemLog, deviceName);

            deviceLogCapturer.StartCapture();

            try
            {
                await crashReporter.StartCaptureAsync();

                _mainLog.WriteLine("Starting the app");

                return(await _processManager.ExecuteCommandAsync(
                           mlaunchArguments,
                           _mainLog,
                           timeout,
                           cancellationToken : cancellationToken));
            }
            finally
            {
                deviceLogCapturer.StopCapture();
                deviceSystemLog.Dispose();
            }
        }
Ejemplo n.º 5
0
        public PeriodicCommand(string command, IProcessManager processManager, TimeSpan interval, ILogs logs, string?arguments = null)
        {
            if (logs == null)
            {
                throw new ArgumentNullException(nameof(logs));
            }

            this.log            = logs.Create("PeriodicCommand.log", "Periodic command log");
            this.command        = command ?? throw new ArgumentNullException(nameof(command));
            this.processManager = processManager ?? throw new ArgumentNullException(nameof(processManager));
            this.interval       = interval;
            this.arguments      = arguments;
        }
Ejemplo n.º 6
0
        private async Task RunDeviceApp(
            MlaunchArguments mlaunchArguments,
            ICrashSnapshotReporter crashReporter,
            string deviceName,
            TimeSpan timeout,
            CancellationToken cancellationToken)
        {
            var deviceSystemLog   = _logs.Create($"device-{deviceName}-{_helpers.Timestamp}.log", LogType.SystemLog.ToString());
            var deviceLogCapturer = _deviceLogCapturerFactory.Create(_mainLog, deviceSystemLog, deviceName);

            deviceLogCapturer.StartCapture();

            try
            {
                await crashReporter.StartCaptureAsync();

                _mainLog.WriteLine("Starting test run");

                await _processManager.ExecuteCommandAsync(
                    mlaunchArguments,
                    _mainLog,
                    timeout,
                    cancellationToken : cancellationToken);
            }
            finally
            {
                deviceLogCapturer.StopCapture();
                deviceSystemLog.Dispose();
            }

            // Upload the system log
            if (File.Exists(deviceSystemLog.FullPath))
            {
                _mainLog.WriteLine("A capture of the device log is: {0}", deviceSystemLog.FullPath);
            }
        }
Ejemplo n.º 7
0
        public async Task<(string DeviceName, TestExecutingResult Result, string ResultMessage)> RunApp(
            AppBundleInformation appInformation,
            TestTarget target,
            TimeSpan timeout,
            TimeSpan testLaunchTimeout,
            string? deviceName = null,
            string? companionDeviceName = null,
            bool ensureCleanSimulatorState = false,
            int verbosity = 1,
            XmlResultJargon xmlResultJargon = XmlResultJargon.xUnit,
            CancellationToken cancellationToken = default)
        {
            var args = new MlaunchArguments
            {
                new SetAppArgumentArgument("-connection-mode"),
                new SetAppArgumentArgument("none"), // This will prevent the app from trying to connect to any IDEs
                new SetAppArgumentArgument("-autostart", true),
                new SetEnvVariableArgument(EnviromentVariables.AutoStart, true),
                new SetAppArgumentArgument("-autoexit", true),
                new SetEnvVariableArgument(EnviromentVariables.AutoExit, true),
                new SetAppArgumentArgument("-enablenetwork", true),
                new SetEnvVariableArgument(EnviromentVariables.EnableNetwork, true),

                // On macOS we can't edit the TCC database easily
                // (it requires adding the mac has to be using MDM: https://carlashley.com/2018/09/28/tcc-round-up/)
                // So by default ignore any tests that would pop up permission dialogs in CI.
                new SetEnvVariableArgument(EnviromentVariables.DisableSystemPermissionTests, 1),
            };

            for (int i = -1; i < verbosity; i++)
            {
                args.Add(new VerbosityArgument());
            }

            var isSimulator = target.IsSimulator();

            if (isSimulator)
            {
                args.Add(new SetAppArgumentArgument("-hostname:127.0.0.1", true));
                args.Add(new SetEnvVariableArgument(EnviromentVariables.HostName, "127.0.0.1"));
            }
            else
            {
                var ipAddresses = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()).AddressList.Select(ip => ip.ToString());
                var ips = string.Join(",", ipAddresses);
                args.Add(new SetAppArgumentArgument($"-hostname:{ips}", true));
                args.Add(new SetEnvVariableArgument(EnviromentVariables.HostName, ips));
            }

            var listenerLog = _logs.Create($"test-{target.AsString()}-{_helpers.Timestamp}.log", LogType.TestLog.ToString(), timestamp: true);
            var (transport, listener, listenerTmpFile) = _listenerFactory.Create(target.ToRunMode(),
                log: _mainLog,
                testLog: listenerLog,
                isSimulator: isSimulator,
                autoExit: true,
                xmlOutput: true); // cli always uses xml

            // Initialize has to be called before we try to get Port (internal implementation of the listener says so)
            // TODO: Improve this to not get into a broken state - it was really hard to debug when I moved this lower
            listener.Initialize();

            args.Add(new SetAppArgumentArgument($"-transport:{transport}", true));
            args.Add(new SetEnvVariableArgument(EnviromentVariables.Transport, transport.ToString().ToUpper()));

            if (transport == ListenerTransport.File)
            {
                args.Add(new SetEnvVariableArgument(EnviromentVariables.LogFilePath, listenerTmpFile));
            }

            args.Add(new SetAppArgumentArgument($"-hostport:{listener.Port}", true));
            args.Add(new SetEnvVariableArgument(EnviromentVariables.HostPort, listener.Port));

            if (_listenerFactory.UseTunnel && !isSimulator) // simulators do not support tunnels
            {
                args.Add(new SetEnvVariableArgument(EnviromentVariables.UseTcpTunnel, true));
            }

            if (_useXmlOutput)
            {
                // let the runner now via envars that we want to get a xml output, else the runner will default to plain text
                args.Add (new SetEnvVariableArgument (EnviromentVariables.EnableXmlOutput, true));
                args.Add (new SetEnvVariableArgument (EnviromentVariables.XmlMode, "wrapped"));
                args.Add (new SetEnvVariableArgument (EnviromentVariables.XmlVersion, $"{xmlResultJargon}"));
            }

            listener.StartAsync();

            var crashLogs = new Logs(_logs.Directory);

            if (appInformation.Extension.HasValue)
            {
                switch (appInformation.Extension)
                {
                    case Extension.TodayExtension:
                        args.Add(isSimulator
                            ? (MlaunchArgument)new LaunchSimulatorExtensionArgument(appInformation.LaunchAppPath, appInformation.BundleIdentifier)
                            : new LaunchDeviceExtensionArgument(appInformation.LaunchAppPath, appInformation.BundleIdentifier));
                        break;
                    case Extension.WatchKit2:
                    default:
                        throw new NotImplementedException();
                }
            }
            else
            {
                args.Add(isSimulator
                    ? (MlaunchArgument)new LaunchSimulatorArgument(appInformation.LaunchAppPath)
                    : new LaunchDeviceArgument(appInformation.LaunchAppPath));
            }

            var runMode = target.ToRunMode();
            ICrashSnapshotReporter crashReporter;
            ITestReporter testReporter;

            if (isSimulator)
            {
                crashReporter = _snapshotReporterFactory.Create(_mainLog, crashLogs, isDevice: !isSimulator, deviceName: null!);
                testReporter = _testReporterFactory.Create(_mainLog,
                    _mainLog,
                    _logs,
                    crashReporter,
                    listener,
                    new XmlResultParser(),
                    appInformation,
                    runMode,
                    xmlResultJargon,
                    device: null,
                    timeout,
                    null,
                    (level, message) => _mainLog.WriteLine(message));

                using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(testReporter.CancellationToken, cancellationToken);

                listener.ConnectedTask
                    .TimeoutAfter(testLaunchTimeout)
                    .ContinueWith(testReporter.LaunchCallback)
                    .DoNotAwait();

                await _simulatorLoader.LoadDevices(_logs.Create($"simulator-list-{_helpers.Timestamp}.log", "Simulator list"), false, false);

                var simulators = await _simulatorLoader.FindSimulators(target, _mainLog);
                if (!(simulators?.Any() ?? false))
                {
                    _mainLog.WriteLine("Didn't find any suitable simulators");
                    throw new NoDeviceFoundException();
                }

                var simulator = string.IsNullOrEmpty(deviceName)
                    ? simulators.FirstOrDefault()
                    : simulators.FirstOrDefault(s => string.Equals(s.Name, deviceName, StringComparison.InvariantCultureIgnoreCase));

                if (simulator == null)
                {
                    throw new NoDeviceFoundException();
                }

                deviceName = simulator.Name;

                if (!target.IsWatchOSTarget())
                {
                    var stderrTty = _helpers.GetTerminalName(2);
                    if (!string.IsNullOrEmpty(stderrTty))
                    {
                        args.Add(new SetStderrArgument(stderrTty));
                    }
                    else
                    {
                        var stdoutLog = _logs.CreateFile($"mlaunch-stdout-{_helpers.Timestamp}.log", "Standard output");
                        var stderrLog = _logs.CreateFile($"mlaunch-stderr-{_helpers.Timestamp}.log", "Standard error");
                        args.Add(new SetStdoutArgument(stdoutLog));
                        args.Add(new SetStderrArgument(stderrLog));
                    }
                }

                var systemLogs = new List<ICaptureLog>();
                foreach (var sim in simulators)
                {
                    // Upload the system log
                    _mainLog.WriteLine("System log for the '{1}' simulator is: {0}", sim.SystemLog, sim.Name);
                    bool isCompanion = sim != simulator;

                    var logDescription = isCompanion ? LogType.CompanionSystemLog.ToString() : LogType.SystemLog.ToString();
                    var log = _captureLogFactory.Create(
                        Path.Combine(_logs.Directory, sim.Name + ".log"),
                        sim.SystemLog,
                        true,
                        logDescription);

                    log.StartCapture();
                    _logs.Add(log);
                    systemLogs.Add(log);
                }

                _mainLog.WriteLine("*** Executing {0}/{1} in the simulator ***", appInformation.AppName, target);

                if (ensureCleanSimulatorState)
                {
                    foreach (var sim in simulators)
                    {
                        await sim.PrepareSimulator(_mainLog, appInformation.BundleIdentifier);
                    }
                }

                args.Add(new SimulatorUDIDArgument(simulator.UDID));

                await crashReporter.StartCaptureAsync();

                _mainLog.WriteLine("Starting test run");

                var result = _processManager.ExecuteCommandAsync(args, _mainLog, timeout, cancellationToken: linkedCts.Token);

                await testReporter.CollectSimulatorResult(result);

                // cleanup after us
                if (ensureCleanSimulatorState)
                {
                    await simulator.KillEverything(_mainLog);
                }

                foreach (var log in systemLogs)
                {
                    log.StopCapture();
                }
            }
            else
            {
                args.Add(new DisableMemoryLimitsArgument());

                if (deviceName == null)
                {
                    IHardwareDevice? companionDevice = null;
                    IHardwareDevice device = await _hardwareDeviceLoader.FindDevice(runMode, _mainLog, includeLocked: false, force: false);

                    if (target.IsWatchOSTarget())
                    {
                        companionDevice = await _hardwareDeviceLoader.FindCompanionDevice(_mainLog, device);
                    }

                    deviceName = companionDevice?.Name ?? device.Name;
                }

                if (deviceName == null)
                {
                    throw new NoDeviceFoundException();
                }

                crashReporter = _snapshotReporterFactory.Create(_mainLog, crashLogs, isDevice: !isSimulator, deviceName);
                testReporter = _testReporterFactory.Create(_mainLog,
                    _mainLog,
                    _logs,
                    crashReporter,
                    listener,
                    new XmlResultParser(),
                    appInformation,
                    runMode,
                    xmlResultJargon,
                    deviceName,
                    timeout,
                    null,
                    (level, message) => _mainLog.WriteLine(message));

                using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(testReporter.CancellationToken, cancellationToken);

                listener.ConnectedTask
                    .TimeoutAfter(testLaunchTimeout)
                    .ContinueWith(testReporter.LaunchCallback)
                    .DoNotAwait();

                _mainLog.WriteLine("*** Executing {0}/{1} on device '{2}' ***", appInformation.AppName, target, deviceName);

                if (target.IsWatchOSTarget())
                {
                    args.Add(new AttachNativeDebuggerArgument()); // this prevents the watch from backgrounding the app.
                }
                else
                {
                    args.Add(new WaitForExitArgument());
                }

                args.Add(new DeviceNameArgument(deviceName));

                var deviceSystemLog = _logs.Create($"device-{deviceName}-{_helpers.Timestamp}.log", "Device log");
                var deviceLogCapturer = _deviceLogCapturerFactory.Create(_mainLog, deviceSystemLog, deviceName);
                deviceLogCapturer.StartCapture();

                try
                {
                    await crashReporter.StartCaptureAsync();

                    // create a tunnel to communicate with the device
                    if (transport == ListenerTransport.Tcp && _listenerFactory.UseTunnel && listener is SimpleTcpListener tcpListener)
                    {
                        // create a new tunnel using the listener
                        var tunnel = _listenerFactory.TunnelBore.Create(deviceName, _mainLog);
                        tunnel.Open(deviceName, tcpListener, timeout, _mainLog);
                        // wait until we started the tunnel
                        await tunnel.Started;
                    }

                    _mainLog.WriteLine("Starting test run");

                    // We need to check for MT1111 (which means that mlaunch won't wait for the app to exit).
                    var aggregatedLog = Log.CreateAggregatedLog(testReporter.CallbackLog, _mainLog);
                    Task<ProcessExecutionResult> runTestTask = _processManager.ExecuteCommandAsync(
                        args,
                        aggregatedLog,
                        timeout,
                        cancellationToken: linkedCts.Token);

                    await testReporter.CollectDeviceResult(runTestTask);
                }
                finally
                {
                    deviceLogCapturer.StopCapture();
                    deviceSystemLog.Dispose();

                    // close a tunnel if it was created
                    if (!isSimulator && _listenerFactory.UseTunnel)
                        await _listenerFactory.TunnelBore.Close(deviceName);
                }

                // Upload the system log
                if (File.Exists(deviceSystemLog.FullPath))
                {
                    _mainLog.WriteLine("A capture of the device log is: {0}", deviceSystemLog.FullPath);
                }
            }

            listener.Cancel();
            listener.Dispose();

            // check the final status, copy all the required data
            var (testResult, resultMessage) = await testReporter.ParseResult();

            return (deviceName, testResult, resultMessage);
        }
Ejemplo n.º 8
0
        public async Task <(string DeviceName, TestExecutingResult Result, string ResultMessage)> RunApp(
            AppBundleInformation appInformation,
            TestTargetOs target,
            TimeSpan timeout,
            TimeSpan testLaunchTimeout,
            string?deviceName              = null,
            string?companionDeviceName     = null,
            bool ensureCleanSimulatorState = false,
            int verbosity = 1,
            XmlResultJargon xmlResultJargon     = XmlResultJargon.xUnit,
            string[]?skippedMethods             = null,
            string[]?skippedTestClasses         = null,
            CancellationToken cancellationToken = default)
        {
            var  runMode     = target.Platform.ToRunMode();
            bool isSimulator = target.Platform.IsSimulator();

            var deviceListenerLog = _logs.Create($"test-{target.AsString()}-{_helpers.Timestamp}.log", LogType.TestLog.ToString(), timestamp: true);

            var(deviceListenerTransport, deviceListener, deviceListenerTmpFile) = _listenerFactory.Create(
                runMode,
                log: _mainLog,
                testLog: deviceListenerLog,
                isSimulator: isSimulator,
                autoExit: true,
                xmlOutput: true); // cli always uses xml

            ISimulatorDevice?simulator          = null;
            ISimulatorDevice?companionSimulator = null;

            // Find devices
            if (isSimulator)
            {
                int       attempt     = 1;
                const int maxAttempts = 3;
                while (true)
                {
                    try
                    {
                        (simulator, companionSimulator) = await _simulatorLoader.FindSimulators(target, _mainLog);

                        break;
                    }
                    catch (Exception e)
                    {
                        _mainLog.WriteLine($"Failed to find/create simulator (attempt {attempt}/{maxAttempts}):" + Environment.NewLine + e);

                        if (attempt == maxAttempts)
                        {
                            throw new NoDeviceFoundException("Failed to find/create suitable simulator");
                        }
                    }
                    finally
                    {
                        attempt++;
                    }
                }

                deviceName = companionSimulator?.Name ?? simulator.Name;
            }
            else
            {
                deviceName ??= await FindDevice(target) ?? throw new NoDeviceFoundException();
            }

            int deviceListenerPort = deviceListener.InitializeAndGetPort();

            deviceListener.StartAsync();

            var crashLogs = new Logs(_logs.Directory);

            ICrashSnapshotReporter crashReporter = _snapshotReporterFactory.Create(_mainLog, crashLogs, isDevice: !isSimulator, deviceName);
            ITestReporter          testReporter  = _testReporterFactory.Create(_mainLog,
                                                                               _mainLog,
                                                                               _logs,
                                                                               crashReporter,
                                                                               deviceListener,
                                                                               _resultParser,
                                                                               appInformation,
                                                                               runMode,
                                                                               xmlResultJargon,
                                                                               deviceName,
                                                                               timeout,
                                                                               null,
                                                                               (level, message) => _mainLog.WriteLine(message));

            deviceListener.ConnectedTask
            .TimeoutAfter(testLaunchTimeout)
            .ContinueWith(testReporter.LaunchCallback)
            .DoNotAwait();

            _mainLog.WriteLine($"*** Executing '{appInformation.AppName}' on {target} '{deviceName}' ***");

            try
            {
                using var combinedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(testReporter.CancellationToken, cancellationToken);

                if (isSimulator)
                {
                    if (simulator == null)
                    {
                        _mainLog.WriteLine("Didn't find any suitable simulator");
                        throw new NoDeviceFoundException();
                    }

                    var mlaunchArguments = GetSimulatorArguments(
                        appInformation,
                        simulator,
                        verbosity,
                        xmlResultJargon,
                        skippedMethods,
                        skippedTestClasses,
                        deviceListenerTransport,
                        deviceListenerPort,
                        deviceListenerTmpFile);

                    await RunSimulatorTests(
                        mlaunchArguments,
                        appInformation,
                        crashReporter,
                        testReporter,
                        simulator,
                        companionSimulator,
                        ensureCleanSimulatorState,
                        timeout,
                        combinedCancellationToken.Token);
                }
                else
                {
                    var mlaunchArguments = GetDeviceArguments(
                        appInformation,
                        deviceName,
                        target.Platform.IsWatchOSTarget(),
                        verbosity,
                        xmlResultJargon,
                        skippedMethods,
                        skippedTestClasses,
                        deviceListenerTransport,
                        deviceListenerPort,
                        deviceListenerTmpFile);

                    await RunDeviceTests(
                        mlaunchArguments,
                        crashReporter,
                        testReporter,
                        deviceListener,
                        deviceName,
                        timeout,
                        combinedCancellationToken.Token);
                }
            }
            finally
            {
                deviceListener.Cancel();
                deviceListener.Dispose();
            }

            // Check the final status, copy all the required data
            var(testResult, resultMessage) = await testReporter.ParseResult();

            return(deviceName, testResult, resultMessage);
        }
Ejemplo n.º 9
0
    private async Task <ExitCode> OrchestrateOperationInternal(
        TestTargetOs target,
        string?deviceName,
        bool includeWirelessDevices,
        bool resetSimulator,
        bool enableLldb,
        GetAppBundleInfoFunc getAppBundle,
        ExecuteMacCatalystAppFunc executeMacCatalystApp,
        ExecuteAppFunc executeApp,
        CancellationToken cancellationToken)
    {
        _lldbFileCreated = false;
        var isLldbEnabled = IsLldbEnabled();

        if (isLldbEnabled && !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 (enableLldb)
        {
            if (!File.Exists(s_mlaunchLldbConfigFile))
            {
                // create empty file
                File.WriteAllText(s_mlaunchLldbConfigFile, string.Empty);
                _lldbFileCreated = true;
            }
        }

        if (includeWirelessDevices && target.Platform.IsSimulator())
        {
            _logger.LogWarning("Including wireless devices while targeting a simulator has no effect");
        }

        if (resetSimulator && !target.Platform.IsSimulator())
        {
            _logger.LogWarning("Targeting device but requesting simulator reset has no effect");
            resetSimulator = false;
        }

        ExitCode             exitCode;
        IDevice              device;
        IDevice?             companionDevice;
        AppBundleInformation appBundleInfo;

        if (target.Platform == TestTarget.MacCatalyst)
        {
            try
            {
                appBundleInfo = await getAppBundle(target, null !, cancellationToken);
            }
            catch (Exception e)
            {
                cancellationToken.ThrowIfCancellationRequested();
                _logger.LogError(e.Message);
                return(ExitCode.PACKAGE_NOT_FOUND);
            }

            try
            {
                return(await executeMacCatalystApp(appBundleInfo));
            }
            catch (Exception e)
            {
                var message = new StringBuilder().AppendLine("Application run failed:");
                exitCode = ExitCode.APP_LAUNCH_FAILURE;

                if (_errorKnowledgeBase.IsKnownTestIssue(_mainLog, out var failure))
                {
                    message.Append(failure.HumanMessage);
                    if (failure.IssueLink != null)
                    {
                        message.AppendLine($" Find more information at {failure.IssueLink}");
                    }

                    if (failure.SuggestedExitCode.HasValue)
                    {
                        exitCode = (ExitCode)failure.SuggestedExitCode.Value;
                    }
                }
                else
                {
                    message.AppendLine(e.ToString());
                }

                _logger.LogError(message.ToString());

                return(exitCode);
            }
        }

        try
        {
            _logger.LogInformation($"Looking for available {target.AsString()} {(target.Platform.IsSimulator() ? "simulators" : "devices")}..");

            var finderLogName = $"list-{target.AsString()}-{_helpers.Timestamp}.log";
            using var finderLog = _logs.Create(finderLogName, "DeviceList", true);

            _mainLog.WriteLine(
                $"Looking for available {target.AsString()} {(target.Platform.IsSimulator() ? "simulators" : "devices")}. " +
                $"Storing logs into {finderLogName}");

            (device, companionDevice) = await _deviceFinder.FindDevice(target, deviceName, finderLog, includeWirelessDevices, cancellationToken);

            _logger.LogInformation($"Found {(target.Platform.IsSimulator() ? "simulator" : "physical")} device '{device.Name}'");

            if (companionDevice != null)
            {
                _logger.LogInformation($"Found companion {(target.Platform.IsSimulator() ? "simulator" : "physical")} device '{companionDevice.Name}'");
            }
        }
        catch (NoDeviceFoundException e)
        {
            _logger.LogError(e.Message);
            return(ExitCode.DEVICE_NOT_FOUND);
        }

        cancellationToken.ThrowIfCancellationRequested();

        try
        {
            appBundleInfo = await getAppBundle(target, device, cancellationToken);
        }
        catch (Exception e)
        {
            cancellationToken.ThrowIfCancellationRequested();
            _logger.LogError(e.Message);
            return(ExitCode.PACKAGE_NOT_FOUND);
        }

        cancellationToken.ThrowIfCancellationRequested();

        if (target.Platform.IsSimulator() && resetSimulator)
        {
            try
            {
                var simulator = (ISimulatorDevice)device;
                var bundleIds = appBundleInfo.BundleIdentifier == string.Empty ? Array.Empty <string>() : new[] { appBundleInfo.BundleIdentifier };

                _logger.LogInformation($"Reseting simulator '{device.Name}'");
                await simulator.PrepareSimulator(_mainLog, bundleIds);

                if (companionDevice != null)
                {
                    _logger.LogInformation($"Reseting companion simulator '{companionDevice.Name}'");
                    var companionSimulator = (ISimulatorDevice)companionDevice;
                    await companionSimulator.PrepareSimulator(_mainLog, bundleIds);
                }

                _logger.LogInformation("Simulator reset finished");
            }
            catch (Exception e)
            {
                _logger.LogError($"Failed to reset simulator: " + Environment.NewLine + e);
                return(ExitCode.SIMULATOR_FAILURE);
            }
        }

        cancellationToken.ThrowIfCancellationRequested();

        // Note down the actual test target
        // For simulators (e.g. "iOS 13.4"), we strip the iOS part and keep the version only, for devices there's no OS
        _diagnosticsData.TargetOS = target.Platform.IsSimulator() ? device.OSVersion.Split(' ', 2).Last() : device.OSVersion;
        _diagnosticsData.Device   = device.Name ?? device.UDID;

        // Uninstall the app first to get a clean state
        if (!resetSimulator)
        {
            await UninstallApp(target.Platform, appBundleInfo.BundleIdentifier, device, isPreparation : true, cancellationToken);

            cancellationToken.ThrowIfCancellationRequested();
        }

        exitCode = await InstallApp(appBundleInfo, device, target, cancellationToken);

        if (exitCode != ExitCode.SUCCESS)
        {
            _logger.LogInformation($"Cleaning up the failed installation from '{device.Name}'");

            var uninstallResult = await UninstallApp(target.Platform, appBundleInfo.BundleIdentifier, device, isPreparation : false, new CancellationToken());

            if (uninstallResult == ExitCode.SIMULATOR_FAILURE)
            {
                // Sometimes the simulator gets in a bad shape and we won't be able to install the app, we can tell here
                return(ExitCode.SIMULATOR_FAILURE);
            }

            return(exitCode);
        }

        try
        {
            exitCode = await executeApp(appBundleInfo, device, companionDevice);
        }
        catch (Exception e)
        {
            exitCode = ExitCode.GENERAL_FAILURE;

            var message = new StringBuilder().AppendLine("Application run failed:");

            if (_errorKnowledgeBase.IsKnownTestIssue(_mainLog, out var failure))
            {
                message.Append(failure.HumanMessage);
                if (failure.IssueLink != null)
                {
                    message.AppendLine($" Find more information at {failure.IssueLink}");
                }

                if (failure.SuggestedExitCode.HasValue)
                {
                    exitCode = (ExitCode)failure.SuggestedExitCode.Value;
                }
            }
            else
            {
                message.AppendLine(e.ToString());
            }

            _logger.LogError(message.ToString());
        }
        finally
        {
            if (target.Platform.IsSimulator() && resetSimulator)
            {
                await CleanUpSimulators(device, companionDevice);
            }
            else if (device != null) // Do not uninstall if device was cleaned up
            {
                var uninstallResult = await UninstallApp(target.Platform, appBundleInfo.BundleIdentifier, device, false, new CancellationToken());

                // We are able to detect a case when simulator is in a bad shape
                // If it also failed the test/run, we should present that as the failure
                if (uninstallResult == ExitCode.SIMULATOR_FAILURE && exitCode != ExitCode.SUCCESS && exitCode != ExitCode.TESTS_FAILED)
                {
                    exitCode = ExitCode.SIMULATOR_FAILURE;
                }
            }
        }

        return(exitCode);
    }