|
import cucumber.api.CucumberOptions; |
|
import cucumber.api.junit.Cucumber; |
|
import cucumber.runtime.junit.FeatureRunner; |
|
import org.junit.runner.Description; |
|
import org.junit.runner.notification.RunNotifier; |
|
import org.junit.runners.ParentRunner; |
|
import org.junit.runners.model.InitializationError; |
|
import org.junit.runners.model.Statement; |
|
|
|
import java.lang.annotation.Annotation; |
|
import java.lang.reflect.Field; |
|
import java.lang.reflect.InvocationTargetException; |
|
import java.lang.reflect.Method; |
|
import java.lang.reflect.Proxy; |
|
import java.util.List; |
|
import java.util.Map; |
|
import java.util.stream.Stream; |
|
|
|
/** |
|
* A {@link Cucumber} runner that outputs one JSON report per run. |
|
* <p> |
|
* Useful in cases where there are multiple runners (possibly sharing a common configuration), and when they are all run |
|
* (with mvn verify or similar). |
|
* <p> |
|
* Currently the JSONs are generated in the hard-coded directory {@code target/cucumber-reports}, in files named after |
|
* the runner class. |
|
*/ |
|
public class IndividualJsonCucumber extends ParentRunner<FeatureRunner> { |
|
private final Cucumber cucumberRunner; |
|
|
|
public IndividualJsonCucumber(Class<?> clazz) throws InitializationError { |
|
super(clazz); |
|
|
|
CucumberOptions cucumberOptions = getCucumberOptions(clazz); |
|
String[] plugin = cucumberOptions.plugin(); |
|
String[] newPlugins = setJsonPlugin(plugin, jsonPlugin(clazz)); |
|
changeAnnotationValue(cucumberOptions, "plugin", newPlugins); |
|
|
|
cucumberRunner = new Cucumber(clazz); |
|
} |
|
|
|
private CucumberOptions getCucumberOptions(Class<?> clazz) { |
|
CucumberOptions annotation = clazz.getAnnotation(CucumberOptions.class); |
|
if (annotation != null) { |
|
return annotation; |
|
} |
|
|
|
Class<?> superclass = clazz.getSuperclass(); |
|
if (superclass == Object.class) { |
|
return null; |
|
} |
|
return getCucumberOptions(superclass); |
|
} |
|
|
|
private String jsonPlugin(Class<?> clazz) { |
|
String name = clazz.getName(); |
|
String path = name.replace(".", "/") + ".json"; |
|
return "json:target/cucumber-reports/" + path; |
|
} |
|
|
|
private String[] setJsonPlugin(String[] original, String newElt) { |
|
Stream<String> originalNonJson = Stream.of(original).filter(p -> !p.startsWith("json")); |
|
return Stream.concat(originalNonJson, Stream.of(newElt)).toArray(String[]::new); |
|
} |
|
|
|
@Override |
|
protected List<FeatureRunner> getChildren() { |
|
return cucumberRunner.getChildren(); |
|
} |
|
|
|
@Override |
|
protected Description describeChild(FeatureRunner child) { |
|
return child.getDescription(); |
|
} |
|
|
|
@Override |
|
protected void runChild(FeatureRunner child, RunNotifier notifier) { |
|
child.run(notifier); |
|
} |
|
|
|
@Override |
|
protected Statement childrenInvoker(RunNotifier notifier) { |
|
try { |
|
Method method = cucumberRunner.getClass().getDeclaredMethod("childrenInvoker", RunNotifier.class); |
|
method.setAccessible(true); |
|
return (Statement) method.invoke(cucumberRunner, notifier); |
|
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { |
|
throw new IllegalStateException(e); |
|
} |
|
} |
|
|
|
/** |
|
* Changes the annotation value for the given key of the given annotation to newValue and returns |
|
* the previous value. |
|
*/ |
|
@SuppressWarnings("unchecked") |
|
private static void changeAnnotationValue(Annotation annotation, String key, Object newValue) { |
|
Object handler = Proxy.getInvocationHandler(annotation); |
|
Field f; |
|
try { |
|
f = handler.getClass().getDeclaredField("memberValues"); |
|
} catch (NoSuchFieldException | SecurityException e) { |
|
throw new IllegalStateException(e); |
|
} |
|
f.setAccessible(true); |
|
Map<String, Object> memberValues; |
|
try { |
|
memberValues = (Map<String, Object>) f.get(handler); |
|
} catch (IllegalArgumentException | IllegalAccessException e) { |
|
throw new IllegalStateException(e); |
|
} |
|
Object oldValue = memberValues.get(key); |
|
if (oldValue == null || oldValue.getClass() != newValue.getClass()) { |
|
throw new IllegalArgumentException(); |
|
} |
|
memberValues.put(key, newValue); |
|
} |
|
} |