1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package vogar.target.junit; 18 19 import java.lang.annotation.Annotation; 20 import java.lang.reflect.Constructor; 21 import java.lang.reflect.InvocationTargetException; 22 import java.lang.reflect.Method; 23 import java.util.ArrayList; 24 import java.util.Collection; 25 import java.util.Collections; 26 import java.util.List; 27 import junit.framework.AssertionFailedError; 28 import org.junit.After; 29 import org.junit.AfterClass; 30 import org.junit.Before; 31 import org.junit.BeforeClass; 32 import org.junit.Ignore; 33 import org.junit.runner.RunWith; 34 import org.junit.runners.Parameterized; 35 import org.junit.runners.Parameterized.Parameters; 36 import org.junit.runners.Suite; 37 import org.junit.runners.Suite.SuiteClasses; 38 39 /** 40 * Utilities for manipulating JUnit4 tests. 41 */ 42 public final class Junit4 { 43 private Junit4() {} 44 45 /** 46 * Creates lazy vogar test instances from the given test case or test 47 * suite. 48 * 49 * @param methodNames if non-empty, this is the list of test method names. 50 */ 51 static List<VogarTest> classToVogarTests(Class<?> testClass, Collection<String> methodNames) { 52 List<VogarTest> result = new ArrayList<VogarTest>(); 53 getSuiteMethods(result, testClass, methodNames); 54 return result; 55 } 56 57 private static void getSuiteMethods( 58 List<VogarTest> out, Class<?> testClass, Collection<String> methodNames) { 59 boolean isJunit4TestClass = false; 60 61 Collection<Object[]> argCollection = findParameters(testClass); 62 63 /* JUnit 4.x: methods marked with @Test annotation. */ 64 if (methodNames.isEmpty()) { 65 for (Method m : testClass.getMethods()) { 66 if (!m.isAnnotationPresent(org.junit.Test.class)) continue; 67 68 isJunit4TestClass = true; 69 70 if (m.isAnnotationPresent(Ignore.class)) { 71 out.add(new IgnoredTest(testClass, m)); 72 } else if (m.getParameterTypes().length == 0) { 73 addAllParameterizedTests(out, testClass, m, argCollection); 74 } else { 75 out.add(new ConfigurationError(testClass.getName() + "#" + m.getName(), 76 new IllegalStateException("Tests may not have parameters!"))); 77 } 78 } 79 } else { 80 for (String methodName : methodNames) { 81 try { 82 addAllParameterizedTests(out, testClass, testClass.getMethod(methodName), 83 argCollection); 84 } catch (final NoSuchMethodException e) { 85 out.add(new ConfigurationError(testClass.getName() + "#" + methodName, e)); 86 } 87 } 88 } 89 90 isJunit4TestClass |= getSuiteTests(out, testClass); 91 92 if (!isJunit4TestClass) { 93 out.add(new ConfigurationError(testClass.getName(), 94 new IllegalStateException("Not a test case: " + testClass))); 95 } 96 } 97 98 @SuppressWarnings("unchecked") 99 private static Collection<Object[]> findParameters(Class<?> testClass) { 100 for (Method m : testClass.getMethods()) { 101 for (Annotation a : m.getAnnotations()) { 102 if (Parameters.class.isAssignableFrom(a.annotationType())) { 103 try { 104 return (Collection<Object[]>) m.invoke(testClass); 105 } catch (Exception ignored) { 106 } 107 } 108 } 109 } 110 111 return null; 112 } 113 114 private static void addAllParameterizedTests(List<VogarTest> out, Class<?> testClass, Method m, 115 Collection<Object[]> argCollection) { 116 if (argCollection == null) { 117 out.add(TestMethod.create(testClass, m, null)); 118 } else { 119 for (Object[] args : argCollection) { 120 out.add(TestMethod.create(testClass, m, args)); 121 } 122 } 123 } 124 125 public static boolean isJunit4Test(Class<?> klass) { 126 boolean isTestSuite = false; 127 boolean hasSuiteClasses = false; 128 129 // @RunWith(Suite.class) 130 // @SuiteClasses( ... ) 131 // public class MyTest { ... } 132 // or 133 // @RunWith(Parameterized.class) 134 // public class MyTest { ... } 135 for (Annotation a : klass.getAnnotations()) { 136 Class<?> annotationClass = a.annotationType(); 137 138 if (RunWith.class.isAssignableFrom(annotationClass)) { 139 Class<?> runnerClass = ((RunWith) a).value(); 140 if (Suite.class.isAssignableFrom(runnerClass)) { 141 isTestSuite = true; 142 } else if (Parameterized.class.isAssignableFrom(runnerClass)) { 143 return true; 144 } 145 } else if (Suite.SuiteClasses.class.isAssignableFrom(annotationClass)) { 146 hasSuiteClasses = true; 147 } 148 149 if (isTestSuite && hasSuiteClasses) { 150 return true; 151 } 152 } 153 154 // public class MyTest { 155 // @Test 156 // public void example() { ... } 157 // } 158 for (Method m : klass.getDeclaredMethods()) { 159 for (Annotation a : m.getAnnotations()) { 160 if (org.junit.Test.class.isAssignableFrom(a.annotationType())) { 161 return true; 162 } 163 } 164 } 165 166 return false; 167 } 168 169 private static boolean getSuiteTests(List<VogarTest> out, Class<?> suite) { 170 boolean isSuite = false; 171 172 /* Check for @RunWith(Suite.class) */ 173 for (Annotation a : suite.getAnnotations()) { 174 if (RunWith.class.isAssignableFrom(a.annotationType())) { 175 if (Suite.class.isAssignableFrom(((RunWith) a).value())) { 176 isSuite = true; 177 } 178 break; 179 } 180 } 181 182 if (!isSuite) { 183 return false; 184 } 185 186 /* Extract classes to run */ 187 for (Annotation a : suite.getAnnotations()) { 188 if (SuiteClasses.class.isAssignableFrom(a.annotationType())) { 189 for (Class<?> clazz : ((SuiteClasses) a).value()) { 190 getSuiteMethods(out, clazz, Collections.<String>emptySet()); 191 } 192 } 193 } 194 195 return true; 196 } 197 198 private abstract static class VogarJUnitTest implements VogarTest { 199 protected final Class<?> testClass; 200 protected final Method method; 201 202 protected VogarJUnitTest(Class<?> testClass, Method method) { 203 this.testClass = testClass; 204 this.method = method; 205 } 206 207 public void run() throws Throwable { 208 Object testCase = getTestCase(); 209 Throwable failure = null; 210 211 try { 212 Class.forName("org.mockito.MockitoAnnotations") 213 .getMethod("initMocks", Object.class) 214 .invoke(null, testCase); 215 } catch (Exception ignored) { 216 } 217 218 try { 219 invokeMethodWithAnnotation(testCase, BeforeClass.class); 220 invokeMethodWithAnnotation(testCase, Before.class); 221 method.invoke(testCase); 222 } catch (InvocationTargetException t) { 223 failure = t.getCause(); 224 } catch (Throwable t) { 225 failure = t; 226 } 227 228 try { 229 invokeMethodWithAnnotation(testCase, After.class); 230 } catch (InvocationTargetException t) { 231 if (failure == null) { 232 failure = t.getCause(); 233 } 234 } catch (Throwable t) { 235 if (failure == null) { 236 failure = t; 237 } 238 } 239 240 try { 241 invokeMethodWithAnnotation(testCase, AfterClass.class); 242 } catch (InvocationTargetException t) { 243 if (failure == null) { 244 failure = t.getCause(); 245 } 246 } catch (Throwable t) { 247 if (failure == null) { 248 failure = t; 249 } 250 } 251 252 if (!meetsExpectations(failure, method)) { 253 if (failure == null) { 254 throw new AssertionFailedError("Expected exception not thrown"); 255 } else { 256 throw failure; 257 } 258 } 259 } 260 261 private void invokeMethodWithAnnotation(Object testCase, Class<?> annotation) 262 throws IllegalAccessException, InvocationTargetException { 263 for (Method m : testCase.getClass().getDeclaredMethods()) { 264 for (Annotation a : m.getAnnotations()) { 265 if (annotation.isAssignableFrom(a.annotationType())) { 266 m.invoke(testCase); 267 } 268 } 269 } 270 } 271 272 protected boolean meetsExpectations(Throwable failure, Method method) { 273 Class<?> expected = null; 274 for (Annotation a : method.getAnnotations()) { 275 if (org.junit.Test.class.isAssignableFrom(a.annotationType())) { 276 expected = ((org.junit.Test) a).expected(); 277 } 278 } 279 return expected == null || org.junit.Test.None.class.isAssignableFrom(expected) 280 ? (failure == null) 281 : (failure != null && expected.isAssignableFrom(failure.getClass())); 282 } 283 284 protected abstract Object getTestCase() throws Exception; 285 } 286 287 /** 288 * A JUnit TestCase constructed on demand and then released. 289 */ 290 private static class TestMethod extends VogarJUnitTest { 291 private final Constructor<?> constructor; 292 private final Object[] constructorArgs; 293 294 private TestMethod(Class<?> testClass, Method method, 295 Constructor<?> constructor, Object[] constructorArgs) { 296 super(testClass, method); 297 this.constructor = constructor; 298 this.constructorArgs = constructorArgs; 299 } 300 301 public static VogarTest create(Class<?> testClass, Method method, 302 Object[] constructorArgs) { 303 if (constructorArgs != null) { 304 for (Constructor<?> c : testClass.getConstructors()) { 305 if (c.getParameterTypes().length == constructorArgs.length) { 306 return new TestMethod(testClass, method, c, constructorArgs); 307 } 308 } 309 310 return new ConfigurationError(testClass.getName() + "#" + method.getName(), 311 new Exception("Parameterized test cases must have " 312 + constructorArgs.length + " arg constructor")); 313 } 314 315 try { 316 return new TestMethod(testClass, method, testClass.getConstructor(), null); 317 } catch (NoSuchMethodException ignored) { 318 } 319 try { 320 return new TestMethod(testClass, method, 321 testClass.getConstructor(String.class), new Object[] { method.getName() }); 322 } catch (NoSuchMethodException ignored) { 323 } 324 325 return new ConfigurationError(testClass.getName() + "#" + method.getName(), 326 new Exception("Test cases must have a no-arg or string constructor.")); 327 } 328 329 @Override protected Object getTestCase() throws Exception { 330 return constructor.newInstance(constructorArgs); 331 } 332 333 @Override public String toString() { 334 return testClass.getName() + "#" + method.getName(); 335 } 336 } 337 338 private static class IgnoredTest extends VogarJUnitTest { 339 private IgnoredTest(Class<?> testClass, Method method) { 340 super(testClass, method); 341 } 342 343 @Override public void run() throws Throwable { 344 System.out.println("@Ignored."); 345 } 346 347 @Override protected Object getTestCase() { 348 throw new UnsupportedOperationException(); 349 } 350 351 @Override public String toString() { 352 return testClass.getName() + "#" + method.getName(); 353 } 354 } 355 } 356