Home | History | Annotate | Download | only in junit
      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