-
Notifications
You must be signed in to change notification settings - Fork 40
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 #72 from jonesbusy/feature/xunit-format
Add support for XUnit .NET format
- Loading branch information
Showing
10 changed files
with
324 additions
and
1 deletion.
There are no files selected for viewing
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/main/java/edu/hm/hafner/coverage/parser/XunitParser.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 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.ModuleNode; | ||
import edu.hm.hafner.coverage.TestCase; | ||
import edu.hm.hafner.coverage.TestCase.TestCaseBuilder; | ||
|
||
/** | ||
* Parses reports in the | ||
* <a href="https://xunit.net/docs/format-xml-v2">XUnit format</a> | ||
* into a Java object model. | ||
* | ||
* @author Valentin Delaye | ||
*/ | ||
@SuppressWarnings("checkstyle:ClassDataAbstractionCoupling") | ||
public class XunitParser extends AbstractTestParser { | ||
private static final long serialVersionUID = -5468593789018138107L; | ||
|
||
private static final QName COLLECTION = new QName("collection"); | ||
private static final QName TEST = new QName("test"); | ||
private static final QName RESULT = new QName("result"); | ||
private static final QName TYPE = new QName("type"); | ||
private static final String PASS = "Pass"; | ||
private static final String FAIL = "Fail"; | ||
private static final String SKIP = "Skip"; | ||
|
||
/** | ||
* Creates a new instance of {@link XunitParser}. | ||
*/ | ||
public XunitParser() { | ||
this(ProcessingMode.FAIL_FAST); | ||
} | ||
|
||
/** | ||
* Creates a new instance of {@link XunitParser}. | ||
* | ||
* @param processingMode | ||
* determines whether to ignore errors | ||
*/ | ||
public XunitParser(final ProcessingMode processingMode) { | ||
super(processingMode, COLLECTION, TEST); | ||
} | ||
|
||
@Override | ||
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())); | ||
|
||
readStatus(testCaseElement, builder); | ||
|
||
while (reader.hasNext()) { | ||
XMLEvent event = reader.nextEvent(); | ||
|
||
if (event.isStartElement() && isFailure(event)) { | ||
readFailure(reader, builder); | ||
} | ||
else if (event.isEndElement() && TEST.equals(event.asEndElement().getName())) { | ||
Check warning on line 64 in src/main/java/edu/hm/hafner/coverage/parser/XunitParser.java GitHub Actions / Quality MonitorMutation survived
Raw output
|
||
var className = getOptionalValueOf(testCaseElement, TYPE).orElse(suiteName); | ||
builder.withClassName(className); | ||
var packageNode = root.findOrCreatePackageNode(EMPTY); | ||
var classNode = packageNode.findOrCreateClassNode(className); | ||
classNode.addTestCase(builder.build()); | ||
return builder.build(); | ||
Check warning on line 70 in src/main/java/edu/hm/hafner/coverage/parser/XunitParser.java GitHub Actions / Quality MonitorMutation survived
Raw output
|
||
} | ||
} | ||
throw createEofException(); | ||
} | ||
|
||
private void readStatus(final StartElement testCaseElement, final TestCaseBuilder builder) { | ||
var status = getValueOf(testCaseElement, RESULT); | ||
switch (status) { | ||
case PASS: | ||
builder.withStatus(TestCase.TestResult.PASSED); | ||
break; | ||
case FAIL: | ||
builder.withStatus(TestCase.TestResult.FAILED); | ||
break; | ||
case SKIP: | ||
default: | ||
builder.withStatus(TestCase.TestResult.SKIPPED); | ||
break; | ||
} | ||
} | ||
|
||
private boolean isFailure(final XMLEvent event) { | ||
return FAILURE.equals(getElementName(event)); | ||
} | ||
|
||
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 109 in src/main/java/edu/hm/hafner/coverage/parser/XunitParser.java GitHub Actions / Quality MonitorPartially covered line
Check warning on line 109 in src/main/java/edu/hm/hafner/coverage/parser/XunitParser.java GitHub Actions / Quality MonitorMutation survived
Raw output
|
||
builder.withDescription(aggregatedContent.toString()); | ||
return; | ||
} | ||
} | ||
} | ||
} |
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
103 changes: 103 additions & 0 deletions
103
src/test/java/edu/hm/hafner/coverage/parser/XunitParserTest.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,103 @@ | ||
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 XunitParserTest extends AbstractParserTest { | ||
private static final String EMPTY = "-"; | ||
|
||
@Override | ||
CoverageParser createParser(final ProcessingMode processingMode) { | ||
return new XunitParser(processingMode); | ||
} | ||
|
||
@Override | ||
protected String getFolder() { | ||
return "xunit"; | ||
} | ||
|
||
@Test | ||
void shouldReadReport() { | ||
ModuleNode tree = readReport("xunit.xml"); | ||
|
||
assertThat(tree).hasName(EMPTY); | ||
assertThat(getPackage(tree)).hasName("-"); | ||
assertThat(getFirstClass(tree)).hasName("test.Tests2"); | ||
assertThat(getFirstTest(tree).getDescription()).contains("Assert.Equal() Failure"); | ||
|
||
assertThat(tree.aggregateValues()).contains(new TestCount(3)); | ||
} | ||
|
||
@Test | ||
void shouldReadReportWithoutFailure() { | ||
ModuleNode tree = readReport("xunit-no-failure-block.xml"); | ||
|
||
assertThat(tree).hasName(EMPTY); | ||
assertThat(getPackage(tree)).hasName("-"); | ||
assertThat(getFirstClass(tree)).hasName("test.Tests2"); | ||
assertThat(getFirstTest(tree).getDescription()).contains(""); | ||
assertThat(tree.aggregateValues()).contains(new TestCount(3)); | ||
} | ||
|
||
@Test | ||
void shouldReadReportWithInvalidStatus() { | ||
ModuleNode tree = readReport("xunit-invalid-status.xml"); | ||
|
||
assertThat(tree).hasName(EMPTY); | ||
assertThat(getPackage(tree)).hasName("-"); | ||
assertThat(getFirstClass(tree)).hasName("test.Tests2"); | ||
assertThat(tree.aggregateValues()).contains(new TestCount(3)); | ||
} | ||
|
||
@Test | ||
void shouldReadReportWithoutErrorMessage() { | ||
ModuleNode tree = readReport("xunit-no-message.xml"); | ||
|
||
assertThat(tree).hasName(EMPTY); | ||
assertThat(getPackage(tree)).hasName("-"); | ||
assertThat(getFirstClass(tree)).hasName("test.Tests2"); | ||
assertThat(getFirstTest(tree).getDescription()).contains(""); | ||
assertThat(tree.aggregateValues()).contains(new TestCount(3)); | ||
} | ||
|
||
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
1 change: 1 addition & 0 deletions
1
src/test/resources/edu/hm/hafner/coverage/parser/xunit/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 @@ | ||
<assembly name="test.dll" run-date="2024-01-23" run-time="12:33:37" total="0" passed="0" failed="0" skipped="0" /> |
29 changes: 29 additions & 0 deletions
29
src/test/resources/edu/hm/hafner/coverage/parser/xunit/xunit-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 @@ | ||
<assemblies timestamp="01/26/2024 13:49:51"> | ||
<assembly name="/home/jenkins/test2/bin/Debug/net8.0/test2.dll" run-date="2024-01-26" run-time="13:49:51" total="3" passed="1" failed="1" skipped="1" time="1.100" errors="0"> | ||
<errors /> | ||
<collection total="3" passed="1" failed="1" skipped="1" name="Test collection for test.Tests2" time="0.005"> | ||
<test name="test.Tests2.ShouldCreateItem" type="test.Tests2" method="ShouldCreateItem" time="0.0026206" result="invalid"> | ||
<traits /> | ||
</test> | ||
<test name="test.Tests2.ShouldCreateItem3" type="test.Tests2" method="ShouldCreateItem3" time="0.0010000" result="invalid"> | ||
<reason><![CDATA[specific reason]]></reason> | ||
<output>specific reason | ||
</output> | ||
<traits /> | ||
</test> | ||
<test name="test.Tests2.ShouldCreateItem2" type="test.Tests2" method="ShouldCreateItem2" time="0.0010159" result="invalid"> | ||
<failure> | ||
<message>Assert.Equal() Failure | ||
↓ (pos 4) | ||
Expected: Test3 | ||
Actual: Test | ||
↑ (pos 4)</message> | ||
<stack-trace> at test.Tests2.ShouldCreateItem2() in /home/jenkins/test2/Tests2.cs:line 36 | ||
at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) | ||
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)</stack-trace> | ||
</failure> | ||
<traits /> | ||
</test> | ||
</collection> | ||
</assembly> | ||
</assemblies> |
19 changes: 19 additions & 0 deletions
19
src/test/resources/edu/hm/hafner/coverage/parser/xunit/xunit-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,19 @@ | ||
<assemblies timestamp="01/26/2024 13:49:51"> | ||
<assembly name="/home/jenkins/test2/bin/Debug/net8.0/test2.dll" run-date="2024-01-26" run-time="13:49:51" total="3" passed="1" failed="1" skipped="1" time="1.100" errors="0"> | ||
<errors /> | ||
<collection total="3" passed="1" failed="1" skipped="1" name="Test collection for test.Tests2" time="0.005"> | ||
<test name="test.Tests2.ShouldCreateItem" type="test.Tests2" method="ShouldCreateItem" time="0.0026206" result="Pass"> | ||
<traits /> | ||
</test> | ||
<test name="test.Tests2.ShouldCreateItem3" type="test.Tests2" method="ShouldCreateItem3" time="0.0010000" result="Skip"> | ||
<reason><![CDATA[specific reason]]></reason> | ||
<output>specific reason | ||
</output> | ||
<traits /> | ||
</test> | ||
<test name="test.Tests2.ShouldCreateItem2" type="test.Tests2" method="ShouldCreateItem2" time="0.0010159" result="Fail"> | ||
<traits /> | ||
</test> | ||
</collection> | ||
</assembly> | ||
</assemblies> |
20 changes: 20 additions & 0 deletions
20
src/test/resources/edu/hm/hafner/coverage/parser/xunit/xunit-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,20 @@ | ||
<assemblies timestamp="01/26/2024 13:49:51"> | ||
<assembly name="/home/jenkins/test2/bin/Debug/net8.0/test2.dll" run-date="2024-01-26" run-time="13:49:51" total="3" passed="1" failed="1" skipped="1" time="1.100" errors="0"> | ||
<errors /> | ||
<collection total="3" passed="1" failed="1" skipped="1" name="Test collection for test.Tests2" time="0.005"> | ||
<test name="test.Tests2.ShouldCreateItem" type="test.Tests2" method="ShouldCreateItem" time="0.0026206" result="Pass"> | ||
<traits /> | ||
</test> | ||
<test name="test.Tests2.ShouldCreateItem3" type="test.Tests2" method="ShouldCreateItem3" time="0.0010000" result="Skip"> | ||
<reason><![CDATA[specific reason]]></reason> | ||
<output>specific reason | ||
</output> | ||
<traits /> | ||
</test> | ||
<test name="test.Tests2.ShouldCreateItem2" type="test.Tests2" method="ShouldCreateItem2" time="0.0010159" result="Fail"> | ||
<failure /> | ||
<traits /> | ||
</test> | ||
</collection> | ||
</assembly> | ||
</assemblies> |
29 changes: 29 additions & 0 deletions
29
src/test/resources/edu/hm/hafner/coverage/parser/xunit/xunit.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 @@ | ||
<assemblies timestamp="01/26/2024 13:49:51"> | ||
<assembly name="/home/jenkins/test2/bin/Debug/net8.0/test2.dll" run-date="2024-01-26" run-time="13:49:51" total="3" passed="1" failed="1" skipped="1" time="1.100" errors="0"> | ||
<errors /> | ||
<collection total="3" passed="1" failed="1" skipped="1" name="Test collection for test.Tests2" time="0.005"> | ||
<test name="test.Tests2.ShouldCreateItem" type="test.Tests2" method="ShouldCreateItem" time="0.0026206" result="Pass"> | ||
<traits /> | ||
</test> | ||
<test name="test.Tests2.ShouldCreateItem3" type="test.Tests2" method="ShouldCreateItem3" time="0.0010000" result="Skip"> | ||
<reason><![CDATA[specific reason]]></reason> | ||
<output>specific reason | ||
</output> | ||
<traits /> | ||
</test> | ||
<test name="test.Tests2.ShouldCreateItem2" type="test.Tests2" method="ShouldCreateItem2" time="0.0010159" result="Fail"> | ||
<failure> | ||
<message>Assert.Equal() Failure | ||
↓ (pos 4) | ||
Expected: Test3 | ||
Actual: Test | ||
↑ (pos 4)</message> | ||
<stack-trace> at test.Tests2.ShouldCreateItem2() in /home/jenkins/test2/Tests2.cs:line 36 | ||
at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) | ||
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)</stack-trace> | ||
</failure> | ||
<traits /> | ||
</test> | ||
</collection> | ||
</assembly> | ||
</assemblies> |