1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.jumpmind.symmetric.test;
21
22 import java.lang.annotation.ElementType;
23 import java.lang.annotation.Retention;
24 import java.lang.annotation.RetentionPolicy;
25 import java.lang.annotation.Target;
26 import java.lang.reflect.Constructor;
27 import java.lang.reflect.InvocationTargetException;
28 import java.lang.reflect.Method;
29 import java.lang.reflect.Modifier;
30 import java.util.Collection;
31 import java.util.HashSet;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Set;
35
36 import org.apache.commons.lang.ArrayUtils;
37 import org.junit.Assert;
38 import org.junit.internal.runners.ClassRoadie;
39 import org.junit.internal.runners.CompositeRunner;
40 import org.junit.internal.runners.InitializationError;
41 import org.junit.internal.runners.JUnit4ClassRunner;
42 import org.junit.internal.runners.MethodValidator;
43 import org.junit.internal.runners.TestClass;
44 import org.junit.runner.notification.RunNotifier;
45 import org.junit.runners.Parameterized.Parameters;
46 import org.junit.runners.Suite.SuiteClasses;
47
48 /***
49 * Merge JUnits Parameterized runner and their Suite runner so we have a nice
50 * efficient way to run an entire suite of tests against parameterized 'sets' of
51 * data across the entire suite.
52 */
53 public class ParameterizedSuite extends CompositeRunner {
54
55 @Retention(RetentionPolicy.RUNTIME)
56 @Target(ElementType.METHOD)
57 public static @interface ParameterMatcher {
58 String[] value();
59 }
60
61 @Retention(RetentionPolicy.RUNTIME)
62 @Target(ElementType.METHOD)
63 public static @interface ParameterExcluder {
64 String[] value();
65 }
66
67 static class TestClassRunnerForParameters extends JUnit4ClassRunner {
68 private final Object[] fParameters;
69
70 private final Constructor<?> fConstructor;
71
72 private List<Method> methods;
73
74 TestClassRunnerForParameters(TestClass testClass, Object[] parameters) throws InitializationError {
75 super(testClass.getJavaClass());
76 fParameters = parameters;
77 fConstructor = getOnlyConstructor();
78 filterParameters();
79 }
80
81 protected void filterParameters() {
82 for (Iterator<Method> iterator = methods.iterator(); iterator.hasNext();) {
83 Method method = (Method) iterator.next();
84 ParameterMatcher match = method.getAnnotation(ParameterMatcher.class);
85 if (match != null) {
86 boolean remove = true;
87 for (Object p : fParameters) {
88 String[] matchValues = match.value();
89 for (String matchValue : matchValues) {
90 if (p != null && p.toString().equals(matchValue)) {
91 remove = false;
92 }
93 }
94 }
95 if (remove) {
96 iterator.remove();
97 }
98 }
99
100 ParameterExcluder excluder = method.getAnnotation(ParameterExcluder.class);
101 if (excluder != null) {
102 boolean remove = false;
103 for (Object p : fParameters) {
104 String[] excludeValues = excluder.value();
105 for (String excludeValue : excludeValues) {
106 if (p != null && p.toString().equals(excludeValue)) {
107 remove = true;
108 }
109 }
110 }
111 if (remove) {
112 iterator.remove();
113 }
114 }
115 }
116 }
117
118 @Override
119 protected Object createTest() throws Exception {
120 return fConstructor.newInstance(fParameters);
121 }
122
123 @Override
124 protected String getName() {
125 return String.format("%s with params %s", getTestClass().getName(), ArrayUtils.toString(fParameters));
126 }
127
128 /***
129 * Get a sneaky handle on methods so we can filter them.
130 */
131 @Override
132 protected List<Method> getTestMethods() {
133 methods = super.getTestMethods();
134 return methods;
135 }
136
137 @Override
138 protected String testName(final Method method) {
139 return String.format("%s with params %s", method.getName(), ArrayUtils.toString(fParameters));
140 }
141
142 private Constructor<?> getOnlyConstructor() {
143 Constructor<?> c = null;
144 Constructor<?>[] constructors = getTestClass().getJavaClass().getConstructors();
145 for (Constructor<?> constructor : constructors) {
146 if (constructor.getGenericParameterTypes().length == fParameters.length) {
147 c = constructor;
148 break;
149 }
150 }
151 Assert.assertNotNull("Could not find an appropriate constructor for " + getName(), c);
152 return c;
153 }
154
155 @Override
156 protected void validate() throws InitializationError {
157
158 }
159
160 @Override
161 public void run(RunNotifier notifier) {
162 runMethods(notifier);
163 }
164 }
165
166 public ParameterizedSuite(Class<?> klass) throws Exception {
167 this(klass, getAnnotatedClasses(klass));
168 }
169
170
171
172 private static Set<Class<?>> parents = new HashSet<Class<?>>();
173 private TestClass fTestClass;
174
175 protected ParameterizedSuite(Class<?> klass, Class<?>[] annotatedClasses) throws Exception {
176 super(klass.getName());
177
178 fTestClass = new TestClass(klass);
179
180 addParent(klass);
181 for (final Object each : getParametersList()) {
182 if (each instanceof Object[]) {
183 for (Class<?> clazz : annotatedClasses) {
184 add(new TestClassRunnerForParameters(new TestClass(clazz), (Object[]) each));
185 }
186 } else {
187 throw new Exception(String.format("%s.%s() must return a Collection of arrays.", fTestClass.getName(),
188 getParametersMethod().getName()));
189 }
190 }
191 removeParent(klass);
192 MethodValidator methodValidator = new MethodValidator(fTestClass);
193 methodValidator.validateStaticMethods();
194 methodValidator.assertValid();
195 }
196
197 private Class<?> addParent(Class<?> parent) throws InitializationError {
198 if (!parents.add(parent))
199 throw new InitializationError(String.format(
200 "class '%s' (possibly indirectly) contains itself as a SuiteClass", parent.getName()));
201 return parent;
202 }
203
204 private void removeParent(Class<?> klass) {
205 parents.remove(klass);
206 }
207
208 private static Class<?>[] getAnnotatedClasses(Class<?> klass) throws InitializationError {
209 SuiteClasses annotation = klass.getAnnotation(SuiteClasses.class);
210 if (annotation == null)
211 throw new InitializationError(String.format("class '%s' must have a SuiteClasses annotation", klass
212 .getName()));
213 Class<?>[] classes = new Class[annotation.value().length + 1];
214 classes[0] = klass;
215 for (int i = 1; i <= annotation.value().length; i++) {
216 classes[i] = annotation.value()[i - 1];
217 }
218 return classes;
219 }
220
221 protected void validate(MethodValidator methodValidator) {
222 methodValidator.validateStaticMethods();
223 methodValidator.validateInstanceMethods();
224 }
225
226 private Collection<?> getParametersList() throws IllegalAccessException, InvocationTargetException, Exception {
227 return (Collection<?>) getParametersMethod().invoke(null);
228 }
229
230 private Method getParametersMethod() throws Exception {
231 List<Method> methods = fTestClass.getAnnotatedMethods(Parameters.class);
232 for (Method each : methods) {
233 int modifiers = each.getModifiers();
234 if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))
235 return each;
236 }
237
238 throw new Exception("No public static parameters method on class " + getName());
239 }
240
241 @Override
242 public void run(final RunNotifier notifier) {
243 new ClassRoadie(notifier, fTestClass, getDescription(), new Runnable() {
244 public void run() {
245 runChildren(notifier);
246 }
247 }).runProtected();
248 }
249
250 }