Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Putative fix for issues/15 #38

Open
wants to merge 3 commits into
base: jmock2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions src/org/jmock/internal/matcher/MethodMatcher.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jmock.internal.matcher;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
Expand All @@ -12,12 +13,49 @@ public MethodMatcher(Method expectedMethod) {
super(Method.class);
this.expectedMethod = expectedMethod;
}


// @Override
// public boolean matchesSafely(Method m) {
// System.out.format("Expected %s, actual %s\n", expectedMethod, m);
// return expectedMethod.equals(m);
// }

@Override
public boolean matchesSafely(Method m) {
return expectedMethod.equals(m);
System.out.format("Expected %s, actual %s\n", expectedMethod, m);
if (expectedMethod.equals(m))
return true;
if (!expectedMethod.getName().equals(m.getName()))
return false;
if (!areImplementingInterfaces(expectedMethod.getDeclaringClass(), m.getDeclaringClass()))
return false;
if (!(Modifier.isAbstract(expectedMethod.getModifiers()) && Modifier.isAbstract(m.getModifiers())))
return false;
if (!m.getReturnType().isAssignableFrom(expectedMethod.getReturnType()))
return false;
if (!parametersAssignable(expectedMethod, m))
return false;
return true;
}


private boolean areImplementingInterfaces(Class<?> expectedClass, Class<?> actualClass) {
if (!actualClass.isAssignableFrom(expectedClass))
return false;
if (!(actualClass.isInterface() && expectedMethod.getDeclaringClass().isInterface()))
return false;
return true;
}

private boolean parametersAssignable(Method m1, Method m2) {
if (m1.getParameterTypes().length != m2.getParameterTypes().length)
return false;
for (int i = 0; i < m2.getParameterTypes().length; i++) {
if (!m2.getParameterTypes()[i].isAssignableFrom(m2.getParameterTypes()[i]))
return false;
}
return true;
}

