Exemplo n.º 1
0
        public async Task KillEverything(ILog log)
        {
            await _processManager.ExecuteCommandAsync("launchctl", new[] { "remove", "com.apple.CoreSimulator.CoreSimulatorService" }, log, TimeSpan.FromSeconds(10));

            var toKill = new string[] { "iPhone Simulator", "iOS Simulator", "Simulator", "Simulator (Watch)", "com.apple.CoreSimulator.CoreSimulatorService", "ibtoold" };

            var args = new List <string>
            {
                "-9"
            };

            args.AddRange(toKill);

            await _processManager.ExecuteCommandAsync("killall", args, log, TimeSpan.FromSeconds(10));

            var dirsToBeDeleted = new[] {
                Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Saved Application State", "com.apple.watchsimulator.savedState"),
                Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Saved Application State", "com.apple.iphonesimulator.savedState"),
            };

            foreach (var dir in dirsToBeDeleted)
            {
                try
                {
                    if (Directory.Exists(dir))
                    {
                        Directory.Delete(dir, true);
                    }
                }
                catch (Exception e)
                {
                    log.WriteLine("Could not delete the directory '{0}': {1}", dir, e.Message);
                }
            }
        }
Exemplo n.º 2
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);
            }
        }
Exemplo n.º 3
0
    public async Task <ProcessExecutionResult> InstallApp(
        AppBundleInformation appBundleInformation,
        TestTargetOs target,
        IDevice device,
        CancellationToken cancellationToken = default)
    {
        if (!Directory.Exists(appBundleInformation.LaunchAppPath))
        {
            throw new DirectoryNotFoundException("Failed to find the app bundle directory");
        }

        var args = new MlaunchArguments();

        if (target.Platform.IsSimulator())
        {
            args.Add(new SimulatorUDIDArgument(device));
            args.Add(new InstallAppOnSimulatorArgument(appBundleInformation.LaunchAppPath));
        }
        else
        {
            args.Add(new DeviceNameArgument(device));
            args.Add(new InstallAppOnDeviceArgument(appBundleInformation.LaunchAppPath));

            if (target.Platform.IsWatchOSTarget())
            {
                args.Add(new DeviceArgument("ios,watchos"));
            }
        }

        var totalSize = Directory.GetFiles(appBundleInformation.LaunchAppPath, "*", SearchOption.AllDirectories).Select((v) => new FileInfo(v).Length).Sum();

        _mainLog.WriteLine($"Installing '{appBundleInformation.LaunchAppPath}' to '{device.Name}' ({totalSize / 1024.0 / 1024.0:N2} MB)");

        return(await _processManager.ExecuteCommandAsync(args, _mainLog, TimeSpan.FromMinutes(15), verbosity : 2, cancellationToken : cancellationToken));
    }
Exemplo n.º 4
0
    public Task <ProcessExecutionResult> UninstallDeviceApp(IHardwareDevice device, string appBundleId, CancellationToken cancellationToken = default)
    {
        var args = new MlaunchArguments
        {
            new UninstallAppFromDeviceArgument(appBundleId),
            new DeviceNameArgument(device)
        };

        return(_processManager.ExecuteCommandAsync(args, _mainLog, TimeSpan.FromMinutes(3), cancellationToken: cancellationToken));
    }
