Skip to content

Commit

Permalink
Add JUnitXmlWriter
Browse files Browse the repository at this point in the history
  • Loading branch information
nowsprinting committed May 6, 2024
1 parent 35ec1c6 commit 7018845
Show file tree
Hide file tree
Showing 17 changed files with 317 additions and 0 deletions.
41 changes: 41 additions & 0 deletions Editor/JUnitXmlWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) 2023-2024 Koji Hasegawa.
// This software is released under the MIT License.

using System.IO;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;
using UnityEditor.TestTools.TestRunner.Api;

namespace TestHelper.Editor
{
public static class JUnitXmlWriter
{
private const string XsltPath = "Packages/com.nowsprinting.test-helper/Editor/nunit3-junit/nunit3-junit.xslt";
// Note: This XSLT file is copied from https://github.com/nunit/nunit-transforms/tree/master/nunit3-junit

public static void WriteTo(ITestResultAdaptor result, string path)
{
// Input
var nunit3XmlStream = new MemoryStream();
var nunit3Writer = XmlWriter.Create(nunit3XmlStream);
result.ToXml().WriteTo(nunit3Writer);
var nunit3Xml = new XPathDocument(nunit3XmlStream);

// Create output directory if it does not exist.
var directory = Path.GetDirectoryName(path);
if (directory != null && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}

// Output (JUnit XML)
var writer = XmlWriter.Create(path);

// Execute the transformation.
var transformer = new XslCompiledTransform();
transformer.Load(XsltPath);
transformer.Transform(nunit3Xml, writer);
}
}
}
2 changes: 2 additions & 0 deletions Editor/JUnitXmlWriter.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 41 additions & 0 deletions Editor/TestRunnerCallbacksImpl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) 2023-2024 Koji Hasegawa.
// This software is released under the MIT License.

using TestHelper.RuntimeInternals;
using UnityEditor;
using UnityEditor.TestTools.TestRunner.Api;
using UnityEngine;

namespace TestHelper.Editor
{
public class TestRunnerCallbacksImpl : ICallbacks
{
[InitializeOnLoadMethod]
private static void SetupCallbacks()
{
var api = ScriptableObject.CreateInstance<TestRunnerApi>();
api.RegisterCallbacks(new TestRunnerCallbacksImpl());
}

public void RunStarted(ITestAdaptor testsToRun)
{
}

public void RunFinished(ITestResultAdaptor result)
{
var path = CommandLineArgs.GetJUnitResultsPath();
if (path != null)
{
JUnitXmlWriter.WriteTo(result, path);
}
}

public void TestStarted(ITestAdaptor test)
{
}

public void TestFinished(ITestResultAdaptor result)
{
}
}
}
3 changes: 3 additions & 0 deletions Editor/TestRunnerCallbacksImpl.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ $(TEST_FILTER) \
$(ASSEMBLY_NAMES) \
-testPlatform $(TEST_PLATFORM) \
-testResults $(LOG_DIR)/test_$(TEST_PLATFORM)_results.xml \
-testHelperJUnitResults $(LOG_DIR)/test_$(TEST_PLATFORM)_junit_results.xml \
-testHelperScreenshotDirectory $(LOG_DIR)/Screenshots
endef

Expand Down
19 changes: 19 additions & 0 deletions RuntimeInternals/CommandLineArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,24 @@ public static string GetScreenshotDirectory(string[] args = null)
return Path.Combine(Application.persistentDataPath, "TestHelper", "Screenshots");
}
}

/// <summary>
/// JUnit XML report save path.
/// </summary>
/// <returns></returns>
public static string GetJUnitResultsPath(string[] args = null)
{
const string JUnitResultsKey = "-testHelperJUnitResults";

try
{
args = args ?? Environment.GetCommandLineArgs();
return DictionaryFromCommandLineArgs(args)[JUnitResultsKey];
}
catch (KeyNotFoundException)
{
return null;
}
}
}
}
46 changes: 46 additions & 0 deletions Tests/Editor/JUnitXmlWriterTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) 2023-2024 Koji Hasegawa.
// This software is released under the MIT License.

using System.IO;
using NUnit.Framework;
using TestHelper.Editor.TestDoubles;

