Permalink
Browse files

Merge pull request #33 from petejohanson/nunit2_output_listener

Initial work on NUnit2 XML output listener.
  • Loading branch information...
2 parents ac55f5c + 024a2c6 commit 08c430fa38bbf811963932553b1f598dd29ec8ef @plioi plioi committed Jan 15, 2014
View
7 src/Fixie.Tests/Fixie.Tests.csproj
@@ -37,6 +37,8 @@
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
+ <Reference Include="System.XML" />
+ <Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\CommonAssemblyInfo.cs">
@@ -52,6 +54,7 @@
<Compile Include="Lifecycle\ComplexLifecycleTests.cs" />
<Compile Include="Lifecycle\ConstructionTests.cs" />
<Compile Include="Lifecycle\InstanceLifecycleTests.cs" />
+ <Compile Include="Listeners\NUnit2XmlOutputListenerTests.cs" />
<Compile Include="Listeners\CompoundListenerTests.cs" />
<Compile Include="TestMethods\AsyncMethodTests.cs" />
<Compile Include="Listeners\ConsoleListenerTests.cs" />
@@ -79,6 +82,10 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
+ <Content Include="Listeners\NUnit2Results.xsd">
+ <SubType>Designer</SubType>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
View
70 src/Fixie.Tests/Listeners/NUnit2Results.xsd
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
+ <xs:complexType name="failureType">
+ <xs:sequence>
+ <xs:element ref="message" />
+ <xs:element ref="stack-trace" />
+ </xs:sequence>
+ </xs:complexType>
+ <xs:complexType name="reasonType">
+ <xs:sequence>
+ <xs:element ref="message" />
+ </xs:sequence>
+ </xs:complexType>
+ <xs:element name="message" type="xs:string" />
+ <xs:complexType name="resultsType">
+ <xs:choice>
+ <xs:element name="test-suite" type="test-suiteType" maxOccurs="unbounded" />
+ <xs:element name="test-case" type="test-caseType" maxOccurs="unbounded" minOccurs="0" />
+ </xs:choice>
+ </xs:complexType>
+ <xs:element name="stack-trace" type="xs:string" />
+ <xs:element name="test-results" type="resultType" />
+ <xs:complexType name="categoriesType">
+ <xs:sequence>
+ <xs:element name="category" type="categoryType" maxOccurs="unbounded" minOccurs="1"/>
+ </xs:sequence>
+ </xs:complexType>
+ <xs:complexType name="categoryType">
+ <xs:attribute name="name" type="xs:string" use="required"/>
+ </xs:complexType>
+ <xs:complexType name="resultType">
+ <xs:sequence>
+ <xs:element name="test-suite" type="test-suiteType" />
+ </xs:sequence>
+ <xs:attribute name="name" type="xs:string" use="required" />
+ <xs:attribute name="total" type="xs:decimal" use="required" />
+ <xs:attribute name="failures" type="xs:decimal" use="required" />
+ <xs:attribute name="not-run" type="xs:decimal" use="required" />
+ <xs:attribute name="date" type="xs:string" use="required" />
+ <xs:attribute name="time" type="xs:string" use="required" />
+ </xs:complexType>
+ <xs:complexType name="test-caseType">
+ <xs:sequence>
+ <xs:element name="categories" type="categoriesType" minOccurs="0" maxOccurs="1" />
+ <xs:choice>
+ <xs:element name="failure" type="failureType" minOccurs="0" />
+ <xs:element name="reason" type="reasonType" minOccurs="0" />
+ </xs:choice>
+ </xs:sequence>
+
+ <xs:attribute name="name" type="xs:string" use="required" />
+ <xs:attribute name="description" type="xs:string" use="optional" />
+ <xs:attribute name="success" type="xs:string" use="optional" />
+ <xs:attribute name="time" type="xs:string" use="optional" />
+ <xs:attribute name="executed" type="xs:string" use="required" />
+ <xs:attribute name="asserts" type="xs:string" use="optional" />
+ </xs:complexType>
+ <xs:complexType name="test-suiteType">
+ <xs:sequence>
+ <xs:element name="categories" type="categoriesType" minOccurs="0" maxOccurs="1" />
+ <xs:element name="results" type="resultsType" />
+ </xs:sequence>
+ <xs:attribute name="name" type="xs:string" use="required" />
+ <xs:attribute name="description" type="xs:string" use="optional" />
+ <xs:attribute name="success" type="xs:string" use="required" />
+ <xs:attribute name="time" type="xs:string" use="required" />
+ <xs:attribute name="asserts" type="xs:string" use="optional" />
+ </xs:complexType>
+
+</xs:schema>
View
99 src/Fixie.Tests/Listeners/NUnit2XmlOutputListenerTests.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+using System.Xml.Schema;
+using System.Xml.XPath;
+using Fixie.Conventions;
+using Fixie.Listeners;
+using Should;
+
+namespace Fixie.Tests.Listeners
+{
+ public class NUnit2XmlOutputListenerTests
+ {
+ public void ShouldProduceAValidXmlOutput ()
+ {
+ var stream = new MemoryStream ();
+ var listener = new NUnit2XmlOutputListener (new StreamWriter (stream));
+ var runner = new Runner (listener);
+
+ runner.RunType (GetType ().Assembly, new SelfTestConvention (), typeof(PassFailTestClass));
+ stream.Position = 0;
+
+ var doc = XDocument.Load (stream);
+// Console.WriteLine(doc);
+
+ var schemaSet = new XmlSchemaSet ();
+ schemaSet.Add (null, XmlReader.Create (Path.Combine ("Listeners", "NUnit2Results.xsd")));
+ doc.Validate (schemaSet, null);
+
+ var root = doc.Root;
+ root.Attribute ("failures").Value.ShouldEqual ("2");
+ root.Attribute ("total").Value.ShouldEqual ("6");
+ root.Attribute ("not-run").Value.ShouldEqual ("1");
+
+ var testSuite = root.Elements ().FirstOrDefault ();
+ testSuite.ShouldNotBeNull ();
+ testSuite.Attribute ("name").Value.ShouldContain ("Fixie.Tests.dll");
+ testSuite.Attribute ("success").Value.ShouldEqual ("False");
+
+ var actualResults = testSuite.XPathSelectElement ("results/test-suite/results");
+
+ // Times vary by test run, so clear them out before comparing.
+ foreach (var c in actualResults.XPathSelectElements ("test-case[@time != '']"))
+ c.SetAttributeValue("time", String.Empty);
+
+ // We can't easily test the stack trace or failure message, so clear them here.
+ foreach (var msg in actualResults.XPathSelectElements ("test-case/failure/message | test-case/failure/stack-trace")) {
+ msg.RemoveAll ();
+ }
+
+// Console.WriteLine(doc);
+ XElement.DeepEquals (actualResults,
+ new XElement ("results",
+ new XElement ("test-case", new XAttribute ("name", "Fixie.Tests.Listeners.NUnit2XmlOutputListenerTests+PassFailTestClass.SkipA"),
+ new XAttribute ("executed", "False"), new XAttribute ("success", "True")),
+ new XElement ("test-case", new XAttribute ("name", "Fixie.Tests.Listeners.NUnit2XmlOutputListenerTests+PassFailTestClass.FailA"),
+ new XAttribute ("executed", "True"), new XAttribute ("success", "False"), new XAttribute("time",""),
+ new XElement("failure", new XElement("message"), new XElement("stack-trace"))),
+ new XElement ("test-case", new XAttribute ("name", "Fixie.Tests.Listeners.NUnit2XmlOutputListenerTests+PassFailTestClass.FailB"),
+ new XAttribute ("executed", "True"), new XAttribute ("success", "False"), new XAttribute ("time", ""),
+ new XElement ("failure", new XElement ("message"), new XElement ("stack-trace"))),
+ new XElement ("test-case", new XAttribute ("name", "Fixie.Tests.Listeners.NUnit2XmlOutputListenerTests+PassFailTestClass.PassA"),
+ new XAttribute ("executed", "True"), new XAttribute ("success", "True"), new XAttribute ("time", "")),
+ new XElement ("test-case", new XAttribute ("name", "Fixie.Tests.Listeners.NUnit2XmlOutputListenerTests+PassFailTestClass.PassB"),
+ new XAttribute ("executed", "True"), new XAttribute ("success", "True"), new XAttribute ("time", "")),
+ new XElement ("test-case", new XAttribute ("name", "Fixie.Tests.Listeners.NUnit2XmlOutputListenerTests+PassFailTestClass.PassC"),
+ new XAttribute ("executed", "True"), new XAttribute ("success", "True"), new XAttribute ("time", ""))
+ )).ShouldBeTrue ();
+ }
+
+ class PassFailTestClass
+ {
+ public void FailA ()
+ {
+ throw new FailureException ();
+ }
+
+ public void PassA () { }
+
+ public void FailB ()
+ {
+ throw new FailureException ();
+ }
+
+ public void PassB () { }
+
+ public void PassC () { }
+
+ public void SkipA () { throw new ShouldBeUnreachableException (); }
+ }
+ }
+}
View
2 src/Fixie/ConsoleRunner.cs
@@ -51,4 +51,4 @@ static Listener CreateDefaultListener()
return new ConsoleListener();
}
}
-}
+}
View
3 src/Fixie/Fixie.csproj
@@ -32,6 +32,8 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
+ <Reference Include="System.XML" />
+ <Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\CommonAssemblyInfo.cs">
@@ -46,6 +48,7 @@
<Compile Include="Listeners\CompoundListener.cs" />
<Compile Include="FailResultExtensions.cs" />
<Compile Include="GenericArgumentResolver.cs" />
+ <Compile Include="Listeners\NUnit2XmlOutputListener.cs" />
<Compile Include="RedirectedConsole.cs" />
<Compile Include="Behaviors\TypeBehavior.cs" />
<Compile Include="Case.cs" />
View
118 src/Fixie/Listeners/NUnit2XmlOutputListener.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Xml;
+using System.Xml.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Fixie.Listeners
+{
+ public class NUnit2XmlOutputListener : Listener
+ {
+ readonly TextWriter writer;
+ readonly XElement testResultsElement;
+ DateTime startTime;
+ readonly Dictionary<Type, XElement> testSuiteDictionary = new Dictionary<Type, XElement> ();
+ readonly Dictionary<Type, TimeSpan> testSuiteDurations = new Dictionary<Type, TimeSpan> ();
+
+ public NUnit2XmlOutputListener (TextWriter writer)
+ {
+ this.writer = writer;
+ testResultsElement =
+ new XElement ("test-results",
+ new XElement("test-suite", new XAttribute("success", "True"), new XElement("results")));
+ }
+
+ public NUnit2XmlOutputListener () : this(new StreamWriter("TestResults.xml"))
+ {
+ }
+
+ public void AssemblyStarted (Assembly assembly)
+ {
+ startTime = DateTime.UtcNow;
+ testResultsElement.SetAttributeValue ("date", startTime.ToString("yyyy-MM-dd"));
+ testResultsElement.SetAttributeValue ("time", startTime.ToString ("HH:mm:ss"));
+ testResultsElement.SetAttributeValue ("name", assembly.Location);
+ testResultsElement.Element("test-suite").SetAttributeValue("name", assembly.Location);
+ }
+
+ public void CaseSkipped (Case @case)
+ {
+ GetClassTestSuite (@case.Class)
+ .Element ("results")
+ .Add (new XElement ("test-case", new XAttribute ("name", @case.Name), new XAttribute ("executed", "False"), new XAttribute ("success", "True")));
+ }
+
+ public void CasePassed (PassResult result)
+ {
+ GetClassTestSuite (result.Case.Class)
+ .Element ("results")
+ .Add (new XElement ("test-case", new XAttribute ("name", result.Case.Name),
+ new XAttribute ("executed", "True"),
+ new XAttribute ("success", "True"),
+ new XAttribute ("time", result.Duration.TotalSeconds.ToString ("0.000"))));
+ }
+
+ public void CaseFailed (FailResult result)
+ {
+ testSuiteDurations[result.Case.Class] = GetClassDuration (result.Case.Class) + result.Duration;
+ var suite = GetClassTestSuite (result.Case.Class);
+ suite.SetAttributeValue("success", "False");
+ testResultsElement.Element ("test-suite").SetAttributeValue ("success", "False");
+ suite
+ .Element ("results")
+ .Add (new XElement ("test-case",
+ new XAttribute ("name", result.Case.Name),
+ new XElement("failure", new XElement("message", new XCData (result.PrimaryExceptionMessage ())), new XElement("stack-trace", new XCData (result.CompoundStackTrace ()))),
+ new XAttribute ("executed", "True"),
+ new XAttribute ("success", "False"),
+ new XAttribute ("time", result.Duration.TotalSeconds.ToString("0.000"))));
+ }
+
+ public void AssemblyCompleted (Assembly assembly, AssemblyResult result)
+ {
+ testResultsElement.SetAttributeValue ("total", result.Total);
+ testResultsElement.SetAttributeValue ("failures", result.Failed);
+ testResultsElement.SetAttributeValue ("not-run", result.Skipped);
+
+ TimeSpan totalTimeSpan = TimeSpan.Zero;
+ foreach (var entry in testSuiteDurations) {
+ var suite = GetClassTestSuite (entry.Key);
+ suite.SetAttributeValue ("time", entry.Value.TotalSeconds.ToString ("0.000"));
+ totalTimeSpan += entry.Value;
+ }
+
+ testResultsElement.Element ("test-suite").SetAttributeValue ("time", totalTimeSpan.TotalSeconds.ToString ("0.000"));
+ using (var xmlWriter = XmlWriter.Create (writer, new XmlWriterSettings () { CloseOutput = false, })) {
+ testResultsElement.WriteTo (xmlWriter);
+ xmlWriter.Flush ();
+ }
+
+ writer.Flush ();
+ }
+
+ XElement GetClassTestSuite (Type @class)
+ {
+ XElement ret;
+ if (!testSuiteDictionary.TryGetValue (@class, out ret)) {
+ ret = new XElement("test-suite", new XElement("results"), new XAttribute("name", @class.FullName), new XAttribute("success", "True"), new XAttribute("time", ""));
+ testResultsElement.Element("test-suite").Element("results").Add(ret);
+ testSuiteDictionary[@class] = ret;
+ }
+
+ return ret;
+ }
+
+ TimeSpan GetClassDuration (Type @class)
+ {
+ TimeSpan duration;
+ if (testSuiteDurations.TryGetValue (@class, out duration))
+ return duration;
+
+ return TimeSpan.Zero;
+ }
+ }
+}
View
7 src/Fixie/Runner.cs
@@ -49,6 +49,13 @@ public AssemblyResult RunType(Assembly assembly, Type type)
return RunTypes(runContext, type);
}
+ public AssemblyResult RunType (Assembly assembly, Convention convention, Type type)
+ {
+ var runContext = new RunContext (assembly, options);
+
+ return RunTypes (runContext, convention, type);
+ }
+
public AssemblyResult RunMethod(Assembly assembly, MethodInfo method)
{
var runContext = new RunContext(assembly, options, method);

0 comments on commit 08c430f

Please sign in to comment.