001/*
002 * (C) Copyright 2012-2013 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     jcarsique
016 */
017package org.nuxeo.runtime.test.runner;
018
019import java.lang.annotation.Annotation;
020import java.lang.annotation.ElementType;
021import java.lang.annotation.Inherited;
022import java.lang.annotation.Retention;
023import java.lang.annotation.RetentionPolicy;
024import java.lang.annotation.Target;
025import java.lang.reflect.Method;
026import java.lang.reflect.Modifier;
027import java.util.ArrayList;
028import java.util.Arrays;
029import java.util.HashMap;
030import java.util.List;
031import java.util.Map;
032
033import org.junit.runner.Description;
034import org.junit.runner.Runner;
035import org.junit.runner.notification.RunNotifier;
036import org.junit.runners.Parameterized.Parameters;
037import org.junit.runners.ParentRunner;
038import org.junit.runners.Suite.SuiteClasses;
039import org.junit.runners.model.FrameworkMethod;
040import org.junit.runners.model.InitializationError;
041import org.junit.runners.model.RunnerBuilder;
042import org.junit.runners.model.TestClass;
043
044/**
045 * JUnit4 ParentRunner that knows how to run a test class on multiple backend types.
046 * <p>
047 * To use it :
048 *
049 * <pre>
050 * &#064;RunWith(ParameterizedSuite.class)
051 * &#064;SuiteClasses(SimpleSession.class)
052 * &#064;ParameterizedFeature(? extends RunnerFeature.class)
053 * public class NuxeoSuiteTest {
054 *     &#064;Parameters
055 *   public static Collection&lt;Object[]&gt; yourParametersMethod() {...}
056 * }
057 * </pre>
058 *
059 * &#064;ParameterizedFeature is optional. If used, the corresponding class must implement a method annotated with
060 * &#064;ParameterizedMethod
061 */
062public class ParameterizedSuite extends ParentRunner<FeaturesRunner> {
063
064    /**
065     * The <code>ParameterizedFeature</code> annotation specifies the class to be parameterized. That class must extend
066     * {@link SimpleFeature} or implement {@link RunnerFeature}.
067     */
068    @Retention(RetentionPolicy.RUNTIME)
069    @Target(ElementType.TYPE)
070    @Inherited
071    public @interface ParameterizedFeature {
072        /**
073         * @return the class to be parameterized
074         */
075        public Class<?> value();
076    }
077
078    @Retention(RetentionPolicy.RUNTIME)
079    @Target(ElementType.METHOD)
080    public static @interface ParameterizedMethod {
081    }
082
083    @SuppressWarnings("unchecked")
084    private List<Object[]> getParametersList(TestClass klass) throws Throwable {
085        return (List<Object[]>) getParametersMethod(klass).invokeExplosively(null);
086    }
087
088    @SuppressWarnings("unchecked")
089    private Class<? extends RunnerFeature> getParameterizedClass(Class<?> klass) throws Throwable {
090        ParameterizedFeature annotation = klass.getAnnotation(ParameterizedFeature.class);
091        return (Class<? extends RunnerFeature>) (annotation != null ? annotation.value() : null);
092    }
093
094    private FrameworkMethod getParametersMethod(TestClass testClass) throws Exception {
095        List<FrameworkMethod> methods = testClass.getAnnotatedMethods(Parameters.class);
096        for (FrameworkMethod each : methods) {
097            int modifiers = each.getMethod().getModifiers();
098            if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))
099                return each;
100        }
101        throw new Exception("Missing public static Parameters method on class " + testClass.getName());
102    }
103
104    private final List<FeaturesRunner> runners = new ArrayList<FeaturesRunner>();
105
106    private final Map<FeaturesRunner, Object[]> runnersParams = new HashMap<FeaturesRunner, Object[]>();
107
108    private List<Object[]> parametersList;
109
110    private Class<? extends RunnerFeature> parameterizedClass;
111
112    public ParameterizedSuite(Class<?> testClass, RunnerBuilder builder) throws InitializationError {
113        this(builder, testClass, getSuiteClasses(testClass));
114    }
115
116    public ParameterizedSuite(RunnerBuilder builder, Class<?> testClass, Class<?>[] classes) throws InitializationError {
117        super(testClass);
118        try {
119            this.parametersList = getParametersList(getTestClass());
120            this.parameterizedClass = getParameterizedClass(testClass);
121        } catch (Throwable e) {
122            throw new InitializationError(e);
123        }
124        for (Object[] params : parametersList) {
125            List<Runner> runners2 = builder.runners(testClass, classes);
126            for (Runner runner : runners2) {
127                if (!(runner instanceof FeaturesRunner)) {
128                    continue;
129                }
130                FeaturesRunner featureRunner = (FeaturesRunner) runner;
131                this.runners.add(featureRunner);
132                try {
133                    if (parameterizedClass != null) {
134                        runnersParams.put(featureRunner, params);
135                        RunnerFeature feature = featureRunner.getFeature(parameterizedClass);
136                        for (Method method : feature.getClass().getMethods()) {
137                            if (method.getAnnotation(ParameterizedMethod.class) != null) {
138                                method.invoke(feature, new Object[] { params });
139                            }
140                        }
141                    }
142                } catch (Throwable e) {
143                    throw new InitializationError(e);
144                }
145            }
146        }
147    }
148
149    protected static Class<?>[] getSuiteClasses(Class<?> klass) throws InitializationError {
150        SuiteClasses annotation = klass.getAnnotation(SuiteClasses.class);
151        if (annotation == null) {
152            throw new InitializationError(String.format("class '%s' must have a SuiteClasses annotation",
153                    klass.getName()));
154        }
155        return annotation.value();
156    }
157
158    @Override
159    protected Description describeChild(FeaturesRunner child) {
160        Description description = child.getDescription();
161        return Description.createTestDescription(description.getTestClass(), description.getDisplayName() + " "
162                + Arrays.toString(runnersParams.get(child)), (Annotation[]) description.getAnnotations().toArray());
163    }
164
165    @Override
166    protected List<FeaturesRunner> getChildren() {
167        return runners;
168    }
169
170    @Override
171    protected void runChild(FeaturesRunner child, RunNotifier notifier) {
172        // for (Object[] params : parametersList) {
173        System.out.println(String.format("\r\n============= RUNNING %s =================", describeChild(child)));
174        // try {
175        // if (parameterizedClass != null) {
176        // RunnerFeature feature = child.getFeature(parameterizedClass);
177        // for (Method method : feature.getClass().getMethods()) {
178        // if (method.getAnnotation(ParameterizedMethod.class) != null) {
179        // method.invoke(feature, new Object[] { params });
180        // }
181        // }
182        // }
183        child.run(notifier);
184        // } catch (Throwable e) {
185        // notifier.fireTestFailure(new Failure(child.getDescription(), e));
186        // }
187        // }
188    }
189}