001package org.atteo.moonshine.tests;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.Iterator;
006import java.util.LinkedHashSet;
007import java.util.List;
008import java.util.Set;
009
010import org.atteo.moonshine.Moonshine;
011import org.atteo.moonshine.tests.MoonshineConfiguration.Alternatives;
012import org.atteo.moonshine.tests.MoonshineConfiguration.Config;
013import org.junit.runner.Description;
014import org.junit.runner.Runner;
015import org.junit.runner.notification.RunNotifier;
016import org.junit.runners.ParentRunner;
017import org.junit.runners.model.InitializationError;
018import org.junit.runners.model.RunnerBuilder;
019
020import com.google.common.base.Splitter;
021import com.google.common.collect.Lists;
022import com.google.common.collect.Sets;
023import com.google.common.reflect.TypeToken;
024
025/**
026 * Runs the tests inside {@link Moonshine} container.
027 *
028 * <p>
029 * You can configure the container by annotating the class with
030 * {@link MoonshineConfiguration}.
031 * </p>
032 * <p>
033 * The test class will be instantiated using global Guice injector of the
034 * Moonshine container.
035 * </p>
036 */
037public class MoonshineMultiRunner extends ParentRunner<Runner> {
038    private final static String CONFIG_IDS = "configIds";
039    private final List<Runner> runners = new ArrayList<>();
040    private final Class<?> klass;
041
042    public MoonshineMultiRunner(Class<?> klass, RunnerBuilder builder) throws InitializationError {
043        super(null);
044        this.klass = klass;
045
046        String configIdsProperty = System.getProperty(CONFIG_IDS, "");
047        List<String> configIds = Splitter.on(",").trimResults().omitEmptyStrings().splitToList(configIdsProperty);
048
049        List<Set<Config>> alternatives = collectAlternatives(klass);
050
051        if (!configIds.isEmpty()) {
052            filterAlternatives(alternatives, configIds);
053        }
054
055        for (List<Config> list : Sets.cartesianProduct(alternatives)) {
056            runners.add(new MoonshineRunner(klass, list));
057        }
058    }
059
060    private void filterAlternatives(List<Set<Config>> alternatives, List<String> configIds) {
061        for (Set<Config> alternative : alternatives) {
062            if (containsAnyConfigId(alternative, configIds)) {
063                Iterator<Config> iterator = alternative.iterator();
064
065                while (iterator.hasNext()) {
066                    Config config = iterator.next();
067                    if (!configIds.contains(config.id())) {
068                        iterator.remove();
069                    }
070                }
071            }
072        }
073    }
074
075    private boolean containsAnyConfigId(Set<Config> alternative, List<String> configIds) {
076        for (Config config : alternative) {
077            if (configIds.contains(config.id())) {
078                return true;
079            }
080        }
081
082        return false;
083    }
084
085    private static List<Set<Config>> collectAlternatives(Class<?> klass) {
086        @SuppressWarnings("unchecked")
087        Set<Class<?>> ancestorSet = (Set<Class<?>>) TypeToken.of(klass).getTypes().rawTypes();
088        List<Class<?>> ancestors = Lists.reverse(new ArrayList<>(ancestorSet));
089
090        List<Set<Config>> alternatives = new ArrayList<>();
091        for (Class<?> ancestor : ancestors) {
092            MoonshineConfiguration annotation = ancestor.getAnnotation(MoonshineConfiguration.class);
093            if (annotation != null) {
094                if (annotation.forEach().length != 0) {
095                    alternatives.add(new LinkedHashSet<>(Arrays.asList(annotation.forEach())));
096                }
097                for (Alternatives alternative : annotation.forCartesianProductOf()) {
098                    alternatives.add(new LinkedHashSet<>(Arrays.asList(alternative.value())));
099                }
100            }
101        }
102        return alternatives;
103    }
104
105    @Override
106    protected void runChild(Runner runner, RunNotifier notifier) {
107        runner.run(notifier);
108    }
109
110    @Override
111    protected List<Runner> getChildren() {
112        return runners;
113    }
114
115    @Override
116    protected Description describeChild(Runner child) {
117        return child.getDescription();
118    }
119}