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.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