5 // Zoltan Varga (vargaz@gmail.com)
7 // Copyright (C) 2008 Novell, Inc (http://www.novell.com)
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 using System.Threading;
31 using System.Diagnostics;
32 using System.Collections.Generic;
33 using System.Globalization;
37 // This is a simple test runner with support for parallel execution
40 public class TestRunner
44 public StreamWriter stdout, stderr;
45 public string stdoutFile, stderrFile;
47 public void CloseStreams () {
60 public string test, opt_set;
63 public static int Main (String[] args) {
66 int timeout = 2 * 60; // in seconds
67 int expectedExitCode = 0;
69 DateTime test_start_time = DateTime.UtcNow;
71 // FIXME: Add support for runtime arguments + env variables
73 string disabled_tests = null;
74 string runtime = "mono";
75 var opt_sets = new List<string> ();
79 while (i < args.Length) {
80 if (args [i].StartsWith ("-")) {
81 if (args [i] == "-j") {
82 if (i + i >= args.Length) {
83 Console.WriteLine ("Missing argument to -j command line option.");
86 if (args [i + 1] == "a")
87 concurrency = Environment.ProcessorCount;
89 concurrency = Int32.Parse (args [i + 1]);
91 } else if (args [i] == "--timeout") {
92 if (i + i >= args.Length) {
93 Console.WriteLine ("Missing argument to --timeout command line option.");
96 timeout = Int32.Parse (args [i + 1]);
98 } else if (args [i] == "--disabled") {
99 if (i + i >= args.Length) {
100 Console.WriteLine ("Missing argument to --disabled command line option.");
103 disabled_tests = args [i + 1];
105 } else if (args [i] == "--runtime") {
106 if (i + i >= args.Length) {
107 Console.WriteLine ("Missing argument to --runtime command line option.");
110 runtime = args [i + 1];
112 } else if (args [i] == "--opt-sets") {
113 if (i + i >= args.Length) {
114 Console.WriteLine ("Missing argument to --opt-sets command line option.");
117 foreach (var s in args [i + 1].Split ())
120 } else if (args [i] == "--expected-exit-code") {
121 if (i + i >= args.Length) {
122 Console.WriteLine ("Missing argument to --expected-exit-code command line option.");
125 expectedExitCode = Int32.Parse (args [i + 1]);
128 Console.WriteLine ("Unknown command line option: '" + args [i] + "'.");
136 var disabled = new Dictionary <string, string> ();
138 if (disabled_tests != null) {
139 foreach (string test in disabled_tests.Split ())
140 disabled [test] = test;
143 // The remaining arguments are the tests
144 var tests = new List<string> ();
145 for (int j = i; j < args.Length; ++j)
146 if (!disabled.ContainsKey (args [j]))
147 tests.Add (args [j]);
152 var processes = new List<Process> ();
153 var passed = new List<ProcessData> ();
154 var failed = new List<ProcessData> ();
155 var process_data = new Dictionary<Process, ProcessData> ();
157 object monitor = new object ();
159 var terminated = new List<Process> ();
161 if (concurrency != 1)
162 Console.WriteLine ("Running tests: ");
164 var test_info = new List<TestInfo> ();
165 if (opt_sets.Count == 0) {
166 foreach (string s in tests)
167 test_info.Add (new TestInfo { test = s });
169 foreach (string opt in opt_sets) {
170 foreach (string s in tests)
171 test_info.Add (new TestInfo { test = s, opt_set = opt });
175 foreach (TestInfo ti in test_info) {
177 while (processes.Count == concurrency) {
178 /* Wait for one process to terminate */
179 Monitor.Wait (monitor);
182 /* Cleaup terminated processes */
183 foreach (Process dead in terminated) {
184 if (process_data [dead].stdout != null)
185 process_data [dead].stdout.Close ();
186 if (process_data [dead].stderr != null)
187 process_data [dead].stderr.Close ();
188 // This is needed to avoid CreateProcess failed errors :(
194 string test = ti.test;
195 string opt_set = ti.opt_set;
197 if (concurrency == 1)
198 Console.Write ("Testing " + test + "... ");
200 /* Spawn a new process */
205 process_args = "-O=" + opt_set + " " + test;
206 ProcessStartInfo info = new ProcessStartInfo (runtime, process_args);
207 info.UseShellExecute = false;
208 info.RedirectStandardOutput = true;
209 info.RedirectStandardError = true;
210 Process p = new Process ();
213 ProcessData data = new ProcessData ();
216 string log_prefix = "";
218 log_prefix = "." + opt_set.Replace ("-", "no").Replace (",", "_");
220 data.stdoutFile = test + log_prefix + ".stdout";
221 data.stdout = new StreamWriter (new FileStream (data.stdoutFile, FileMode.Create));
223 data.stderrFile = test + log_prefix + ".stderr";
224 data.stderr = new StreamWriter (new FileStream (data.stderrFile, FileMode.Create));
226 p.OutputDataReceived += delegate (object sender, DataReceivedEventArgs e) {
227 Process p2 = (Process)sender;
232 fs = process_data [p2].stdout;
235 process_data [p2].stdout = null;
238 if (e.Data == null) {
241 fs.WriteLine (e.Data);
246 p.ErrorDataReceived += delegate (object sender, DataReceivedEventArgs e) {
247 Process p2 = (Process)sender;
252 fs = process_data [p2].stderr;
255 process_data [p2].stderr = null;
259 if (e.Data == null) {
263 process_data [p2].stderr = null;
266 fs.WriteLine (e.Data);
273 process_data [p] = data;
277 p.BeginOutputReadLine ();
278 p.BeginErrorReadLine ();
280 ThreadPool.QueueUserWorkItem (o => {
281 Process process = (Process) o;
283 process.WaitForExit ();
286 if (process.ExitCode == expectedExitCode) {
287 if (concurrency == 1)
288 Console.WriteLine ("passed.");
291 passed.Add(process_data [process]);
294 if (concurrency == 1)
295 Console.WriteLine ("failed.");
298 failed.Add (process_data [process]);
301 processes.Remove (process);
302 terminated.Add (process);
303 Monitor.Pulse (monitor);
308 bool timed_out = false;
310 /* Wait for all processes to terminate */
313 int nprocesses = processes.Count;
318 bool res = Monitor.Wait (monitor, 1000 * timeout);
326 TimeSpan test_time = DateTime.UtcNow - test_start_time;
327 XmlWriterSettings xmlWriterSettings = new XmlWriterSettings ();
328 xmlWriterSettings.NewLineOnAttributes = true;
329 xmlWriterSettings.Indent = true;
330 using (XmlWriter writer = XmlWriter.Create ("TestResults_runtime.xml", xmlWriterSettings)) {
331 // <?xml version="1.0" encoding="utf-8" standalone="no"?>
332 writer.WriteStartDocument ();
333 // <!--This file represents the results of running a test suite-->
334 writer.WriteComment ("This file represents the results of running a test suite");
335 // <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">
336 writer.WriteStartElement ("test-results");
337 writer.WriteAttributeString ("name", "runtime-tests.dummy");
338 writer.WriteAttributeString ("total", (npassed + nfailed).ToString());
339 writer.WriteAttributeString ("failures", nfailed.ToString());
340 writer.WriteAttributeString ("not-run", "0");
341 writer.WriteAttributeString ("date", DateTime.Now.ToString ("yyyy-MM-dd"));
342 writer.WriteAttributeString ("time", DateTime.Now.ToString ("HH:mm:ss"));
343 // <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" />
344 writer.WriteStartElement ("environment");
345 writer.WriteAttributeString ("nunit-version", "2.4.8.0" );
346 writer.WriteAttributeString ("clr-version", Environment.Version.ToString() );
347 writer.WriteAttributeString ("os-version", Environment.OSVersion.ToString() );
348 writer.WriteAttributeString ("platform", Environment.OSVersion.Platform.ToString() );
349 writer.WriteAttributeString ("cwd", Environment.CurrentDirectory );
350 writer.WriteAttributeString ("machine-name", Environment.MachineName );
351 writer.WriteAttributeString ("user", Environment.UserName );
352 writer.WriteAttributeString ("user-domain", Environment.UserDomainName );
353 writer.WriteEndElement ();
354 // <culture-info current-culture="en-GB" current-uiculture="en-GB" />
355 writer.WriteStartElement ("culture-info");
356 writer.WriteAttributeString ("current-culture", CultureInfo.CurrentCulture.Name );
357 writer.WriteAttributeString ("current-uiculture", CultureInfo.CurrentUICulture.Name );
358 writer.WriteEndElement ();
359 // <test-suite name="corlib_test_net_4_5.dll" success="True" time="114.318" asserts="0">
360 writer.WriteStartElement ("test-suite");
361 writer.WriteAttributeString ("name","runtime-tests.dummy");
362 writer.WriteAttributeString ("success", (nfailed == 0).ToString());
363 writer.WriteAttributeString ("time", test_time.Seconds.ToString());
364 writer.WriteAttributeString ("asserts", nfailed.ToString());
366 writer.WriteStartElement ("results");
367 // <test-suite name="MonoTests" success="True" time="114.318" asserts="0">
368 writer.WriteStartElement ("test-suite");
369 writer.WriteAttributeString ("name","MonoTests");
370 writer.WriteAttributeString ("success", (nfailed == 0).ToString());
371 writer.WriteAttributeString ("time", test_time.Seconds.ToString());
372 writer.WriteAttributeString ("asserts", nfailed.ToString());
374 writer.WriteStartElement ("results");
375 // <test-suite name="MonoTests" success="True" time="114.318" asserts="0">
376 writer.WriteStartElement ("test-suite");
377 writer.WriteAttributeString ("name","runtime");
378 writer.WriteAttributeString ("success", (nfailed == 0).ToString());
379 writer.WriteAttributeString ("time", test_time.Seconds.ToString());
380 writer.WriteAttributeString ("asserts", nfailed.ToString());
382 writer.WriteStartElement ("results");
383 // Dump all passing tests first
384 foreach (ProcessData pd in passed) {
385 // <test-case name="MonoTests.Microsoft.Win32.RegistryKeyTest.bug79051" executed="True" success="True" time="0.063" asserts="0" />
386 writer.WriteStartElement ("test-case");
387 writer.WriteAttributeString ("name", "MonoTests.runtime." + pd.test);
388 writer.WriteAttributeString ("executed", "True");
389 writer.WriteAttributeString ("success", "True");
390 writer.WriteAttributeString ("time", "0");
391 writer.WriteAttributeString ("asserts", "0");
392 writer.WriteEndElement ();
394 // Now dump all failing tests
395 foreach (ProcessData pd in failed) {
396 // <test-case name="MonoTests.Microsoft.Win32.RegistryKeyTest.bug79051" executed="True" success="True" time="0.063" asserts="0" />
397 writer.WriteStartElement ("test-case");
398 writer.WriteAttributeString ("name", "MonoTests.runtime." + pd.test);
399 writer.WriteAttributeString ("executed", "True");
400 writer.WriteAttributeString ("success", "False");
401 writer.WriteAttributeString ("time", "0");
402 writer.WriteAttributeString ("asserts", "1");
403 writer.WriteStartElement ("failure");
404 writer.WriteStartElement ("message");
405 writer.WriteCData (DumpPseudoTrace (pd.stdoutFile));
406 writer.WriteEndElement ();
407 writer.WriteStartElement ("stack-trace");
408 writer.WriteCData (DumpPseudoTrace (pd.stderrFile));
409 writer.WriteEndElement ();
410 writer.WriteEndElement ();
411 writer.WriteEndElement ();
414 writer.WriteEndElement ();
416 writer.WriteEndElement ();
418 writer.WriteEndElement ();
420 writer.WriteEndElement ();
422 writer.WriteEndElement ();
424 writer.WriteEndElement ();
426 writer.WriteEndElement ();
427 writer.WriteEndDocument ();
430 Console.WriteLine ();
433 Console.WriteLine ("\nrunning tests timed out:\n");
434 Console.WriteLine (npassed + nfailed);
436 foreach (Process p in processes) {
437 ProcessData pd = process_data [p];
439 Console.WriteLine (pd.test);
441 DumpFile (pd.stdoutFile);
442 DumpFile (pd.stderrFile);
448 Console.WriteLine ("" + npassed + " test(s) passed. " + nfailed + " test(s) did not pass.");
450 Console.WriteLine ("\nFailed tests:\n");
451 foreach (ProcessData pd in failed) {
452 Console.WriteLine (pd.test);
453 DumpFile (pd.stdoutFile);
454 DumpFile (pd.stderrFile);
462 static void DumpFile (string filename) {
463 if (File.Exists (filename)) {
464 Console.WriteLine ("=============== {0} ===============", filename);
465 Console.WriteLine (File.ReadAllText (filename));
466 Console.WriteLine ("=============== EOF ===============");
470 static string DumpPseudoTrace (string filename) {
471 if (File.Exists (filename))
472 return File.ReadAllText (filename);