namespace TestHelper.Editor
{
[TestFixture]
public class JUnitXmlWriterTest
{
private const string TestResourcesPath = "Packages/com.nowsprinting.test-helper/Tests/Editor/TestResources";
private const string TestOutputDirectoryPath = "Logs/JUnitXmlWriterTest";
// Note: relative path from the project root directory.

[Test, Order(0)]
public void WriteTo_DirectoryDoesNotExist_CreateDirectoryAndWriteToFile()
{
if (Directory.Exists(TestOutputDirectoryPath))
{
Directory.Delete(TestOutputDirectoryPath, true);
}

var nunitXmlPath = Path.Combine(TestResourcesPath, "nunit3.xml");
var result = new FakeTestResultAdaptor(nunitXmlPath);
var path = Path.Combine(TestOutputDirectoryPath, TestContext.CurrentContext.Test.Name + ".xml");
JUnitXmlWriter.WriteTo(result, path);

Assert.That(path, Does.Exist);
}

[Test]
public void WriteTo_WriteToFile()
{
var nunitXmlPath = Path.Combine(TestResourcesPath, "nunit3.xml");
var result = new FakeTestResultAdaptor(nunitXmlPath);
var path = Path.Combine(TestOutputDirectoryPath, TestContext.CurrentContext.Test.Name + ".xml");
JUnitXmlWriter.WriteTo(result, path);

var actual = File.ReadAllText(path);
var expected = File.ReadAllText(Path.Combine(TestResourcesPath, "junit.xml"));
Assert.That(actual, Is.EqualTo(expected));
}
}
}
2 changes: 2 additions & 0 deletions Tests/Editor/JUnitXmlWriterTest.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Tests/Editor/TestDoubles.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 54 additions & 0 deletions Tests/Editor/TestDoubles/FakeTestResultAdaptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) 2023-2024 Koji Hasegawa.
// This software is released under the MIT License.

using System;
using System.Collections.Generic;
using System.IO;
using NUnit.Framework.Interfaces;
using UnityEditor.TestTools.TestRunner.Api;
using TestStatus = UnityEditor.TestTools.TestRunner.Api.TestStatus;

namespace TestHelper.Editor.TestDoubles
{
/// <summary>
/// Implemented only <c>ToXml</c> method.
/// </summary>
public class FakeTestResultAdaptor : ITestResultAdaptor
{
private readonly string _path;

/// <summary>
/// Constructor.
/// </summary>
/// <param name="path">NUnit3 XML file path used in <c>ToXml</c> method.</param>
public FakeTestResultAdaptor(string path)
{
_path = path;
}

public TNode ToXml()
{
var xmlText = File.ReadAllText(_path);
return TNode.FromXml(xmlText);
}

public ITestAdaptor Test { get { throw new NotImplementedException(); } }
public string Name { get { throw new NotImplementedException(); } }
public string FullName { get { throw new NotImplementedException(); } }
public string ResultState { get { throw new NotImplementedException(); } }
public TestStatus TestStatus { get { throw new NotImplementedException(); } }
public double Duration { get { throw new NotImplementedException(); } }
public DateTime StartTime { get { throw new NotImplementedException(); } }
public DateTime EndTime { get { throw new NotImplementedException(); } }
public string Message { get { throw new NotImplementedException(); } }
public string StackTrace { get { throw new NotImplementedException(); } }
public int AssertCount { get { throw new NotImplementedException(); } }
public int FailCount { get { throw new NotImplementedException(); } }
public int PassCount { get { throw new NotImplementedException(); } }
public int SkipCount { get { throw new NotImplementedException(); } }
public int InconclusiveCount { get { throw new NotImplementedException(); } }
public bool HasChildren { get { throw new NotImplementedException(); } }
public IEnumerable<ITestResultAdaptor> Children { get { throw new NotImplementedException(); } }
public string Output { get { throw new NotImplementedException(); } }
}
}
3 changes: 3 additions & 0 deletions Tests/Editor/TestDoubles/FakeTestResultAdaptor.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Tests/Editor/TestResources.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions Tests/Editor/TestResources/junit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?><testsuites tests="3" failures="0" disabled="0" time="0.2538825"><testsuite tests="1" time="0.092622" errors="0" failures="0" skipped="0" timestamp="2024-05-03 10:09:28Z" name="UnityProject~.TestHelper.Editor."><testcase name="Attach_CreateNewSceneWithCameraAndLight" assertions="0" time="0.036802" status="Passed" classname="TestHelper.Editor.CreateSceneAttributeTest"><system-out>[Code Coverage] Code Coverage results for visited sequence points were saved in /github/workspace/CodeCoverage/UnityProject~-opencov/EditMode/TestCoverageResults_0001.xml
</system-out></testcase></testsuite><testsuite tests="1" time="0.089184" errors="0" failures="0" skipped="0" timestamp="2024-05-03 10:09:28Z" name="UnityProject~.TestHelper.Editor."><testcase name="Attach_LoadedSceneNotInBuild" assertions="0" time="0.053176" status="Passed" classname="TestHelper.Editor.LoadSceneAttributeTest"><system-out>[Code Coverage] Code Coverage results for visited sequence points were saved in /github/workspace/CodeCoverage/UnityProject~-opencov/EditMode/TestCoverageResults_0002.xml
</system-out></testcase></testsuite><testsuite tests="1" time="0.055223" errors="0" failures="0" skipped="0" timestamp="2024-05-03 10:09:28Z" name="UnityProject~.TestHelper.Editor."><testcase name="GetScenesUsingInTest_AttachedToMethod_ReturnScenesSpecifiedByAttribute" assertions="0" time="0.014506" status="Passed" classname="TestHelper.Editor.TemporaryBuildScenesUsingInTestTest"><system-out>[Code Coverage] Code Coverage results for visited sequence points were saved in /github/workspace/CodeCoverage/UnityProject~-opencov/EditMode/TestCoverageResults_0003.xml
[Code Coverage] Code Coverage Report was generated in /github/workspace/CodeCoverage/Report
Included Assemblies: TestHelper,TestHelper.RuntimeInternals,TestHelper.Editor,
Excluded Assemblies: *.Tests
Included Paths: &lt;Not specified&gt;
Excluded Paths: &lt;Not specified&gt;
Saving results to: /github/workspace/artifacts/editmode-results.xml
</system-out></testcase></testsuite></testsuites>
7 changes: 7 additions & 0 deletions Tests/Editor/TestResources/junit.xml.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

