Rod Hilton's rants about software development, technology, and sometimes Star Wars

Be Careful With Clover in Jenkins

We’ve got a common library used by a number of projects. This library has it’s own git repository and it’s own job in Jenkins to build it.

One of the tasks of the Jenkins job is to, when the tests pass, copy the jar file artifact into a team-wide Nexus instance, which is added as a repository in our other projects.

Lately I started having a problem where a new project that used this library was unable to run tests correctly - strange failures that seemed unrelated to any actual changes. The culprit wound up being an unlikely source: Clover.

The Jenkins Maven build goal for our library used to look like this:

clean compile test deploy

We wanted to start using Clover to track our test coverage of this library, so we installed it and altered the goal to this:

clean compile clover2:setup test clover2:aggregate clover2:clover deploy

This did what we expected, and we got to track our test coverage. Something interesting happened later on, though.

Another project, which did not use clover at all, was using this library. A strange error started popping up when trying to compile and run the tests for this new project:

[CLOVER] FATAL ERROR: Clover could not be initialised. Are you sure you have Clover in the runtime classpath? (class java.lang.NoClassDefFoundError:com_cenqua_clover/CloverVersionInfo)

java.lang.NoClassDefFoundError: com_cenqua_clover/CoverageRecorder

This was really strange, since this new project didn’t use clover at all. Tracing the problem down to the class that generated this error, I found this in the decompiled stub generated by IntellJ when loading a class within the library from the open IntellIJ project that uses that library:

public static class __CLR3_0_26b6bgqwyiqsv  {
  public static final com_cenqua_clover.CoverageRecorder R;

  public __CLR3_0_26b6bgqwyiqsv() { /* compiled code */ }
}

It turns out that when instrumenting clover, it actually compiles the classes somewhat differently, including some hooks for Clover (this seems to happen more in enums). When the jar file was being deployed, it was deploying the one with the Clover instrumentation. Thus, when the new project downloaded it’s dependencies from Nexus, it pulled a class with a dependency on Clover, but Clover was not on the classpath since this project didn’t use it.

My solution was to split my job into two jobs.

The first job is focused on the health of the library, so it runs the tests, does the Clover information gathering, runs FindBugs, etc.

The second job, the “deploy” job runs only when the first job completes successfully, then does a clean compile deploy without the Clover instrumentation.

This is the more correct methodology anyway, as it’s closer to the Continuous Delivery pipeline advocated in Jez Humble’s book, Continuous Delivery, though it somewhat bothers me that the exact jar file being used for running tests is not the same jar file being deployed.

comments powered by Disqus