Exemplo n.º 5
0
        public void Open(string device, ITunnelListener simpleListener, TimeSpan timeout, ILog mainLog)
        {
            if (device == null)
            {
                throw new ArgumentNullException(nameof(device));
            }

            if (simpleListener == null)
            {
                throw new ArgumentNullException(nameof(simpleListener));
            }

            if (mainLog == null)
            {
                throw new ArgumentNullException(nameof(mainLog));
            }

            lock (_processExecutionLock)
            {
                // launch app, but do not await for the result, since we need to create the tunnel
                var tcpArgs = new MlaunchArguments {
                    new TcpTunnelArgument(simpleListener.Port),
                    new VerbosityArgument(),
                    new DeviceNameArgument(device),
                };

                // use a cancelation token, later will be used to kill the tcp tunnel process
                _cancellationToken = new CancellationTokenSource();
                mainLog.WriteLine($"Starting tcp tunnel between mac port: {simpleListener.Port} and devie port {simpleListener.Port}.");
                Port = simpleListener.Port;
                var tunnelbackLog = new CallbackLog((line) =>
                {
                    mainLog.WriteLine($"The tcp tunnel output is {line}");
                    if (line.Contains("Tcp tunnel started on device"))
                    {
                        mainLog.Write($"Tcp tunnel created on port {simpleListener.Port}");
                        startedCompletionSource.TrySetResult(true);
                        simpleListener.TunnelHoleThrough.TrySetResult(true);
                    }
                });
                // do not await since we are going to be running the process in parallel
                _tcpTunnelExecutionTask = _processManager.ExecuteCommandAsync(tcpArgs, tunnelbackLog, timeout, cancellationToken: _cancellationToken.Token);
                _tcpTunnelExecutionTask.ContinueWith(delegate(Task <ProcessExecutionResult> task)
                {
                    // if the task completes, means that we had issues with the creation of the tunnel and the process
                    // exited, if that is the case, we do not want to make the app wait, therefore, set the hole to false
                    // which will throw an exception from the listener.
                    simpleListener.TunnelHoleThrough.TrySetResult(task.Result.Succeeded);
                });
            }
        }
Exemplo n.º 6
0
        public async Task <ProcessExecutionResult> UninstallApp(string deviceName, string appBundleId, CancellationToken cancellationToken = default)
        {
            var args = new MlaunchArguments();

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

            args.Add(new UninstallAppFromDeviceArgument(appBundleId));
            args.Add(new DeviceNameArgument(deviceName));

            return(await _processManager.ExecuteCommandAsync(args, _mainLog, TimeSpan.FromMinutes(1), cancellationToken : cancellationToken));
        }
Exemplo n.º 7
0
        Task BuildTestLibrariesAsync()
        {
            var sb           = new StringBuilder();
            var callback_log = new CallbackLog((v) => sb.Append(v));
            var log          = Log.CreateAggregatedLog(callback_log, MainLog);

            return(processManager.ExecuteCommandAsync("make", new [] { "all", $"-j{Environment.ProcessorCount}", "-C", Path.Combine(HarnessConfiguration.RootDirectory, "test-libraries") }, log, TimeSpan.FromMinutes(10)).ContinueWith((v) => {
                var per = v.Result;
                if (!per.Succeeded)
                {
                    // Only show the log if something went wrong.
                    using var fn = Logs.Create("build-test-libraries.log", "⚠️ Build test/test-libraries failed ⚠️");
                    File.WriteAllText(fn.FullPath, sb.ToString());
                }
            }));
        }