61 changes: 61 additions & 0 deletions Tests/Editor/TestResources/nunit3.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<test-run id="2" testcasecount="3" result="Passed" total="3" passed="3" failed="0" inconclusive="0" skipped="0" asserts="0" engine-version="3.5.0.0" clr-version="4.0.30319.42000" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.2538825">
<test-suite type="TestSuite" id="1000" name="UnityProject~" fullname="UnityProject~" runstate="Runnable" testcasecount="3" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.253883" total="3" passed="3" failed="0" inconclusive="0" skipped="0" asserts="0">
<properties>
<property name="platform" value="EditMode" />
</properties>
<test-suite type="Assembly" id="1009" name="TestHelper.Editor.Tests.dll" fullname="/github/workspace/UnityProject~/Library/ScriptAssemblies/TestHelper.Editor.Tests.dll" runstate="Runnable" testcasecount="3" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.246003" total="3" passed="3" failed="0" inconclusive="0" skipped="0" asserts="0">
<properties>
<property name="_PID" value="3301" />
<property name="_APPDOMAIN" value="Unity Child Domain" />
<property name="platform" value="EditMode" />
<property name="EditorOnly" value="True" />
</properties>
<test-suite type="TestSuite" id="1010" name="TestHelper" fullname="TestHelper" runstate="Runnable" testcasecount="3" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.245391" total="3" passed="3" failed="0" inconclusive="0" skipped="0" asserts="0">
<properties />
<test-suite type="TestSuite" id="1011" name="Editor" fullname="TestHelper.Editor" runstate="Runnable" testcasecount="3" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.241916" total="3" passed="3" failed="0" inconclusive="0" skipped="0" asserts="0">
<properties />
<test-suite type="TestFixture" id="1003" name="CreateSceneAttributeTest" fullname="TestHelper.Editor.CreateSceneAttributeTest" classname="TestHelper.Editor.CreateSceneAttributeTest" runstate="Runnable" testcasecount="1" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.092622" total="1" passed="1" failed="0" inconclusive="0" skipped="0" asserts="0">
<properties />
<test-case id="1004" name="Attach_CreateNewSceneWithCameraAndLight" fullname="TestHelper.Editor.CreateSceneAttributeTest.Attach_CreateNewSceneWithCameraAndLight" methodname="Attach_CreateNewSceneWithCameraAndLight" classname="TestHelper.Editor.CreateSceneAttributeTest" runstate="Runnable" seed="1087926775" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.036802" asserts="0">
<properties>
<property name="retryIteration" value="0" />
<property name="repeatIteration" value="0" />
</properties>
<output><![CDATA[[Code Coverage] Code Coverage results for visited sequence points were saved in /github/workspace/CodeCoverage/UnityProject~-opencov/EditMode/TestCoverageResults_0001.xml
]]></output>
</test-case>
</test-suite>
<test-suite type="TestFixture" id="1005" name="LoadSceneAttributeTest" fullname="TestHelper.Editor.LoadSceneAttributeTest" classname="TestHelper.Editor.LoadSceneAttributeTest" runstate="Runnable" testcasecount="1" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.089184" total="1" passed="1" failed="0" inconclusive="0" skipped="0" asserts="0">
<properties />
<test-case id="1006" name="Attach_LoadedSceneNotInBuild" fullname="TestHelper.Editor.LoadSceneAttributeTest.Attach_LoadedSceneNotInBuild" methodname="Attach_LoadedSceneNotInBuild" classname="TestHelper.Editor.LoadSceneAttributeTest" runstate="Runnable" seed="818183097" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.053176" asserts="0">
<properties>
<property name="retryIteration" value="0" />
<property name="repeatIteration" value="0" />
</properties>
<output><![CDATA[[Code Coverage] Code Coverage results for visited sequence points were saved in /github/workspace/CodeCoverage/UnityProject~-opencov/EditMode/TestCoverageResults_0002.xml
]]></output>
</test-case>
</test-suite>
<test-suite type="TestFixture" id="1007" name="TemporaryBuildScenesUsingInTestTest" fullname="TestHelper.Editor.TemporaryBuildScenesUsingInTestTest" classname="TestHelper.Editor.TemporaryBuildScenesUsingInTestTest" runstate="Runnable" testcasecount="1" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.055223" total="1" passed="1" failed="0" inconclusive="0" skipped="0" asserts="0">
<properties />
<test-case id="1008" name="GetScenesUsingInTest_AttachedToMethod_ReturnScenesSpecifiedByAttribute" fullname="TestHelper.Editor.TemporaryBuildScenesUsingInTestTest.GetScenesUsingInTest_AttachedToMethod_ReturnScenesSpecifiedByAttribute" methodname="GetScenesUsingInTest_AttachedToMethod_ReturnScenesSpecifiedByAttribute" classname="TestHelper.Editor.TemporaryBuildScenesUsingInTestTest" runstate="Runnable" seed="1243330322" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.014506" asserts="0">
<properties>
<property name="retryIteration" value="0" />
<property name="repeatIteration" value="0" />
</properties>
<output><![CDATA[[Code Coverage] Code Coverage results for visited sequence points were saved in /github/workspace/CodeCoverage/UnityProject~-opencov/EditMode/TestCoverageResults_0003.xml
[Code Coverage] Code Coverage Report was generated in /github/workspace/CodeCoverage/Report
Included Assemblies: TestHelper,TestHelper.RuntimeInternals,TestHelper.Editor,
Excluded Assemblies: *.Tests
Included Paths: <Not specified>
Excluded Paths: <Not specified>
Saving results to: /github/workspace/artifacts/editmode-results.xml
]]></output>
</test-case>
</test-suite>
</test-suite>
</test-suite>
</test-suite>
</test-suite>
</test-run>
7 changes: 7 additions & 0 deletions Tests/Editor/TestResources/nunit3.xml.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions Tests/RuntimeInternals/CommandLineArgsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,19 @@ public void GetScreenshotDirectory_WithoutArgument_GotDefaultDirectory()
var actual = CommandLineArgs.GetScreenshotDirectory(Array.Empty<string>());
Assert.That(actual, Is.EqualTo(Path.Combine(Application.persistentDataPath, "TestHelper", "Screenshots")));
}

[Test]
public void GetJUnitResultsPath_WithArgument_GotSpecifiedPath()
{
var actual = CommandLineArgs.GetJUnitResultsPath(new[] { "-testHelperJUnitResults", "Test" });
Assert.That(actual, Is.EqualTo("Test"));
}

[Test]
public void GetJUnitResultsPath_WithoutArgument_ReturnsNull()
{
var actual = CommandLineArgs.GetJUnitResultsPath(Array.Empty<string>());
Assert.That(actual, Is.Null);
}
}
}

0 comments on commit 7018845

Please sign in to comment.