views:

689

answers:

3

As part of my current project I've created a custom class loader. Part of the unit tests for the custom loader involves using some JAR files to demonstrate the proper behavior of the loader.

I'd like to build the test JAR files from Java sources ahead of running the actual unit tests. Further, the test JAR files cannot be on the class path when the unit tests are run, since I want to dynamically load them during the test execution.

Is there a standard pattern for accomplishing this sort of "build some JARs on the side before the test phase but leave them out of the class path" requirement? I can't believe I'm the first person to try doing this with Maven 2, but I can't seem to hit on the right POM structure and dependencies. Usually I end up with some of the test jars not being built ahead of the test phase, but I've also had problems with inconsistent order-of-build causing the build to work properly on one machine, but fail to build some of the test jars on another.

+5  A: 

I would try to set up everything your tests needs from within the test. The main advantage is that there is no magic unseen setup that is implicit for the test. The test can run in every environment. Additionally it is much easier to add new strictly isolated scenarios as you are not dependent on some mixed scenario setup.

The setup should not be too hard:

  • serialize a java class:
    • with some type code engineering library
    • Alternatively, use a java class file renamed to some file suffix other than .class. Put it under the test resource folder and load with the class loader (getResourceAsStream(...)).
  • zip the class file (`java.util.zip.GZIPOutputStream`)
  • load the class file with your class loader

There is an alternative approach that uses the java class loader design and works without generation of additional classes.

Java has a class loader hierarchy. Every class loader has a parent class loader. The root of the class loader hierarchy is the boot class loader. When a class is loaded with a class loader it will try to load the class first with the parent class loader and then itself.

You can load the test class with the current class loader. Jar it and load it with your own class loader. The only difference is that you set the parent class loader to one that cannot load your test class.

String resource = My.class.getName().replace(".", "/") + ".class";

//class loader of your test class
ClassLoader myClassLoader = currentThread().getContextClassLoader();
assert ! toList(myClassLoader.getResources(resource)).isEmpty();

//just to be sure that the resource cannot be loaded from the parent classloader
ClassLoader parentClassloader = getSystemClassLoader().getParent();
assert toList(parentClassloader.getResources(resource)).isEmpty();

//your class loader
URLClassLoader myLoader = new URLClassLoader(new URL[0], parentClassloader);
assert toList(myLoader.getResources(resource)).isEmpty();
Thomas Jung
I provided an answer, but if the problem's constraints can be loosened, I prefer this one.
SingleShot
You could even use the JarOutputStream, which extends the GZIPOutputStream class, to create a jar if you so desire.
aperkins
What's the differnce? Manifest support?
Thomas Jung
A: 

Maven resolves build order via dependency analysis, so normally your JARs would build in order because the one that uses your test JARs would simply declare them as dependencies. However, dependencies are also placed on the classpath. The "scope" of a dependency determines which classpath it goes on. For example 'compile' dependencies are on the classpath for compiling, testing, and running; 'runtime' dependencies are on the classpath for testing and running; 'test' dependencies are only on the classpath during test. Unfortunately, you have a case not covered by any of the available scopes: you have a dependency, but you don't want it on the classpath. This is a fringe use case and is why you are having trouble discovering examples.

So, unless some Maven guru rears up to indicate the contrary, I suggest this is impossible without writing a special Maven plugin. Instead of that, however, I recommend something else. Do you really need custom-built JARs to test your classloader? That sounds fishy to me. Perhaps you can use any old JAR? If so, I would use the maven-dependency-plugin to copy some JAR known to always be in your repository (log4j for example) into your local module's target directory. Your test can then access that JAR via filepath at target/log4j-xxx.jar and you can do your thing.

SingleShot
I really do need special jars. The tests are intended to show support for specific patterns for a plug-in architecture we are implementing (too simple for something like OSGi). "Standard" jars don't fit this pattern, AFAIK.
rtenhove
+4  A: 

The simplest thing to do is to set up another project to package the classes for your test jar, then set that as a normal test-scoped dependency.

If you don't want/aren't able to do that, you can use the assembly plugin to create a jar in the process-test-classes phase (i.e. after the tests have been compiled but before the tests are executed). The configuration below will invoke the assembly plugin to create a jar called classloader-test-deps in that phase in the target directory. Your tests can then use that jar as needed.

The assembly plugin uses an assembly descriptor (in src/main/assembly, called test-assembly.xml) that packages the contents of target/test-classes. I've set up a filter to include the contents of com.test package and its children. This assumes you have some package name convention you can apply for the contents of the jar.

The assembly plugin will by default attach the jar as an additional artifact, by specifying attach as false, it will not be installed/deployed.

<plugin>
  <artifactId>maven-assembly-plugin</artifactId>
  <version>2.2-beta-2</version>
  <executions>
    <execution>
      <id>create-test-dependency</id>
      <phase>process-test-classes</phase>
      <goals>
        <goal>single</goal>
      </goals>
      <configuration>
        <finalName>classloader-test-deps</finalName>
        <attach>false</attach>
        <descriptors>
          <descriptor>src/main/assembly/test-assembly.xml</descriptor>
        </descriptors>
      </configuration>
    </execution>
  </executions>
</plugin>

This is the content of test-assembly.xml

<assembly>
  <id>test-classloader</id>
  <formats>
    <format>jar</format>
  </formats>
  <includeBaseDirectory>false</includeBaseDirectory>
  <fileSets>
    <fileSet>
      <directory>${project.build.testOutputDirectory}</directory>
      <outputDirectory>/</outputDirectory>
      <!--modify/add include to match your package(s) -->
      <includes>
        <include>com/test/**</include>
      </includes>
    </fileSet>
  </fileSets>
</assembly>
Rich Seller
The appropriate dependencies are indeed test-scoped. The assembly plug-in approach you suggested has promise. I'll give it a whirl!
rtenhove
Rich Seller: both of these answers turned out to be correct. Due to some of my own stupidity, I managed to subtly break the POM for generating the test jars. Once fixed, test-scoping worked as expected, although the clues Maven gave me made that fix very difficult.I also tried out the assembly plug-in, and it worked as well. Its flexibility could prove very useful in more complex situations.
rtenhove