Sample Cobertura ant build script

Summary: A Sample Cobertura Ant build script.

I still haven't gotten around to writing a Cobertura code-coverage tutorial, but in lieu of that, I thought I'd include an ant build script here that does a lot of powerful things, including a task that generates Cobertura code-coverage reports.

If you've never seen a sample Cobertura report, there's a sample report here.

If you've never heard some of the benefits of code coverage in general, I wrote a short blog about the benefits of code coverage here.

I'll just provide the entire ant build script here, and then discuss some of the features afterwards:

<project name="WikiTeX Editor" basedir=".." default="package">

  <!-- project-specific variables -->
  <property name="package.name" value="wikitex.jar" />
  <property name="properties.file" value="wikitex.properties" />
  <property name="manifest.main.class" value="com.devdaily.wikitex.Main" />
  <property name="manifest.built.by" value="Alvin J. Alexander" />
  <property name="manifest.created.by" value="DevDaily.com" />
  <property name="version.number" value="1.0" />
  <property name="build.number" value="1984" />
  <property name="lib.dir" value="lib" />
  <property name="lib.cobertura.dir" value="${lib.dir}/cobertura-1.9" />
  <property name="cobertura.jar.file" value="${lib.dir}/cobertura-1.9/cobertura.jar" />
  <property name="src.tests.dir" value="src-tests" />
  <property name="cob.ser.file" value="cobertura.ser" />

  <property environment="env" />
  <property name="build.dir" value="build" />
  <property file="${build.dir}/build.${env.HOSTNAME}" />

  <property name="src.dir" value="src" />
  <property name="reports.dir" value="reports" />
  <property name="resources.dir" value="resources" />
  <property name="dest.dir" value="target" />
  <property name="dest.dir.classes" value="${dest.dir}/classes" />
  <property name="dest.dir.lib" value="${dest.dir.classes}/lib" />
  <property name="package.file" value="${dest.dir}/${package.name}" />

  <!-- cobertura properties -->
  <property name="cobertura.dir" value="cobertura" />
  <property name="cob.instrumented.dir" value="${cobertura.dir}/instrumented" />
  <property name="cob.reports.dir" value="${cobertura.dir}/reports" />
  <property name="cob.reports.xml.dir" value="${cob.reports.dir}/junit-xml" />
  <property name="cob.reports.html.dir" value="${cob.reports.dir}/junit-html" />
  <property name="cob.coverage.xml.dir" value="${cob.reports.dir}/cobertura-xml" />
  <property name="cob.coverage.html.dir" value="${cob.reports.dir}/cobertura-html" />

  <path id="build.class.path">
    <fileset dir="lib">
      <include name="**/*.jar" />
    </fileset>
  </path>

  <target name="clean">
    <delete dir="${dest.dir}" />
    <delete dir="${reports.dir}" />
    <delete dir="${cobertura.dir}" />
    <delete file="${cob.ser.file}" />
  </target>

  <target name="prepare" depends="clean">
    <mkdir dir="${dest.dir}" />
    <mkdir dir="${dest.dir.classes}" />
    <mkdir dir="${dest.dir.classes}/META-INF" />
    <mkdir dir="${reports.dir}" />
    <!-- cobertura directories -->
    <mkdir dir="${cobertura.dir}" />
    <mkdir dir="${cob.instrumented.dir}" />
    <mkdir dir="${cob.reports.xml.dir}" />
    <mkdir dir="${cob.reports.html.dir}" />
    <mkdir dir="${cob.coverage.xml.dir}" />
    <mkdir dir="${cob.coverage.html.dir}" />
  </target>

  <target name="compile" depends="prepare">
    <echo>=== COMPILE ===</echo>
        <echo>Compiling ${src.dir} files ...</echo>
    <javac debug="on" srcdir="${src.dir}" destdir="${dest.dir.classes}" includes="com/**">
      <classpath refid="build.class.path" />
      <!-- classpath refid="cobertura.classpath" -->
    </javac>

    <!-- compile files on the src-tests path -->
        <echo>Compiling ${src.tests.dir} files ...</echo>
    <javac debug="on" srcdir="${src.tests.dir}" destdir="${dest.dir.classes}" includes="com/**">
      <classpath refid="build.class.path" />
      <!-- classpath refid="cobertura.classpath" -->
    </javac>
  </target>

  <target name="package" depends="compile">
    <echo>=== PACKAGE ===</echo>
    
    <!-- convert classpath to flat list for use in manifest task -->
    <pathconvert property="mf.classpath" pathsep=" ">
        <path refid="build.class.path" />
        <flattenmapper />
    </pathconvert>
    
    <tstamp/><!-- needed for TODAY -->
    <manifest file="MANIFEST.MF">
          <attribute name="Built-By" value="${manifest.built.by}"/>
          <attribute name="Created-By" value="${manifest.created.by}"/>
        <attribute name="Main-Class" value="${manifest.main.class}"/>
          <attribute name="Implementation-Version" value="${version.number}-b${build.number}"/>   
          <attribute name="Built-Date" value="${TODAY}"/>
         <attribute name="Class-Path" value="${mf.classpath}" />
    </manifest>
    
    <jar basedir="${dest.dir.classes}" 
       destfile="${package.file}"
       includes="**/*.*"
       excludes="**/*Test*"
        manifest="MANIFEST.MF" />

    <copy todir="${dest.dir}">
          <fileset dir="${lib.dir}">
            <exclude name="${lib.cobertura.dir}" />
            <exclude name="junit*" />
            <include name="*.jar"/>
            <include name="*.zip"/>
          </fileset>
    </copy>
    <!-- move this file before the 'jar' task (and put it in the 'classes' dir) if you want to 
         include it in the jar -->
    <copy file="${resources.dir}/log4j.properties" tofile="${dest.dir}/log4j.properties" overwrite="true" />
    <copy file="${resources.dir}/${properties.file}" tofile="${dest.dir}/${properties.file}" overwrite="true" />
    <delete dir="${dest.dir.classes}" />
  </target>
  
  <!-- INSTALL (no install target is needed) -->

  <!-- classpath for test needs to include the dist/classes directory -->
  <path id="classpath.test">
    <pathelement location="${dest.dir.classes}" />
    <fileset dir="lib">
      <include name="**/*.jar" />
    </fileset>
  </path>

  <!-- may want to modify this, and only allow deployment if the tests run -->
  <target name="test" description="Run JUnit Tests" depends="compile">
    <echo>=== UNIT TESTS ===</echo>
    <delete>
      <fileset dir="${reports.dir}" includes="**/TEST-*.txt"/>
    </delete>
    <junit dir="./" printsummary="yes" fork="yes" haltonfailure="yes">
      <formatter type="plain"/>
      <batchtest fork="yes" todir="${reports.dir}">
          <fileset dir="${dest.dir.classes}">
            <include name="**/Test*"/>
            <include name="**/*Tests*"/>
            <exclude name="**/AllTests*"/>
          <exclude name="**/TestingUtils*"/>
          </fileset>
          </batchtest>
      <classpath>
              <path refid="classpath.test"/>
        </classpath>
    </junit>
  </target>

  <!-- cobertura task definition -->
  <path id="cobertura.classpath">
    <fileset dir="${lib.dir}">
      <include name="${cobertura.jar.file}" />
      <include name="**/*.jar" />
    </fileset>
  </path>
    <taskdef classpathref="cobertura.classpath" resource="tasks.properties"/>
  
  <target name="instrument" depends="compile">
    <cobertura-instrument todir="${cob.instrumented.dir}">
      <fileset dir="${dest.dir.classes}">
        <include name="**/*.class"/>
      </fileset>
    </cobertura-instrument>
  </target>

  <target name="cover-test" depends="instrument">
    <junit dir="./" failureproperty="test.failure" printsummary="yes" 
      fork="true" haltonerror="true">
        <classpath location="${cobertura.jar.file}"/>
        <classpath location="${cob.instrumented.dir}"/>
      <classpath>
              <path refid="build.class.path"/>
        </classpath>
      <batchtest fork="yes" todir="${reports.dir}">
          <fileset dir="${cob.instrumented.dir}">
            <include name="**/Test*"/>
            <include name="**/*Tests*"/>
            <include name="org/jaxen/javabean/*Test*" />
          <exclude name="**/TestingUtils*"/>
            <exclude name="**/AllTests*"/>
          </fileset>
          </batchtest>
    </junit>
  </target>
  
  <!-- run this target to generate the coverage reports -->
  <target name="coverage-report" depends="cover-test">
    <cobertura-report srcdir="${src.dir}" destdir="${cobertura.dir}"/>
  </target>

</project>

I could write about this build script for a long time, but I don't have that much time today, so here are the highlights, especially the Cobertura-related highlights:

  • Any Ant build script like this is dependent on your directory structure. If you're used to Ant you can infer my directory names from the properties I set early in the script, but to summarize them here, my project directories are build, classes, lib, src, src-test, and reports. Also, this build.xml file is kept in the build subdirectory, and I run Ant from that directory.
  • The tasks I typically run are test or compile (depending on where I am in the development process), or coverage-report when I want to see my code coverage results.
  • The package task creates an executable jar file that can be easily run by users with the java-jar command.
  • The executable jar file includes a manifest file, and that manifest file includes a classpath that I create dynamically, based on the jar files in my lib directory.