-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #69 from jonesbusy/feature/nunit-parser
Add support for NUnit format
- Loading branch information
Showing
10 changed files
with
570 additions
and
0 deletions.
There are no files selected for viewing
165 changes: 165 additions & 0 deletions
165
src/main/java/edu/hm/hafner/coverage/parser/NunitParser.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
package edu.hm.hafner.coverage.parser; | ||
|
||
import java.io.Reader; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.UUID; | ||
|
||
import javax.xml.namespace.QName; | ||
import javax.xml.stream.XMLEventReader; | ||
import javax.xml.stream.XMLStreamException; | ||
import javax.xml.stream.events.StartElement; | ||
import javax.xml.stream.events.XMLEvent; | ||
|
||
import edu.hm.hafner.coverage.CoverageParser; | ||
import edu.hm.hafner.coverage.ModuleNode; | ||
import edu.hm.hafner.coverage.TestCase; | ||
import edu.hm.hafner.coverage.TestCase.TestCaseBuilder; | ||
import edu.hm.hafner.util.FilteredLog; | ||
import edu.hm.hafner.util.SecureXmlParserFactory; | ||
import edu.hm.hafner.util.SecureXmlParserFactory.ParsingException; | ||
|
||
/** | ||
* Parses reports in the <a href="https://docs.nunit.org/articles/nunit/technical-notes/usage/Test-Result-XML-Format.html">NUnit format</a> into a Java object model. | ||
* | ||
* @author Valentin Delaye | ||
*/ | ||
@SuppressWarnings("checkstyle:ClassDataAbstractionCoupling") | ||
public class NunitParser extends CoverageParser { | ||
private static final long serialVersionUID = -5468593789018138107L; | ||
|
||
private static final QName TEST_SUITE = new QName("test-suite"); | ||
private static final QName TEST_CASE = new QName("test-case"); | ||
private static final QName NAME = new QName("name"); | ||
private static final QName CLASS_NAME = new QName("classname"); | ||
private static final QName FAILURE = new QName("failure"); | ||
private static final QName MESSAGE = new QName("message"); | ||
private static final QName RESULT = new QName("result"); | ||
private static final String PASSED = "Passed"; | ||
private static final String FAILED = "Failed"; | ||
private static final String SKIPPED = "Skipped"; | ||
|
||
/** | ||
* Creates a new instance of {@link NunitParser}. | ||
*/ | ||
public NunitParser() { | ||
this(ProcessingMode.FAIL_FAST); | ||
} | ||
|
||
/** | ||
* Creates a new instance of {@link NunitParser}. | ||
* | ||
* @param processingMode | ||
* determines whether to ignore errors | ||
*/ | ||
public NunitParser(final ProcessingMode processingMode) { | ||
super(processingMode); | ||
} | ||
|
||
@Override | ||
protected ModuleNode parseReport(final Reader reader, final FilteredLog log) { | ||
try { | ||
var factory = new SecureXmlParserFactory(); | ||
var eventReader = factory.createXmlEventReader(reader); | ||
|
||
var root = new ModuleNode(EMPTY); | ||
var tests = readTestCases(eventReader, root); | ||
handleEmptyResults(log, tests.isEmpty()); | ||
return root; | ||
} | ||
catch (XMLStreamException exception) { | ||
throw new ParsingException(exception); | ||
} | ||
} | ||
|
||
private List<Object> readTestCases(final XMLEventReader eventReader, | ||
final ModuleNode root) throws XMLStreamException { | ||
String suiteName = EMPTY; | ||
var tests = new ArrayList<>(); | ||
while (eventReader.hasNext()) { | ||
XMLEvent event = eventReader.nextEvent(); | ||
|
||
if (event.isStartElement() && TEST_SUITE.equals(event.asStartElement().getName())) { | ||
suiteName = getOptionalValueOf(event.asStartElement(), NAME).orElse(EMPTY); | ||
} | ||
else if (event.isStartElement() && TEST_CASE.equals(event.asStartElement().getName())) { | ||
tests.add(readTestCase(eventReader, event.asStartElement(), suiteName, root)); | ||
} | ||
} | ||
return tests; | ||
} | ||
|
||
private TestCase readTestCase(final XMLEventReader reader, final StartElement testCaseElement, | ||
final String suiteName, final ModuleNode root) | ||
throws XMLStreamException { | ||
var builder = new TestCaseBuilder(); | ||
|
||
builder.withTestName(getOptionalValueOf(testCaseElement, NAME).orElse(createId())); | ||
|
||
var status = getValueOf(testCaseElement, RESULT); | ||
switch (status) { | ||
case PASSED: | ||
builder.withStatus(TestCase.TestResult.PASSED); | ||
break; | ||
case FAILED: | ||
builder.withStatus(TestCase.TestResult.FAILED); | ||
break; | ||
case SKIPPED: | ||
default: | ||
builder.withStatus(TestCase.TestResult.SKIPPED); | ||
break; | ||
} | ||
|
||
while (reader.hasNext()) { | ||
XMLEvent event = reader.nextEvent(); | ||
|
||
if (event.isStartElement() && isFailure(event)) { | ||
readFailure(reader, builder); | ||
} | ||
else if (event.isEndElement() && TEST_CASE.equals(event.asEndElement().getName())) { | ||
var className = getOptionalValueOf(testCaseElement, CLASS_NAME).orElse(suiteName); | ||
builder.withClassName(className); | ||
var packageNode = root.findOrCreatePackageNode(EMPTY); | ||
var classNode = packageNode.findOrCreateClassNode(className); | ||
classNode.addTestCase(builder.build()); | ||
break; | ||
} | ||
} | ||
return builder.build(); | ||
Check warning on line 128 in src/main/java/edu/hm/hafner/coverage/parser/NunitParser.java
|
||
} | ||
|
||
private boolean isFailure(final XMLEvent event) { | ||
QName name; | ||
if (event.isStartElement()) { | ||
name = event.asStartElement().getName(); | ||
} | ||
else { | ||
name = event.asEndElement().getName(); | ||
} | ||
|
||
return FAILURE.equals(name); | ||
} | ||
|
||
private void readFailure(final XMLEventReader reader, final TestCaseBuilder builder) | ||
throws XMLStreamException { | ||
builder.withFailure(); | ||
var aggregatedContent = new StringBuilder(); | ||
while (true) { | ||
XMLEvent event = reader.nextEvent(); | ||
if (event.isCharacters()) { | ||
aggregatedContent.append(event.asCharacters().getData()); | ||
} | ||
else if (event.isEndElement() && isFailure(event)) { | ||
return; | ||
} | ||
else if (event.isEndElement() && event.asEndElement().getName().equals(MESSAGE)) { | ||
Check warning on line 155 in src/main/java/edu/hm/hafner/coverage/parser/NunitParser.java
|
||
builder.withDescription(aggregatedContent.toString()); | ||
return; | ||
} | ||
} | ||
} | ||
|
||
private String createId() { | ||
return UUID.randomUUID().toString(); | ||
Check warning on line 163 in src/main/java/edu/hm/hafner/coverage/parser/NunitParser.java
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
115 changes: 115 additions & 0 deletions
115
src/test/java/edu/hm/hafner/coverage/parser/NunitParserTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package edu.hm.hafner.coverage.parser; | ||
|
||
import java.util.Collection; | ||
import java.util.NoSuchElementException; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
import edu.hm.hafner.coverage.ClassNode; | ||
import edu.hm.hafner.coverage.CoverageParser; | ||
import edu.hm.hafner.coverage.CoverageParser.ProcessingMode; | ||
import edu.hm.hafner.coverage.Metric; | ||
import edu.hm.hafner.coverage.ModuleNode; | ||
import edu.hm.hafner.coverage.Node; | ||
import edu.hm.hafner.coverage.PackageNode; | ||
import edu.hm.hafner.coverage.TestCase; | ||
import edu.hm.hafner.coverage.TestCase.TestResult; | ||
import edu.hm.hafner.coverage.TestCount; | ||
|
||
import static edu.hm.hafner.coverage.assertions.Assertions.*; | ||
|
||
class NunitParserTest extends AbstractParserTest { | ||
private static final String EMPTY = "-"; | ||
|
||
@Override | ||
CoverageParser createParser(final ProcessingMode processingMode) { | ||
return new NunitParser(processingMode); | ||
} | ||
|
||
@Override | ||
protected String getFolder() { | ||
return "nunit"; | ||
} | ||
|
||
@Test | ||
void shouldReadReport() { | ||
ModuleNode tree = readReport("nunit.xml"); | ||
|
||
assertThat(tree).hasName(EMPTY); | ||
assertThat(getPackage(tree)).hasName("-"); | ||
assertThat(getFirstClass(tree)).hasName("Tests"); | ||
assertThat(getFirstTest(tree).getDescription()).contains("Expected string length 4 but was 5. Strings differ at index 4"); | ||
|
||
assertThat(tree.aggregateValues()).contains(new TestCount(4)); | ||
} | ||
|
||
@Test | ||
void shouldReadReportInV2Format() { | ||
ModuleNode tree = readReport("nunit2-format.xml"); | ||
|
||
assertThat(tree).hasName(EMPTY); | ||
assertThat(getPackage(tree)).hasName("-"); | ||
assertThat(getFirstClass(tree)).hasName("MockTestFixture"); | ||
assertThat(getFirstTest(tree).getDescription()).contains("Intentional failure"); | ||
|
||
assertThat(tree.aggregateValues()).contains(new TestCount(28)); | ||
} | ||
|
||
@Test | ||
void shouldReadReportWithoutErrorMessage() { | ||
ModuleNode tree = readReport("nunit-no-message.xml"); | ||
|
||
assertThat(tree).hasName(EMPTY); | ||
assertThat(getPackage(tree)).hasName("-"); | ||
assertThat(getFirstClass(tree)).hasName("Tests"); | ||
assertThat(getFirstTest(tree).getDescription()).contains(""); | ||
assertThat(tree.aggregateValues()).contains(new TestCount(4)); | ||
} | ||
|
||
@Test | ||
void shouldReadReportWithoutFailure() { | ||
ModuleNode tree = readReport("nunit-no-failure-block.xml"); | ||
|
||
assertThat(tree).hasName(EMPTY); | ||
assertThat(getPackage(tree)).hasName("-"); | ||
assertThat(getFirstClass(tree)).hasName("Tests"); | ||
assertThat(getFirstTest(tree).getDescription()).contains(""); | ||
assertThat(tree.aggregateValues()).contains(new TestCount(4)); | ||
} | ||
|
||
@Test | ||
void shouldReadReportWithInvalidStatus() { | ||
ModuleNode tree = readReport("nunit-invalid-status.xml"); | ||
|
||
assertThat(tree).hasName(EMPTY); | ||
assertThat(getPackage(tree)).hasName("-"); | ||
assertThat(getFirstClass(tree)).hasName("Tests"); | ||
assertThat(tree.aggregateValues()).contains(new TestCount(4)); | ||
} | ||
|
||
private PackageNode getPackage(final Node node) { | ||
var children = node.getChildren(); | ||
assertThat(children).hasSize(1).first().isInstanceOf(PackageNode.class); | ||
|
||
return (PackageNode) children.get(0); | ||
} | ||
|
||
private ClassNode getFirstClass(final Node node) { | ||
var packageNode = getPackage(node); | ||
|
||
var children = packageNode.getChildren(); | ||
assertThat(children).isNotEmpty().first().isInstanceOf(ClassNode.class); | ||
|
||
return (ClassNode) children.get(0); | ||
} | ||
|
||
private TestCase getFirstTest(final Node node) { | ||
return node.getAll(Metric.CLASS).stream() | ||
.map(ClassNode.class::cast) | ||
.map(ClassNode::getTestCases) | ||
.flatMap(Collection::stream) | ||
.filter(test -> test.getResult() == TestResult.FAILED) | ||
.findFirst() | ||
.orElseThrow(() -> new NoSuchElementException("No failed test found")); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
src/test/resources/edu/hm/hafner/coverage/parser/nunit/empty.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
<test-run id="1" duration="0.0" testcasecount="0" total="0" passed="0" failed="0" inconclusive="0" skipped="0" result="Passed" start-time="2024-01-23T 12:33:37Z" end-time="2024-01-23T 12:33:37Z"> | ||
</test-run> |
29 changes: 29 additions & 0 deletions
29
src/test/resources/edu/hm/hafner/coverage/parser/nunit/nunit-invalid-status.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<test-run id="2" duration="0.46225700000000003" testcasecount="4" total="4" passed="2" failed="1" inconclusive="0" skipped="1" result="Failed" start-time="2024-01-23T 12:33:37Z" end-time="2024-01-23T 12:33:47Z"> | ||
<test-suite type="Assembly" name="test.dll" fullname="/home/jenkins/bin/Debug/net8.0/test.dll" total="4" passed="2" failed="1" inconclusive="0" skipped="1" result="Failed" start-time="2024-01-23T 12:33:46Z" end-time="2024-01-23T 12:33:47Z" duration="0.462257"> | ||
<test-suite type="TestSuite" name="test" fullname="test" total="4" passed="2" failed="1" inconclusive="0" skipped="1" result="Failed" start-time="2024-01-23T 12:33:46Z" end-time="2024-01-23T 12:33:47Z" duration="0.462257"> | ||
<test-suite type="TestFixture" name="Tests" fullname="test.Tests" classname="test.Tests" total="4" passed="2" failed="1" inconclusive="0" skipped="1" result="Failed" start-time="2024-01-23T 12:33:46Z" end-time="2024-01-23T 12:33:47Z" duration="0.462257"> | ||
<test-case name="FailedTEst" fullname="test.Tests.FailedTEst" methodname="FailedTEst" classname="Tests" result="Failed" start-time="2024-01-23T 12:33:46Z" end-time="2024-01-23T 12:33:46Z" duration="0.057283" asserts="0" seed="1556857297"> | ||
<failure> | ||
<message> Expected string length 4 but was 5. Strings differ at index 4. | ||
Expected: "Test" | ||
But was: "Test1" | ||
---------------^ | ||
</message> | ||
<stack-trace> at test.Tests.FailedTEst() in /home/jenkins/Tests.cs:line 50 | ||
|
||
1) at test.Tests.FailedTEst() in /home/jenkins/Tests.cs:line 50 | ||
|
||
</stack-trace> | ||
</failure> | ||
</test-case> | ||
<test-case name="IgnoredTest" fullname="test.Tests.IgnoredTest" methodname="IgnoredTest" classname="Tests" result="Invalid" start-time="2024-01-23T 12:33:46Z" end-time="2024-01-23T 12:33:46Z" duration="0.000378" asserts="0" seed="1438305193"> | ||
<output><![CDATA[Skipping this test | ||
]]></output> | ||
</test-case> | ||
<test-case name="ShouldConnectToDatabase" fullname="test.Tests.ShouldConnectToDatabase" methodname="ShouldConnectToDatabase" classname="Tests" result="Invalid" start-time="2024-01-23T 12:33:46Z" end-time="2024-01-23T 12:33:47Z" duration="0.404245" asserts="0" seed="802014458" /> | ||
<test-case name="ShouldCreateItem" fullname="test.Tests.ShouldCreateItem" methodname="ShouldCreateItem" classname="Tests" result="Passed" start-time="2024-01-23T 12:33:47Z" end-time="2024-01-23T 12:33:47Z" duration="0.000351" asserts="0" seed="606875445" /> | ||
</test-suite> | ||
</test-suite> | ||
<errors /> | ||
</test-suite> | ||
</test-run> |
16 changes: 16 additions & 0 deletions
16
src/test/resources/edu/hm/hafner/coverage/parser/nunit/nunit-no-failure-block.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<test-run id="2" duration="0.46225700000000003" testcasecount="4" total="4" passed="2" failed="1" inconclusive="0" skipped="1" result="Failed" start-time="2024-01-23T 12:33:37Z" end-time="2024-01-23T 12:33:47Z"> | ||
<test-suite type="Assembly" name="test.dll" fullname="/home/jenkins/bin/Debug/net8.0/test.dll" total="4" passed="2" failed="1" inconclusive="0" skipped="1" result="Failed" start-time="2024-01-23T 12:33:46Z" end-time="2024-01-23T 12:33:47Z" duration="0.462257"> | ||
<test-suite type="TestSuite" name="test" fullname="test" total="4" passed="2" failed="1" inconclusive="0" skipped="1" result="Failed" start-time="2024-01-23T 12:33:46Z" end-time="2024-01-23T 12:33:47Z" duration="0.462257"> | ||
<test-suite type="TestFixture" name="Tests" fullname="test.Tests" classname="test.Tests" total="4" passed="2" failed="1" inconclusive="0" skipped="1" result="Failed" start-time="2024-01-23T 12:33:46Z" end-time="2024-01-23T 12:33:47Z" duration="0.462257"> | ||
<test-case name="FailedTEst" fullname="test.Tests.FailedTEst" methodname="FailedTEst" classname="Tests" result="Failed" start-time="2024-01-23T 12:33:46Z" end-time="2024-01-23T 12:33:46Z" duration="0.057283" asserts="0" seed="1556857297" /> | ||
<test-case name="IgnoredTest" fullname="test.Tests.IgnoredTest" methodname="IgnoredTest" classname="Tests" result="Skipped" start-time="2024-01-23T 12:33:46Z" end-time="2024-01-23T 12:33:46Z" duration="0.000378" asserts="0" seed="1438305193"> | ||
<output><![CDATA[Skipping this test | ||
]]></output> | ||
</test-case> | ||
<test-case name="ShouldConnectToDatabase" fullname="test.Tests.ShouldConnectToDatabase" methodname="ShouldConnectToDatabase" classname="Tests" result="Passed" start-time="2024-01-23T 12:33:46Z" end-time="2024-01-23T 12:33:47Z" duration="0.404245" asserts="0" seed="802014458" /> | ||
<test-case name="ShouldCreateItem" fullname="test.Tests.ShouldCreateItem" methodname="ShouldCreateItem" classname="Tests" result="Passed" start-time="2024-01-23T 12:33:47Z" end-time="2024-01-23T 12:33:47Z" duration="0.000351" asserts="0" seed="606875445" /> | ||
</test-suite> | ||
</test-suite> | ||
<errors /> | ||
</test-suite> | ||
</test-run> |
18 changes: 18 additions & 0 deletions
18
src/test/resources/edu/hm/hafner/coverage/parser/nunit/nunit-no-message.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<test-run id="2" duration="0.46225700000000003" testcasecount="4" total="4" passed="2" failed="1" inconclusive="0" skipped="1" result="Failed" start-time="2024-01-23T 12:33:37Z" end-time="2024-01-23T 12:33:47Z"> | ||
<test-suite type="Assembly" name="test.dll" fullname="/home/jenkins/bin/Debug/net8.0/test.dll" total="4" passed="2" failed="1" inconclusive="0" skipped="1" result="Failed" start-time="2024-01-23T 12:33:46Z" end-time="2024-01-23T 12:33:47Z" duration="0.462257"> | ||
<test-suite type="TestSuite" name="test" fullname="test" total="4" passed="2" failed="1" inconclusive="0" skipped="1" result="Failed" start-time="2024-01-23T 12:33:46Z" end-time="2024-01-23T 12:33:47Z" duration="0.462257"> | ||
<test-suite type="TestFixture" name="Tests" fullname="test.Tests" classname="test.Tests" total="4" passed="2" failed="1" inconclusive="0" skipped="1" result="Failed" start-time="2024-01-23T 12:33:46Z" end-time="2024-01-23T 12:33:47Z" duration="0.462257"> | ||
<test-case name="FailedTEst" fullname="test.Tests.FailedTEst" methodname="FailedTEst" classname="Tests" result="Failed" start-time="2024-01-23T 12:33:46Z" end-time="2024-01-23T 12:33:46Z" duration="0.057283" asserts="0" seed="1556857297"> | ||
<failure /> | ||
</test-case> | ||
<test-case name="IgnoredTest" fullname="test.Tests.IgnoredTest" methodname="IgnoredTest" classname="Tests" result="Skipped" start-time="2024-01-23T 12:33:46Z" end-time="2024-01-23T 12:33:46Z" duration="0.000378" asserts="0" seed="1438305193"> | ||
<output><![CDATA[Skipping this test | ||
]]></output> | ||
</test-case> | ||
<test-case name="ShouldConnectToDatabase" fullname="test.Tests.ShouldConnectToDatabase" methodname="ShouldConnectToDatabase" classname="Tests" result="Passed" start-time="2024-01-23T 12:33:46Z" end-time="2024-01-23T 12:33:47Z" duration="0.404245" asserts="0" seed="802014458" /> | ||
<test-case name="ShouldCreateItem" fullname="test.Tests.ShouldCreateItem" methodname="ShouldCreateItem" classname="Tests" result="Passed" start-time="2024-01-23T 12:33:47Z" end-time="2024-01-23T 12:33:47Z" duration="0.000351" asserts="0" seed="606875445" /> | ||
</test-suite> | ||
</test-suite> | ||
<errors /> | ||
</test-suite> | ||
</test-run> |
Oops, something went wrong.