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