[test-runner] Try to dump threads on timeout with lldb and gdb
[mono.git] / mono / tests / test-runner.cs
index cf62e2d1689ecabb3db43859db72cf2ab5c44a91..025e01cd46f5d0225770cee3df9fd98b6a382b05 100644 (file)
@@ -18,7 +18,7 @@ using System.Xml;
 using System.Text;
 using System.Text.RegularExpressions;
 
-#if !MOBILE_STATIC
+#if !FULL_AOT_DESKTOP && !MOBILE
 using Mono.Unix.Native;
 #endif
 
@@ -35,6 +35,7 @@ public class TestRunner
        class ProcessData {
                public string test;
                public StringBuilder stdout, stderr;
+               public object stdoutLock = new object (), stderrLock = new object ();
                public string stdoutName, stderrName;
        }
 
@@ -346,14 +347,16 @@ public class TestRunner
                                        data.stderr = new StringBuilder ();
 
                                        p.OutputDataReceived += delegate (object sender, DataReceivedEventArgs e) {
-                                               if (e.Data != null) {
-                                                       data.stdout.AppendLine (e.Data);
+                                               lock (data.stdoutLock) {
+                                                       if (e.Data != null)
+                                                               data.stdout.AppendLine (e.Data);
                                                }
                                        };
 
                                        p.ErrorDataReceived += delegate (object sender, DataReceivedEventArgs e) {
-                                               if (e.Data != null) {
-                                                       data.stderr.AppendLine (e.Data);
+                                               lock (data.stderrLock) {
+                                                       if (e.Data != null)
+                                                               data.stderr.AppendLine (e.Data);
                                                }
                                        };
 
@@ -369,14 +372,8 @@ public class TestRunner
                                                        timedout.Add (data);
                                                }
 
-#if !MOBILE_STATIC
                                                // Force the process to print a thread dump
-                                               try {
-                                                       Syscall.kill (p.Id, Signum.SIGQUIT);
-                                                       Thread.Sleep (1000);
-                                               } catch {
-                                               }
-#endif
+                                               TryThreadDump (p.Id, data);
 
                                                if (verbose) {
                                                        output.Write ($"timed out ({timeout}s)");
@@ -432,7 +429,9 @@ public class TestRunner
                XmlWriterSettings xmlWriterSettings = new XmlWriterSettings ();
                xmlWriterSettings.NewLineOnAttributes = true;
                xmlWriterSettings.Indent = true;
-               using (XmlWriter writer = XmlWriter.Create (String.Format ("TestResult-{0}.xml", testsuiteName), xmlWriterSettings)) {
+
+               string xmlPath = String.Format ("TestResult-{0}.xml", testsuiteName);
+               using (XmlWriter writer = XmlWriter.Create (xmlPath, xmlWriterSettings)) {
                        // <?xml version="1.0" encoding="utf-8" standalone="no"?>
                        writer.WriteStartDocument ();
                        // <!--This file represents the results of running a test suite-->
@@ -549,6 +548,16 @@ public class TestRunner
                        // </test-results>
                        writer.WriteEndElement ();
                        writer.WriteEndDocument ();
+
+                       string babysitterXmlList = Environment.GetEnvironmentVariable("MONO_BABYSITTER_NUNIT_XML_LIST_FILE");
+                       if (!String.IsNullOrEmpty(babysitterXmlList)) {
+                               try {
+                                       string fullXmlPath = Path.GetFullPath(xmlPath);
+                                       File.AppendAllText(babysitterXmlList, fullXmlPath + Environment.NewLine);
+                               } catch (Exception e) {
+                                       Console.WriteLine("Attempted to record XML path to file {0} but failed.", babysitterXmlList);
+                               }
+                       }
                }
 
                if (verbose) {
@@ -599,4 +608,119 @@ public class TestRunner
                // Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]      /* any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. */
                return Regex.Replace (text, @"[^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD\u10000-\u10FFFF]", "");
        }
+
+       static void TryThreadDump (int pid, ProcessData data)
+       {
+               try {
+                       TryGDB (pid, data);
+                       return;
+               } catch {
+               }
+
+#if !FULL_AOT_DESKTOP && !MOBILE
+               /* LLDB cannot produce managed stacktraces for all the threads */
+               try {
+                       Syscall.kill (pid, Signum.SIGQUIT);
+                       Thread.Sleep (1000);
+               } catch {
+               }
+#endif
+
+               try {
+                       TryLLDB (pid, data);
+                       return;
+               } catch {
+               }
+       }
+
+       static void TryLLDB (int pid, ProcessData data)
+       {
+               string filename = Path.GetTempFileName ();
+
+               using (StreamWriter sw = new StreamWriter (new FileStream (filename, FileMode.Open, FileAccess.Write)))
+               {
+                       sw.WriteLine ("process attach --pid " + pid);
+                       sw.WriteLine ("thread list");
+                       sw.WriteLine ("thread backtrace all");
+                       sw.WriteLine ("detach");
+                       sw.WriteLine ("quit");
+                       sw.Flush ();
+
+                       ProcessStartInfo psi = new ProcessStartInfo {
+                               FileName = "lldb",
+                               Arguments = "--batch --source \"" + filename + "\" --no-lldbinit",
+                               UseShellExecute = false,
+                               RedirectStandardError = true,
+                               RedirectStandardOutput = true,
+                       };
+
+                       using (Process process = new Process { StartInfo = psi })
+                       {
+                               process.OutputDataReceived += delegate (object sender, DataReceivedEventArgs e) {
+                                       lock (data.stdoutLock) {
+                                               if (e.Data != null)
+                                                       data.stdout.AppendLine (e.Data);
+                                       }
+                               };
+
+                               process.ErrorDataReceived += delegate (object sender, DataReceivedEventArgs e) {
+                                       lock (data.stderrLock) {
+                                               if (e.Data != null)
+                                                       data.stderr.AppendLine (e.Data);
+                                       }
+                               };
+
+                               process.Start ();
+                               process.BeginOutputReadLine ();
+                               process.BeginErrorReadLine ();
+                               if (!process.WaitForExit (60 * 1000))
+                                       process.Kill ();
+                       }
+               }
+       }
+
+       static void TryGDB (int pid, ProcessData data)
+       {
+               string filename = Path.GetTempFileName ();
+
+               using (StreamWriter sw = new StreamWriter (new FileStream (filename, FileMode.Open, FileAccess.Write)))
+               {
+                       sw.WriteLine ("attach " + pid);
+                       sw.WriteLine ("info threads");
+                       sw.WriteLine ("thread apply all p mono_print_thread_dump(0)");
+                       sw.WriteLine ("thread apply all backtrace");
+                       sw.Flush ();
+
+                       ProcessStartInfo psi = new ProcessStartInfo {
+                               FileName = "gdb",
+                               Arguments = "-batch -x \"" + filename + "\" -nx",
+                               UseShellExecute = false,
+                               RedirectStandardError = true,
+                               RedirectStandardOutput = true,
+                       };
+
+                       using (Process process = new Process { StartInfo = psi })
+                       {
+                               process.OutputDataReceived += delegate (object sender, DataReceivedEventArgs e) {
+                                       lock (data.stdoutLock) {
+                                               if (e.Data != null)
+                                                       data.stdout.AppendLine (e.Data);
+                                       }
+                               };
+
+                               process.ErrorDataReceived += delegate (object sender, DataReceivedEventArgs e) {
+                                       lock (data.stderrLock) {
+                                               if (e.Data != null)
+                                                       data.stderr.AppendLine (e.Data);
+                                       }
+                               };
+
+                               process.Start ();
+                               process.BeginOutputReadLine ();
+                               process.BeginErrorReadLine ();
+                               if (!process.WaitForExit (60 * 1000))
+                                       process.Kill ();
+                       }
+               }
+       }
 }