Skip to content

Commit

Permalink
NUnit result file
Browse files Browse the repository at this point in the history
  • Loading branch information
tobiasfloescher-geberit committed Sep 7, 2021
1 parent 85da067 commit a02714a
Show file tree
Hide file tree
Showing 15 changed files with 522 additions and 24 deletions.
18 changes: 12 additions & 6 deletions documentation/ReleaseNotes.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
# Release Notes
* v1.3.4 [September 2021]
* NUnit result file

* v1.3.3 [September 2021]
* Clients using .net core 3.1
* Shared using .net standard 2.0

* v1.3 [21Q02]
* 'Run all tests from an assembly' command in console application
* Change of parameters in console application
Expand All @@ -13,8 +20,12 @@
* Multi test execution
* Async execution

* v0.9 [19Q04]
* v0.10 [20Q02]
* Revit 2021 Support
* .NET 4.8
* Support of NUnit Attributes 'TestCase'

* v0.9 [19Q04]
* Using NUnit3
* No reference to Revit.TestRunner needed in test assembly
* Support of NUnit attributes SetUp and TearDown
Expand All @@ -23,8 +34,3 @@
* Installation script for libraries
* Post build event in VS project to create addin files

* v0.10 [20Q02]

* Revit 2021 Support
* .NET 4.8
* Support of NUnit Attributes 'TestCase'
6 changes: 3 additions & 3 deletions src/AssemblyInfo/AssemblyInfo.Version.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Reflection;

[assembly: AssemblyVersion( "1.3.3.0" )]
[assembly: AssemblyFileVersion( "1.3.3.0" )]
[assembly: AssemblyVersion( "1.3.3.1" )]
[assembly: AssemblyFileVersion( "1.3.3.1" )]

[assembly: AssemblyInformationalVersion( "1.3.3.0" )]
[assembly: AssemblyInformationalVersion( "1.3.3.1" )]
1 change: 1 addition & 0 deletions src/Revit.TestRunner.Shared/Communication/FileNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public static class FileNames
public static string WatchDirectory => Path.Combine( Environment.GetFolderPath( Environment.SpecialFolder.ApplicationData ), "Revit.TestRunner" );

public const string RunResult = "result.json";
public const string RunResultXml = "result.xml";
public const string RunSummary = "summary.txt";

public const string ExploreResultFileName = "explore.xml";
Expand Down
7 changes: 6 additions & 1 deletion src/Revit.TestRunner.Shared/Dto/TestCaseDto.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Revit.TestRunner.Shared.Communication;
using System;
using Revit.TestRunner.Shared.Communication;

namespace Revit.TestRunner.Shared.Dto
{
Expand All @@ -20,5 +21,9 @@ public class TestCaseDto
public string Message { get; set; }

public string StackTrace { get; set; }

public DateTime StartTime { get; set; }

public DateTime EndTime { get; set; }
}
}
5 changes: 4 additions & 1 deletion src/Revit.TestRunner.Shared/Dto/TestResponseDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public TestResponseDto() : base( DtoType.TestResponseDto )
public string ResultFile { get; set; }

[JsonProperty( Order = 13 )]
public string ResultXmlFile { get; set; }

[JsonProperty( Order = 14 )]
public string SummaryFile { get; set; }
}
}
}
7 changes: 5 additions & 2 deletions src/Revit.TestRunner.Shared/Dto/TestRunStateDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ public class TestRunStateDto
public DateTime StartTime { get; set; }

[JsonProperty( Order = 6 )]
public string Output { get; set; }
public DateTime EndTime { get; set; }

[JsonProperty( Order = 7 )]
public string SummaryFile { get; set; }
public string Output { get; set; }

[JsonProperty( Order = 8 )]
public string SummaryFile { get; set; }

[JsonProperty( Order = 9 )]
public TestState State { get; set; }

[JsonProperty( Order = 10 )]
Expand Down
203 changes: 203 additions & 0 deletions src/Revit.TestRunner.Shared/NUnit/ResultXmlWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using Revit.TestRunner.Shared.Communication;
using Revit.TestRunner.Shared.Dto;

