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
;
21 #if !FULL_AOT_DESKTOP && !MOBILE
22 using Mono
.Unix
.Native
;
26 // This is a simple test runner with support for parallel execution
29 public class TestRunner
31 const string TEST_TIME_FORMAT
= "mm\\:ss\\.fff";
32 const string ENV_TIMEOUT
= "TEST_DRIVER_TIMEOUT_SEC";
33 const string MONO_PATH
= "MONO_PATH";
37 public StringBuilder stdout
, stderr
;
38 public object stdoutLock
= new object (), stderrLock
= new object ();
39 public string stdoutName
, stderrName
;
43 public string test
, opt_set
;
46 public static int Main (String
[] args
) {
49 int timeout
= 2 * 60; // in seconds
50 int expectedExitCode
= 0;
52 string testsuiteName
= null;
53 string inputFile
= null;
55 string disabled_tests
= null;
56 string runtime
= "mono";
58 string mono_path
= null;
59 var opt_sets
= new List
<string> ();
61 string aot_run_flags
= null;
62 string aot_build_flags
= null;
66 while (i
< args
.Length
) {
67 if (args
[i
].StartsWith ("-")) {
68 if (args
[i
] == "-j") {
69 if (i
+ 1 >= args
.Length
) {
70 Console
.WriteLine ("Missing argument to -j command line option.");
73 if (args
[i
+ 1] == "a")
74 concurrency
= Environment
.ProcessorCount
;
76 concurrency
= Int32
.Parse (args
[i
+ 1]);
78 } else if (args
[i
] == "--timeout") {
79 if (i
+ 1 >= args
.Length
) {
80 Console
.WriteLine ("Missing argument to --timeout command line option.");
83 timeout
= Int32
.Parse (args
[i
+ 1]);
85 } else if (args
[i
] == "--disabled") {
86 if (i
+ 1 >= args
.Length
) {
87 Console
.WriteLine ("Missing argument to --disabled command line option.");
90 disabled_tests
= args
[i
+ 1];
92 } else if (args
[i
] == "--runtime") {
93 if (i
+ 1 >= args
.Length
) {
94 Console
.WriteLine ("Missing argument to --runtime command line option.");
97 runtime
= args
[i
+ 1];
99 } else if (args
[i
] == "--config") {
100 if (i
+ 1 >= args
.Length
) {
101 Console
.WriteLine ("Missing argument to --config command line option.");
104 config
= args
[i
+ 1];
106 } else if (args
[i
] == "--opt-sets") {
107 if (i
+ 1 >= args
.Length
) {
108 Console
.WriteLine ("Missing argument to --opt-sets command line option.");
111 foreach (var s
in args
[i
+ 1].Split ())
114 } else if (args
[i
] == "--expected-exit-code") {
115 if (i
+ 1 >= args
.Length
) {
116 Console
.WriteLine ("Missing argument to --expected-exit-code command line option.");
119 expectedExitCode
= Int32
.Parse (args
[i
+ 1]);
121 } else if (args
[i
] == "--testsuite-name") {
122 if (i
+ 1 >= args
.Length
) {
123 Console
.WriteLine ("Missing argument to --testsuite-name command line option.");
126 testsuiteName
= args
[i
+ 1];
128 } else if (args
[i
] == "--input-file") {
129 if (i
+ 1 >= args
.Length
) {
130 Console
.WriteLine ("Missing argument to --input-file command line option.");
133 inputFile
= args
[i
+ 1];
135 } else if (args
[i
] == "--runtime") {
136 if (i
+ 1 >= args
.Length
) {
137 Console
.WriteLine ("Missing argument to --runtime command line option.");
140 runtime
= args
[i
+ 1];
142 } else if (args
[i
] == "--mono-path") {
143 if (i
+ 1 >= args
.Length
) {
144 Console
.WriteLine ("Missing argument to --mono-path command line option.");
147 mono_path
= args
[i
+ 1].Substring(0, args
[i
+ 1].Length
);
150 } else if (args
[i
] == "--aot-run-flags") {
151 if (i
+ 1 >= args
.Length
) {
152 Console
.WriteLine ("Missing argument to --aot-run-flags command line option.");
155 aot_run_flags
= args
[i
+ 1].Substring(0, args
[i
+ 1].Length
);
157 } else if (args
[i
] == "--aot-build-flags") {
158 if (i
+ 1 >= args
.Length
) {
159 Console
.WriteLine ("Missing argument to --aot-build-flags command line option.");
162 aot_build_flags
= args
[i
+ 1].Substring(0, args
[i
+ 1].Length
);
164 } else if (args
[i
] == "--verbose") {
168 Console
.WriteLine ("Unknown command line option: '" + args
[i
] + "'.");
176 if (String
.IsNullOrEmpty (testsuiteName
)) {
177 Console
.WriteLine ("Missing the required --testsuite-name command line option.");
181 var disabled
= new Dictionary
<string, string> ();
183 if (disabled_tests
!= null) {
184 foreach (string test
in disabled_tests
.Split ())
185 disabled
[test
] = test
;
188 var tests
= new List
<string> ();
190 if (!String
.IsNullOrEmpty (inputFile
)) {
191 tests
.AddRange (File
.ReadAllLines (inputFile
));
193 // The remaining arguments are the tests
194 for (int j
= i
; j
< args
.Length
; ++j
)
195 if (!disabled
.ContainsKey (args
[j
]))
196 tests
.Add (args
[j
]);
199 var passed
= new List
<ProcessData
> ();
200 var failed
= new List
<ProcessData
> ();
201 var timedout
= new List
<ProcessData
> ();
203 object monitor
= new object ();
205 Console
.WriteLine ("Running tests: ");
207 var test_info
= new Queue
<TestInfo
> ();
208 if (opt_sets
.Count
== 0) {
209 foreach (string s
in tests
)
210 test_info
.Enqueue (new TestInfo { test = s }
);
212 foreach (string opt
in opt_sets
) {
213 foreach (string s
in tests
)
214 test_info
.Enqueue (new TestInfo { test = s, opt_set = opt }
);
218 /* compute the max length of test names, to have an optimal output width */
219 int output_width
= -1;
220 foreach (TestInfo ti
in test_info
) {
221 if (ti
.test
.Length
> output_width
)
222 output_width
= Math
.Min (120, ti
.test
.Length
);
225 if (aot_build_flags
!= null) {
226 Console
.WriteLine("AOT compiling tests");
228 object aot_monitor
= new object ();
229 var aot_queue
= new Queue
<String
> (tests
);
231 List
<Thread
> build_threads
= new List
<Thread
> (concurrency
);
233 for (int j
= 0; j
< concurrency
; ++j
) {
234 Thread thread
= new Thread (() => {
239 if (aot_queue
.Count
== 0)
241 test_name
= aot_queue
.Dequeue ();
244 string test_bitcode_output
= test_name
+ "_bitcode_tmp";
245 string test_bitcode_arg
= ",temp-path=" + test_bitcode_output
;
246 string aot_args
= aot_build_flags
+ test_bitcode_arg
+ " " + test_name
;
248 Directory
.CreateDirectory(test_bitcode_output
);
250 ProcessStartInfo job
= new ProcessStartInfo (runtime
, aot_args
);
251 job
.UseShellExecute
= false;
252 job
.EnvironmentVariables
[ENV_TIMEOUT
] = timeout
.ToString();
253 job
.EnvironmentVariables
[MONO_PATH
] = mono_path
;
254 Process compiler
= new Process ();
255 compiler
.StartInfo
= job
;
259 if (!compiler
.WaitForExit (timeout
* 1000)) {
264 throw new Exception(String
.Format("Timeout AOT compiling tests, output in {0}", test_bitcode_output
));
265 } else if (compiler
.ExitCode
!= 0) {
266 throw new Exception(String
.Format("Error AOT compiling tests, output in {0}", test_bitcode_output
));
268 Directory
.Delete (test_bitcode_output
, true);
275 build_threads
.Add (thread
);
278 for (int j
= 0; j
< build_threads
.Count
; ++j
)
279 build_threads
[j
].Join ();
281 Console
.WriteLine("Done compiling");
284 List
<Thread
> threads
= new List
<Thread
> (concurrency
);
286 DateTime test_start_time
= DateTime
.UtcNow
;
288 for (int j
= 0; j
< concurrency
; ++j
) {
289 Thread thread
= new Thread (() => {
294 if (test_info
.Count
== 0)
296 ti
= test_info
.Dequeue ();
299 var output
= new StringWriter ();
301 string test
= ti
.test
;
302 string opt_set
= ti
.opt_set
;
305 output
.Write (String
.Format ("{{0,-{0}}} ", output_width
), test
);
312 if (aot_run_flags
!= null)
313 test_invoke
= aot_run_flags
+ " " + test
;
317 /* Spawn a new process */
320 process_args
= test_invoke
;
322 process_args
= "-O=" + opt_set
+ " " + test_invoke
;
324 ProcessStartInfo info
= new ProcessStartInfo (runtime
, process_args
);
325 info
.UseShellExecute
= false;
326 info
.RedirectStandardOutput
= true;
327 info
.RedirectStandardError
= true;
328 info
.EnvironmentVariables
[ENV_TIMEOUT
] = timeout
.ToString();
330 info
.EnvironmentVariables
["MONO_CONFIG"] = config
;
331 if (mono_path
!= null)
332 info
.EnvironmentVariables
[MONO_PATH
] = mono_path
;
333 Process p
= new Process ();
336 ProcessData data
= new ProcessData ();
339 string log_prefix
= "";
341 log_prefix
= "." + opt_set
.Replace ("-", "no").Replace (",", "_");
343 data
.stdoutName
= test
+ log_prefix
+ ".stdout";
344 data
.stdout
= new StringBuilder ();
346 data
.stderrName
= test
+ log_prefix
+ ".stderr";
347 data
.stderr
= new StringBuilder ();
349 p
.OutputDataReceived
+= delegate (object sender
, DataReceivedEventArgs e
) {
350 lock (data
.stdoutLock
) {
352 data
.stdout
.AppendLine (e
.Data
);
356 p
.ErrorDataReceived
+= delegate (object sender
, DataReceivedEventArgs e
) {
357 lock (data
.stderrLock
) {
359 data
.stderr
.AppendLine (e
.Data
);
363 var start
= DateTime
.UtcNow
;
367 p
.BeginOutputReadLine ();
368 p
.BeginErrorReadLine ();
370 if (!p
.WaitForExit (timeout
* 1000)) {
375 // Force the process to print a thread dump
376 TryThreadDump (p
.Id
, data
);
379 output
.Write ($"timed out ({timeout}s)");
386 } else if (p
.ExitCode
!= expectedExitCode
) {
387 var end
= DateTime
.UtcNow
;
394 output
.Write ("failed, time: {0}, exit code: {1}", (end
- start
).ToString (TEST_TIME_FORMAT
), p
.ExitCode
);
396 var end
= DateTime
.UtcNow
;
403 output
.Write ("passed, time: {0}", (end
- start
).ToString (TEST_TIME_FORMAT
));
410 Console
.WriteLine (output
.ToString ());
417 threads
.Add (thread
);
420 for (int j
= 0; j
< threads
.Count
; ++j
)
423 TimeSpan test_time
= DateTime
.UtcNow
- test_start_time
;
425 int npassed
= passed
.Count
;
426 int nfailed
= failed
.Count
;
427 int ntimedout
= timedout
.Count
;
429 XmlWriterSettings xmlWriterSettings
= new XmlWriterSettings ();
430 xmlWriterSettings
.NewLineOnAttributes
= true;
431 xmlWriterSettings
.Indent
= true;
433 string xmlPath
= String
.Format ("TestResult-{0}.xml", testsuiteName
);
434 using (XmlWriter writer
= XmlWriter
.Create (xmlPath
, xmlWriterSettings
)) {
435 // <?xml version="1.0" encoding="utf-8" standalone="no"?>
436 writer
.WriteStartDocument ();
437 // <!--This file represents the results of running a test suite-->
438 writer
.WriteComment ("This file represents the results of running a test suite");
439 // <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">
440 writer
.WriteStartElement ("test-results");
441 writer
.WriteAttributeString ("name", String
.Format ("{0}-tests.dummy", testsuiteName
));
442 writer
.WriteAttributeString ("total", (npassed
+ nfailed
+ ntimedout
).ToString());
443 writer
.WriteAttributeString ("failures", (nfailed
+ ntimedout
).ToString());
444 writer
.WriteAttributeString ("not-run", "0");
445 writer
.WriteAttributeString ("date", DateTime
.Now
.ToString ("yyyy-MM-dd"));
446 writer
.WriteAttributeString ("time", DateTime
.Now
.ToString ("HH:mm:ss"));
447 // <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" />
448 writer
.WriteStartElement ("environment");
449 writer
.WriteAttributeString ("nunit-version", "2.4.8.0" );
450 writer
.WriteAttributeString ("clr-version", Environment
.Version
.ToString() );
451 writer
.WriteAttributeString ("os-version", Environment
.OSVersion
.ToString() );
452 writer
.WriteAttributeString ("platform", Environment
.OSVersion
.Platform
.ToString() );
453 writer
.WriteAttributeString ("cwd", Environment
.CurrentDirectory
);
454 writer
.WriteAttributeString ("machine-name", Environment
.MachineName
);
455 writer
.WriteAttributeString ("user", Environment
.UserName
);
456 writer
.WriteAttributeString ("user-domain", Environment
.UserDomainName
);
457 writer
.WriteEndElement ();
458 // <culture-info current-culture="en-GB" current-uiculture="en-GB" />
459 writer
.WriteStartElement ("culture-info");
460 writer
.WriteAttributeString ("current-culture", CultureInfo
.CurrentCulture
.Name
);
461 writer
.WriteAttributeString ("current-uiculture", CultureInfo
.CurrentUICulture
.Name
);
462 writer
.WriteEndElement ();
463 // <test-suite name="corlib_test_net_4_5.dll" success="True" time="114.318" asserts="0">
464 writer
.WriteStartElement ("test-suite");
465 writer
.WriteAttributeString ("name", String
.Format ("{0}-tests.dummy", testsuiteName
));
466 writer
.WriteAttributeString ("success", (nfailed
+ ntimedout
== 0).ToString());
467 writer
.WriteAttributeString ("time", test_time
.Seconds
.ToString());
468 writer
.WriteAttributeString ("asserts", (nfailed
+ ntimedout
).ToString());
470 writer
.WriteStartElement ("results");
471 // <test-suite name="MonoTests" success="True" time="114.318" asserts="0">
472 writer
.WriteStartElement ("test-suite");
473 writer
.WriteAttributeString ("name","MonoTests");
474 writer
.WriteAttributeString ("success", (nfailed
+ ntimedout
== 0).ToString());
475 writer
.WriteAttributeString ("time", test_time
.Seconds
.ToString());
476 writer
.WriteAttributeString ("asserts", (nfailed
+ ntimedout
).ToString());
478 writer
.WriteStartElement ("results");
479 // <test-suite name="MonoTests" success="True" time="114.318" asserts="0">
480 writer
.WriteStartElement ("test-suite");
481 writer
.WriteAttributeString ("name", testsuiteName
);
482 writer
.WriteAttributeString ("success", (nfailed
+ ntimedout
== 0).ToString());
483 writer
.WriteAttributeString ("time", test_time
.Seconds
.ToString());
484 writer
.WriteAttributeString ("asserts", (nfailed
+ ntimedout
).ToString());
486 writer
.WriteStartElement ("results");
487 // Dump all passing tests first
488 foreach (ProcessData pd
in passed
) {
489 // <test-case name="MonoTests.Microsoft.Win32.RegistryKeyTest.bug79051" executed="True" success="True" time="0.063" asserts="0" />
490 writer
.WriteStartElement ("test-case");
491 writer
.WriteAttributeString ("name", String
.Format ("MonoTests.{0}.{1}", testsuiteName
, pd
.test
));
492 writer
.WriteAttributeString ("executed", "True");
493 writer
.WriteAttributeString ("success", "True");
494 writer
.WriteAttributeString ("time", "0");
495 writer
.WriteAttributeString ("asserts", "0");
496 writer
.WriteEndElement ();
498 // Now dump all failing tests
499 foreach (ProcessData pd
in failed
) {
500 // <test-case name="MonoTests.Microsoft.Win32.RegistryKeyTest.bug79051" executed="True" success="True" time="0.063" asserts="0" />
501 writer
.WriteStartElement ("test-case");
502 writer
.WriteAttributeString ("name", String
.Format ("MonoTests.{0}.{1}", testsuiteName
, pd
.test
));
503 writer
.WriteAttributeString ("executed", "True");
504 writer
.WriteAttributeString ("success", "False");
505 writer
.WriteAttributeString ("time", "0");
506 writer
.WriteAttributeString ("asserts", "1");
507 writer
.WriteStartElement ("failure");
508 writer
.WriteStartElement ("message");
509 writer
.WriteCData (FilterInvalidXmlChars (pd
.stdout
.ToString ()));
510 writer
.WriteEndElement ();
511 writer
.WriteStartElement ("stack-trace");
512 writer
.WriteCData (FilterInvalidXmlChars (pd
.stderr
.ToString ()));
513 writer
.WriteEndElement ();
514 writer
.WriteEndElement ();
515 writer
.WriteEndElement ();
517 // Then dump all timing out tests
518 foreach (ProcessData pd
in timedout
) {
519 // <test-case name="MonoTests.Microsoft.Win32.RegistryKeyTest.bug79051" executed="True" success="True" time="0.063" asserts="0" />
520 writer
.WriteStartElement ("test-case");
521 writer
.WriteAttributeString ("name", String
.Format ("MonoTests.{0}.{1}_timedout", testsuiteName
, pd
.test
));
522 writer
.WriteAttributeString ("executed", "True");
523 writer
.WriteAttributeString ("success", "False");
524 writer
.WriteAttributeString ("time", "0");
525 writer
.WriteAttributeString ("asserts", "1");
526 writer
.WriteStartElement ("failure");
527 writer
.WriteStartElement ("message");
528 writer
.WriteCData (FilterInvalidXmlChars (pd
.stdout
.ToString ()));
529 writer
.WriteEndElement ();
530 writer
.WriteStartElement ("stack-trace");
531 writer
.WriteCData (FilterInvalidXmlChars (pd
.stderr
.ToString ()));
532 writer
.WriteEndElement ();
533 writer
.WriteEndElement ();
534 writer
.WriteEndElement ();
537 writer
.WriteEndElement ();
539 writer
.WriteEndElement ();
541 writer
.WriteEndElement ();
543 writer
.WriteEndElement ();
545 writer
.WriteEndElement ();
547 writer
.WriteEndElement ();
549 writer
.WriteEndElement ();
550 writer
.WriteEndDocument ();
552 string babysitterXmlList
= Environment
.GetEnvironmentVariable("MONO_BABYSITTER_NUNIT_XML_LIST_FILE");
553 if (!String
.IsNullOrEmpty(babysitterXmlList
)) {
555 string fullXmlPath
= Path
.GetFullPath(xmlPath
);
556 File
.AppendAllText(babysitterXmlList
, fullXmlPath
+ Environment
.NewLine
);
557 } catch (Exception e
) {
558 Console
.WriteLine("Attempted to record XML path to file {0} but failed.", babysitterXmlList
);
564 Console
.WriteLine ();
565 Console
.WriteLine ("Time: {0}", test_time
.ToString (TEST_TIME_FORMAT
));
566 Console
.WriteLine ();
567 Console
.WriteLine ("{0,4} test(s) passed", npassed
);
568 Console
.WriteLine ("{0,4} test(s) failed", nfailed
);
569 Console
.WriteLine ("{0,4} test(s) timed out", ntimedout
);
571 Console
.WriteLine ();
572 Console
.WriteLine (String
.Format ("{0} test(s) passed, {1} test(s) did not pass.", npassed
, nfailed
));
576 Console
.WriteLine ();
577 Console
.WriteLine ("Failed test(s):");
578 foreach (ProcessData pd
in failed
) {
579 Console
.WriteLine ();
580 Console
.WriteLine (pd
.test
);
581 DumpFile (pd
.stdoutName
, pd
.stdout
.ToString ());
582 DumpFile (pd
.stderrName
, pd
.stderr
.ToString ());
587 Console
.WriteLine ();
588 Console
.WriteLine ("Timed out test(s):");
589 foreach (ProcessData pd
in timedout
) {
590 Console
.WriteLine ();
591 Console
.WriteLine (pd
.test
);
592 DumpFile (pd
.stdoutName
, pd
.stdout
.ToString ());
593 DumpFile (pd
.stderrName
, pd
.stderr
.ToString ());
597 return (ntimedout
== 0 && nfailed
== 0) ? 0 : 1;
600 static void DumpFile (string filename
, string text
) {
601 Console
.WriteLine ("=============== {0} ===============", filename
);
602 Console
.WriteLine (text
);
603 Console
.WriteLine ("=============== EOF ===============");
606 static string FilterInvalidXmlChars (string text
) {
607 // Spec at http://www.w3.org/TR/2008/REC-xml-20081126/#charsets says only the following chars are valid in XML:
608 // Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] /* any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. */
609 return Regex
.Replace (text
, @"[^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD\u10000-\u10FFFF]", "");
612 static void TryThreadDump (int pid
, ProcessData data
)
620 #if !FULL_AOT_DESKTOP && !MOBILE
621 /* LLDB cannot produce managed stacktraces for all the threads */
623 Syscall
.kill (pid
, Signum
.SIGQUIT
);
636 static void TryLLDB (int pid
, ProcessData data
)
638 string filename
= Path
.GetTempFileName ();
640 using (StreamWriter sw
= new StreamWriter (new FileStream (filename
, FileMode
.Open
, FileAccess
.Write
)))
642 sw
.WriteLine ("process attach --pid " + pid
);
643 sw
.WriteLine ("thread list");
644 sw
.WriteLine ("thread backtrace all");
645 sw
.WriteLine ("detach");
646 sw
.WriteLine ("quit");
649 ProcessStartInfo psi
= new ProcessStartInfo
{
651 Arguments
= "--batch --source \"" + filename
+ "\" --no-lldbinit",
652 UseShellExecute
= false,
653 RedirectStandardError
= true,
654 RedirectStandardOutput
= true,
657 using (Process process
= new Process { StartInfo = psi }
)
659 process
.OutputDataReceived
+= delegate (object sender
, DataReceivedEventArgs e
) {
660 lock (data
.stdoutLock
) {
662 data
.stdout
.AppendLine (e
.Data
);
666 process
.ErrorDataReceived
+= delegate (object sender
, DataReceivedEventArgs e
) {
667 lock (data
.stderrLock
) {
669 data
.stderr
.AppendLine (e
.Data
);
674 process
.BeginOutputReadLine ();
675 process
.BeginErrorReadLine ();
676 if (!process
.WaitForExit (60 * 1000))
682 static void TryGDB (int pid
, ProcessData data
)
684 string filename
= Path
.GetTempFileName ();
686 using (StreamWriter sw
= new StreamWriter (new FileStream (filename
, FileMode
.Open
, FileAccess
.Write
)))
688 sw
.WriteLine ("attach " + pid
);
689 sw
.WriteLine ("info threads");
690 sw
.WriteLine ("thread apply all p mono_print_thread_dump(0)");
691 sw
.WriteLine ("thread apply all backtrace");
694 ProcessStartInfo psi
= new ProcessStartInfo
{
696 Arguments
= "-batch -x \"" + filename
+ "\" -nx",
697 UseShellExecute
= false,
698 RedirectStandardError
= true,
699 RedirectStandardOutput
= true,
702 using (Process process
= new Process { StartInfo = psi }
)
704 process
.OutputDataReceived
+= delegate (object sender
, DataReceivedEventArgs e
) {
705 lock (data
.stdoutLock
) {
707 data
.stdout
.AppendLine (e
.Data
);
711 process
.ErrorDataReceived
+= delegate (object sender
, DataReceivedEventArgs e
) {
712 lock (data
.stderrLock
) {
714 data
.stderr
.AppendLine (e
.Data
);
719 process
.BeginOutputReadLine ();
720 process
.BeginErrorReadLine ();
721 if (!process
.WaitForExit (60 * 1000))