public static void AddDeviceName(StringBuilder args, string device_name) { if (!string.IsNullOrEmpty(device_name)) { args.Append(" --devname "); args.Append(Harness.Quote(device_name)); } }
public static async Task KillTreeAsync(int pid, Log log, bool diagnostics = true) { var pids = new List <int> (); GetChildrenPS(log, pids, pid); log.WriteLine($"Pids to kill: {string.Join (", ", pids.Select ((v) => v.ToString ()).ToArray ())}"); if (diagnostics) { using (var ps = new Process()) { log.WriteLine("Writing process list:"); ps.StartInfo.FileName = "ps"; ps.StartInfo.Arguments = "-A -o pid,ruser,ppid,pgid,%cpu=%CPU,%mem=%MEM,flags=FLAGS,lstart,rss,vsz,tty,state,time,command"; await ps.RunAsync(log, true, TimeSpan.FromSeconds(5)); } foreach (var diagnose_pid in pids) { var template = Path.GetTempFileName(); try { var commands = new StringBuilder(); using (var dbg = new Process()) { commands.AppendLine($"process attach --pid {diagnose_pid}"); commands.AppendLine("thread list"); commands.AppendLine("thread backtrace all"); commands.AppendLine("detach"); commands.AppendLine("quit"); dbg.StartInfo.FileName = "/usr/bin/lldb"; dbg.StartInfo.Arguments = $"--source {Harness.Quote (template)}"; File.WriteAllText(template, commands.ToString()); log.WriteLine($"Printing backtrace for pid={pid}"); await dbg.RunAsync(log, true, TimeSpan.FromSeconds(30)); } } finally { try { File.Delete(template); } catch { // Don't care } } } } using (var kill = new Process()) { kill.StartInfo.FileName = "kill"; // Send SIGABRT since that produces a crash report // lldb may fail to attach to system processes, but crash reports will still be produced with potentially helpful stack traces. kill.StartInfo.Arguments = "-6 " + string.Join(" ", pids.Select((v) => v.ToString()).ToArray()); await kill.RunAsync(log, true, TimeSpan.FromSeconds(2.5)); } using (var kill = new Process()) { kill.StartInfo.FileName = "kill"; // send kill -9 anyway as a last resort kill.StartInfo.Arguments = "-9 " + string.Join(" ", pids.Select((v) => v.ToString()).ToArray()); await kill.RunAsync(log, true, TimeSpan.FromSeconds(2.5)); } }
public static async Task KillEverythingAsync(Log log) { await ProcessHelper.ExecuteCommandAsync("launchctl", "remove com.apple.CoreSimulator.CoreSimulatorService", log, TimeSpan.FromSeconds(10)); var to_kill = new string [] { "iPhone Simulator", "iOS Simulator", "Simulator", "Simulator (Watch)", "com.apple.CoreSimulator.CoreSimulatorService" }; await ProcessHelper.ExecuteCommandAsync("killall", "-9 " + string.Join(" ", to_kill.Select((v) => Harness.Quote(v)).ToArray()), log, TimeSpan.FromSeconds(10)); foreach (var dir in new string [] { 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"), }) { try { if (Directory.Exists(dir)) { Directory.Delete(dir, true); } } catch (Exception e) { log.WriteLine("Could not delete the directory '{0}': {1}", dir, e.Message); } } }
public void StartCapture() { writer = new StreamWriter(new FileStream(LogPath, FileMode.Create)); streamEnds = new CountdownEvent(2); process = new Process(); process.StartInfo.FileName = Harness.MlaunchPath; var sb = new StringBuilder(); sb.Append("--logdev "); sb.Append("--sdkroot ").Append(Harness.Quote(Harness.XcodeRoot)).Append(' '); AppRunner.AddDeviceName(sb, DeviceName); process.StartInfo.Arguments = sb.ToString(); process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.OutputDataReceived += (object sender, DataReceivedEventArgs e) => { if (e.Data == null) { streamEnds.Signal(); } else { lock (writer) { writer.WriteLine(e.Data); } } }; process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => { if (e.Data == null) { streamEnds.Signal(); } else { lock (writer) { writer.WriteLine(e.Data); } } }; Harness.Log(1, "{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); }
public async Task EndCaptureAsync(TimeSpan timeout) { // Check for crash reports var crash_report_search_done = false; var crash_report_search_timeout = timeout.TotalSeconds; var watch = new Stopwatch(); watch.Start(); do { var end_crashes = await Harness.CreateCrashReportsSnapshotAsync(Log, !Device, DeviceName); end_crashes.ExceptWith(InitialSet); Reports = end_crashes; if (end_crashes.Count > 0) { Log.WriteLine("Found {0} new crash report(s)", end_crashes.Count); List <LogFile> crash_reports; if (!Device) { crash_reports = new List <LogFile> (end_crashes.Count); foreach (var path in end_crashes) { var logPath = Path.Combine(LogDirectory, Path.GetFileName(path)); File.Copy(path, logPath, true); crash_reports.Add(Logs.CreateFile("Crash report: " + Path.GetFileName(path), logPath)); } } else { // Download crash reports from the device. We put them in the project directory so that they're automatically deleted on wrench // (if we put them in /tmp, they'd never be deleted). var downloaded_crash_reports = new List <LogFile> (); foreach (var file in end_crashes) { var crash_report_target = Logs.CreateFile("Crash report: " + Path.GetFileName(file), Path.Combine(LogDirectory, Path.GetFileName(file))); var sb = new StringBuilder(); sb.Append(" --download-crash-report=").Append(Harness.Quote(file)); sb.Append(" --download-crash-report-to=").Append(Harness.Quote(crash_report_target.Path)); sb.Append(" --sdkroot ").Append(Harness.Quote(Harness.XcodeRoot)); if (!string.IsNullOrEmpty(DeviceName)) { sb.Append(" --devname ").Append(Harness.Quote(DeviceName)); } var result = await ProcessHelper.ExecuteCommandAsync(Harness.MlaunchPath, sb.ToString(), Log, TimeSpan.FromMinutes(1)); if (result.Succeeded) { Log.WriteLine("Downloaded crash report {0} to {1}", file, crash_report_target.Path); crash_report_target = await Harness.SymbolicateCrashReportAsync(Log, crash_report_target); Logs.Add(crash_report_target); downloaded_crash_reports.Add(crash_report_target); } else { Log.WriteLine("Could not download crash report {0}", file); } } crash_reports = downloaded_crash_reports; } foreach (var cp in crash_reports) { Harness.LogWrench("@MonkeyWrench: AddFile: {0}", cp.Path); Log.WriteLine(" {0}", cp.Path); } crash_report_search_done = true; } else { if (watch.Elapsed.TotalSeconds > crash_report_search_timeout) { crash_report_search_done = true; } else { Log.WriteLine("No crash reports, waiting a second to see if the crash report service just didn't complete in time ({0})", (int)(crash_report_search_timeout - watch.Elapsed.TotalSeconds)); Thread.Sleep(TimeSpan.FromSeconds(1)); } } } while (!crash_report_search_done); }
public async Task <int> RunAsync() { CrashReportSnapshot crash_reports; LogStream device_system_log = null; LogStream listener_log = null; Log run_log = main_log; Initialize(); if (!isSimulator) { FindDevice(); } crash_reports = new CrashReportSnapshot() { Device = !isSimulator, DeviceName = device_name, Harness = Harness, Log = main_log, Logs = Logs, LogDirectory = LogDirectory, }; var args = new StringBuilder(); if (!string.IsNullOrEmpty(Harness.XcodeRoot)) { args.Append(" --sdkroot ").Append(Harness.XcodeRoot); } for (int i = -1; i < Harness.Verbosity; i++) { args.Append(" -v "); } args.Append(" -argument=-connection-mode -argument=none"); // This will prevent the app from trying to connect to any IDEs args.Append(" -argument=-app-arg:-autostart"); args.Append(" -setenv=NUNIT_AUTOSTART=true"); args.Append(" -argument=-app-arg:-autoexit"); args.Append(" -setenv=NUNIT_AUTOEXIT=true"); args.Append(" -argument=-app-arg:-enablenetwork"); args.Append(" -setenv=NUNIT_ENABLE_NETWORK=true"); // detect if we are using a jenkins bot. if (Harness.InJenkins) { args.Append(" -setenv=NUNIT_ENABLE_XML_OUTPUT=true"); } if (isSimulator) { args.Append(" -argument=-app-arg:-hostname:127.0.0.1"); args.Append(" -setenv=NUNIT_HOSTNAME=127.0.0.1"); } else { var ips = new StringBuilder(); var ipAddresses = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()).AddressList; for (int i = 0; i < ipAddresses.Length; i++) { if (i > 0) { ips.Append(','); } ips.Append(ipAddresses [i].ToString()); } args.AppendFormat(" -argument=-app-arg:-hostname:{0}", ips.ToString()); args.AppendFormat(" -setenv=NUNIT_HOSTNAME={0}", ips.ToString()); } var transport = mode == "watchos" ? "HTTP" : "TCP"; args.AppendFormat(" -argument=-app-arg:-transport:{0}", transport); args.AppendFormat(" -setenv=NUNIT_TRANSPORT={0}", transport); SimpleListener listener; switch (transport) { case "HTTP": listener = new SimpleHttpListener(); break; case "TCP": listener = new SimpleTcpListener(); break; default: throw new NotImplementedException(); } listener_log = Logs.CreateStream(LogDirectory, string.Format("test-{0}-{1:yyyyMMdd_HHmmss}.log", mode, DateTime.Now), "Test log"); listener.TestLog = listener_log; listener.Log = main_log; listener.AutoExit = true; listener.Address = System.Net.IPAddress.Any; listener.Initialize(); args.AppendFormat(" -argument=-app-arg:-hostport:{0}", listener.Port); args.AppendFormat(" -setenv=NUNIT_HOSTPORT={0}", listener.Port); listener.StartAsync(); var cancellation_source = new CancellationTokenSource(); var timed_out = false; ThreadPool.QueueUserWorkItem((v) => { if (!listener.WaitForConnection(TimeSpan.FromMinutes(Harness.LaunchTimeout))) { cancellation_source.Cancel(); main_log.WriteLine("Test launch timed out after {0} minute(s).", Harness.LaunchTimeout); timed_out = true; } else { main_log.WriteLine("Test run started"); } }); foreach (var kvp in Harness.EnvironmentVariables) { args.AppendFormat(" -setenv={0}={1}", kvp.Key, kvp.Value); } bool?success = null; bool launch_failure = false; if (isExtension) { switch (extension) { case Extension.TodayExtension: args.Append(isSimulator ? " --launchsimbundleid" : " --launchdevbundleid"); args.Append(" todayviewforextensions:"); args.Append(BundleIdentifier); args.Append(" --observe-extension "); args.Append(Harness.Quote(launchAppPath)); break; case Extension.WatchKit2: default: throw new NotImplementedException(); } } else { args.Append(isSimulator ? " --launchsim " : " --launchdev "); args.Append(Harness.Quote(launchAppPath)); } if (isSimulator) { if (!await FindSimulatorAsync()) { return(1); } if (mode != "watchos") { var stderr_tty = Marshal.PtrToStringAuto(ttyname(2)); if (!string.IsNullOrEmpty(stderr_tty)) { args.Append(" --stdout=").Append(Harness.Quote(stderr_tty)); args.Append(" --stderr=").Append(Harness.Quote(stderr_tty)); } else { var stdout_log = Logs.CreateFile("Standard output", Path.Combine(LogDirectory, "stdout.log")); var stderr_log = Logs.CreateFile("Standard error", Path.Combine(LogDirectory, "stderr.log")); args.Append(" --stdout=").Append(Harness.Quote(stdout_log.FullPath)); args.Append(" --stderr=").Append(Harness.Quote(stderr_log.FullPath)); } } var systemLogs = new List <CaptureLog> (); foreach (var sim in simulators) { // Upload the system log main_log.WriteLine("System log for the '{1}' simulator is: {0}", sim.SystemLog, sim.Name); bool isCompanion = sim != simulator; var log = new CaptureLog(sim.SystemLog, entire_file: Harness.Action != HarnessAction.Jenkins) { Path = Path.Combine(LogDirectory, sim.Name + ".log"), Description = isCompanion ? "System log (companion)" : "System log", }; log.StartCapture(); Logs.Add(log); systemLogs.Add(log); Harness.LogWrench("@MonkeyWrench: AddFile: {0}", log.Path); } main_log.WriteLine("*** Executing {0}/{1} in the simulator ***", appName, mode); if (EnsureCleanSimulatorState) { foreach (var sim in simulators) { await sim.PrepareSimulatorAsync(main_log, bundle_identifier); } } args.Append(" --device=:v2:udid=").Append(simulator.UDID).Append(" "); await crash_reports.StartCaptureAsync(); main_log.WriteLine("Starting test run"); var result = await ProcessHelper.ExecuteCommandAsync(Harness.MlaunchPath, args.ToString(), run_log, TimeSpan.FromMinutes(Harness.Timeout), cancellation_token : cancellation_source.Token); if (result.TimedOut) { timed_out = true; success = false; main_log.WriteLine("Test run timed out after {0} minute(s).", Harness.Timeout); } else if (result.Succeeded) { main_log.WriteLine("Test run completed"); success = true; } else { main_log.WriteLine("Test run failed"); success = false; } if (!success.Value) { // find pid var pid = -1; using (var reader = run_log.GetReader()) { while (!reader.EndOfStream) { var line = reader.ReadLine(); if (line.StartsWith("Application launched. PID = ", StringComparison.Ordinal)) { var pidstr = line.Substring("Application launched. PID = ".Length); if (!int.TryParse(pidstr, out pid)) { main_log.WriteLine("Could not parse pid: {0}", pidstr); } } else if (line.Contains("Xamarin.Hosting: Launched ") && line.Contains(" with pid ")) { var pidstr = line.Substring(line.LastIndexOf(' ')); if (!int.TryParse(pidstr, out pid)) { main_log.WriteLine("Could not parse pid: {0}", pidstr); } } else if (line.Contains("error MT1008")) { launch_failure = true; } } } if (pid > 0) { var launchTimedout = cancellation_source.IsCancellationRequested; var timeoutType = launchTimedout ? "Launch" : "Completion"; var timeoutValue = launchTimedout ? Harness.LaunchTimeout : Harness.Timeout; main_log.WriteLine($"{timeoutType} timed out after {timeoutValue}"); await Process_Extensions.KillTreeAsync(pid, main_log, true); } else { main_log.WriteLine("Could not find pid in mtouch output."); } } listener.Cancel(); // cleanup after us if (EnsureCleanSimulatorState) { await SimDevice.KillEverythingAsync(main_log); } foreach (var log in systemLogs) { log.StopCapture(); } } else { main_log.WriteLine("*** Executing {0}/{1} on device '{2}' ***", appName, mode, device_name); if (mode == "watchos") { args.Append(" --attach-native-debugger"); // this prevents the watch from backgrounding the app. } else { args.Append(" --wait-for-exit"); } AddDeviceName(args); device_system_log = Logs.CreateStream(LogDirectory, $"device-{device_name}-{DateTime.Now:yyyyMMdd_HHmmss}.log", "Device log"); var logdev = new DeviceLogCapturer() { Harness = Harness, Log = device_system_log, DeviceName = device_name, }; logdev.StartCapture(); await crash_reports.StartCaptureAsync(); main_log.WriteLine("Starting test run"); var result = await ProcessHelper.ExecuteCommandAsync(Harness.MlaunchPath, args.ToString(), main_log, TimeSpan.FromMinutes(Harness.Timeout), cancellation_token : cancellation_source.Token); if (result.TimedOut) { timed_out = true; success = false; main_log.WriteLine("Test run timed out after {0} minute(s).", Harness.Timeout); } else if (result.Succeeded) { main_log.WriteLine("Test run completed"); success = true; } else { main_log.WriteLine("Test run failed"); success = false; } logdev.StopCapture(); // Upload the system log if (File.Exists(device_system_log.FullPath)) { main_log.WriteLine("A capture of the device log is: {0}", device_system_log.FullPath); Harness.LogWrench("@MonkeyWrench: AddFile: {0}", device_system_log.FullPath); } } listener.Dispose(); // check the final status var crashed = false; if (File.Exists(listener_log.FullPath)) { Harness.LogWrench("@MonkeyWrench: AddFile: {0}", listener_log.FullPath); success = TestsSucceeded(listener_log, timed_out, crashed); } else if (timed_out) { Harness.LogWrench("@MonkeyWrench: AddSummary: <b><i>{0} never launched</i></b><br/>", mode); main_log.WriteLine("Test run never launched"); success = false; } else if (launch_failure) { Harness.LogWrench("@MonkeyWrench: AddSummary: <b><i>{0} failed to launch</i></b><br/>", mode); main_log.WriteLine("Test run failed to launch"); success = false; } else { Harness.LogWrench("@MonkeyWrench: AddSummary: <b><i>{0} crashed at startup (no log)</i></b><br/>", mode); main_log.WriteLine("Test run crashed before it started (no log file produced)"); crashed = true; success = false; } if (!success.HasValue) { success = false; } await crash_reports.EndCaptureAsync(TimeSpan.FromSeconds(success.Value ? 0 : 5)); if (timed_out) { Result = TestExecutingResult.TimedOut; } else if (crashed) { Result = TestExecutingResult.Crashed; } else if (success.Value) { Result = TestExecutingResult.Succeeded; } else { Result = TestExecutingResult.Failed; } return(success.Value ? 0 : 1); }
public static Task KillEverythingAsync(Log log) { var to_kill = new string [] { "iPhone Simulator", "iOS Simulator", "Simulator", "Simulator (Watch)", "com.apple.CoreSimulator.CoreSimulatorService" }; return(ProcessHelper.ExecuteCommandAsync("killall", "-9 " + string.Join(" ", to_kill.Select((v) => Harness.Quote(v)).ToArray()), log, TimeSpan.FromSeconds(10))); }
public static void ResolveAllPaths(this XmlDocument csproj, string project_path) { var dir = System.IO.Path.GetDirectoryName(project_path); var nodes_with_paths = new string [] { "AssemblyOriginatorKeyFile", "CodesignEntitlements", "TestLibrariesDirectory", "HintPath", }; var attributes_with_paths = new string [] [] { new string [] { "None", "Include" }, new string [] { "Compile", "Include" }, new string [] { "ProjectReference", "Include" }, new string [] { "InterfaceDefinition", "Include" }, new string [] { "BundleResource", "Include" }, new string [] { "EmbeddedResource", "Include" }, new string [] { "ImageAsset", "Include" }, new string [] { "GeneratedTestInput", "Include" }, new string [] { "GeneratedTestOutput", "Include" }, new string [] { "TestLibrariesInput", "Include" }, new string [] { "TestLibrariesOutput", "Include" }, new string [] { "TestLibrariesOutput", "Include" }, new string [] { "Content", "Include" }, new string [] { "ObjcBindingApiDefinition", "Include" }, new string [] { "ObjcBindingCoreSource", "Include" }, new string [] { "ObjcBindingNativeLibrary", "Include" }, new string [] { "ObjcBindingNativeFramework", "Include" }, }; var nodes_with_variables = new string [] { "MtouchExtraArgs", }; Func <string, string> convert = (input) => { if (input [0] == '/') { return(input); // This is already a full path. } input = input.Replace('\\', '/'); // make unix-style input = System.IO.Path.GetFullPath(System.IO.Path.Combine(dir, input)); input = input.Replace('/', '\\'); // make windows-style again return(input); }; foreach (var key in nodes_with_paths) { var nodes = csproj.SelectElementNodes(key); foreach (var node in nodes) { node.InnerText = convert(node.InnerText); } } foreach (var key in nodes_with_variables) { var nodes = csproj.SelectElementNodes(key); foreach (var node in nodes) { node.InnerText = node.InnerText.Replace("${ProjectDir}", Harness.Quote(System.IO.Path.GetDirectoryName(project_path))); } } foreach (var kvp in attributes_with_paths) { var element = kvp [0]; var attrib = kvp [1]; var nodes = csproj.SelectElementNodes(element); foreach (XmlNode node in nodes) { var a = node.Attributes [attrib]; if (a == null) { continue; } // Fix any default LogicalName values (but don't change existing ones). var ln = node.SelectElementNodes("LogicalName")?.SingleOrDefault(); var links = node.SelectElementNodes("Link"); if (ln == null && !links.Any()) { ln = csproj.CreateElement("LogicalName", MSBuild_Namespace); node.AppendChild(ln); string logicalName = a.Value; switch (element) { case "BundleResource": if (logicalName.StartsWith("Resources\\", StringComparison.Ordinal)) { logicalName = logicalName.Substring("Resources\\".Length); } break; default: break; } ln.InnerText = logicalName; } a.Value = convert(a.Value); } } }