package com.xtremelabs.robolectric.bytecode; import com.xtremelabs.robolectric.Robolectric; import com.xtremelabs.robolectric.internal.Implementation; import com.xtremelabs.robolectric.internal.Implements; import com.xtremelabs.robolectric.util.Join; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; public class RobolectricWiringTest { private List mismatches; @Before public void setUp() throws Exception { mismatches = new ArrayList(); } @Test public void testAllImplementationMethodsHaveCorrectSignature() throws Exception { for (Class shadowClass : Robolectric.getDefaultShadowClasses()) { verifyClass(shadowClass); } Assert.assertEquals("@Implementation method mismatch: " + Join.join("\n", mismatches), 0, mismatches.size()); } private void verifyClass(final Class shadowClass) { Implements annotation = shadowClass.getAnnotation(Implements.class); Class implementedClass = annotation.value(); try { shadowClass.getConstructor(implementedClass); } catch (NoSuchMethodException e) { try { shadowClass.getConstructor(); } catch (NoSuchMethodException e1) { mismatches.add("Missing constructor for " + shadowClass.getSimpleName()); } } for (Method shadowMethod : shadowClass.getDeclaredMethods()) { verifyMethod(implementedClass, shadowMethod); } } private void verifyMethod(Class implementedClass, Method shadowMethod) { Member implementedMember; boolean isConstructor = shadowMethod.getName().equals("__constructor__"); if (isAnnotatedImplementation(shadowMethod) || isConstructor) { if (isConstructor) { implementedMember = findConstructor(implementedClass, shadowMethod); } else { implementedMember = findMethod(implementedClass, shadowMethod); } if (implementedMember == null) { mismatches.add(shadowMethod.toGenericString() + " doesn't match a real method"); } else if (staticMismatch(shadowMethod, implementedMember)) { mismatches.add(shadowMethod.toGenericString() + " doesn't match the staticness of the real method"); } if (!Modifier.isPublic(shadowMethod.getModifiers())) { mismatches.add(shadowMethod.toGenericString() + " should be public"); } } else { implementedMember = findMethod(implementedClass, shadowMethod); if (implementedMember != null) { mismatches.add(shadowMethod.toGenericString() + " should be annotated @Implementation"); } } } private boolean isAnnotatedImplementation(Method shadowMethod) { // works around a weird bug causing overridden methods to show no annotations try { return shadowMethod.getDeclaringClass().getDeclaredMethod(shadowMethod.getName(), shadowMethod.getParameterTypes()).isAnnotationPresent(Implementation.class); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } private Member findConstructor(Class implementedClass, Method shadowMethod) { Class[] parameterTypes = shadowMethod.getParameterTypes(); try { return implementedClass.getConstructor(parameterTypes); } catch (NoSuchMethodException e1) { try { return implementedClass.getDeclaredConstructor(parameterTypes); } catch (NoSuchMethodException e2) { return null; } } } private Member findMethod(Class implementedClass, Method shadowMethod) { Class[] parameterTypes = shadowMethod.getParameterTypes(); String methodName = shadowMethod.getName(); try { return implementedClass.getMethod(methodName, parameterTypes); } catch (NoSuchMethodException e1) { try { return implementedClass.getDeclaredMethod(methodName, parameterTypes); } catch (NoSuchMethodException e2) { return null; } } } private boolean staticMismatch(Member shadowMethod, Member implementedMethod) { return Modifier.isStatic(implementedMethod.getModifiers()) != Modifier.isStatic(shadowMethod.getModifiers()); } }