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}