[GEEKERY] Wrestling with Ant and JUnit...

topic posted Sun, June 24, 2007 - 10:29 PM by  Brian
I spent some time today obsessing over getting my web of ant files to work perfectly. During my last days with Tribe I was working on a build system that was a simple library of ant files that provide some basic functionality that projects always need - compile, test, coverage, jar, war, publish, etc. It's like the apache-maven project, but less opaque. One thing that I wanted to get working was to load the junit task into the running ant instance without having to put junit.jar into the $ANT_HOME/lib directory. The reason for this is that I want new developers to be able to load the stock version of Ant on their machines, check out my code, and start testing without having to download any more software. Mac OS X also already has Ant installed if you install the Java developer tools. My "build" module has a set of jar files that are added to ant in order to provide functionality like scp, dependency management (via antlion), and junit - or so I thought.

The first thing I tried out was this:

<taskdef name="junit" classname="org.apache.tools.ant.taskdefs.optional.junit.JUnitTask">
<classpath>
<pathelement location="${test.basedir}/lib/junit.jar"/>
</classpath>
</taskdef>

I left off the ant-junit.jar because it is in the $ANT_HOME/lib directory already. However, this didn't quite work right. The error I got was this:

build/test.xml:15: taskdef A class needed by class org.apache.tools.ant.taskdefs.optional.junit.JUnitTask cannot be found:
junit/framework/Test

This was pretty frustrating, as I knew that this class was right in the jar that I specified as the classpath for this task. I tried lots of things to fix this along the way, including the following:

<taskdef name="junit" classname="org.apache.tools.ant.taskdefs.optional.junit.JUnitTask">
<classpath>
<pathelement location="${test.basedir}/lib/junit.jar"/>
<pathelement location="${test.basedir}/lib/ant-junit.jar"/>
</classpath>
</taskdef>

The change above was to explicitly include the ant-junit hook in my classpath for the junit task, but even this did not work. Well, it did not work until I REMOVED ant-junit.jar from $ANT_HOME/lib. Once I did that, everything worked as expected. Great, but unfortunately it still requires that you mess with the stock distribution of Ant for junit to work correctly which is exactly what I was trying to get around!

It would appear that this is a classloader issue, similar to the problems that people run into with tomcat when trying to access classes that were loaded by different classloaders (well, I guess it is EXACTLY that problem!) What I wanted to do was find a way in Ant to have a classloader just re-load the ant-junit jar along with the junit jar so that everything would work correctly. This assumes, of course, that the problem is that Ant is automatically loading all of the classes in $ANT_HOME/lib on startup in a different classloader than the one used for loading the junit.jar.

I have tried using the loaderref attribute of the typedef task, but to no avail. In fact, I tried a couple things with that attribute - first setting it to a unique value in an attempt to get junit to work only with its own classloader, then to try loaderref="root" in an attempt to get junit.jar onto the classpath after Ant had already started.

Of course, it was right about this time that I finally figured out what it was I should have been googling for and found this link:

ant.apache.org/faq.html#d...sloader-1.6

which explains everything. What this entry told me that I didn't know about was the option to have a directory called $HOME/.ant/lib for your external dependencies. Using this mechanism, it is just a matter of needing to include an ant task as part of my testing environment that will make that directory if it does not exist and drop the junit.jar file in it.
posted by:
Brian
SF Bay Area