Exemplo n.º 8
0
    public async Task <bool> AgreeToPromptsAsync(string simRuntime, string TCCDb, string udid, ILog log, params string[] bundleIdentifiers)
    {
        if (bundleIdentifiers == null || bundleIdentifiers.Length == 0)
        {
            log.WriteLine("No bundle identifiers given when requested permission editing.");
            return(false);
        }

        var sim_services = new string[]
        {
            "kTCCServiceAll",     // You'd think 'All' means all prompts, but some prompts still show up.
            "kTCCServiceAddressBook",
            "kTCCServiceCalendar",
            "kTCCServiceCamera",
            "kTCCServicePhotos",
            "kTCCServiceMediaLibrary",
            "kTCCServiceMicrophone",
            "kTCCServiceUbiquity",
            "kTCCServiceWillow"
        };

        var failure          = false;
        var tcc_edit_timeout = 3;
        var watch            = new Stopwatch();

        watch.Start();
        var format = GetTCCFormat(simRuntime);

        if (format >= 4)
        {
            // We don't care if booting fails (it'll fail if it's already booted for instance)
            await _processManager.ExecuteXcodeCommandAsync("simctl", new[] { "boot", udid }, log, TimeSpan.FromMinutes(1));

            // execute 'simctl privacy <udid> grant all <bundle identifier>' for each bundle identifier
            foreach (var bundle_identifier in bundleIdentifiers)
            {
                foreach (var bundle_id in new[] { bundle_identifier, bundle_identifier + ".watchkitapp" })
                {
                    foreach (var service in sim_services)
                    {
                        var args = new List <string>
                        {
                            "privacy",
                            udid,
                            "grant",
                            service,
                            bundle_id
                        };
                        var rv = await _processManager.ExecuteXcodeCommandAsync("simctl", args, log, TimeSpan.FromSeconds(30));

                        if (!rv.Succeeded)
                        {
                            failure = true;
                            break;
                        }
                    }
                }

                if (failure)
                {
                    break;
                }
            }
        }
        else
        {
            do
            {
                if (failure)
                {
                    log.WriteLine("Failed to edit TCC.db, trying again in 1 second... ", (int)(tcc_edit_timeout - watch.Elapsed.TotalSeconds));
                    await Task.Delay(TimeSpan.FromSeconds(1));
                }
                failure = false;
                foreach (var bundle_identifier in bundleIdentifiers)
                {
                    var args = new List <string>();
                    var sql  = new System.Text.StringBuilder("\n");
                    args.Add(TCCDb);
                    foreach (var bundle_id in new[] { bundle_identifier, bundle_identifier + ".watchkitapp" })
                    {
                        foreach (var service in sim_services)
                        {
                            switch (format)
                            {
                            case 1:
                                // CREATE TABLE access (service TEXT NOT NULL, client TEXT NOT NULL, client_type INTEGER NOT NULL, allowed INTEGER NOT NULL, prompt_count INTEGER NOT NULL, csreq BLOB, CONSTRAINT key PRIMARY KEY (service, client, client_type));
                                sql.AppendFormat("DELETE FROM access WHERE service = '{0}' AND client = '{1}';\n", service, bundle_id);
                                sql.AppendFormat("INSERT INTO access VALUES('{0}','{1}',0,1,0,NULL);\n", service, bundle_id);
                                break;

                            case 2:
                                // CREATE TABLE access (service	TEXT NOT NULL, client TEXT NOT NULL, client_type INTEGER NOT NULL, allowed INTEGER NOT NULL, prompt_count INTEGER NOT NULL, csreq BLOB, policy_id INTEGER, PRIMARY KEY (service, client, client_type), FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE ON UPDATE CASCADE);
                                sql.AppendFormat("DELETE FROM access WHERE service = '{0}' AND client = '{1}';\n", service, bundle_id);
                                sql.AppendFormat("INSERT INTO access VALUES('{0}','{1}',0,1,0,NULL,NULL);\n", service, bundle_id);
                                break;

                            case 3:     // Xcode 10+
                                        // CREATE TABLE access (service TEXT NOT NULL, client TEXT NOT NULL, client_type INTEGER NOT NULL, allowed INTEGER NOT NULL, prompt_count INTEGER NOT NULL, csreq BLOB, policy_id INTEGER, indirect_object_identifier_type INTEGER, indirect_object_identifier TEXT, indirect_object_code_identity BLOB, flags INTEGER, last_modified  INTEGER NOT NULL DEFAULT (CAST(strftime('%s','now') AS INTEGER)), PRIMARY KEY (service, client, client_type, indirect_object_identifier), FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE ON UPDATE CASCADE)
                                sql.AppendFormat("INSERT OR REPLACE INTO access VALUES('{0}','{1}',0,1,0,NULL,NULL,NULL,'UNUSED',NULL,NULL,{2});\n", service, bundle_id, DateTimeOffset.Now.ToUnixTimeSeconds());
                                break;

                            default:
                                throw new NotImplementedException();
                            }
                        }
                    }

                    args.Add(sql.ToString());

                    var rv = await _processManager.ExecuteCommandAsync("sqlite3", args, log, TimeSpan.FromSeconds(5));

                    if (!rv.Succeeded)
                    {
                        failure = true;
                        break;
                    }
                }
            } while (failure && watch.Elapsed.TotalSeconds <= tcc_edit_timeout);
        }

        if (failure)
        {
            log.WriteLine("Failed to edit TCC.db, the test run might hang due to permission request dialogs");
        }
        else
        {
            log.WriteLine("Successfully edited TCC.db");
        }

        log.WriteLine("Current TCC database contents:");
        await _processManager.ExecuteCommandAsync("sqlite3", new[] { TCCDb, ".dump" }, log, TimeSpan.FromSeconds(5));

        return(!failure);
    }
