1 // ***********************************************************************
2 // Copyright (c) 2011 Charlie Poole
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 // ***********************************************************************
25 #define NUNIT_FRAMEWORK
30 using System.Collections;
31 using System.Collections.Generic;
32 using System.Globalization;
33 using System.Reflection;
37 using NUnit.Framework.Interfaces;
38 using NUnit.Framework.Internal;
43 /// NUnit2XmlOutputWriter is able to create an xml file representing
44 /// the result of a test run in NUnit 2.x format.
46 public class NUnit2XmlOutputWriter : OutputWriter
48 private XmlWriter xmlWriter;
51 /// Write info about a test
53 /// <param name="test">The test</param>
54 /// <param name="writer">A TextWriter</param>
55 public override void WriteTestFile(ITest test, TextWriter writer)
57 throw new NotImplementedException("Explore test output is not supported by the NUnit2 format.");
61 /// Writes the result of a test run to a specified TextWriter.
63 /// <param name="result">The test result for the run</param>
64 /// <param name="writer">The TextWriter to which the xml will be written</param>
65 /// <param name="runSettings"></param>
66 /// <param name="filter"></param>
67 public override void WriteResultFile(ITestResult result, TextWriter writer, IDictionary<string, object> runSettings, TestFilter filter)
69 // NOTE: Under .NET 1.1, XmlTextWriter does not implement IDisposable,
70 // but does implement Close(). Hence we cannot use a 'using' clause.
71 //using (XmlTextWriter xmlWriter = new XmlTextWriter(writer))
73 XmlWriter xmlWriter = XmlWriter.Create(writer);
75 //XmlW xmlWriter = new XmlTextWriter(writer);
76 //xmlWriter.Formatting = Formatting.Indented;
77 XmlWriter xmlWriter = XmlWriter.Create(writer);
82 WriteXmlOutput(result, xmlWriter);
90 private void WriteXmlOutput(ITestResult result, XmlWriter xmlWriter)
92 this.xmlWriter = xmlWriter;
94 InitializeXmlFile(result);
95 WriteResultElement(result);
99 private void InitializeXmlFile(ITestResult result)
101 ResultSummary summary = new ResultSummary(result);
103 xmlWriter.WriteStartDocument(false);
104 xmlWriter.WriteComment("This file represents the results of running a test suite");
106 xmlWriter.WriteStartElement("test-results");
108 xmlWriter.WriteAttributeString("name", result.FullName);
109 xmlWriter.WriteAttributeString("total", summary.TestCount.ToString());
110 xmlWriter.WriteAttributeString("errors", summary.ErrorCount.ToString());
111 xmlWriter.WriteAttributeString("failures", summary.FailureCount.ToString());
112 var notRunTotal = summary.SkipCount + summary.FailureCount + summary.InvalidCount;
113 xmlWriter.WriteAttributeString("not-run", notRunTotal.ToString());
114 xmlWriter.WriteAttributeString("inconclusive", summary.InconclusiveCount.ToString());
115 xmlWriter.WriteAttributeString("ignored", summary.IgnoreCount.ToString());
116 xmlWriter.WriteAttributeString("skipped", summary.SkipCount.ToString());
117 xmlWriter.WriteAttributeString("invalid", summary.InvalidCount.ToString());
119 xmlWriter.WriteAttributeString("date", result.StartTime.ToString("yyyy-MM-dd"));
120 xmlWriter.WriteAttributeString("time", result.StartTime.ToString("HH:mm:ss"));
125 private void WriteCultureInfo()
127 xmlWriter.WriteStartElement("culture-info");
128 xmlWriter.WriteAttributeString("current-culture",
129 CultureInfo.CurrentCulture.ToString());
130 xmlWriter.WriteAttributeString("current-uiculture",
131 CultureInfo.CurrentUICulture.ToString());
132 xmlWriter.WriteEndElement();
135 private void WriteEnvironment()
137 // xmlWriter.WriteStartElement("environment");
138 // var assemblyName = AssemblyHelper.GetAssemblyName(Assembly.GetExecutingAssembly());
139 // xmlWriter.WriteAttributeString("nunit-version",
140 // assemblyName.Version.ToString());
141 // xmlWriter.WriteAttributeString("clr-version",
142 // Environment.Version.ToString());
143 // xmlWriter.WriteAttributeString("os-version",
144 // Environment.OSVersion.ToString());
145 // xmlWriter.WriteAttributeString("platform",
146 // Environment.OSVersion.Platform.ToString());
148 // xmlWriter.WriteAttributeString("cwd",
149 // Environment.CurrentDirectory);
151 // xmlWriter.WriteAttributeString("machine-name",
152 // Environment.MachineName);
153 // xmlWriter.WriteAttributeString("user",
154 // Environment.UserName);
155 // xmlWriter.WriteAttributeString("user-domain",
156 // Environment.UserDomainName);
159 // xmlWriter.WriteEndElement();
162 private void WriteResultElement(ITestResult result)
164 StartTestElement(result);
166 WriteCategories(result);
167 WriteProperties(result);
169 switch (result.ResultState.Status)
171 case TestStatus.Skipped:
172 if (result.Message != null)
173 WriteReasonElement(result.Message);
175 case TestStatus.Failed:
176 WriteFailureElement(result.Message, result.StackTrace);
180 if (result.Test is TestSuite)
181 WriteChildResults(result);
183 xmlWriter.WriteEndElement(); // test element
186 private void TerminateXmlFile()
188 xmlWriter.WriteEndElement(); // test-results
189 xmlWriter.WriteEndDocument();
195 #region Element Creation Helpers
197 private void StartTestElement(ITestResult result)
199 ITest test = result.Test;
200 TestSuite suite = test as TestSuite;
204 xmlWriter.WriteStartElement("test-suite");
205 xmlWriter.WriteAttributeString("type", suite.TestType == "ParameterizedMethod" ? "ParameterizedTest" : suite.TestType);
206 xmlWriter.WriteAttributeString("name", suite.TestType == "Assembly" || suite.TestType == "Project"
207 ? result.Test.FullName
212 xmlWriter.WriteStartElement("test-case");
213 xmlWriter.WriteAttributeString("name", result.Name);
216 if (test.Properties.ContainsKey(PropertyNames.Description))
218 string description = (string)test.Properties.Get(PropertyNames.Description);
219 xmlWriter.WriteAttributeString("description", description);
222 TestStatus status = result.ResultState.Status;
223 string translatedResult = TranslateResult(result.ResultState);
225 if (status != TestStatus.Skipped)
227 xmlWriter.WriteAttributeString("executed", "True");
228 xmlWriter.WriteAttributeString("result", translatedResult);
229 xmlWriter.WriteAttributeString("success", status == TestStatus.Passed ? "True" : "False");
230 xmlWriter.WriteAttributeString("time", result.Duration.ToString("0.000", NumberFormatInfo.InvariantInfo));
231 xmlWriter.WriteAttributeString("asserts", result.AssertCount.ToString());
235 xmlWriter.WriteAttributeString("executed", "False");
236 xmlWriter.WriteAttributeString("result", translatedResult);
240 private string TranslateResult(ResultState resultState)
242 switch (resultState.Status)
245 case TestStatus.Passed:
247 case TestStatus.Inconclusive:
248 return "Inconclusive";
249 case TestStatus.Failed:
250 switch (resultState.Label)
254 return resultState.Label;
258 case TestStatus.Skipped:
259 switch (resultState.Label)
264 return "NotRunnable";
271 private void WriteCategories(ITestResult result)
273 IPropertyBag properties = result.Test.Properties;
275 if (properties.ContainsKey(PropertyNames.Category))
277 xmlWriter.WriteStartElement("categories");
279 foreach (string category in properties[PropertyNames.Category])
281 xmlWriter.WriteStartElement("category");
282 xmlWriter.WriteAttributeString("name", category);
283 xmlWriter.WriteEndElement();
286 xmlWriter.WriteEndElement();
290 private void WriteProperties(ITestResult result)
292 IPropertyBag properties = result.Test.Properties;
295 foreach (string key in properties.Keys)
297 if (key != PropertyNames.Category)
300 xmlWriter.WriteStartElement("properties");
302 foreach (object prop in properties[key])
304 xmlWriter.WriteStartElement("property");
305 xmlWriter.WriteAttributeString("name", key);
306 xmlWriter.WriteAttributeString("value", prop.ToString());
307 xmlWriter.WriteEndElement();
313 xmlWriter.WriteEndElement();
316 private void WriteReasonElement(string message)
318 xmlWriter.WriteStartElement("reason");
319 xmlWriter.WriteStartElement("message");
321 xmlWriter.WriteEndElement();
322 xmlWriter.WriteEndElement();
325 private void WriteFailureElement(string message, string stackTrace)
327 xmlWriter.WriteStartElement("failure");
328 xmlWriter.WriteStartElement("message");
330 xmlWriter.WriteEndElement();
331 xmlWriter.WriteStartElement("stack-trace");
332 if (stackTrace != null)
333 WriteCData(stackTrace);
334 xmlWriter.WriteEndElement();
335 xmlWriter.WriteEndElement();
338 private void WriteChildResults(ITestResult result)
340 xmlWriter.WriteStartElement("results");
342 foreach (ITestResult childResult in result.Children)
343 WriteResultElement(childResult);
345 xmlWriter.WriteEndElement();
350 #region Output Helpers
352 ///// Makes string safe for xml parsing, replacing control chars with '?'
354 ///// <param name="encodedString">string to make safe</param>
355 ///// <returns>xml safe string</returns>
356 //private static string CharacterSafeString(string encodedString)
358 // /*The default code page for the system will be used.
359 // Since all code pages use the same lower 128 bytes, this should be sufficient
360 // for finding unprintable control characters that make the xslt processor error.
361 // We use characters encoded by the default code page to avoid mistaking bytes as
362 // individual characters on non-latin code pages.*/
363 // char[] encodedChars = System.Text.Encoding.Default.GetChars(System.Text.Encoding.Default.GetBytes(encodedString));
365 // System.Collections.ArrayList pos = new System.Collections.ArrayList();
366 // for (int x = 0; x < encodedChars.Length; x++)
368 // char currentChar = encodedChars[x];
369 // //unprintable characters are below 0x20 in Unicode tables
370 // //some control characters are acceptable. (carriage return 0x0D, line feed 0x0A, horizontal tab 0x09)
371 // if (currentChar < 32 && (currentChar != 9 && currentChar != 10 && currentChar != 13))
373 // //save the array index for later replacement.
377 // foreach (int index in pos)
379 // encodedChars[index] = '?';//replace unprintable control characters with ?(3F)
381 // return System.Text.Encoding.Default.GetString(System.Text.Encoding.Default.GetBytes(encodedChars));
384 private void WriteCData(string text)
389 int illegal = text.IndexOf("]]>", start);
392 xmlWriter.WriteCData(text.Substring(start, illegal - start + 2));
394 if (start >= text.Length)
399 xmlWriter.WriteCData(text.Substring(start));
401 xmlWriter.WriteCData(text);