-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce Test Result Loader SPI (#46)
* feat(#41): Introduce Test Result Loader SPI * fix(refactor): Move JUnit test reporter parser to its own module * fix(reviewcomments): Fix elements coming from review * fix(refactor): Add javadoc ad refactor some names * fix(codereview): Minor changes from Code Review * chore(renamemethod): Rename method to make clear its purpose
- Loading branch information
1 parent
9a6e3f5
commit 65b56f2
Showing
21 changed files
with
425 additions
and
131 deletions.
There are no files selected for viewing
81 changes: 81 additions & 0 deletions
81
core/src/main/java/org/arquillian/smart/testing/spi/JavaSPILoader.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,81 @@ | ||
package org.arquillian.smart.testing.spi; | ||
|
||
import java.util.Iterator; | ||
import java.util.Optional; | ||
import java.util.ServiceLoader; | ||
import java.util.function.Predicate; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.StreamSupport; | ||
|
||
|
||
public class JavaSPILoader { | ||
|
||
public JavaSPILoader() { | ||
} | ||
|
||
/** | ||
* Get all Java services that implements given interface. | ||
* @param serviceType interface | ||
* @return Iterable of all services implementing serviceType interface and present in classpath. | ||
*/ | ||
public <SERVICE> Iterable<SERVICE> all(Class<SERVICE> serviceType) { | ||
return ServiceLoader.load(serviceType); | ||
} | ||
|
||
/** | ||
* Get all Java services that implements given interface and meets the given predicate. | ||
* @param serviceType interface | ||
* @param predicate to set filtering options | ||
* @return Iterable of all services implementing serviceType interface, meeting predicate condition and present in classpath. | ||
*/ | ||
public <SERVICE> Iterable<SERVICE> all(Class<SERVICE> serviceType, Predicate<SERVICE> predicate) { | ||
return StreamSupport.stream(all(serviceType).spliterator(), false).filter(predicate).collect(Collectors.toList()); | ||
} | ||
|
||
/** | ||
* Get only one serviceType of given type. This method is used when you are sure that only one implementation of given serviceType is in classpath. | ||
* If there are more than one, then {@link IllegalStateException} is thrown. | ||
* @param serviceType interface | ||
* @return The serviceType of given type (if there are any) or an exception in case of more than one | ||
*/ | ||
public <SERVICE> Optional<SERVICE> onlyOne(Class<SERVICE> serviceType) { | ||
Iterable<SERVICE> all = all(serviceType); | ||
return ensureOnlyOneServiceLoaded(serviceType, all); | ||
} | ||
|
||
/** | ||
* Get only one serviceType of given type. This method is used when you want to filter from all possible implementations of given serviceType. | ||
* If there are more than one, then {@link IllegalStateException} is thrown. | ||
* @param serviceType interface | ||
* @param predicate to set filtering options | ||
* @return The serviceType of given type and meeting predicate condition (if any) or an exception in case of more than one. | ||
*/ | ||
public <SERVICE> Optional<SERVICE> onlyOne(Class<SERVICE> serviceType, Predicate<SERVICE> predicate) { | ||
final Iterable<SERVICE> all = all(serviceType, predicate); | ||
return ensureOnlyOneServiceLoaded(serviceType, all); | ||
} | ||
|
||
private <SERVICE> Optional<SERVICE> ensureOnlyOneServiceLoaded(Class<SERVICE> serviceType, Iterable<SERVICE> all) { | ||
final Iterator<SERVICE> allIterator = all.iterator(); | ||
if (allIterator.hasNext()) { | ||
SERVICE serviceInstance = allIterator.next(); | ||
|
||
if (allIterator.hasNext()) { | ||
throw new IllegalStateException( | ||
"Multiple serviceType implementations found for " + serviceType + ". " + toClassString(all)); | ||
} | ||
|
||
return Optional.of(serviceInstance); | ||
} else { | ||
return Optional.empty(); | ||
} | ||
} | ||
|
||
private <SERVICE> String toClassString(Iterable<SERVICE> providers) { | ||
StringBuilder sb = new StringBuilder(); | ||
for (Object provider : providers) { | ||
sb.append(provider.getClass().getName()).append(", "); | ||
} | ||
return sb.subSequence(0, sb.length() - 2).toString(); | ||
} | ||
} |
2 changes: 1 addition & 1 deletion
2
...testing/strategies/failed/TestResult.java → ...uillian/smart/testing/spi/TestResult.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
25 changes: 25 additions & 0 deletions
25
core/src/main/java/org/arquillian/smart/testing/spi/TestResultParser.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,25 @@ | ||
package org.arquillian.smart.testing.spi; | ||
|
||
import java.io.InputStream; | ||
import java.util.Set; | ||
|
||
/** | ||
* Java SPI for parsing test results stored in a stream. | ||
*/ | ||
public interface TestResultParser { | ||
|
||
/** | ||
* Parse given stream and return results in form of smart test model {@link TestResult} | ||
* | ||
* This method does not close the stream. | ||
* @param reportInputStream where results are stored. | ||
* @return Set of all resutls parsed. | ||
*/ | ||
Set<TestResult> parse(InputStream reportInputStream); | ||
|
||
/** | ||
* Type of parser. | ||
* @return Type of parser such as junit. | ||
*/ | ||
String type(); | ||
} |
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,30 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<parent> | ||
<artifactId>smart-testing-parent</artifactId> | ||
<groupId>org.arquillian.smart.testing</groupId> | ||
<version>0.0.1-SNAPSHOT</version> | ||
</parent> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<artifactId>junit-test-result-parser</artifactId> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.arquillian.smart.testing</groupId> | ||
<artifactId>smart-testing-core</artifactId> | ||
<version>${project.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>junit</groupId> | ||
<artifactId>junit</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.assertj</groupId> | ||
<artifactId>assertj-core</artifactId> | ||
</dependency> | ||
</dependencies> | ||
|
||
</project> |
108 changes: 108 additions & 0 deletions
108
...parser/src/main/java/org/arquillian/smart/testing/parser/junit/JUnitTestResultParser.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,108 @@ | ||
package org.arquillian.smart.testing.parser.junit; | ||
|
||
import java.io.InputStream; | ||
import java.util.HashSet; | ||
import java.util.Iterator; | ||
import java.util.Set; | ||
import javax.xml.stream.XMLEventReader; | ||
import javax.xml.stream.XMLInputFactory; | ||
import javax.xml.stream.XMLStreamException; | ||
import javax.xml.stream.events.Attribute; | ||
import javax.xml.stream.events.EndElement; | ||
import javax.xml.stream.events.StartElement; | ||
import javax.xml.stream.events.XMLEvent; | ||
import org.arquillian.smart.testing.spi.TestResultParser; | ||
import org.arquillian.smart.testing.spi.TestResult; | ||
|
||
public class JUnitTestResultParser implements TestResultParser { | ||
|
||
@Override | ||
public Set<TestResult> parse(InputStream junitInputStream) { | ||
final Set<TestResult> testResults = new HashSet<>(); | ||
XMLEventReader eventReader = null; | ||
try { | ||
final XMLInputFactory inputFactory = XMLInputFactory.newInstance(); | ||
eventReader = inputFactory.createXMLEventReader(junitInputStream); | ||
|
||
TestResult currentTestResult = null; | ||
|
||
while (eventReader.hasNext()) { | ||
XMLEvent event = eventReader.nextEvent(); | ||
|
||
if (event.isStartElement()) { | ||
final StartElement startElement = event.asStartElement(); | ||
if ("testcase".equalsIgnoreCase(startElement.getName().getLocalPart())) { | ||
// Read attributes | ||
String name = null, classname = null, duration = null; | ||
|
||
final Iterator<Attribute> attributes = startElement.getAttributes(); | ||
|
||
while (attributes.hasNext()) { | ||
final Attribute attribute = attributes.next(); | ||
if ("classname".equalsIgnoreCase(attribute.getName().toString())) { | ||
classname = attribute.getValue(); | ||
} | ||
|
||
if ("name".equalsIgnoreCase(attribute.getName().toString())) { | ||
name = attribute.getValue(); | ||
} | ||
|
||
if ("time".equalsIgnoreCase(attribute.getName().toString())) { | ||
duration = attribute.getValue(); | ||
} | ||
} | ||
|
||
final float durationInSeconds = Float.parseFloat(duration); | ||
currentTestResult = new TestResult(classname, name, durationInSeconds); | ||
} | ||
|
||
setCurrentTestResult(currentTestResult, startElement); | ||
} | ||
|
||
if (event.isEndElement()) { | ||
final EndElement endElementElement = event.asEndElement(); | ||
if ("testcase".equalsIgnoreCase(endElementElement.getName().getLocalPart())) { | ||
testResults.add(currentTestResult); | ||
} | ||
} | ||
} | ||
} catch (XMLStreamException e) { | ||
throw new IllegalStateException("Error parsing JUnit Test Result", e); | ||
} finally { | ||
// XmlEventReader does not implement autoclosable | ||
if (eventReader != null) { | ||
try { | ||
eventReader.close(); | ||
} catch (XMLStreamException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
} | ||
|
||
return testResults; | ||
|
||
} | ||
|
||
private void setCurrentTestResult(TestResult currentTestResult, StartElement startElement) { | ||
if ("failure".equalsIgnoreCase(startElement.getName().getLocalPart())) { | ||
currentTestResult.setResult(TestResult.Result.FAILURE); | ||
} | ||
|
||
if ("error".equalsIgnoreCase(startElement.getName().getLocalPart())) { | ||
currentTestResult.setResult(TestResult.Result.ERROR); | ||
} | ||
|
||
if ("skipped".equalsIgnoreCase(startElement.getName().getLocalPart())) { | ||
currentTestResult.setResult(TestResult.Result.SKIPPED); | ||
} | ||
|
||
if ("rerunFailure".equalsIgnoreCase(startElement.getName().getLocalPart())) { | ||
currentTestResult.setResult(TestResult.Result.RE_RUN_FAILURE); | ||
} | ||
} | ||
|
||
@Override | ||
public String type() { | ||
return "junit"; | ||
} | ||
} |
1 change: 1 addition & 0 deletions
1
...er/src/main/resources/META-INF/services/org.arquillian.smart.testing.spi.TestResultParser
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 @@ | ||
org.arquillian.smart.testing.parser.junit.JUnitTestResultParser |
15 changes: 9 additions & 6 deletions
15
...s/failed/surefire/SurefireReaderTest.java → ...rser/junit/JUnitTestResultParserTest.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
Oops, something went wrong.