@Override
protected void describeMismatchSafely(Method m, Description mismatchDescription) {
mismatchDescription.appendText("was ").appendText(m.getName());
Expand Down
23 changes: 19 additions & 4 deletions src/org/jmock/lib/legacy/ClassImposteriser.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,24 @@ public class ClassImposteriser implements Imposteriser {
public static final Imposteriser INSTANCE = new ClassImposteriser();

private ClassImposteriser() {}


private static final Method FINALIZE_METHOD = findFinalizeMethod();

private static final NamingPolicy NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES = new DefaultNamingPolicy() {
@Override
public String getClassName(String prefix, String source, Object key, Predicate names) {
return "org.jmock.codegen." + super.getClassName(prefix, source, key, names);
}
};

private static final CallbackFilter IGNORE_BRIDGE_METHODS = new CallbackFilter() {
private static final CallbackFilter IGNORED_METHODS = new CallbackFilter() {
public int accept(Method method) {
return method.isBridge() ? 1 : 0;
if (method.isBridge())
return 1;
else if (method.equals(FINALIZE_METHOD))
return 1;
else
return 0;
}
};

Expand Down Expand Up @@ -105,7 +112,7 @@ protected void filterConstructors(Class sc, List constructors) {
enhancer.setInterfaces(ancilliaryTypes);
}
enhancer.setCallbackTypes(new Class[]{InvocationHandler.class, NoOp.class});
enhancer.setCallbackFilter(IGNORE_BRIDGE_METHODS);
enhancer.setCallbackFilter(IGNORED_METHODS);
if (mockedType.getSigners() != null) {
enhancer.setNamingPolicy(NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES);
}
Expand Down Expand Up @@ -140,6 +147,14 @@ private Class<?>[] prepend(Class<?> first, Class<?>... rest) {
System.arraycopy(rest, 0, all, 1, rest.length);
return all;
}

private static Method findFinalizeMethod() {
try {
return Object.class.getDeclaredMethod("finalize");
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Could not find finalize method on Object");
}
}

public static class ClassWithSuperclassToWorkAroundCglibBug {}
}
118 changes: 118 additions & 0 deletions test/org/jmock/test/acceptance/GenericInvocationAcceptanceTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package org.jmock.test.acceptance;

import org.jmock.Expectations;
import org.jmock.integration.junit4.JUnitRuleMockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class GenericInvocationAcceptanceTests
{
@Rule public JUnitRuleMockery context = new JUnitRuleMockery();

private static interface Function<F, T> {
T apply(F input);
}

private static interface StringFunction extends Function<String, String> {
@Override public String apply(String input);
}

private static abstract class AbstractStringFunction implements Function<String, String> {
@Override public abstract String apply(String input);
}

private static String apply(Function<String, String> f, String arg) {
return f.apply(arg);
}

@SuppressWarnings("unchecked")
private static Object objectApply(Function f, Object arg) {
return f.apply(arg);
}

@Test
public void plain_old_invocation()
{
@SuppressWarnings("unchecked")
final Function<String, String> f = context.mock(Function.class);

// NB - don't try to remove this duplication - the resulting erasure invalidates the tests
context.checking(new Expectations() {{
allowing(f).apply("alice");
will (returnValue("bob"));
}});
assertEquals("bob", f.apply("alice"));
assertEquals("bob", apply(f, "alice"));
assertEquals("bob", objectApply(f, "alice"));
}

@Test
public void invocation_of_overriden_class_method()
{
context.setImposteriser(ClassImposteriser.INSTANCE);
final AbstractStringFunction f = context.mock(AbstractStringFunction.class);

// NB - don't try to remove this duplication - the resulting erasure invalidates the tests
context.checking(new Expectations() {{
allowing(f).apply("alice");
will (returnValue("bob"));
}});
assertEquals("bob", f.apply("alice"));
assertEquals("bob", apply(f, "alice"));
assertEquals("bob", objectApply(f, "alice"));
}

@Test
public void invocation_of_overriden_interface_method_with_capture_of_generic_interface()
{
final Function<String, String> f = context.mock(StringFunction.class);

// NB - don't try to remove this duplication - the resulting erasure invalidates the tests
context.checking(new Expectations() {{
allowing(f).apply("alice");
will (returnValue("bob"));
}});
assertEquals("bob", f.apply("alice"));
assertEquals("bob", apply(f, "alice"));
assertEquals("bob", objectApply(f, "alice"));
}

@Test
public void invocation_of_overriden_interface_method_with_capture_of_concrete_interface()
{
final StringFunction f = context.mock(StringFunction.class);

// NB - don't try to remove this duplication - the resulting erasure invalidates the tests
context.checking(new Expectations() {{
allowing(f).apply("alice");
will (returnValue("bob"));
}});
assertEquals("bob", f.apply("alice"));
assertEquals("bob", apply(f, "alice"));
assertEquals("bob", objectApply(f, "alice"));
}

@Test
@Ignore("Fails, and cure might be worse than the disease?")
public void invocation_of_overriden_interface_method_with_capture_via_function()
{
final StringFunction f = context.mock(StringFunction.class);

expectApply(f, "alice", "bob");

assertEquals("bob", f.apply("alice"));
assertEquals("bob", apply(f, "alice"));
assertEquals("bob", objectApply(f, "alice"));
}

private void expectApply(final Function<String, String> f, final String arg, final String result) {
context.checking(new Expectations() {{
allowing(f).apply(arg);
will (returnValue(result));
}});
}

}
61 changes: 49 additions & 12 deletions test/org/jmock/test/unit/lib/legacy/ClassImposteriserTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,25 @@
import junit.framework.TestCase;
import org.jmock.api.Action;
import org.jmock.api.Imposteriser;
import org.jmock.api.Invocation;
import org.jmock.api.Invokable;
import org.jmock.lib.action.ReturnValueAction;
import org.jmock.lib.action.VoidAction;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Test;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Date;

public class ClassImposteriserTests extends TestCase {
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

public class ClassImposteriserTests {
Action action = new ReturnValueAction("result");

Imposteriser imposteriser = ClassImposteriser.INSTANCE;
Expand Down Expand Up @@ -63,7 +72,8 @@ private AClassWithAPrivateConstructor(String someArgument) {}
public String foo() {return "original result";}
}

public void testCanImposteriseInterfacesAndNonFinalInstantiableClasses() {
@Test
public void canImposteriseInterfacesAndNonFinalInstantiableClasses() {
assertTrue("should report that it can imposterise interfaces",
imposteriser.canImposterise(Runnable.class));
assertTrue("should report that it can imposterise classes",
Expand All @@ -82,28 +92,32 @@ public void testCanImposteriseInterfacesAndNonFinalInstantiableClasses() {
!imposteriser.canImposterise(void.class));
}

public void testCanImposteriseAConcreteClassWithoutCallingItsConstructorOrInstanceInitialiserBlocks() {
@Test
public void canImposteriseAConcreteClassWithoutCallingItsConstructorOrInstanceInitialiserBlocks() {
ConcreteClassWithNastyConstructor imposter =
imposteriser.imposterise(action, ConcreteClassWithNastyConstructor.class);

assertEquals("result", imposter.foo());
}

public void testCanImposteriseAnInterface() {

@Test
public void canImposteriseAnInterface() {
AnInterface imposter =
imposteriser.imposterise(action, AnInterface.class);

assertEquals("result", imposter.foo());
}

public void testCanImposteriseAClassWithAPrivateConstructor() {

@Test
public void canImposteriseAClassWithAPrivateConstructor() {
AClassWithAPrivateConstructor imposter =
imposteriser.imposterise(action, AClassWithAPrivateConstructor.class);

assertEquals("result", imposter.foo());
}

public void testCanImposteriseAClassInASignedJarFile() throws Exception {

@Test
public void canImposteriseAClassInASignedJarFile() throws Exception {
File jarFile = new File("build/testdata/signed.jar");

assertTrue("Signed JAR file does not exist (use Ant to build it", jarFile.exists());
Expand All @@ -125,7 +139,8 @@ public final String toString() {
}

// See issue JMOCK-150
public void testCannotImposteriseAClassWithAFinalToStringMethod() {
@Test
public void cannotImposteriseAClassWithAFinalToStringMethod() {
assertTrue("should not be able to imposterise it", !imposteriser.canImposterise(ClassWithFinalToStringMethod.class));

try {
Expand All @@ -136,15 +151,37 @@ public void testCannotImposteriseAClassWithAFinalToStringMethod() {

}
}


// See issue JMOCK-256 (Github #36)
@Test
public void doesntDelegateFinalizeMethod() throws Exception {
Invokable failIfInvokedAction = new Invokable() {
@Override
public Object invoke(Invocation invocation) throws Throwable {
fail("invocation should not have happened");
return null;
}
};

Object imposter = imposteriser.imposterise(failIfInvokedAction, Object.class);
invokeMethod(imposter, Object.class.getDeclaredMethod("finalize"));
}

public interface EmptyInterface {}

// See issue JMOCK-145
public void testWorksAroundBugInCglibWhenAskedToImposteriseObject() {
@Test
public void worksAroundBugInCglibWhenAskedToImposteriseObject() {
imposteriser.imposterise(new VoidAction(), Object.class);

imposteriser.imposterise(new VoidAction(), Object.class, EmptyInterface.class);

imposteriser.imposterise(new VoidAction(), Object.class, AnInterface.class);
}

private Object invokeMethod(Object object, Method method, Object... args) throws IllegalAccessException, InvocationTargetException {
method.setAccessible(true);
return method.invoke(object, args);
}

}