Exemplo n.º 9
0
        private async Task RunSimulatorApp(
            MlaunchArguments mlaunchArguments,
            AppBundleInformation appInformation,
            ICrashSnapshotReporter crashReporter,
            ISimulatorDevice simulator,
            ISimulatorDevice?companionSimulator,
            bool ensureCleanSimulatorState,
            TimeSpan timeout,
            CancellationToken cancellationToken)
        {
            var systemLogs = new List <ICaptureLog>();

            try
            {
                _mainLog.WriteLine("System log for the '{1}' simulator is: {0}", simulator.SystemLog, simulator.Name);

                var simulatorLog = _captureLogFactory.Create(
                    path: Path.Combine(_logs.Directory, simulator.Name + ".log"),
                    systemLogPath: simulator.SystemLog,
                    entireFile: false,
                    LogType.SystemLog.ToString());

                simulatorLog.StartCapture();
                _logs.Add(simulatorLog);
                systemLogs.Add(simulatorLog);

                if (companionSimulator != null)
                {
                    _mainLog.WriteLine("System log for the '{1}' companion simulator is: {0}", companionSimulator.SystemLog, companionSimulator.Name);

                    var companionLog = _captureLogFactory.Create(
                        path: Path.Combine(_logs.Directory, companionSimulator.Name + ".log"),
                        systemLogPath: companionSimulator.SystemLog,
                        entireFile: false,
                        LogType.CompanionSystemLog.ToString());

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

                if (ensureCleanSimulatorState)
                {
                    await simulator.PrepareSimulator(_mainLog, appInformation.BundleIdentifier);

                    if (companionSimulator != null)
                    {
                        await companionSimulator.PrepareSimulator(_mainLog, appInformation.BundleIdentifier);
                    }
                }

                await crashReporter.StartCaptureAsync();

                _mainLog.WriteLine("Starting test run");

                await _processManager.ExecuteCommandAsync(mlaunchArguments, _mainLog, timeout, cancellationToken : cancellationToken);

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

                    if (companionSimulator != null)
                    {
                        await companionSimulator.KillEverything(_mainLog);
                    }
                }
            }
            finally
            {
                foreach (ICaptureLog?log in systemLogs)
                {
                    log.StopCapture();
                    log.Dispose();
                }
            }
        }
