package org.atteo.moonshine.tests;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.atteo.moonshine.Moonshine;
import org.atteo.moonshine.tests.MoonshineConfiguration.Alternatives;
import org.atteo.moonshine.tests.MoonshineConfiguration.Config;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.ParentRunner;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerBuilder;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.reflect.TypeToken;
/**
* Runs the tests inside {@link Moonshine} container.
*
* <p>
* You can configure the container by annotating the class with
* {@link MoonshineConfiguration}.
* </p>
* <p>
* The test class will be instantiated using global Guice injector of the
* Moonshine container.
* </p>
*/
public class MoonshineMultiRunner extends ParentRunner<Runner> {
private final static String CONFIG_IDS = "configIds";
private final List<Runner> runners = new ArrayList<>();
private final Class<?> klass;
public MoonshineMultiRunner(Class<?> klass, RunnerBuilder builder) throws InitializationError {
super(null);
this.klass = klass;
String configIdsProperty = System.getProperty(CONFIG_IDS, "");
List<String> configIds = Splitter.on(",").trimResults().omitEmptyStrings().splitToList(configIdsProperty);
List<Set<Config>> alternatives = collectAlternatives(klass);
if (!configIds.isEmpty()) {
filterAlternatives(alternatives, configIds);
}
for (List<Config> list : Sets.cartesianProduct(alternatives)) {
runners.add(new MoonshineRunner(klass, list));
}
}
private void filterAlternatives(List<Set<Config>> alternatives, List<String> configIds) {
for (Set<Config> alternative : alternatives) {
if (containsAnyConfigId(alternative, configIds)) {
Iterator<Config> iterator = alternative.iterator();
while (iterator.hasNext()) {
Config config = iterator.next();
if (!configIds.contains(config.id())) {
iterator.remove();
}
}
}
}
}
private boolean containsAnyConfigId(Set<Config> alternative, List<String> configIds) {
for (Config config : alternative) {
if (configIds.contains(config.id())) {
return true;
}
}
return false;
}
private static List<Set<Config>> collectAlternatives(Class<?> klass) {
@SuppressWarnings("unchecked")
Set<Class<?>> ancestorSet = (Set<Class<?>>) TypeToken.of(klass).getTypes().rawTypes();
List<Class<?>> ancestors = Lists.reverse(new ArrayList<>(ancestorSet));
List<Set<Config>> alternatives = new ArrayList<>();
for (Class<?> ancestor : ancestors) {
MoonshineConfiguration annotation = ancestor.getAnnotation(MoonshineConfiguration.class);
if (annotation != null) {
if (annotation.forEach().length != 0) {
alternatives.add(new LinkedHashSet<>(Arrays.asList(annotation.forEach())));
}
for (Alternatives alternative : annotation.forCartesianProductOf()) {
alternatives.add(new LinkedHashSet<>(Arrays.asList(alternative.value())));
}
}
}
return alternatives;
}
@Override
protected void runChild(Runner runner, RunNotifier notifier) {
runner.run(notifier);
}
@Override
protected List<Runner> getChildren() {
return runners;
}
@Override
protected Description describeChild(Runner child) {
return child.getDescription();
}
}