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 * @RunWith(ParameterizedSuite.class) 051 * @SuiteClasses(SimpleSession.class) 052 * @ParameterizedFeature(? extends RunnerFeature.class) 053 * public class NuxeoSuiteTest { 054 * @Parameters 055 * public static Collection<Object[]> yourParametersMethod() {...} 056 * } 057 * </pre> 058 * 059 * @ParameterizedFeature is optional. If used, the corresponding class must implement a method annotated with 060 * @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}