greyblue9 / dexmaker

Automatically exported from code.google.com/p/dexmaker
1 stars 0 forks source link

Cannot mock class with final methods #7

Closed GoogleCodeExporter closed 8 years ago

GoogleCodeExporter commented 8 years ago
*What steps will reproduce the problem?
1. Attempt to mock OrmLiteSqliteOpenHelper 
http://ormlite.com/javadoc/ormlite-android/com/j256/ormlite/android/apptools/Orm
LiteSqliteOpenHelper.html
2. Crash with a jvm error

*What is the expected output? What do you see instead?
Would expect the mock to work as long as the final method isn't called (I'm not 
super familiar with mockito on normal java, so I might be mistaken?). Instead 
get the following errors logged. 

W/dalvikvm( 1484): Method LDatabaseHelper_Proxy;.onCreate overrides final 
Lcom/j256/ormlite/android/apptools/OrmLiteSqliteOpenHelper;.onCreate
W/dalvikvm( 1484): failed creating vtable
W/dalvikvm( 1484): Link of class 'LDatabaseHelper_Proxy;' failed

I/TestRunner( 1484): java.lang.VirtualMachineError
I/TestRunner( 1484):    at dalvik.system.DexFile.defineClass(Native Method)
I/TestRunner( 1484):    at 
dalvik.system.DexFile.loadClassBinaryName(DexFile.java:195)
I/TestRunner( 1484):    at 
dalvik.system.DexPathList.findClass(DexPathList.java:315)
I/TestRunner( 1484):    at 
dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:58)
I/TestRunner( 1484):    at java.lang.ClassLoader.loadClass(ClassLoader.java:501)
I/TestRunner( 1484):    at java.lang.ClassLoader.loadClass(ClassLoader.java:461)
I/TestRunner( 1484):    at 
com.google.dexmaker.stock.ProxyBuilder.loadClass(ProxyBuilder.java:272)
I/TestRunner( 1484):    at 
com.google.dexmaker.stock.ProxyBuilder.buildProxyClass(ProxyBuilder.java:254)
I/TestRunner( 1484):    at 
com.google.dexmaker.mockito.DexmakerMockMaker.createMock(DexmakerMockMaker.java:
51)
I/TestRunner( 1484):    at 
org.mockito.internal.util.MockUtil.createMock(MockUtil.java:41)
I/TestRunner( 1484):    at 
org.mockito.internal.MockitoCore.mock(MockitoCore.java:41)
I/TestRunner( 1484):    at org.mockito.Mockito.mock(Mockito.java:1061)
I/TestRunner( 1484):    at org.mockito.Mockito.mock(Mockito.java:955)

What version of the product are you using? On what operating system?
Using the code that shipped with the example you posted to the mockito mailing 
list. 

Please provide any additional information below.
I noticed that OrmLiteSqliteOpenHelper has two onCreate methods- one final, one 
not. I  tried creating a simple class with an overloaded method bar to match 
this, where one bar was final and the other not. I was successfully able to 
mock this test class. Not sure what the OrmLiteSqliteOpenHelper does 
differently. Have you seen this sort of issue before?  Let me know if you need 
any more details. 

Here is the code from my test class that i *was* able to mock
    public final void bar(int a)
    {
        System.out.println("bar");

    }

    public void bar(String b)
    {
        System.out.println(b);
    }

Original issue reported on code.google.com by MhaleK...@gmail.com on 14 May 2012 at 6:28

GoogleCodeExporter commented 8 years ago
I can't reproduce this. I was able to mock this class without problem:

    public static abstract class HasFinalAndAbstractOverloads {
        abstract void overload(String s);
        final void overload(String s, int i) {
        }
    }

If you can isolate what it is about OrmLiteSqliteOpenHelper that causes 
dexmaker to crash, I can fix the problem.

Original comment by jessewil...@google.com on 15 May 2012 at 2:22

GoogleCodeExporter commented 8 years ago
I managed to replicate the issue. 

/////////////////////////////////////

public abstract class AnAbstractClass {
    public abstract void bar(String s);
}

/////////////////////////////////////

public class ClassWithFinal extends AnAbstractClass 
{       
    public final void bar(String b)
    {
        System.out.println(b);
    }
}

/////////////////////////////////////
      public void testFinal()
      {
          Mockito.mock(ClassWithFinal.class);
      }
//////////////////////////////////////

If you run the above test, it will crash the same way that ORMLite does. I 
added in a second overloaded method named "void bar(int a)" but it made no 
difference on how the test ran.   Hopefully this helps narrow the cause down.

java.lang.VirtualMachineError
at dalvik.system.DexFile.defineClass(Native Method)
at dalvik.system.DexFile.loadClassBinaryName(DexFile.java:195)
at dalvik.system.DexPathList.findClass(DexPathList.java:315)
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:58)
at java.lang.ClassLoader.loadClass(ClassLoader.java:501)
at java.lang.ClassLoader.loadClass(ClassLoader.java:461)
at com.google.dexmaker.stock.ProxyBuilder.loadClass(ProxyBuilder.java:272)
at com.google.dexmaker.stock.ProxyBuilder.buildProxyClass(ProxyBuilder.java:254)
at 
com.google.dexmaker.mockito.DexmakerMockMaker.createMock(DexmakerMockMaker.java:
51)
at org.mockito.internal.util.MockUtil.createMock(MockUtil.java:41)
at org.mockito.internal.MockitoCore.mock(MockitoCore.java:41)
at org.mockito.Mockito.mock(Mockito.java:1061)
at org.mockito.Mockito.mock(Mockito.java:955)
at sandbox.test.ATest.testFinal(ATest.java:21)

Original comment by MhaleK...@gmail.com on 15 May 2012 at 6:17

GoogleCodeExporter commented 8 years ago
I modified ProxyBuilder.java. I adjusted the following two methods to fix the 
issue.  I can't run the unit tests though, so I can't cay for sure that I 
didn't break anything else with these changes. 

    private Method[] getMethodsToProxyRecursive() {
        Set<MethodSetEntry> methodsToProxy = new HashSet<MethodSetEntry>();

        List<Class<?>> classChain = new LinkedList<Class<?>>();
        for (Class<?> c = baseClass; c != null; c = c.getSuperclass()) {
            classChain.add(0, c);
        }

        for (Class<?> c: classChain) {
            getMethodsToProxy(methodsToProxy, c);
        }
        for (Class<?> c : interfaces) {
            getMethodsToProxy(methodsToProxy, c);
        }

        Method[] results = new Method[methodsToProxy.size()];
        int i = 0;
        for (MethodSetEntry entry : methodsToProxy) {
            results[i++] = entry.originalMethod;
        }
        return results;
    }

    private void getMethodsToProxy(Set<MethodSetEntry> sink, Class<?> c) {
        for (Method method : c.getDeclaredMethods()) {
            if ((method.getModifiers() & Modifier.FINAL) != 0) {
                // Skip final methods, we can't override them.
                sink.remove(new MethodSetEntry(method));
                continue;
            }
            if ((method.getModifiers() & STATIC) != 0) {
                // Skip static methods, overriding them has no effect.
                sink.remove(method);
                continue;
            }
            if (method.getName().equals("finalize") && method.getParameterTypes().length == 0) {
                // Skip finalize method, it's likely important that it execute as normal.
                continue;
            }
            sink.add(new MethodSetEntry(method));
        }

        for (Class<?> i : c.getInterfaces()) {
            getMethodsToProxy(sink, i);
        }
    }

Original comment by MhaleK...@gmail.com on 23 May 2012 at 8:07