| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package vogar.target.junit; |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import junit.framework.AssertionFailedError; |
| import org.junit.After; |
| import org.junit.AfterClass; |
| import org.junit.Before; |
| import org.junit.BeforeClass; |
| import org.junit.Ignore; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| import org.junit.runners.Suite; |
| import org.junit.runners.Suite.SuiteClasses; |
| |
| /** |
| * Utilities for manipulating JUnit4 tests. |
| */ |
| public final class Junit4 { |
| private Junit4() {} |
| |
| /** |
| * Creates lazy vogar test instances from the given test case or test |
| * suite. |
| * |
| * @param methodNames if non-empty, this is the list of test method names. |
| */ |
| static List<VogarTest> classToVogarTests(Class<?> testClass, Collection<String> methodNames) { |
| List<VogarTest> result = new ArrayList<>(); |
| getSuiteMethods(result, testClass, methodNames); |
| return result; |
| } |
| |
| private static void getSuiteMethods( |
| List<VogarTest> out, Class<?> testClass, Collection<String> methodNames) { |
| |
| // Try the suites first, if we find one then exit as suites take priority. |
| boolean isJunit4TestClass = getSuiteTests(out, testClass); |
| if (isJunit4TestClass) { |
| return; |
| } |
| |
| Collection<Object[]> argCollection = findParameters(testClass); |
| |
| /* JUnit 4.x: methods marked with @Test annotation. */ |
| if (methodNames.isEmpty()) { |
| for (Method m : testClass.getMethods()) { |
| if (!m.isAnnotationPresent(org.junit.Test.class)) continue; |
| |
| isJunit4TestClass = true; |
| |
| addTest(out, testClass, m, argCollection); |
| } |
| } else { |
| for (String methodName : methodNames) { |
| try { |
| Method m = testClass.getMethod(methodName); |
| if (!m.isAnnotationPresent(org.junit.Test.class)) continue; |
| |
| isJunit4TestClass = true; |
| |
| addTest(out, testClass, m, argCollection); |
| } catch (final NoSuchMethodException e) { |
| // Check to see if any method with the specified name exists. |
| Throwable cause = null; |
| for (Method m : testClass.getMethods()) { |
| if (m.getName().equals(methodName)) { |
| cause = new Exception("Method " + methodName |
| + " should have no parameters"); |
| break; |
| } |
| } |
| if (cause == null) { |
| cause = new AssertionFailedError("Method \"" + methodName + "\" not found"); |
| } |
| ConfigurationError error = new ConfigurationError( |
| testClass.getName() + "#" + methodName, |
| cause); |
| out.add(error); |
| |
| // An error happened so just treat this as a JUnit4 test otherwise it will end |
| // up producing another error below. |
| isJunit4TestClass = true; |
| } |
| } |
| } |
| |
| if (!isJunit4TestClass) { |
| out.add(new ConfigurationError(testClass.getName(), |
| new IllegalStateException("Not a test case: " + testClass))); |
| } |
| } |
| |
| private static void addTest(List<VogarTest> out, Class<?> testClass, Method m, Collection<Object[]> argCollection) { |
| if (m.isAnnotationPresent(Ignore.class)) { |
| // Ignore the test. |
| } else if (m.getParameterTypes().length == 0) { |
| addAllParameterizedTests(out, testClass, m, argCollection); |
| } else { |
| String name = m.getName(); |
| out.add(new ConfigurationError(testClass.getName() + "#" + name, |
| new Exception("Method " + name + " should have no parameters"))); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static Collection<Object[]> findParameters(Class<?> testClass) { |
| for (Method m : testClass.getMethods()) { |
| for (Annotation a : m.getAnnotations()) { |
| if (Parameters.class.isAssignableFrom(a.annotationType())) { |
| try { |
| return (Collection<Object[]>) m.invoke(testClass); |
| } catch (Exception ignored) { |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| private static void addAllParameterizedTests(List<VogarTest> out, Class<?> testClass, Method m, |
| Collection<Object[]> argCollection) { |
| if (argCollection == null) { |
| out.add(TestMethod.create(testClass, m, null)); |
| } else { |
| for (Object[] args : argCollection) { |
| out.add(TestMethod.create(testClass, m, args)); |
| } |
| } |
| } |
| |
| public static boolean isJunit4Test(Class<?> klass) { |
| boolean isTestSuite = false; |
| boolean hasSuiteClasses = false; |
| |
| boolean concrete = !Modifier.isAbstract(klass.getModifiers()); |
| |
| // @RunWith(Suite.class) |
| // @SuiteClasses( ... ) |
| // public class MyTest { ... } |
| // or |
| // @RunWith(Parameterized.class) |
| // public class MyTest { ... } |
| for (Annotation a : klass.getAnnotations()) { |
| Class<?> annotationClass = a.annotationType(); |
| |
| if (RunWith.class.isAssignableFrom(annotationClass)) { |
| Class<?> runnerClass = ((RunWith) a).value(); |
| if (Suite.class.isAssignableFrom(runnerClass)) { |
| isTestSuite = true; |
| } else if (Parameterized.class.isAssignableFrom(runnerClass)) { |
| // @Parameterized test classes are instantiated so must be concrete. |
| return concrete; |
| } |
| } else if (Suite.SuiteClasses.class.isAssignableFrom(annotationClass)) { |
| hasSuiteClasses = true; |
| } |
| |
| if (isTestSuite && hasSuiteClasses) { |
| // This doesn't instantiate the class so doesn't care if it's abstract. |
| return true; |
| } |
| } |
| |
| // Test classes that have methods annotated with @Test are instantiated so must be concrete. |
| if (!concrete) { |
| return false; |
| } |
| |
| // public class MyTest { |
| // @Test |
| // public void example() { ... } |
| // } |
| for (Method m : klass.getDeclaredMethods()) { |
| for (Annotation a : m.getAnnotations()) { |
| if (org.junit.Test.class.isAssignableFrom(a.annotationType())) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| private static boolean getSuiteTests(List<VogarTest> out, Class<?> suite) { |
| boolean isSuite = false; |
| |
| /* Check for @RunWith(Suite.class) */ |
| for (Annotation a : suite.getAnnotations()) { |
| if (RunWith.class.isAssignableFrom(a.annotationType())) { |
| if (Suite.class.isAssignableFrom(((RunWith) a).value())) { |
| isSuite = true; |
| } |
| break; |
| } |
| } |
| |
| if (!isSuite) { |
| return false; |
| } |
| |
| /* Extract classes to run */ |
| for (Annotation a : suite.getAnnotations()) { |
| if (SuiteClasses.class.isAssignableFrom(a.annotationType())) { |
| for (Class<?> clazz : ((SuiteClasses) a).value()) { |
| getSuiteMethods(out, clazz, Collections.<String>emptySet()); |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| private abstract static class VogarJUnitTest implements VogarTest { |
| protected final Class<?> testClass; |
| protected final Method method; |
| |
| protected VogarJUnitTest(Class<?> testClass, Method method) { |
| this.testClass = testClass; |
| this.method = method; |
| } |
| |
| public void run() throws Throwable { |
| Object testCase = getTestCase(); |
| Throwable failure = null; |
| |
| try { |
| Class.forName("org.mockito.MockitoAnnotations") |
| .getMethod("initMocks", Object.class) |
| .invoke(null, testCase); |
| } catch (Exception ignored) { |
| } |
| |
| try { |
| invokeMethodWithAnnotation(testCase, BeforeClass.class); |
| invokeMethodWithAnnotation(testCase, Before.class); |
| method.invoke(testCase); |
| } catch (InvocationTargetException t) { |
| failure = t.getCause(); |
| } catch (Throwable t) { |
| failure = t; |
| } |
| |
| try { |
| invokeMethodWithAnnotation(testCase, After.class); |
| } catch (InvocationTargetException t) { |
| if (failure == null) { |
| failure = t.getCause(); |
| } |
| } catch (Throwable t) { |
| if (failure == null) { |
| failure = t; |
| } |
| } |
| |
| try { |
| invokeMethodWithAnnotation(testCase, AfterClass.class); |
| } catch (InvocationTargetException t) { |
| if (failure == null) { |
| failure = t.getCause(); |
| } |
| } catch (Throwable t) { |
| if (failure == null) { |
| failure = t; |
| } |
| } |
| |
| if (!meetsExpectations(failure, method)) { |
| if (failure == null) { |
| throw new AssertionFailedError("Expected exception not thrown"); |
| } else { |
| throw failure; |
| } |
| } |
| } |
| |
| private void invokeMethodWithAnnotation(Object testCase, Class<?> annotation) |
| throws IllegalAccessException, InvocationTargetException { |
| for (Method m : testCase.getClass().getDeclaredMethods()) { |
| for (Annotation a : m.getAnnotations()) { |
| if (annotation.isAssignableFrom(a.annotationType())) { |
| m.invoke(testCase); |
| } |
| } |
| } |
| } |
| |
| protected boolean meetsExpectations(Throwable failure, Method method) { |
| Class<?> expected = null; |
| for (Annotation a : method.getAnnotations()) { |
| if (org.junit.Test.class.isAssignableFrom(a.annotationType())) { |
| expected = ((org.junit.Test) a).expected(); |
| } |
| } |
| return expected == null || org.junit.Test.None.class.isAssignableFrom(expected) |
| ? (failure == null) |
| : (failure != null && expected.isAssignableFrom(failure.getClass())); |
| } |
| |
| protected abstract Object getTestCase() throws Throwable; |
| } |
| |
| /** |
| * A JUnit TestCase constructed on demand and then released. |
| */ |
| private static class TestMethod extends VogarJUnitTest { |
| private final Constructor<?> constructor; |
| private final Object[] constructorArgs; |
| |
| private TestMethod(Class<?> testClass, Method method, |
| Constructor<?> constructor, Object[] constructorArgs) { |
| super(testClass, method); |
| this.constructor = constructor; |
| this.constructorArgs = constructorArgs; |
| } |
| |
| public static VogarTest create(Class<?> testClass, Method method, |
| Object[] constructorArgs) { |
| if (constructorArgs != null) { |
| for (Constructor<?> c : testClass.getConstructors()) { |
| if (c.getParameterTypes().length == constructorArgs.length) { |
| return new TestMethod(testClass, method, c, constructorArgs); |
| } |
| } |
| |
| return new ConfigurationError(testClass.getName() + "#" + method.getName(), |
| new Exception("Parameterized test cases must have " |
| + constructorArgs.length + " arg constructor")); |
| } |
| |
| try { |
| return new TestMethod(testClass, method, testClass.getConstructor(), null); |
| } catch (NoSuchMethodException ignored) { |
| } |
| try { |
| return new TestMethod(testClass, method, |
| testClass.getConstructor(String.class), new Object[] { method.getName() }); |
| } catch (NoSuchMethodException ignored) { |
| } |
| |
| return new ConfigurationError(testClass.getName() + "#" + method.getName(), |
| new Exception("Test cases must have a no-arg or string constructor.")); |
| } |
| |
| @Override protected Object getTestCase() throws Throwable { |
| try { |
| return constructor.newInstance(constructorArgs); |
| } catch (InvocationTargetException e) { |
| throw e.getCause(); |
| } |
| } |
| |
| @Override public String toString() { |
| return testClass.getName() + "#" + method.getName(); |
| } |
| } |
| |
| private static class IgnoredTest extends VogarJUnitTest { |
| private IgnoredTest(Class<?> testClass, Method method) { |
| super(testClass, method); |
| } |
| |
| @Override public void run() throws Throwable { |
| System.out.println("@Ignored."); |
| } |
| |
| @Override protected Object getTestCase() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override public String toString() { |
| return testClass.getName() + "#" + method.getName(); |
| } |
| } |
| } |