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.reflect.Constructor; 20 import java.lang.reflect.InvocationTargetException; 21 import java.lang.reflect.Method; 22 import java.lang.reflect.Modifier; 23 import java.util.ArrayList; 24 import java.util.Collection; 25 import java.util.Collections; 26 import java.util.Enumeration; 27 import java.util.List; 28 29 import junit.framework.AssertionFailedError; 30 import junit.framework.Test; 31 import junit.framework.TestCase; 32 import junit.framework.TestSuite; 33 import vogar.ClassAnalyzer; 34 35 /** 36 * Utilities for manipulating JUnit tests. 37 */ 38 public final class Junit3 { 39 private Junit3() {} 40 41 private static final Method setUp; 42 private static final Method tearDown; 43 private static final Method runTest; 44 static { 45 try { 46 setUp = TestCase.class.getDeclaredMethod("setUp"); 47 setUp.setAccessible(true); 48 tearDown = TestCase.class.getDeclaredMethod("tearDown"); 49 tearDown.setAccessible(true); 50 runTest = TestCase.class.getDeclaredMethod("runTest"); 51 runTest.setAccessible(true); 52 } catch (NoSuchMethodException e) { 53 throw new AssertionError(); 54 } 55 } 56 57 /** 58 * Creates eager JUnit Test instances from the given test case or test 59 * suite. 60 */ 61 public static List<Test> classToJunitTests(Class<?> testClass) { 62 try { 63 try { 64 Method suiteMethod = testClass.getMethod("suite"); 65 return Collections.singletonList((Test) suiteMethod.invoke(null)); 66 } catch (NoSuchMethodException ignored) { 67 } 68 69 if (TestCase.class.isAssignableFrom(testClass)) { 70 List<Test> result = new ArrayList<Test>(); 71 for (Method m : testClass.getMethods()) { 72 if (!m.getName().startsWith("test")) { 73 continue; 74 } 75 if (m.getParameterTypes().length == 0) { 76 TestCase testCase = (TestCase) testClass.newInstance(); 77 testCase.setMethod(m); 78 result.add(testCase); 79 } else { 80 // TODO: warn 81 } 82 } 83 return result; 84 } 85 } catch (Exception e) { 86 throw new RuntimeException(e); 87 } 88 89 throw new IllegalArgumentException("Unknown test class: " + testClass); 90 } 91 92 /** 93 * Creates lazy vogar test instances from the given test case or test 94 * suite. 95 * 96 * @param methodNames if non-empty, this is the list of test method names. 97 */ 98 static List<VogarTest> classToVogarTests(Class<?> testClass, Collection<String> methodNames) { 99 List<VogarTest> result = new ArrayList<VogarTest>(); 100 getSuiteMethods(result, testClass, methodNames); 101 return result; 102 } 103 104 public static boolean isJunit3Test(Class<?> klass) { 105 // public class FooTest extends TestCase {...} 106 // or 107 // public class FooSuite { 108 // public static Test suite() {...} 109 // } 110 return (TestCase.class.isAssignableFrom(klass) && !Modifier.isAbstract(klass.getModifiers())) 111 || new ClassAnalyzer(klass).hasMethod(true, Test.class, "suite"); 112 } 113 114 private static void getSuiteMethods( 115 List<VogarTest> out, Class<?> testClass, Collection<String> methodNames) { 116 /* 117 * Handle classes assignable to TestCase 118 */ 119 if (TestCase.class.isAssignableFrom(testClass)) { 120 @SuppressWarnings("unchecked") 121 Class<? extends TestCase> testCaseClass = (Class<? extends TestCase>) testClass; 122 123 if (methodNames.isEmpty()) { 124 for (Method m : testClass.getMethods()) { 125 if (!m.getName().startsWith("test")) { 126 continue; 127 } 128 if (m.getParameterTypes().length == 0) { 129 out.add(TestMethod.create(testCaseClass, m)); 130 } else { 131 // TODO: warn 132 } 133 } 134 } else { 135 for (String methodName : methodNames) { 136 try { 137 out.add(TestMethod.create(testCaseClass, testClass.getMethod(methodName))); 138 } catch (final NoSuchMethodException e) { 139 AssertionFailedError cause = new AssertionFailedError( 140 "Method \"" + methodName + "\" not found"); 141 ConfigurationError error = new ConfigurationError( 142 testClass.getName() + "#" + methodName, 143 cause); 144 out.add(error); 145 } 146 } 147 } 148 149 return; 150 } 151 152 /* 153 * Handle classes that define suite() 154 */ 155 try { 156 Method suiteMethod = testClass.getMethod("suite"); 157 junit.framework.Test test; 158 try { 159 test = (junit.framework.Test) suiteMethod.invoke(null); 160 } catch (Throwable e) { 161 out.add(new ConfigurationError(testClass.getName() + "#suite", e)); 162 return; 163 } 164 165 if (test instanceof TestCase) { 166 getTestCaseTest(out, (TestCase) test, methodNames); 167 } else if (test instanceof TestSuite) { 168 getTestSuiteTests(out, (TestSuite) test, methodNames); 169 } else { 170 out.add(new ConfigurationError(testClass.getName() + "#suite", 171 new IllegalStateException("Unknown suite() result: " + test))); 172 } 173 return; 174 } catch (NoSuchMethodException ignored) { 175 } 176 177 out.add(new ConfigurationError(testClass.getName() + "#suite", 178 new IllegalStateException("Not a test case: " + testClass))); 179 } 180 181 private static void getTestSuiteTests(List<VogarTest> out, TestSuite suite, 182 Collection<String> methodNames) { 183 for (Object testsOrSuite : getTestsAndSuites(suite)) { 184 if (testsOrSuite instanceof Class) { 185 getSuiteMethods(out, (Class<?>) testsOrSuite, methodNames); 186 } else if (testsOrSuite instanceof TestCase) { 187 getTestCaseTest(out, (TestCase) testsOrSuite, methodNames); 188 } else if (testsOrSuite instanceof TestSuite) { 189 getTestSuiteTests(out, (TestSuite) testsOrSuite, methodNames); 190 } else if (testsOrSuite != null) { 191 out.add(new ConfigurationError(testsOrSuite.getClass().getName() + "#getClass", 192 new IllegalStateException("Unknown test: " + testsOrSuite))); 193 } 194 } 195 } 196 197 private static List<Object> getTestsAndSuites(TestSuite suite) { 198 try { 199 return suite.getTestsAndSuites(); 200 } catch (NoSuchMethodError e) { 201 // This is running with the standard JUnit TestSuite class and not the Vogar specific 202 // one so the getTestsAndSuites() method is not available. Fall back to using the 203 // tests() method. 204 if (!e.getMessage().contains("getTestsAndSuites()Ljava/util/List;")) { 205 throw e; 206 } 207 208 // Create a list from the enumeration, cannot use Collections.list() without a lot of 209 // ugly casting. 210 Enumeration<?> enumeration = suite.tests(); 211 List<Object> tests = new ArrayList<Object>(); 212 while (enumeration.hasMoreElements()) { 213 tests.add(enumeration.nextElement()); 214 } 215 return tests; 216 } 217 } 218 219 220 private static void getTestCaseTest(List<VogarTest> out, TestCase testCase, 221 Collection<String> methodNames) { 222 if (methodNames.isEmpty() || methodNames.contains(testCase.getName())) { 223 try { 224 out.add(new TestCaseInstance(testCase, testCase.getMethod())); 225 } catch (NoSuchMethodError e) { 226 if (!e.getMessage().contains("getMethod()Ljava/lang/reflect/Method;")) { 227 throw e; 228 } 229 230 out.add(new TestCaseInstance(testCase, runTest)); 231 } 232 } 233 } 234 235 private abstract static class VogarJUnitTest implements VogarTest { 236 protected final Class<? extends TestCase> testClass; 237 protected final Method method; 238 239 protected VogarJUnitTest(Class<? extends TestCase> testClass, Method method) { 240 this.testClass = testClass; 241 this.method = method; 242 } 243 244 public void run() throws Throwable { 245 TestCase testCase = getTestCase(); 246 Throwable failure = null; 247 try { 248 setUp.invoke(testCase); 249 method.invoke(testCase); 250 } catch (InvocationTargetException t) { 251 failure = t.getCause(); 252 } catch (Throwable t) { 253 failure = t; 254 } 255 256 try { 257 tearDown.invoke(testCase); 258 } catch (InvocationTargetException t) { 259 if (failure == null) { 260 failure = t.getCause(); 261 } 262 } catch (Throwable t) { 263 if (failure == null) { 264 failure = t; 265 } 266 } 267 268 if (failure != null) { 269 throw failure; 270 } 271 } 272 273 protected abstract TestCase getTestCase() throws Exception; 274 } 275 276 /** 277 * A JUnit TestCase constructed on demand and then released. 278 */ 279 private static class TestMethod extends VogarJUnitTest { 280 private final Constructor<? extends TestCase> constructor; 281 private final Object[] constructorArgs; 282 283 private TestMethod(Class<? extends TestCase> testClass, Method method, 284 Constructor<? extends TestCase> constructor, Object[] constructorArgs) { 285 super(testClass, method); 286 this.constructor = constructor; 287 this.constructorArgs = constructorArgs; 288 } 289 290 public static VogarTest create(Class<? extends TestCase> testClass, Method method) { 291 try { 292 return new TestMethod(testClass, method, testClass.getConstructor(), new Object[0]); 293 } catch (NoSuchMethodException ignored) { 294 } 295 try { 296 return new TestMethod(testClass, method, testClass.getConstructor(String.class), 297 new Object[] { method.getName() }); 298 } catch (NoSuchMethodException ignored) { 299 } 300 return new ConfigurationError(testClass.getName() + "#" + method.getName(), 301 new Exception("Test cases must have a no-arg or string constructor.")); 302 } 303 304 @Override protected TestCase getTestCase() throws Exception { 305 TestCase testCase = constructor.newInstance(constructorArgs); 306 // If the test case used the no argument constructor then make sure to set its name 307 // correctly. 308 if (constructor.getParameterTypes().length == 0) { 309 testCase.setName(method.getName()); 310 } 311 return testCase; 312 } 313 314 @Override public String toString() { 315 return testClass.getName() + "#" + method.getName(); 316 } 317 } 318 319 /** 320 * A JUnit TestCase already constructed. 321 */ 322 private static class TestCaseInstance extends VogarJUnitTest { 323 private final TestCase testCase; 324 325 private TestCaseInstance(TestCase testCase, Method method) { 326 super(testCase.getClass(), method); 327 this.testCase = testCase; 328 } 329 330 @Override protected TestCase getTestCase() throws Exception { 331 return testCase; 332 } 333 334 @Override public String toString() { 335 return testCase.getClass().getName() + "#" + testCase.getName(); 336 } 337 } 338 } 339