Skip to content

Commit

Permalink
Introduce Test Result Loader SPI (#46)
Browse files Browse the repository at this point in the history
* 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
lordofthejars committed Jul 5, 2017
1 parent 9a6e3f5 commit 65b56f2
Show file tree
Hide file tree
Showing 21 changed files with 425 additions and 131 deletions.
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();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.arquillian.smart.testing.strategies.failed;
package org.arquillian.smart.testing.spi;

public class TestResult {

Expand Down
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();
}
30 changes: 30 additions & 0 deletions junit-test-result-parser/pom.xml
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>
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";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.arquillian.smart.testing.parser.junit.JUnitTestResultParser
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
package org.arquillian.smart.testing.strategies.failed.surefire;
package org.arquillian.smart.testing.parser.junit;

import java.util.Set;
import javax.xml.stream.XMLStreamException;
import org.arquillian.smart.testing.strategies.failed.TestResult;
import org.arquillian.smart.testing.spi.TestResult;
import org.arquillian.smart.testing.spi.TestResultParser;
import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;

public class SurefireReaderTest {
public class JUnitTestResultParserTest {

@Test
public void should_read_test_class_with_failures() throws XMLStreamException {
public void should_read_test_class_with_failures() {

TestResultParser junitTestResultParser = new JUnitTestResultParser();

final Set<TestResult> testResults =
SurefireReader.loadTestResults(SurefireReader.class.getResourceAsStream("/surefire-with-failure.xml"));
junitTestResultParser.parse(JUnitTestResultParser.class.getResourceAsStream("/surefire-with-failure.xml"));

assertThat(testResults)
.extracting(TestResult::getClassName, TestResult::getResult)
Expand Down
Loading

0 comments on commit 65b56f2

Please sign in to comment.