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