keeganwitt / gmock

Automatically exported from code.google.com/p/gmock
6 stars 2 forks source link

gmock is incompatible with Grails 2.3.x "forked" test execution #140

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
Grails 2.3.0 introduced the ability to run tests on a separate JVM. This JVM is 
launched with a "minimal" classpath; build dependencies on Grails' 
configuration are mostly ignored. Instead grails looks for some whitelisted 
artifacts in the build and test scopes. See for instance 
https://github.com/grails/grails-core/blob/c35b06ffd7801e8cbd13093aa367316d73725
a85/grails-bootstrap/src/main/groovy/org/codehaus/groovy/grails/cli/fork/testing
/ForkedGrailsTestRunner.groovy#L155

An example of the result process command line is attached.

Test dependencies are configured in a separate classloader whose parent is the 
application classloader configured via the command line. It's this child-most 
classloader that will include gmock.

From what I understand, the problem is that when doing `mock(Foobar)`, gmock 
tries to use the classloader that loaded `Foobar` when loading the new 
dynamically generated class. But this generated bytecode seems to reference 
some renamed cglib in gmock. If Foobar was not loaded with child-most Grails 
classloader, which includes the test dependencies and gmock, loading the 
generated class will fail. I think. Anyway, I'm getting this exception:

java.lang.NoClassDefFoundError: org/gmock/internal/cglib/proxy/Factory

See here for a (fully reproducible) travis build exhibiting this problem: 
https://travis-ci.org/thehyve/transmart-core-db/builds/21218938#L415

Because Grails uses the same version of cglib (2.2.2) as gmock and cglib is the 
command line classpath and hence in an upper classloader, I could solve this 
problem by changing the gradle configuration not to rename the cglib classes 
and depend on the cglib artifact instead. This is probably not a very good 
general solution.

0.8.3 (but latest SVN doesn't fix it), Grails 2.3.5

Original issue reported on code.google.com by contrate...@gmail.com on 21 Mar 2014 at 12:04

Attachments:

GoogleCodeExporter commented 9 years ago
Are there any configurations of Grails to include all classes in gmock's jar 
file? The hard coded whitelist sounds like an ugly solution to me.

Original comment by JohnnyJianHY on 22 Mar 2014 at 1:42

GoogleCodeExporter commented 9 years ago
It is possible to disable forked execution by setting grails.project.fork.test 
to false. In that case, the build dependencies are mixed with the test (and I 
guess compile and runtime) dependencies in a GrailsRootLoader classloader. I 
also think the build dependencies are not cherry-picked in this case. 
Obviously, in this case, gmock work fine.

But forked execution is the new default in Grails and it's recommended to use 
for several reasons (see 
http://grails.io/post/43484836985/road-to-grails-2-3-forked-execution ). If 
it's not possible to make gmock work out-of-the-box with forked execution, I 
think at least there should be a way to make it use the context classloader or 
to be able to specify the classloader to be used (perhaps globally in the 
GMockController).

Original comment by contrate...@gmail.com on 22 Mar 2014 at 2:39

GoogleCodeExporter commented 9 years ago
It sounds more like an issue of grails instead of gmock to me. Don't you think 
so?

Original comment by JohnnyJianHY on 22 Mar 2014 at 4:53

GoogleCodeExporter commented 9 years ago
IMHO, Grails should allow you more control over the -classpath Classloader (it 
should put all build dependencies there). If it did, we could make gmock a 
build dependency and everything would work.

This doesn't mean that gmock doesn't have a deficiency here. The fact is that, 
depending o which classloader gmock was loaded, some classes will be 
unmockable. It should have some mechanism (e.g. using the somewhat crude 
mechanism of the context classloader) to go around these kinds of problems. 
This is not specific to Grails. You can test this problem with a script like 
this:

#!/usr/bin/groovy

def cl = new URLClassLoader(
        [new URL('file:/home/glopes/.m2/repository/org/gmock/gmock/0.8.3/gmock-0.8.3.jar')] as URL[],
        getClass().classLoader ?: ClassLoader.systemClassLoader)

def gmockControllerClass = cl.loadClass('org.gmock.GMockController', true)

Thread.currentThread().contextClassLoader = cl //doesn't help

def gmockController = gmockControllerClass.newInstance()

gmockController.mock(gmockControllerClass) //works fine because it was loaded 
by cl
gmockController.mock(Closure) //fails: ClassNotFoundException: 
org.gmock.internal.cglib.proxy.Factory

Original comment by contrate...@gmail.com on 22 Mar 2014 at 10:47

GoogleCodeExporter commented 9 years ago
You've got a point there.

I was wondering why there is a ClassNotFoundException, so I took a look into 
the code of cglib. It turns out that cglib uses Closure's classloader to load 
the generated class which refers the Factory class of cglib (which is loadded 
by the classloader of gmock that you provided). The classloader of Closure is 
the parent of the classloader of gmock, so of course it doesn't know the 
classes of gmock.

Of course gmock can make sure cglib uses the classloader of gmock, but what if 
the class under mock and gmock use two separated classloaders (no hierarchical 
relationship)? I don't think cglib support that.

To deal with this situation, gmock needs to catch this exception and when it 
does catch one, it should give up using cglib and only modify the metaclass of 
the class under mock.

Original comment by JohnnyJianHY on 23 Mar 2014 at 4:15

GoogleCodeExporter commented 9 years ago
@contratempo, it is fixed on the trunk, please give it a try.

Original comment by JohnnyJianHY on 12 Apr 2014 at 7:56

GoogleCodeExporter commented 9 years ago
Works for me in trunk. Any ETA about the release?
I don't want to build it myself :)

Original comment by mauro.ci...@cymait.com on 24 Aug 2014 at 11:49