Exemplo n.º 10
0
        public async Task LoadDevices(ILog log, bool includeLocked = false, bool forceRefresh = false, bool listExtraData = false)
        {
            await _semaphore.WaitAsync();

            if (_loaded)
            {
                if (!forceRefresh)
                {
                    _semaphore.Release();
                    return;
                }

                _supportedRuntimes.Reset();
                _supportedDeviceTypes.Reset();
                _availableDevices.Reset();
                _availableDevicePairs.Reset();
            }

            var tmpfile = Path.GetTempFileName();

            try
            {
                var arguments = new MlaunchArguments(
                    new ListSimulatorsArgument(tmpfile),
                    new XmlOutputFormatArgument());

                var result = await _processManager.ExecuteCommandAsync(arguments, log, timeout : TimeSpan.FromMinutes(6));

                if (!result.Succeeded)
                {
                    // mlaunch can sometimes return 0 but hang and timeout. It still outputs returns valid content to the tmp file
                    log.WriteLine($"mlaunch failed when listing simulators but trying to parse the results anyway");
                }

                var fileInfo = new FileInfo(tmpfile);
                if (!fileInfo.Exists || fileInfo.Length == 0)
                {
                    throw new Exception($"Failed to list simulators - no XML with devices found. " +
                                        $"mlaunch {(result.TimedOut ? "timed out" : "exited")} with {result.ExitCode})");
                }

                log.WriteLine($"Simulator listing finished ({Math.Ceiling(((double)fileInfo.Length) / 1024)} kB)");

                var simulatorData = new XmlDocument();
                simulatorData.LoadWithoutNetworkAccess(tmpfile);
                foreach (XmlNode?sim in simulatorData.SelectNodes("/MTouch/Simulator/SupportedRuntimes/SimRuntime"))
                {
                    if (sim == null)
                    {
                        continue;
                    }

                    _supportedRuntimes.Add(new SimRuntime(
                                               name: sim.SelectSingleNode("Name").InnerText,
                                               identifier: sim.SelectSingleNode("Identifier").InnerText,
                                               version: long.Parse(sim.SelectSingleNode("Version").InnerText)));
                }

                foreach (XmlNode?sim in simulatorData.SelectNodes("/MTouch/Simulator/SupportedDeviceTypes/SimDeviceType"))
                {
                    if (sim == null)
                    {
                        continue;
                    }

                    _supportedDeviceTypes.Add(new SimDeviceType(
                                                  name: sim.SelectSingleNode("Name").InnerText,
                                                  identifier: sim.SelectSingleNode("Identifier").InnerText,
                                                  productFamilyId: sim.SelectSingleNode("ProductFamilyId").InnerText,
                                                  minRuntimeVersion: long.Parse(sim.SelectSingleNode("MinRuntimeVersion").InnerText),
                                                  maxRuntimeVersion: long.Parse(sim.SelectSingleNode("MaxRuntimeVersion").InnerText),
                                                  supports64Bits: bool.Parse(sim.SelectSingleNode("Supports64Bits").InnerText)));
                }

                foreach (XmlNode?sim in simulatorData.SelectNodes("/MTouch/Simulator/AvailableDevices/SimDevice"))
                {
                    if (sim == null)
                    {
                        continue;
                    }

                    _availableDevices.Add(new SimulatorDevice(_processManager, new TCCDatabase(_processManager))
                    {
                        Name          = sim.Attributes["Name"].Value,
                        UDID          = sim.Attributes["UDID"].Value,
                        SimRuntime    = sim.SelectSingleNode("SimRuntime").InnerText,
                        SimDeviceType = sim.SelectSingleNode("SimDeviceType").InnerText,
                        DataPath      = sim.SelectSingleNode("DataPath").InnerText,
                        LogPath       = sim.SelectSingleNode("LogPath").InnerText,
                    });
                }

                var sim_device_pairs = simulatorData.
                                       SelectNodes("/MTouch/Simulator/AvailableDevicePairs/SimDevicePair").
                                       Cast <XmlNode>().
                                       // There can be duplicates, so remove those.
                                       Distinct(new SimulatorXmlNodeComparer());

                foreach (XmlNode sim in sim_device_pairs)
                {
                    _availableDevicePairs.Add(new SimDevicePair(
                                                  uDID: sim.Attributes["UDID"].Value,
                                                  companion: sim.SelectSingleNode("Companion").InnerText,
                                                  gizmo: sim.SelectSingleNode("Gizmo").InnerText));
                }

                _loaded = true;
            }
            finally
            {
                _supportedRuntimes.SetCompleted();
                _supportedDeviceTypes.SetCompleted();
                _availableDevices.SetCompleted();
                _availableDevicePairs.SetCompleted();
                File.Delete(tmpfile);
                _semaphore.Release();
            }
        }
