001/* 002 * Copyright 2012 Atteo. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.atteo.moonshine.tests; 017 018import java.lang.reflect.InvocationTargetException; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collections; 022import java.util.List; 023import java.util.Set; 024import java.util.concurrent.atomic.AtomicBoolean; 025 026import org.atteo.moonshine.Moonshine; 027import org.atteo.moonshine.tests.MoonshineConfiguration.Config; 028import org.junit.rules.MethodRule; 029import org.junit.rules.TestRule; 030import org.junit.runner.Description; 031import org.junit.runners.BlockJUnit4ClassRunner; 032import org.junit.runners.model.FrameworkMethod; 033import org.junit.runners.model.InitializationError; 034 035import com.google.common.base.Joiner; 036import com.google.common.collect.Lists; 037import com.google.common.reflect.TypeToken; 038import com.google.inject.AbstractModule; 039import com.google.inject.TypeLiteral; 040 041/** 042 * Runs the tests inside {@link Moonshine} container. 043 * 044 * <p> 045 * You can configure the container by annotating the class with 046 * {@link MoonshineConfiguration}. 047 * </p> 048 * <p> 049 * The test class will be instantiated using global Guice injector of the 050 * Moonshine container. 051 * </p> 052 */ 053public class MoonshineRunner extends BlockJUnit4ClassRunner { 054 055 private MoonshineRule moonshineRule = null; 056 private boolean requestPerClass = false; 057 private final List<Config> iterationConfigs; 058 private final List<String> iterationIds; 059 private final Class<?> klass; 060 061 public MoonshineRunner(Class<?> klass) throws InitializationError { 062 super(klass); 063 this.klass = klass; 064 iterationConfigs = Collections.emptyList(); 065 iterationIds = Collections.emptyList(); 066 } 067 068 /** 069 * Used by {@link MoonshineMultiRunner}. 070 */ 071 MoonshineRunner(Class<?> klass, List<Config> iterationConfigs) throws InitializationError { 072 super(klass); 073 this.klass = klass; 074 this.iterationConfigs = iterationConfigs; 075 iterationIds = getIterationIds(iterationConfigs); 076 } 077 078 @Override 079 protected Object createTest() throws Exception { 080 return moonshineRule.getGlobalInjector().getInstance(getTestClass().getJavaClass()); 081 } 082 083 @Override 084 protected List<TestRule> classRules() { 085 @SuppressWarnings("unchecked") 086 Set<Class<?>> ancestorSet = (Set<Class<?>>) TypeToken.of(getTestClass().getJavaClass()).getTypes().rawTypes(); 087 List<Class<?>> ancestors = Lists.reverse(new ArrayList<>(ancestorSet)); 088 089 final List<String> configPaths = new ArrayList<>(); 090 List<MoonshineConfigurator> configurators = new ArrayList<>(); 091 AtomicBoolean loadTestConfigXml = new AtomicBoolean(true); 092 093 for (Class<?> ancestor : ancestors) { 094 analyseAncestor(ancestor, configPaths, configurators, loadTestConfigXml); 095 } 096 097 analyseIterationConfigs(configPaths, configurators); 098 099 moonshineRule = new MoonshineRule(configurators, configPaths.toArray(new String[configPaths.size()])); 100 moonshineRule.setLoadTestConfigXml(loadTestConfigXml.get()); 101 102 List<TestRule> rules = super.classRules(); 103 if (requestPerClass) { 104 rules.add(new RequestRule()); 105 } 106 107 rules.add(moonshineRule); 108 return rules; 109 } 110 111 @Override 112 protected String getName() { 113 String name = super.getName(); 114 if (!iterationConfigs.isEmpty()) { 115 return name + " with config [" + Joiner.on(",").join(iterationIds) + "]"; 116 } 117 return name; 118 } 119 120 @Override 121 public Description getDescription() { 122 Description description = Description.createTestDescription(klass, getName(), getRunnerAnnotations()); 123 for (FrameworkMethod child : getChildren()) { 124 description.addChild(describeChild(child)); 125 } 126 return description; 127 } 128 129 private void analyseIterationConfigs(final List<String> configs, List<MoonshineConfigurator> configurators) { 130 if (iterationConfigs.isEmpty()) { 131 return; 132 } 133 for (Config config : iterationConfigs) { 134 if (config.value().length != 0) { 135 configs.addAll(Arrays.asList(config.value())); 136 } 137 } 138 139 configurators.add(new MoonshineConfigurator() { 140 @Override 141 public void configureMoonshine(Moonshine.Builder builder) { 142 for (Config config : iterationConfigs) { 143 if (!config.fromString().isEmpty()) { 144 builder.addConfigurationFromString(config.fromString()); 145 } 146 } 147 builder.addModule(new AbstractModule() { 148 @Override 149 protected void configure() { 150 bind(new TypeLiteral<List<String>>() {}).annotatedWith(EnabledConfigs.class) 151 .toInstance(iterationIds); 152 } 153 }); 154 builder.applicationName(klass.getSimpleName() + "[" + Joiner.on(",").join(iterationIds) + "]"); 155 } 156 }); 157 } 158 159 private static List<String> getIterationIds(List<Config> iterationConfigs) { 160 final List<String> iterationIds = new ArrayList<>(); 161 for (Config config : iterationConfigs) { 162 iterationIds.add(config.id()); 163 } 164 return iterationIds; 165 } 166 167 private static String getPathToResource(Class<?> klass, String annotationValue) { 168 if (annotationValue.startsWith("/")) { 169 return annotationValue; 170 } else { 171 return "/" + klass.getPackage().getName().replace(".", "/") + "/" + annotationValue; 172 } 173 } 174 175 private void analyseAncestor(Class<?> ancestor, final List<String> configs, 176 List<MoonshineConfigurator> configurators, final AtomicBoolean loadTestConfigXml) { 177 final MoonshineConfiguration annotation = ancestor.getAnnotation(MoonshineConfiguration.class); 178 if (annotation == null) { 179 return; 180 } 181 loadTestConfigXml.set(false); 182 for (String config : annotation.value()) { 183 configs.add(getPathToResource(ancestor, config)); 184 } 185 requestPerClass = annotation.oneRequestPerClass(); 186 if ((annotation.forEach().length != 0 || annotation.forCartesianProductOf().length != 0) 187 && iterationConfigs.isEmpty()) { 188 throw new RuntimeException("Error on class " + ancestor.getName() 189 + ": @" + MoonshineConfiguration.class.getSimpleName() 190 + " forEach and forCartesianProductOf can be used only with " 191 + MoonshineMultiRunner.class.getSimpleName()); 192 } 193 Class<? extends MoonshineConfigurator> configuratorKlass = annotation.configurator(); 194 if (configuratorKlass != null && configuratorKlass != MoonshineConfigurator.class) { 195 try { 196 MoonshineConfigurator configurator = configuratorKlass.getConstructor().newInstance(); 197 configurators.add(configurator); 198 } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException 199 | IllegalArgumentException | InvocationTargetException e) { 200 throw new RuntimeException(e); 201 } 202 } 203 if (annotation.autoConfiguration() || annotation.skipDefault() || !annotation.fromString().isEmpty() 204 || annotation.arguments().length != 0) { 205 MoonshineConfigurator configurator = new MoonshineConfigurator() { 206 @Override 207 public void configureMoonshine(Moonshine.Builder builder) { 208 if (annotation.autoConfiguration()) { 209 builder.autoConfiguration(); 210 } 211 if (annotation.skipDefault()) { 212 builder.skipDefaultConfigurationFiles(); 213 } 214 if (!annotation.fromString().isEmpty()) { 215 builder.addConfigurationFromString(annotation.fromString()); 216 } 217 218 builder.arguments(annotation.arguments()); 219 } 220 }; 221 configurators.add(configurator); 222 } 223 } 224 225 @Override 226 protected List<TestRule> getTestRules(Object target) { 227 List<TestRule> rules = super.getTestRules(target); 228 if (!requestPerClass) { 229 rules.add(new RequestRule()); 230 } 231 return rules; 232 } 233 234 @Override 235 protected List<MethodRule> rules(Object target) { 236 List<MethodRule> rules = super.rules(target); 237 rules.add(moonshineRule.injectMembers(target)); 238 rules.add(new MockitoRule()); 239 return rules; 240 } 241}