namespace Revit.TestRunner.Shared.NUnit
{
/// <summary>
/// Write a NUnit result xml file.
/// https://docs.nunit.org/articles/nunit/technical-notes/usage/Test-Result-XML-Format.html#test-suite
/// </summary>
public class ResultXmlWriter
{
private readonly FileInfo mFile;
private readonly XmlDocument mDoc;

/// <summary>
/// Constructor
/// </summary>
public ResultXmlWriter( string filePath )
{
if( string.IsNullOrEmpty( filePath ) ) throw new ArgumentNullException();

mFile = new FileInfo( filePath );
mDoc = new XmlDocument();
}

/// <summary>
/// Write the result xml file.
/// </summary>
public void Write( TestRunStateDto runStateDto )
{
CleanFile();

if( runStateDto.Cases == null ) runStateDto.Cases = Array.Empty<TestCaseDto>();

var environment = GetEnvironment();
var testRun = GetTestRun( runStateDto );
var testSuites = GetCompleteTestSuites( runStateDto );

testRun.AppendChild( environment );
foreach( var ts in testSuites ) {
testRun.AppendChild( ts );
}

mDoc.AppendChild( testRun );

using( TextWriter sw = new StreamWriter( mFile.FullName, false, Encoding.UTF8 ) ) {
mDoc.Save( sw );
}
}

/// <summary>
/// Create Directory, delete existing file.
/// </summary>
private void CleanFile()
{
FileHelper.GetDirectory( mFile.Directory.FullName );

if( mFile.Exists ) {
FileHelper.DeleteWithLock( mFile.FullName );
}
}

/// <summary>
/// Get a new environment element.
/// </summary>
private XmlElement GetEnvironment()
{
var result = mDoc.CreateElement( "environment" );
result.SetAttribute( "os-version", Environment.OSVersion.VersionString );
result.SetAttribute( "clr-version", Environment.Version.ToString() );
result.SetAttribute( "platform", Environment.OSVersion.Platform.ToString() );
result.SetAttribute( "cwd", Environment.CurrentDirectory );
result.SetAttribute( "machine-name", Environment.MachineName );
result.SetAttribute( "user", Environment.UserName );
result.SetAttribute( "user-domain", Environment.UserDomainName );
result.SetAttribute( "culture", CultureInfo.CurrentCulture.ToString() );
result.SetAttribute( "uiculture", CultureInfo.CurrentUICulture.ToString() );
result.SetAttribute( "os-architecture", RuntimeInformation.ProcessArchitecture.ToString() );

return result;
}

/// <summary>
/// Get a new test-run element representing the test run.
/// </summary>
private XmlElement GetTestRun( TestRunStateDto runStateDto )
{
var result = mDoc.CreateElement( "test-run" );
result.SetAttribute( "id", runStateDto.Id );
result.SetAttribute( "testcasecount", runStateDto.Cases.Length.ToString() );
result.SetAttribute( "result", runStateDto.State.ToString() );
result.SetAttribute( "total", runStateDto.Cases.Count( c => c.State == TestState.Failed || c.State == TestState.Passed ).ToString() );
result.SetAttribute( "passed", runStateDto.Cases.Count( c => c.State == TestState.Passed ).ToString() );
result.SetAttribute( "failed", runStateDto.Cases.Count( c => c.State == TestState.Failed ).ToString() );
result.SetAttribute( "skipped", runStateDto.Cases.Count( c => c.State == TestState.Ignore ).ToString() );
result.SetAttribute( "clr-version", Environment.Version.ToString() );
result.SetAttribute( "start-time", runStateDto.StartTime.ToString( CultureInfo.InvariantCulture ) );
result.SetAttribute( "end-time", runStateDto.EndTime.ToString( CultureInfo.InvariantCulture ) );
result.SetAttribute( "duration", (runStateDto.EndTime - runStateDto.StartTime).ToString() );

return result;
}

/// <summary>
/// Get new test-suite elements with sub elements representing all test cases.
/// Each root test-suite element representing an assembly.
/// </summary>
private IEnumerable<XmlElement> GetCompleteTestSuites( TestRunStateDto runStateDto )
{
var result = new List<XmlElement>();
var assemblyGroups = runStateDto.Cases.GroupBy( tc => tc.AssemblyPath );

foreach( IGrouping<string, TestCaseDto> assemblyGroup in assemblyGroups ) {
var assemblyTestSuite = mDoc.CreateElement( "test-suite" );
assemblyTestSuite.SetAttribute( "type", "Assembly" );
assemblyTestSuite.SetAttribute( "fullname", new FileInfo( assemblyGroup.Key ).Name );
assemblyTestSuite.SetAttribute( "fullname", assemblyGroup.Key );
assemblyTestSuite.SetAttribute( "total", assemblyGroup.Count().ToString() );
assemblyTestSuite.SetAttribute( "passed", assemblyGroup.Count( c => c.State == TestState.Passed ).ToString() );
assemblyTestSuite.SetAttribute( "failed", assemblyGroup.Count( c => c.State == TestState.Failed ).ToString() );
assemblyTestSuite.SetAttribute( "skipped", assemblyGroup.Count( c => c.State == TestState.Ignore ).ToString() );
assemblyTestSuite.SetAttribute( "time", GetTime( assemblyGroup ).ToString() );
result.Add( assemblyTestSuite );

var classGroups = assemblyGroup.GroupBy( tc => tc.TestClass );

foreach( IGrouping<string, TestCaseDto> classGroup in classGroups ) {
var classTestSuite = mDoc.CreateElement( "test-suite" );
classTestSuite.SetAttribute( "type", "TestFixture" );
classTestSuite.SetAttribute( "name", GetLast( classGroup.Key ) );
classTestSuite.SetAttribute( "fullname", classGroup.Key );
classTestSuite.SetAttribute( "total", classGroup.Count().ToString() );
classTestSuite.SetAttribute( "passed", classGroup.Count( c => c.State == TestState.Passed ).ToString() );
classTestSuite.SetAttribute( "failed", classGroup.Count( c => c.State == TestState.Failed ).ToString() );
classTestSuite.SetAttribute( "skipped", classGroup.Count( c => c.State == TestState.Ignore ).ToString() );
classTestSuite.SetAttribute( "time", GetTime( classGroup ).ToString() );
assemblyTestSuite.AppendChild( classTestSuite );

foreach( TestCaseDto caseDto in classGroup ) {
var testCase = mDoc.CreateElement( "test-case" );
testCase.SetAttribute( "id", caseDto.Id );
testCase.SetAttribute( "name", caseDto.MethodName );
testCase.SetAttribute( "fullname", $"{caseDto.TestClass}.{caseDto.MethodName}" );
testCase.SetAttribute( "result", caseDto.State.ToString() );
testCase.SetAttribute( "time", (caseDto.EndTime - caseDto.StartTime).ToString() );
testCase.SetAttribute( "assets", "0" );

if( caseDto.State == TestState.Failed ) {
var failure = mDoc.CreateElement( "failure" );
testCase.AppendChild( failure );

if( !string.IsNullOrEmpty( caseDto.Message ) ) {
var message = mDoc.CreateElement( "message" );
message.InnerText = caseDto.Message;
failure.AppendChild( message );
}

if( !string.IsNullOrEmpty( caseDto.StackTrace ) ) {
var stackTrace = mDoc.CreateElement( "stack-trace" );
stackTrace.InnerText = caseDto.StackTrace;
failure.AppendChild( stackTrace );
}
}

classTestSuite.AppendChild( testCase );
}

}
}


return result;
}

/// <summary>
/// Get the last segment of the string, split by '.'.
/// </summary>
private string GetLast( string inputString )
{
var split = inputString.Split( '.' );
return split.Last();
}

/// <summary>
/// Get the total time of all <paramref name="caseDtos"/>.
/// </summary>
private TimeSpan GetTime( IEnumerable<TestCaseDto> caseDtos )
{
var result = new TimeSpan();
caseDtos.ToList().ForEach( c => result += (c.EndTime - c.StartTime) );

return result;
}
}
}
4 changes: 2 additions & 2 deletions src/Revit.TestRunner.Shared/Revit.TestRunner.Shared.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
<DebugType>pdbonly</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
<OutputPath>..\bin\</OutputPath>
Expand Down
31 changes: 31 additions & 0 deletions src/Revit.TestRunner.Test/NUnit/ResultXmlFileWriterTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.IO;
using NUnit.Framework;
using Revit.TestRunner.Shared;
using Revit.TestRunner.Shared.Dto;
using Revit.TestRunner.Shared.NUnit;

namespace Revit.TestRunner.Test.NUnit
{
[TestFixture]
public class ResultXmlFileWriterTest
{
[Test]
public void WriteXmlFile()
{
var exampleRunTestStateString = File.ReadAllText( "NUnit\\example.json" );
var exampleRunTestStateDto = JsonHelper.FromString<TestRunStateDto>( exampleRunTestStateString );

var outputFile = new FileInfo( @"C:\temp\test.runner\result.xml" );
FileHelper.DeleteWithLock( outputFile.FullName );
Assert.IsFalse( File.Exists( outputFile.FullName ) );

ResultXmlWriter writer = new ResultXmlWriter( outputFile.FullName );
writer.Write( exampleRunTestStateDto );

Assert.IsTrue( File.Exists( outputFile.FullName ) );
Assert.Greater( outputFile.Length, 1000 );
Assert.Greater( DateTime.Now, outputFile.LastWriteTime );
}
}
}
Loading

0 comments on commit a02714a

Please sign in to comment.