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