5 // Zoltan Varga (vargaz@gmail.com)
7 // Copyright (C) 2008 Novell, Inc (http://www.novell.com)
9 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
13 using System
.Threading
;
14 using System
.Diagnostics
;
15 using System
.Collections
.Generic
;
16 using System
.Globalization
;
19 using System
.Text
.RegularExpressions
;
22 #if !FULL_AOT_DESKTOP && !MOBILE
23 using Mono
.Unix
.Native
;
27 // This is a simple test runner with support for parallel execution
30 public class TestRunner
32 const string TEST_TIME_FORMAT
= "mm\\:ss\\.fff";
33 const string ENV_TIMEOUT
= "TEST_DRIVER_TIMEOUT_SEC";
34 const string MONO_PATH
= "MONO_PATH";
35 const string MONO_GAC_PREFIX
= "MONO_GAC_PREFIX";
39 public StringBuilder stdout
, stderr
;
40 public object stdoutLock
= new object (), stderrLock
= new object ();
41 public string stdoutName
, stderrName
;
42 public TimeSpan duration
;
46 public string test
, opt_set
;
49 public static int Main (String
[] args
) {
52 int timeout
= 2 * 60; // in seconds
53 int expectedExitCode
= 0;
55 string testsuiteName
= null;
56 string inputFile
= null;
59 string disabled_tests
= null;
60 string runtime
= "mono";
62 string mono_path
= null;
63 string runtime_args
= null;
64 string mono_gac_prefix
= null;
65 string xunit_output_path
= null;
66 var opt_sets
= new List
<string> ();
70 while (i
< args
.Length
) {
71 if (args
[i
].StartsWith ("-")) {
72 if (args
[i
] == "-j") {
73 if (i
+ 1 >= args
.Length
) {
74 Console
.WriteLine ("Missing argument to -j command line option.");
77 if (args
[i
+ 1] == "a")
78 concurrency
= Environment
.ProcessorCount
;
80 concurrency
= Int32
.Parse (args
[i
+ 1]);
82 } else if (args
[i
] == "--timeout") {
83 if (i
+ 1 >= args
.Length
) {
84 Console
.WriteLine ("Missing argument to --timeout command line option.");
87 timeout
= Int32
.Parse (args
[i
+ 1]);
89 } else if (args
[i
] == "--disabled") {
90 if (i
+ 1 >= args
.Length
) {
91 Console
.WriteLine ("Missing argument to --disabled command line option.");
94 disabled_tests
= args
[i
+ 1];
96 } else if (args
[i
] == "--runtime") {
97 if (i
+ 1 >= args
.Length
) {
98 Console
.WriteLine ("Missing argument to --runtime command line option.");
101 runtime
= args
[i
+ 1];
103 } else if (args
[i
] == "--runtime-args") {
104 if (i
+ 1 >= args
.Length
) {
105 Console
.WriteLine ("Missing argument to --runtime-args command line option.");
108 runtime_args
= (runtime_args
?? "") + " " + args
[i
+ 1];
110 } else if (args
[i
] == "--config") {
111 if (i
+ 1 >= args
.Length
) {
112 Console
.WriteLine ("Missing argument to --config command line option.");
115 config
= args
[i
+ 1];
117 } else if (args
[i
] == "--opt-sets") {
118 if (i
+ 1 >= args
.Length
) {
119 Console
.WriteLine ("Missing argument to --opt-sets command line option.");
122 foreach (var s
in args
[i
+ 1].Split ())
125 } else if (args
[i
] == "--expected-exit-code") {
126 if (i
+ 1 >= args
.Length
) {
127 Console
.WriteLine ("Missing argument to --expected-exit-code command line option.");
130 expectedExitCode
= Int32
.Parse (args
[i
+ 1]);
132 } else if (args
[i
] == "--testsuite-name") {
133 if (i
+ 1 >= args
.Length
) {
134 Console
.WriteLine ("Missing argument to --testsuite-name command line option.");
137 testsuiteName
= args
[i
+ 1];
139 } else if (args
[i
] == "--xunit") {
140 if (i
+ 1 >= args
.Length
) {
141 Console
.WriteLine ("Missing argument to --xunit command line option.");
144 xunit_output_path
= args
[i
+ 1].Substring(0, args
[i
+ 1].Length
);
147 } else if (args
[i
] == "--input-file") {
148 if (i
+ 1 >= args
.Length
) {
149 Console
.WriteLine ("Missing argument to --input-file command line option.");
152 inputFile
= args
[i
+ 1];
154 } else if (args
[i
] == "--mono-path") {
155 if (i
+ 1 >= args
.Length
) {
156 Console
.WriteLine ("Missing argument to --mono-path command line option.");
159 mono_path
= args
[i
+ 1].Substring(0, args
[i
+ 1].Length
);
162 } else if (args
[i
] == "--mono-gac-prefix") {
163 if (i
+ 1 >= args
.Length
) {
164 Console
.WriteLine ("Missing argument to --mono-gac-prefix command line option.");
167 mono_gac_prefix
= args
[i
+ 1];
169 } else if (args
[i
] == "--verbose") {
172 } else if (args
[i
] == "--repeat") {
173 if (i
+ 1 >= args
.Length
) {
174 Console
.WriteLine ("Missing argument to --repeat command line option.");
177 repeat
= Int32
.Parse (args
[i
+ 1]);
179 Console
.WriteLine ("Invalid argument to --repeat command line option, should be > 1");
184 Console
.WriteLine ("Unknown command line option: '" + args
[i
] + "'.");
192 if (String
.IsNullOrEmpty (testsuiteName
)) {
193 Console
.WriteLine ("Missing the required --testsuite-name command line option.");
197 var disabled
= new Dictionary
<string, string> ();
199 if (disabled_tests
!= null) {
200 foreach (string test
in disabled_tests
.Split ())
201 disabled
[test
] = test
;
204 var tests
= new List
<string> ();
206 if (!String
.IsNullOrEmpty (inputFile
)) {
207 foreach (string l
in File
.ReadAllLines (inputFile
)) {
208 for (int r
= 0; r
< repeat
; ++r
)
212 // The remaining arguments are the tests
213 for (int j
= i
; j
< args
.Length
; ++j
)
214 if (!disabled
.ContainsKey (args
[j
])) {
215 for (int r
= 0; r
< repeat
; ++r
)
216 tests
.Add (args
[j
]);
221 Console
.WriteLine ("No tests selected, exiting.");
225 /* If tests are repeated, we don't want the same test to run consecutively, so we need to randomise their order.
226 * But to ease reproduction of certain order-based bugs (if and only if test A and B execute at the same time),
227 * we want to use a constant seed so the tests always run in the same order. */
228 var random
= new Random (0);
229 tests
= tests
.OrderBy (t
=> random
.Next ()).ToList ();
231 var passed
= new List
<ProcessData
> ();
232 var failed
= new List
<ProcessData
> ();
233 var timedout
= new List
<ProcessData
> ();
235 object monitor
= new object ();
237 Console
.WriteLine ("Running tests: ");
239 var test_info
= new Queue
<TestInfo
> ();
240 if (opt_sets
.Count
== 0) {
241 foreach (string s
in tests
)
242 test_info
.Enqueue (new TestInfo { test = s }
);
244 foreach (string opt
in opt_sets
) {
245 foreach (string s
in tests
)
246 test_info
.Enqueue (new TestInfo { test = s, opt_set = opt }
);
250 /* compute the max length of test names, to have an optimal output width */
251 int output_width
= -1;
252 foreach (TestInfo ti
in test_info
) {
253 if (ti
.test
.Length
> output_width
)
254 output_width
= Math
.Min (120, ti
.test
.Length
);
257 List
<Thread
> threads
= new List
<Thread
> (concurrency
);
259 DateTime test_start_time
= DateTime
.UtcNow
;
261 for (int j
= 0; j
< concurrency
; ++j
) {
262 Thread thread
= new Thread (() => {
267 if (test_info
.Count
== 0)
269 ti
= test_info
.Dequeue ();
272 var output
= new StringWriter ();
274 string test
= ti
.test
;
275 string opt_set
= ti
.opt_set
;
278 output
.Write (String
.Format ("{{0,-{0}}} ", output_width
), test
);
283 /* Spawn a new process */
285 string process_args
= "";
288 process_args
+= " -O=" + opt_set
;
289 if (runtime_args
!= null)
290 process_args
+= " " + runtime_args
;
292 process_args
+= " " + test
;
294 ProcessStartInfo info
= new ProcessStartInfo (runtime
, process_args
);
295 info
.UseShellExecute
= false;
296 info
.RedirectStandardOutput
= true;
297 info
.RedirectStandardError
= true;
298 info
.EnvironmentVariables
[ENV_TIMEOUT
] = timeout
.ToString();
300 info
.EnvironmentVariables
["MONO_CONFIG"] = config
;
301 if (mono_path
!= null)
302 info
.EnvironmentVariables
[MONO_PATH
] = mono_path
;
303 if (mono_gac_prefix
!= null)
304 info
.EnvironmentVariables
[MONO_GAC_PREFIX
] = mono_gac_prefix
;
305 Process p
= new Process ();
308 ProcessData data
= new ProcessData ();
311 string log_prefix
= "";
313 log_prefix
= "." + opt_set
.Replace ("-", "no").Replace (",", "_");
315 data
.stdoutName
= test
+ log_prefix
+ ".stdout";
316 data
.stdout
= new StringBuilder ();
318 data
.stderrName
= test
+ log_prefix
+ ".stderr";
319 data
.stderr
= new StringBuilder ();
321 p
.OutputDataReceived
+= delegate (object sender
, DataReceivedEventArgs e
) {
322 lock (data
.stdoutLock
) {
324 data
.stdout
.AppendLine (e
.Data
);
328 p
.ErrorDataReceived
+= delegate (object sender
, DataReceivedEventArgs e
) {
329 lock (data
.stderrLock
) {
331 data
.stderr
.AppendLine (e
.Data
);
335 var start
= DateTime
.UtcNow
;
339 p
.BeginOutputReadLine ();
340 p
.BeginErrorReadLine ();
342 if (!p
.WaitForExit (timeout
* 1000)) {
343 var end
= DateTime
.UtcNow
;
344 data
.duration
= end
- start
;
350 // Force the process to print a thread dump
351 TryThreadDump (p
.Id
, data
);
354 output
.Write ($"timed out ({timeout}s)");
361 } else if (p
.ExitCode
!= expectedExitCode
) {
362 var end
= DateTime
.UtcNow
;
363 data
.duration
= end
- start
;
370 output
.Write ("failed, time: {0}, exit code: {1}", data
.duration
.ToString (TEST_TIME_FORMAT
), p
.ExitCode
);
372 var end
= DateTime
.UtcNow
;
373 data
.duration
= end
- start
;
380 output
.Write ("passed, time: {0}", data
.duration
.ToString (TEST_TIME_FORMAT
));
387 Console
.WriteLine (output
.ToString ());
394 threads
.Add (thread
);
397 for (int j
= 0; j
< threads
.Count
; ++j
)
400 TimeSpan test_time
= DateTime
.UtcNow
- test_start_time
;
402 if (xunit_output_path
!= null) {
403 WriteXUnitOutput (xunit_output_path
, testsuiteName
, test_time
, passed
, failed
, timedout
);
406 var xmlPath
= String
.Format ("TestResult-{0}.xml", testsuiteName
);
407 WriteNUnitOutput (xmlPath
, testsuiteName
, test_time
, passed
, failed
, timedout
);
408 string babysitterXmlList
= Environment
.GetEnvironmentVariable("MONO_BABYSITTER_NUNIT_XML_LIST_FILE");
409 if (!String
.IsNullOrEmpty(babysitterXmlList
)) {
411 string fullXmlPath
= Path
.GetFullPath(xmlPath
);
412 File
.AppendAllText(babysitterXmlList
, fullXmlPath
+ Environment
.NewLine
);
413 } catch (Exception e
) {
414 Console
.WriteLine("Attempted to record XML path to file {0} but failed.", babysitterXmlList
);
420 Console
.WriteLine ();
421 Console
.WriteLine ("Time: {0}", test_time
.ToString (TEST_TIME_FORMAT
));
422 Console
.WriteLine ();
423 Console
.WriteLine ("{0,4} test(s) passed", passed
.Count
);
424 Console
.WriteLine ("{0,4} test(s) failed", failed
.Count
);
425 Console
.WriteLine ("{0,4} test(s) timed out", timedout
.Count
);
427 Console
.WriteLine ();
428 Console
.WriteLine (String
.Format ("{0} test(s) passed, {1} test(s) did not pass.", passed
.Count
, failed
.Count
));
431 if (failed
.Count
> 0) {
432 Console
.WriteLine ();
433 Console
.WriteLine ("Failed test(s):");
434 foreach (ProcessData pd
in failed
) {
435 Console
.WriteLine ();
436 Console
.WriteLine (pd
.test
);
437 DumpFile (pd
.stdoutName
, pd
.stdout
.ToString ());
438 DumpFile (pd
.stderrName
, pd
.stderr
.ToString ());
442 if (timedout
.Count
> 0) {
443 Console
.WriteLine ();
444 Console
.WriteLine ("Timed out test(s):");
445 foreach (ProcessData pd
in timedout
) {
446 Console
.WriteLine ();
447 Console
.WriteLine (pd
.test
);
448 DumpFile (pd
.stdoutName
, pd
.stdout
.ToString ());
449 DumpFile (pd
.stderrName
, pd
.stderr
.ToString ());
453 return (timedout
.Count
== 0 && failed
.Count
== 0) ? 0 : 1;
456 static void DumpFile (string filename
, string text
) {
457 Console
.WriteLine ("=============== {0} ===============", filename
);
458 Console
.WriteLine (text
);
459 Console
.WriteLine ("=============== EOF ===============");
462 static void WriteNUnitOutput (string path
, string testsuiteName
, TimeSpan test_time
, List
<ProcessData
> passed
, List
<ProcessData
> failed
, List
<ProcessData
> timedout
) {
463 XmlWriterSettings xmlWriterSettings
= new XmlWriterSettings ();
464 xmlWriterSettings
.NewLineOnAttributes
= true;
465 xmlWriterSettings
.Indent
= true;
467 using (XmlWriter writer
= XmlWriter
.Create (path
, xmlWriterSettings
)) {
468 // <?xml version="1.0" encoding="utf-8" standalone="no"?>
469 writer
.WriteStartDocument ();
470 // <!--This file represents the results of running a test suite-->
471 writer
.WriteComment ("This file represents the results of running a test suite");
472 // <test-results name="/home/charlie/Dev/NUnit/nunit-2.5/work/src/bin/Debug/tests/mock-assembly.dll" total="21" errors="1" failures="1" not-run="7" inconclusive="1" ignored="4" skipped="0" invalid="3" date="2010-10-18" time="13:23:35">
473 writer
.WriteStartElement ("test-results");
474 writer
.WriteAttributeString ("name", String
.Format ("{0}-tests.dummy", testsuiteName
));
475 writer
.WriteAttributeString ("total", (passed
.Count
+ failed
.Count
+ timedout
.Count
).ToString());
476 writer
.WriteAttributeString ("failures", (failed
.Count
+ timedout
.Count
).ToString());
477 writer
.WriteAttributeString ("not-run", "0");
478 writer
.WriteAttributeString ("date", DateTime
.Now
.ToString ("yyyy-MM-dd"));
479 writer
.WriteAttributeString ("time", DateTime
.Now
.ToString ("HH:mm:ss"));
480 // <environment nunit-version="2.4.8.0" clr-version="4.0.30319.17020" os-version="Unix 3.13.0.45" platform="Unix" cwd="/home/directhex/Projects/mono/mcs/class/corlib" machine-name="marceline" user="directhex" user-domain="marceline" />
481 writer
.WriteStartElement ("environment");
482 writer
.WriteAttributeString ("nunit-version", "2.4.8.0" );
483 writer
.WriteAttributeString ("clr-version", Environment
.Version
.ToString() );
484 writer
.WriteAttributeString ("os-version", Environment
.OSVersion
.ToString() );
485 writer
.WriteAttributeString ("platform", Environment
.OSVersion
.Platform
.ToString() );
486 writer
.WriteAttributeString ("cwd", Environment
.CurrentDirectory
);
487 writer
.WriteAttributeString ("machine-name", Environment
.MachineName
);
488 writer
.WriteAttributeString ("user", Environment
.UserName
);
489 writer
.WriteAttributeString ("user-domain", Environment
.UserDomainName
);
490 writer
.WriteEndElement ();
491 // <culture-info current-culture="en-GB" current-uiculture="en-GB" />
492 writer
.WriteStartElement ("culture-info");
493 writer
.WriteAttributeString ("current-culture", CultureInfo
.CurrentCulture
.Name
);
494 writer
.WriteAttributeString ("current-uiculture", CultureInfo
.CurrentUICulture
.Name
);
495 writer
.WriteEndElement ();
496 // <test-suite name="corlib_test_net_4_5.dll" success="True" time="114.318" asserts="0">
497 writer
.WriteStartElement ("test-suite");
498 writer
.WriteAttributeString ("name", String
.Format ("{0}-tests.dummy", testsuiteName
));
499 writer
.WriteAttributeString ("success", (failed
.Count
+ timedout
.Count
== 0).ToString());
500 writer
.WriteAttributeString ("time", test_time
.TotalSeconds
.ToString(CultureInfo
.InvariantCulture
));
501 writer
.WriteAttributeString ("asserts", (failed
.Count
+ timedout
.Count
).ToString());
503 writer
.WriteStartElement ("results");
504 // <test-suite name="MonoTests" success="True" time="114.318" asserts="0">
505 writer
.WriteStartElement ("test-suite");
506 writer
.WriteAttributeString ("name","MonoTests");
507 writer
.WriteAttributeString ("success", (failed
.Count
+ timedout
.Count
== 0).ToString());
508 writer
.WriteAttributeString ("time", test_time
.TotalSeconds
.ToString(CultureInfo
.InvariantCulture
));
509 writer
.WriteAttributeString ("asserts", (failed
.Count
+ timedout
.Count
).ToString());
511 writer
.WriteStartElement ("results");
512 // <test-suite name="MonoTests" success="True" time="114.318" asserts="0">
513 writer
.WriteStartElement ("test-suite");
514 writer
.WriteAttributeString ("name", testsuiteName
);
515 writer
.WriteAttributeString ("success", (failed
.Count
+ timedout
.Count
== 0).ToString());
516 writer
.WriteAttributeString ("time", test_time
.TotalSeconds
.ToString(CultureInfo
.InvariantCulture
));
517 writer
.WriteAttributeString ("asserts", (failed
.Count
+ timedout
.Count
).ToString());
519 writer
.WriteStartElement ("results");
520 // Dump all passing tests first
521 foreach (ProcessData pd
in passed
) {
522 // <test-case name="MonoTests.Microsoft.Win32.RegistryKeyTest.bug79051" executed="True" success="True" time="0.063" asserts="0" />
523 writer
.WriteStartElement ("test-case");
524 writer
.WriteAttributeString ("name", String
.Format ("MonoTests.{0}.{1}", testsuiteName
, pd
.test
));
525 writer
.WriteAttributeString ("executed", "True");
526 writer
.WriteAttributeString ("success", "True");
527 writer
.WriteAttributeString ("time", pd
.duration
.TotalSeconds
.ToString(CultureInfo
.InvariantCulture
));
528 writer
.WriteAttributeString ("asserts", "0");
529 writer
.WriteEndElement ();
531 // Now dump all failing tests
532 foreach (ProcessData pd
in failed
) {
533 // <test-case name="MonoTests.Microsoft.Win32.RegistryKeyTest.bug79051" executed="True" success="True" time="0.063" asserts="0" />
534 writer
.WriteStartElement ("test-case");
535 writer
.WriteAttributeString ("name", String
.Format ("MonoTests.{0}.{1}", testsuiteName
, pd
.test
));
536 writer
.WriteAttributeString ("executed", "True");
537 writer
.WriteAttributeString ("success", "False");
538 writer
.WriteAttributeString ("time", pd
.duration
.TotalSeconds
.ToString(CultureInfo
.InvariantCulture
));
539 writer
.WriteAttributeString ("asserts", "1");
540 writer
.WriteStartElement ("failure");
541 writer
.WriteStartElement ("message");
542 writer
.WriteCData (FilterInvalidXmlChars (pd
.stdout
.ToString ()));
543 writer
.WriteEndElement ();
544 writer
.WriteStartElement ("stack-trace");
545 writer
.WriteCData (FilterInvalidXmlChars (pd
.stderr
.ToString ()));
546 writer
.WriteEndElement ();
547 writer
.WriteEndElement ();
548 writer
.WriteEndElement ();
550 // Then dump all timing out tests
551 foreach (ProcessData pd
in timedout
) {
552 // <test-case name="MonoTests.Microsoft.Win32.RegistryKeyTest.bug79051" executed="True" success="True" time="0.063" asserts="0" />
553 writer
.WriteStartElement ("test-case");
554 writer
.WriteAttributeString ("name", String
.Format ("MonoTests.{0}.{1}_timedout", testsuiteName
, pd
.test
));
555 writer
.WriteAttributeString ("executed", "True");
556 writer
.WriteAttributeString ("success", "False");
557 writer
.WriteAttributeString ("time", pd
.duration
.TotalSeconds
.ToString(CultureInfo
.InvariantCulture
));
558 writer
.WriteAttributeString ("asserts", "1");
559 writer
.WriteStartElement ("failure");
560 writer
.WriteStartElement ("message");
561 writer
.WriteCData (FilterInvalidXmlChars (pd
.stdout
.ToString ()));
562 writer
.WriteEndElement ();
563 writer
.WriteStartElement ("stack-trace");
564 writer
.WriteCData (FilterInvalidXmlChars (pd
.stderr
.ToString ()));
565 writer
.WriteEndElement ();
566 writer
.WriteEndElement ();
567 writer
.WriteEndElement ();
570 writer
.WriteEndElement ();
572 writer
.WriteEndElement ();
574 writer
.WriteEndElement ();
576 writer
.WriteEndElement ();
578 writer
.WriteEndElement ();
580 writer
.WriteEndElement ();
582 writer
.WriteEndElement ();
583 writer
.WriteEndDocument ();
587 static void WriteXUnitOutput (string path
, string testsuiteName
, TimeSpan test_time
, List
<ProcessData
> passed
, List
<ProcessData
> failed
, List
<ProcessData
> timedout
) {
588 XmlWriterSettings xmlWriterSettings
= new XmlWriterSettings ();
589 xmlWriterSettings
.Indent
= true;
591 using (XmlWriter writer
= XmlWriter
.Create (path
, xmlWriterSettings
)) {
593 writer
.WriteStartDocument ();
595 writer
.WriteStartElement ("assemblies");
597 writer
.WriteStartElement ("assembly");
599 writer
.WriteAttributeString ("name", testsuiteName
);
600 writer
.WriteAttributeString ("environment", $"test-runner-version: {System.Reflection.Assembly.GetExecutingAssembly ().GetName ()}, clr-version: {Environment.Version}, os-version: {Environment.OSVersion}, platform: {Environment.OSVersion.Platform}, cwd: {Environment.CurrentDirectory}, machine-name: {Environment.MachineName}, user: {Environment.UserName}, user-domain: {Environment.UserDomainName}");
601 writer
.WriteAttributeString ("test-framework", "test-runner");
602 writer
.WriteAttributeString ("run-date", XmlConvert
.ToString (DateTime
.Now
, "yyyy-MM-dd"));
603 writer
.WriteAttributeString ("run-time", XmlConvert
.ToString (DateTime
.Now
, "HH:mm:ss"));
605 writer
.WriteAttributeString ("total", (passed
.Count
+ failed
.Count
+ timedout
.Count
).ToString ());
606 writer
.WriteAttributeString ("errors", 0.ToString ());
607 writer
.WriteAttributeString ("failed", (failed
.Count
+ timedout
.Count
).ToString ());
608 writer
.WriteAttributeString ("skipped", 0.ToString ());
610 writer
.WriteAttributeString ("passed", passed
.Count
.ToString ());
612 writer
.WriteStartElement ("collection");
613 writer
.WriteAttributeString ("name", "tests");
615 foreach (var pd
in passed
) {
616 writer
.WriteStartElement ("test");
617 writer
.WriteAttributeString ("name", testsuiteName
+ ".tests." + pd
.test
);
618 writer
.WriteAttributeString ("type", testsuiteName
+ ".tests");
619 writer
.WriteAttributeString ("method", pd
.test
.ToString ());
620 writer
.WriteAttributeString ("result", "Pass");
621 writer
.WriteAttributeString ("time", pd
.duration
.TotalSeconds
.ToString(CultureInfo
.InvariantCulture
));
622 writer
.WriteEndElement (); // test element
625 foreach (var pd
in failed
) {
626 writer
.WriteStartElement ("test");
627 writer
.WriteAttributeString ("name", testsuiteName
+ ".tests." + pd
.test
);
628 writer
.WriteAttributeString ("type", testsuiteName
+ ".tests");
629 writer
.WriteAttributeString ("method", pd
.test
.ToString ());
630 writer
.WriteAttributeString ("result", "Fail");
631 writer
.WriteAttributeString ("time", pd
.duration
.TotalSeconds
.ToString(CultureInfo
.InvariantCulture
));
633 writer
.WriteStartElement ("failure");
634 writer
.WriteAttributeString ("exception-type", "TestRunnerException");
635 writer
.WriteStartElement ("message");
636 writer
.WriteCData (FilterInvalidXmlChars ("STDOUT:\n" + pd
.stdout
.ToString () + "\n\nSTDERR:\n" + pd
.stderr
.ToString ()));
637 writer
.WriteEndElement (); // message element
638 writer
.WriteEndElement (); // failure element
640 writer
.WriteEndElement(); // test element
643 foreach (var pd
in timedout
) {
644 writer
.WriteStartElement ("test");
645 writer
.WriteAttributeString ("name", testsuiteName
+ ".tests." + pd
.test
+ "_timedout");
646 writer
.WriteAttributeString ("type", testsuiteName
+ ".tests");
647 writer
.WriteAttributeString ("method", pd
.test
.ToString ());
648 writer
.WriteAttributeString ("result", "Fail");
649 writer
.WriteAttributeString ("time", pd
.duration
.TotalSeconds
.ToString(CultureInfo
.InvariantCulture
));
651 writer
.WriteStartElement ("failure");
652 writer
.WriteAttributeString ("exception-type", "TestRunnerException");
653 writer
.WriteStartElement ("message");
654 writer
.WriteCData (FilterInvalidXmlChars ("STDOUT:\n" + pd
.stdout
.ToString () + "\n\nSTDERR:\n" + pd
.stderr
.ToString ()));
655 writer
.WriteEndElement (); // message element
656 writer
.WriteEndElement (); // failure element
658 writer
.WriteEndElement(); // test element
661 writer
.WriteEndElement (); // collection
662 writer
.WriteEndElement (); // assembly
663 writer
.WriteEndElement (); // assemblies
664 writer
.WriteEndDocument ();
668 static string FilterInvalidXmlChars (string text
) {
669 // Spec at http://www.w3.org/TR/2008/REC-xml-20081126/#charsets says only the following chars are valid in XML:
670 // Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] /* any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. */
671 return Regex
.Replace (text
, @"[^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD\u10000-\u10FFFF]", "");
674 static void TryThreadDump (int pid
, ProcessData data
)
682 #if !FULL_AOT_DESKTOP && !MOBILE
683 /* LLDB cannot produce managed stacktraces for all the threads */
685 Syscall
.kill (pid
, Signum
.SIGQUIT
);
698 static void TryLLDB (int pid
, ProcessData data
)
700 string filename
= Path
.GetTempFileName ();
702 using (StreamWriter sw
= new StreamWriter (new FileStream (filename
, FileMode
.Open
, FileAccess
.Write
)))
704 sw
.WriteLine ("process attach --pid " + pid
);
705 sw
.WriteLine ("thread list");
706 sw
.WriteLine ("thread backtrace all");
707 sw
.WriteLine ("detach");
708 sw
.WriteLine ("quit");
711 ProcessStartInfo psi
= new ProcessStartInfo
{
713 Arguments
= "--batch --source \"" + filename
+ "\" --no-lldbinit",
714 UseShellExecute
= false,
715 RedirectStandardError
= true,
716 RedirectStandardOutput
= true,
719 using (Process process
= new Process { StartInfo = psi }
)
721 process
.OutputDataReceived
+= delegate (object sender
, DataReceivedEventArgs e
) {
722 lock (data
.stdoutLock
) {
724 data
.stdout
.AppendLine (e
.Data
);
728 process
.ErrorDataReceived
+= delegate (object sender
, DataReceivedEventArgs e
) {
729 lock (data
.stderrLock
) {
731 data
.stderr
.AppendLine (e
.Data
);
736 process
.BeginOutputReadLine ();
737 process
.BeginErrorReadLine ();
738 if (!process
.WaitForExit (60 * 1000))
744 static void TryGDB (int pid
, ProcessData data
)
746 string filename
= Path
.GetTempFileName ();
748 using (StreamWriter sw
= new StreamWriter (new FileStream (filename
, FileMode
.Open
, FileAccess
.Write
)))
750 sw
.WriteLine ("attach " + pid
);
751 sw
.WriteLine ("info threads");
752 sw
.WriteLine ("thread apply all p mono_print_thread_dump(0)");
753 sw
.WriteLine ("thread apply all backtrace");
756 ProcessStartInfo psi
= new ProcessStartInfo
{
758 Arguments
= "-batch -x \"" + filename
+ "\" -nx",
759 UseShellExecute
= false,
760 RedirectStandardError
= true,
761 RedirectStandardOutput
= true,
764 using (Process process
= new Process { StartInfo = psi }
)
766 process
.OutputDataReceived
+= delegate (object sender
, DataReceivedEventArgs e
) {
767 lock (data
.stdoutLock
) {
769 data
.stdout
.AppendLine (e
.Data
);
773 process
.ErrorDataReceived
+= delegate (object sender
, DataReceivedEventArgs e
) {
774 lock (data
.stderrLock
) {
776 data
.stderr
.AppendLine (e
.Data
);
781 process
.BeginOutputReadLine ();
782 process
.BeginErrorReadLine ();
783 if (!process
.WaitForExit (60 * 1000))