Wednesday, July 17, 2013

Integration Testing Using The Maven Tomcat Plugin And JaCoCo Code Coverage

One of the frustrating things about using Maven and its many plugins is often they lack some type of key functionality that you would think would be a no-brainer to include.  Take for instance the Tomcat Maven Plugin - One would think that its fork option would spin up a truly forked instance of Tomcat and accept JVM arguments such as -Xmx, -XX:MaxPermSize, etc.  Disappointingly, the Tomcat plugin merely runs Tomcat in the same process as the Maven command and is incapable of accepting JVM arguments directly (BTW, the maven-jetty-plugin does this very well with its run-forked option).  The only way to provide them is through the MAVEN_OPTS environment variable before the Maven command is executed.  This might be no big deal while working in your local environment but what if you want your continuous integration system to run Tomcat integration tests as part of a build pipeline?  In Jenkins or Hudson, you can include a shell command to set MAVEN_OPTS.  That might work well if all you want to do is set a max memory parameter.  But what if you want to report code coverage statistics using the JaCoCo Maven plugin?  Because the Maven Tomcat plugin doesn't have a way to accept JVM arguments, you'll have to find a way to capture the JaCoCo Java Agent JVM argument and set it in the MAVEN_OPTS variable.  Here's the steps I took in order to accomplish that.  Understand that it will take multiple commands to accomplish this.  Admittedly it's a bit clunky - here goes...

  1. First, you'll need to generate a command that sets the MAVEN_OPTS environment variable.  For this I use a special Maven profile that employs the jacoco-maven-plugin and the exec-maven-plugin to generate a command file to be run before the verify goal of the build process.  I'm binding the goals to the clean phase.  I chose that phase because I wanted to insure that this command will be successfully generated before any of the subsequent Maven commands.  Shown below is an example of this profile. Here's an example of the Maven command using this profile:
    mvn clean -Pjacoco-maven-opts
    Note that this generates a simple Windows DOS command named mvnopts.bat (you could easily modify this to run a command suited for Linux). Shown below is an example of the generated mvnopts.bat command:
  2. The preceding mvn command generates the mvnopts.bat command file in the project's ${basedir} directory which where the pom.xml is located. Simply run the command from that directory to update MAVEN_OPTS. For example:
    C:\myproject\mvnopts.bat
  3. After you execute the mvnopts.bat command, your environment will be ready for running a JaCoCo aware Maven verify phase. You'll want to use the maven-failsafe-plugin and bind the tomcat7-maven-plugin run and shutdown goals to the pre-integration-test and post-integration-test phases respectively. It might be helpful to encapsulate the build plugins for Tomcat integration into a profile too.  The following is an example of such a profile:
  4. Note that the Tomcat plugin should be defined in the <build> section as follows: Notice the fork=true option. As stated above, the fork option does not fork a new JVM. Rather it is run in a thread created by the Maven Launcher class (org.codehaus.plexus.classworlds.launcher.Launcher) that is invoked by the mvn command.
  5. To run your Tomcat integration tests, you would run the following command:
    mvn verify -Ptomcat-it-coverage
    The following JVisualVM screen shots show the JaCoCo JVM argument provided by MAVEN_OPTS and the Tomcat thread spawned by the Maven Launcher:

Sample Jenkins Build Job Script:

Now that you have these Maven commands worked out, you can use them a Jenkins job. I like to run my Maven test stuff in a "free-style software project" rather than a "maven2/3" type of project. With a free style project, you have greater flexibility mixing Maven and other types of commands and metrics gathering is more consistent.  Simply enter the commands you wish to execute in a "Windows batch command" text box.  Here's an example using the commands described above:


1 comment:

  1. Very good Baldhauser. I await your next blog entry in 2016!

    ReplyDelete