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}