Exemplo n.º 11
0
    protected async Task <ProcessExecutionResult> RunMacCatalystApp(
        AppBundleInformation appInformation,
        ILog appOutputLog,
        TimeSpan timeout,
        bool waitForExit,
        IEnumerable <string> extraArguments,
        Dictionary <string, string> environmentVariables,
        CancellationToken cancellationToken)
    {
        using var systemLog = _captureLogFactory.Create(
                  path: _logs.CreateFile("MacCatalyst.system.log", LogType.SystemLog),
                  systemLogPath: SystemLogPath,
                  entireFile: false,
                  LogType.SystemLog);

        // We need to make the binary executable
        var binaryPath = Path.Combine(appInformation.AppPath, "Contents", "MacOS", appInformation.BundleExecutable ?? appInformation.AppName);

        if (File.Exists(binaryPath))
        {
            await _processManager.ExecuteCommandAsync("chmod", new[] { "+x", binaryPath }, _mainLog, TimeSpan.FromSeconds(10), cancellationToken : cancellationToken);
        }

        // On Big Sur it seems like the launch services database is not updated fast enough after we do some I/O with the app bundle.
        // Force registration for the app by running
        // /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -f /path/to/app.app
        var lsRegisterPath = @"/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister";
        await _processManager.ExecuteCommandAsync(lsRegisterPath, new[] { "-f", appInformation.LaunchAppPath }, _mainLog, TimeSpan.FromSeconds(10), cancellationToken : cancellationToken);

        var arguments = new List <string>
        {
            "-n" // Open a new instance of the application(s) even if one is already running.
        };

        if (waitForExit)
        {
            // Wait until the applications exit (even if they were already open)
            arguments.Add("-W");
        }

        arguments.Add(appInformation.LaunchAppPath);
        arguments.AddRange(extraArguments);

        systemLog.StartCapture();

        var logStreamScanToken = CaptureMacCatalystLog(appInformation, cancellationToken);

        try
        {
            return(await _processManager.ExecuteCommandAsync(
                       "open",
                       arguments,
                       _mainLog,
                       appOutputLog,
                       appOutputLog,
                       timeout,
                       environmentVariables,
                       cancellationToken));
        }
        finally
        {
            if (waitForExit)
            {
                systemLog.StopCapture(waitIfEmpty: TimeSpan.FromSeconds(10));
            }

            // Stop scanning the logs
            logStreamScanToken.Cancel();
        }
    }
Exemplo n.º 12
0
        public async Task <(string deviceName, ProcessExecutionResult result)> InstallApp(AppBundleInformation appBundleInformation, TestTargetOs target, string?deviceName = null, CancellationToken cancellationToken = default)
        {
            if (target.Platform.IsSimulator())
            {
                // We reset the simulator when running, so a separate install step does not make much sense.
                throw new InvalidOperationException("Installing to a simulator is not supported.");
            }

            if (!Directory.Exists(appBundleInformation.LaunchAppPath))
            {
                throw new DirectoryNotFoundException("Failed to find the app bundle directory");
            }

            if (deviceName == null)
            {
                // the _deviceLoader.FindDevice will return the fist device of the type, but we want to make sure that
                // the device we use is if the correct arch, therefore, we will use the LoadDevices and return the
                // correct one
                await _deviceLoader.LoadDevices(_mainLog, false, false);

                IHardwareDevice?device = null;
                if (appBundleInformation.Supports32Bit)
                {
                    // we only support 32b on iOS, therefore we can ignore the target
                    device = _deviceLoader.Connected32BitIOS.FirstOrDefault();
                }
                else
                {
                    device = target.Platform switch
                    {
                        TestTarget.Device_iOS => _deviceLoader.Connected64BitIOS.FirstOrDefault(),
                        TestTarget.Device_tvOS => _deviceLoader.ConnectedTV.FirstOrDefault(),
                        _ => device
                    };
                }

                deviceName = target.Platform.IsWatchOSTarget() ? (await _deviceLoader.FindCompanionDevice(_mainLog, device)).Name : device?.Name;
            }

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

            var args = new MlaunchArguments();

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

            args.Add(new InstallAppOnDeviceArgument(appBundleInformation.LaunchAppPath));
            args.Add(new DeviceNameArgument(deviceName));

            if (target.Platform.IsWatchOSTarget())
            {
                args.Add(new DeviceArgument("ios,watchos"));
            }

            var totalSize = Directory.GetFiles(appBundleInformation.LaunchAppPath, "*", SearchOption.AllDirectories).Select((v) => new FileInfo(v).Length).Sum();

            _mainLog.WriteLine($"Installing '{appBundleInformation.LaunchAppPath}' to '{deviceName}' ({totalSize / 1024.0 / 1024.0:N2} MB)");

            ProcessExecutionResult result = await _processManager.ExecuteCommandAsync(args, _mainLog, TimeSpan.FromHours(1), cancellationToken : cancellationToken);

            return(deviceName, result);
        }
    }