//
// Copyright (C) 2008 Novell, Inc (http://www.novell.com)
//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.IO;
using System.Collections.Generic;
using System.Globalization;
using System.Xml;
+using System.Text.RegularExpressions;
+using Mono.Unix.Native;
//
// This is a simple test runner with support for parallel execution
public class TestRunner
{
+ const string TEST_TIME_FORMAT = "mm\\:ss\\.fff";
+ const string ENV_TIMEOUT = "TEST_DRIVER_TIMEOUT_SEC";
+
class ProcessData {
public string test;
public StreamWriter stdout, stderr;
int timeout = 2 * 60; // in seconds
int expectedExitCode = 0;
string testsuiteName = null;
-
- DateTime test_start_time = DateTime.UtcNow;
+ string inputFile = null;
// FIXME: Add support for runtime arguments + env variables
while (i < args.Length) {
if (args [i].StartsWith ("-")) {
if (args [i] == "-j") {
- if (i + i >= args.Length) {
+ if (i + 1 >= args.Length) {
Console.WriteLine ("Missing argument to -j command line option.");
return 1;
}
concurrency = Int32.Parse (args [i + 1]);
i += 2;
} else if (args [i] == "--timeout") {
- if (i + i >= args.Length) {
+ if (i + 1 >= args.Length) {
Console.WriteLine ("Missing argument to --timeout command line option.");
return 1;
}
timeout = Int32.Parse (args [i + 1]);
i += 2;
} else if (args [i] == "--disabled") {
- if (i + i >= args.Length) {
+ if (i + 1 >= args.Length) {
Console.WriteLine ("Missing argument to --disabled command line option.");
return 1;
}
disabled_tests = args [i + 1];
i += 2;
} else if (args [i] == "--runtime") {
- if (i + i >= args.Length) {
+ if (i + 1 >= args.Length) {
Console.WriteLine ("Missing argument to --runtime command line option.");
return 1;
}
runtime = args [i + 1];
i += 2;
} else if (args [i] == "--opt-sets") {
- if (i + i >= args.Length) {
+ if (i + 1 >= args.Length) {
Console.WriteLine ("Missing argument to --opt-sets command line option.");
return 1;
}
opt_sets.Add (s);
i += 2;
} else if (args [i] == "--expected-exit-code") {
- if (i + i >= args.Length) {
+ if (i + 1 >= args.Length) {
Console.WriteLine ("Missing argument to --expected-exit-code command line option.");
return 1;
}
expectedExitCode = Int32.Parse (args [i + 1]);
i += 2;
} else if (args [i] == "--testsuite-name") {
- if (i + i >= args.Length) {
+ if (i + 1 >= args.Length) {
Console.WriteLine ("Missing argument to --testsuite-name command line option.");
return 1;
}
testsuiteName = args [i + 1];
i += 2;
+ } else if (args [i] == "--input-file") {
+ if (i + 1 >= args.Length) {
+ Console.WriteLine ("Missing argument to --input-file command line option.");
+ return 1;
+ }
+ inputFile = args [i + 1];
+ i += 2;
} else {
Console.WriteLine ("Unknown command line option: '" + args [i] + "'.");
return 1;
disabled [test] = test;
}
- // The remaining arguments are the tests
var tests = new List<string> ();
- for (int j = i; j < args.Length; ++j)
- if (!disabled.ContainsKey (args [j]))
- tests.Add (args [j]);
+
+ if (!String.IsNullOrEmpty (inputFile)) {
+ tests.AddRange (File.ReadAllLines (inputFile));
+ } else {
+ // The remaining arguments are the tests
+ for (int j = i; j < args.Length; ++j)
+ if (!disabled.ContainsKey (args [j]))
+ tests.Add (args [j]);
+ }
var passed = new List<ProcessData> ();
var failed = new List<ProcessData> ();
object monitor = new object ();
- if (concurrency != 1)
- Console.WriteLine ("Running tests: ");
+ Console.WriteLine ("Running tests: ");
var test_info = new Queue<TestInfo> ();
if (opt_sets.Count == 0) {
foreach (string s in tests)
test_info.Enqueue (new TestInfo { test = s, opt_set = opt });
}
- }
+ }
+
+ /* compute the max length of test names, to have an optimal output width */
+ int output_width = -1;
+ foreach (TestInfo ti in test_info) {
+ if (ti.test.Length > output_width)
+ output_width = Math.Min (120, ti.test.Length);
+ }
List<Thread> threads = new List<Thread> (concurrency);
+ DateTime test_start_time = DateTime.UtcNow;
+
for (int j = 0; j < concurrency; ++j) {
Thread thread = new Thread (() => {
while (true) {
ti = test_info.Dequeue ();
}
+ var output = new StringWriter ();
+
string test = ti.test;
string opt_set = ti.opt_set;
- if (concurrency == 1)
- Console.Write ("Testing " + test + "... ");
+ output.Write (String.Format ("{{0,-{0}}} ", output_width), test);
/* Spawn a new process */
string process_args;
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
+ info.EnvironmentVariables[ENV_TIMEOUT] = timeout.ToString();
Process p = new Process ();
p.StartInfo = info;
}
};
+ var start = DateTime.UtcNow;
+
p.Start ();
p.BeginOutputReadLine ();
timedout.Add (data);
}
- if (concurrency == 1)
- Console.WriteLine ("timed out.");
- else
- Console.Write (".");
+ // Force the process to print a thread dump
+ try {
+ Syscall.kill (p.Id, Signum.SIGQUIT);
+ Thread.Sleep (1000);
+ } catch {
+ }
+
+ output.Write ("timed out");
p.Kill ();
} else if (p.ExitCode != expectedExitCode) {
+ var end = DateTime.UtcNow;
+
lock (monitor) {
failed.Add (data);
}
- if (concurrency == 1)
- Console.WriteLine ("failed.");
- else
- Console.Write (".");
+ output.Write ("failed, time: {0}, exit code: {1}", (end - start).ToString (TEST_TIME_FORMAT), p.ExitCode);
} else {
+ var end = DateTime.UtcNow;
+
lock (monitor) {
passed.Add (data);
}
- if (concurrency == 1)
- Console.WriteLine ("passed.");
- else
- Console.Write (".");
+ output.Write ("passed, time: {0}", (end - start).ToString (TEST_TIME_FORMAT));
}
p.Close ();
+
+ lock (monitor) {
+ Console.WriteLine (output.ToString ());
+ }
}
});
for (int j = 0; j < threads.Count; ++j)
threads [j].Join ();
+ TimeSpan test_time = DateTime.UtcNow - test_start_time;
+
int npassed = passed.Count;
int nfailed = failed.Count;
int ntimedout = timedout.Count;
- TimeSpan test_time = DateTime.UtcNow - test_start_time;
XmlWriterSettings xmlWriterSettings = new XmlWriterSettings ();
xmlWriterSettings.NewLineOnAttributes = true;
xmlWriterSettings.Indent = true;
- using (XmlWriter writer = XmlWriter.Create (String.Format ("TestResults_{0}.xml", testsuiteName), xmlWriterSettings)) {
+ using (XmlWriter writer = XmlWriter.Create (String.Format ("TestResult-{0}.xml", testsuiteName), xmlWriterSettings)) {
// <?xml version="1.0" encoding="utf-8" standalone="no"?>
writer.WriteStartDocument ();
// <!--This file represents the results of running a test suite-->
writer.WriteEndDocument ();
}
+ Console.WriteLine ();
+ Console.WriteLine ("Time: {0}", test_time.ToString (TEST_TIME_FORMAT));
Console.WriteLine ();
Console.WriteLine ("{0,4} test(s) passed", npassed);
Console.WriteLine ("{0,4} test(s) failed", nfailed);
static string DumpPseudoTrace (string filename) {
if (File.Exists (filename))
- return File.ReadAllText (filename);
+ return FilterInvalidXmlChars (File.ReadAllText (filename));
else
return string.Empty;
}
+
+ static string FilterInvalidXmlChars (string text) {
+ // Spec at http://www.w3.org/TR/2008/REC-xml-20081126/#charsets says only the following chars are valid in XML:
+ // 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]", "");
+ }
}