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); } } }
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); } }
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)); }
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)); }
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); }); } }
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)); }
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()); } })); }
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); }
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(); } } }
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(); } }
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(); } }
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); } }