Ant task for offline instrumentation.
diff --git a/org.jacoco.ant.test/src/org/jacoco/ant/InstrumentTaskTest.java b/org.jacoco.ant.test/src/org/jacoco/ant/InstrumentTaskTest.java
new file mode 100644
index 0000000..a418f72
--- /dev/null
+++ b/org.jacoco.ant.test/src/org/jacoco/ant/InstrumentTaskTest.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2012 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Brock Janiczak - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.ant;
+
+import java.io.File;
+import java.io.IOException;
+
+import junit.framework.TestSuite;
+
+import org.apache.ant.antunit.junit3.AntUnitSuite;
+import org.apache.ant.antunit.junit4.AntUnitSuiteRunner;
+import org.jacoco.agent.AgentJar;
+import org.junit.runner.RunWith;
+
+@RunWith(AntUnitSuiteRunner.class)
+public class InstrumentTaskTest {
+
+ public static TestSuite suite() throws IOException {
+ System.setProperty("org.jacoco.ant.instrumentTaskTest.classes.dir",
+ TestTarget.getClassPath());
+ System.setProperty("org.jacoco.ant.instrumentTaskTest.agent.file",
+ AgentJar.extractToTempLocation().getAbsolutePath());
+ final File file = new File("src/org/jacoco/ant/InstrumentTaskTest.xml");
+ return new AntUnitSuite(file, InstrumentTaskTest.class);
+ }
+
+}
diff --git a/org.jacoco.ant.test/src/org/jacoco/ant/InstrumentTaskTest.xml b/org.jacoco.ant.test/src/org/jacoco/ant/InstrumentTaskTest.xml
new file mode 100644
index 0000000..ba61b72
--- /dev/null
+++ b/org.jacoco.ant.test/src/org/jacoco/ant/InstrumentTaskTest.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Copyright (c) 2009, 2012 Mountainminds GmbH & Co. KG and Contributors
+ All rights reserved. This program and the accompanying materials
+ are made available under the terms of the Eclipse Public License v1.0
+ which accompanies this distribution, and is available at
+ http://www.eclipse.org/legal/epl-v10.html
+
+ Contributors:
+ Brock Janiczak - initial API and implementation
+
+ $Id: $
+-->
+
+<project name="JaCoCo Instrument Task Tests" xmlns:au="antlib:org.apache.ant.antunit" xmlns:jacoco="antlib:org.jacoco.ant">
+
+ <target name="setUp">
+ <tempfile property="temp.dir" prefix="jacocoTest" destdir="${java.io.tmpdir}" />
+ <mkdir dir="${temp.dir}"/>
+ <property name="exec.file" location="${temp.dir}/exec.file" />
+ </target>
+
+ <target name="tearDown">
+ <delete dir="${temp.dir}" quiet="false" failonerror="true"/>
+ </target>
+
+ <target name="testInstrumentNoDestination">
+ <au:expectfailure expectedMessage="Destination directory must be supplied">
+ <jacoco:instrument/>
+ </au:expectfailure>
+ </target>
+
+ <target name="testInstrumentInvalidClassFile">
+ <mkdir dir="${temp.dir}/output"/>
+ <property name="broken.file" location="${temp.dir}/broken.class"/>
+ <echo file="${broken.file}">Not a class.</echo>
+ <au:expectfailure expectedMessage="Error while instrumenting ${broken.file}">
+ <jacoco:instrument destdir="${temp.dir}/output">
+ <fileset dir="${temp.dir}" includes="broken.class"/>
+ </jacoco:instrument>
+ </au:expectfailure>
+ <au:assertFileDoesntExist file="${temp.dir}/output/broken.class" />
+ </target>
+
+ <target name="testInstrumentIgnoreDirectories">
+ <jacoco:instrument destdir="${temp.dir}">
+ <dirset dir="${org.jacoco.ant.instrumentTaskTest.classes.dir}" includes="**"/>
+ </jacoco:instrument>
+ </target>
+
+ <target name="testInstrumentAndRun">
+ <jacoco:instrument destdir="${temp.dir}">
+ <fileset dir="${org.jacoco.ant.instrumentTaskTest.classes.dir}" includes="**/*.class"/>
+ </jacoco:instrument>
+ <au:assertLogContains text="Instrumented 12 classes to ${temp.dir}"/>
+ <au:assertFileExists file="${temp.dir}/org/jacoco/ant/InstrumentTaskTest.class" />
+
+ <java classname="org.jacoco.ant.TestTarget" failonerror="true" fork="true">
+ <sysproperty key="jacoco.destfile" file="${temp.dir}/test.exec"/>
+ <classpath>
+ <pathelement path="${org.jacoco.ant.instrumentTaskTest.agent.file}"/>
+ <pathelement path="${temp.dir}"/>
+ </classpath>
+ </java>
+ <au:assertFileExists file="${temp.dir}/test.exec" />
+ </target>
+
+</project>
\ No newline at end of file
diff --git a/org.jacoco.ant/META-INF/MANIFEST.MF b/org.jacoco.ant/META-INF/MANIFEST.MF
index 062f637..3ef60cb 100644
--- a/org.jacoco.ant/META-INF/MANIFEST.MF
+++ b/org.jacoco.ant/META-INF/MANIFEST.MF
@@ -6,9 +6,11 @@
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Bundle-Vendor: Mountainminds GmbH & Co. KG and Contributors
Require-Bundle: org.apache.ant;bundle-version="[1.7.0,2.0.0)"
-Import-Package: org.jacoco.agent;bundle-version="[0.6.1,0.6.2)",
+Import-Package: org.objectweb.asm;version="[4.1.0,4.2.0)",
+ org.jacoco.agent;bundle-version="[0.6.1,0.6.2)",
org.jacoco.core.analysis;bundle-version="[0.6.1,0.6.2)",
org.jacoco.core.data;bundle-version="[0.6.1,0.6.2)",
+ org.jacoco.core.instr;version="[0.6.1,0.6.2)",
org.jacoco.core.runtime;bundle-version="[0.6.1,0.6.2)",
org.jacoco.report;bundle-version="[0.6.1,0.6.2)",
org.jacoco.report.csv;bundle-version="[0.6.1,0.6.2)",
diff --git a/org.jacoco.ant/src/org/jacoco/ant/InstrumentTask.java b/org.jacoco.ant/src/org/jacoco/ant/InstrumentTask.java
new file mode 100644
index 0000000..13e2442
--- /dev/null
+++ b/org.jacoco.ant/src/org/jacoco/ant/InstrumentTask.java
@@ -0,0 +1,102 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2012 Mountainminds GmbH & Co. KG and Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Brock Janiczak - initial API and implementation
+ *
+ *******************************************************************************/
+package org.jacoco.ant;
+
+import static java.lang.String.format;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Iterator;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.Resource;
+import org.apache.tools.ant.types.ResourceCollection;
+import org.apache.tools.ant.types.resources.Union;
+import org.apache.tools.ant.util.FileUtils;
+import org.jacoco.core.instr.Instrumenter;
+import org.jacoco.core.runtime.OfflineInstrumentationAccessGenerator;
+
+/**
+ * Task for offline instrumentation of class files.
+ */
+public class InstrumentTask extends Task {
+
+ private File destdir;
+
+ private final Union files = new Union();
+
+ /**
+ * Sets the location of the instrumented classes.
+ *
+ * @param destdir
+ * destination folder for instrumented classes
+ */
+ public void setDestdir(final File destdir) {
+ this.destdir = destdir;
+ }
+
+ /**
+ * This task accepts any number of class file resources.
+ *
+ * @param resources
+ * Execution data resources
+ */
+ public void addConfigured(final ResourceCollection resources) {
+ files.add(resources);
+ }
+
+ @Override
+ public void execute() throws BuildException {
+ if (destdir == null) {
+ throw new BuildException("Destination directory must be supplied",
+ getLocation());
+ }
+ int total = 0;
+ final Instrumenter instrumenter = new Instrumenter(
+ new OfflineInstrumentationAccessGenerator());
+ final Iterator<?> resourceIterator = files.iterator();
+ while (resourceIterator.hasNext()) {
+ final Resource resource = (Resource) resourceIterator.next();
+ if (resource.isDirectory()) {
+ continue;
+ }
+ total += instrument(instrumenter, resource);
+ }
+ log(format("Instrumented %s classes to %s", Integer.valueOf(total),
+ destdir.getAbsolutePath()));
+ }
+
+ private int instrument(final Instrumenter instrumenter,
+ final Resource resource) {
+ InputStream input = null;
+ OutputStream output = null;
+ try {
+ input = resource.getInputStream();
+ final byte[] buffer = instrumenter.instrument(input);
+ final File file = new File(destdir, resource.getName());
+ file.getParentFile().mkdirs();
+ output = new FileOutputStream(file);
+ output.write(buffer);
+ return 1;
+ } catch (final Exception e) {
+ throw new BuildException(format("Error while instrumenting %s",
+ resource), e, getLocation());
+ } finally {
+ FileUtils.close(input);
+ FileUtils.close(output);
+ }
+ }
+
+}
diff --git a/org.jacoco.ant/src/org/jacoco/ant/antlib.xml b/org.jacoco.ant/src/org/jacoco/ant/antlib.xml
index a2d7ace..52b86f4 100644
--- a/org.jacoco.ant/src/org/jacoco/ant/antlib.xml
+++ b/org.jacoco.ant/src/org/jacoco/ant/antlib.xml
@@ -19,4 +19,5 @@
<taskdef name="report" classname="org.jacoco.ant.ReportTask"/>
<taskdef name="merge" classname="org.jacoco.ant.MergeTask"/>
<taskdef name="dump" classname="org.jacoco.ant.DumpTask"/>
+ <taskdef name="instrument" classname="org.jacoco.ant.InstrumentTask"/>
</antlib>
\ No newline at end of file
diff --git a/org.jacoco.core/src/org/jacoco/core/instr/Instrumenter.java b/org.jacoco.core/src/org/jacoco/core/instr/Instrumenter.java
index 57b1aad..2db62ac 100644
--- a/org.jacoco.core/src/org/jacoco/core/instr/Instrumenter.java
+++ b/org.jacoco.core/src/org/jacoco/core/instr/Instrumenter.java
@@ -50,8 +50,8 @@
*/
private ClassVisitor createInstrumentingVisitor(final long classid,
final ClassVisitor cv) {
- return new ClassProbesAdapter(new ClassInstrumenter(classid, accessGenerator,
- cv));
+ return new ClassProbesAdapter(new ClassInstrumenter(classid,
+ accessGenerator, cv));
}
/**
@@ -59,7 +59,7 @@
*
* @param reader
* definition of the class as ASM reader
- * @return instrumented definition or <code>null</code>
+ * @return instrumented definition
*
*/
public byte[] instrument(final ClassReader reader) {
@@ -75,7 +75,7 @@
*
* @param buffer
* definition of the class
- * @return instrumented definition or <code>null</code>
+ * @return instrumented definition
*
*/
public byte[] instrument(final byte[] buffer) {
@@ -87,7 +87,7 @@
*
* @param input
* stream to read class definition from
- * @return instrumented definition or <code>null</code>
+ * @return instrumented definition
* @throws IOException
* if reading data from the stream fails
*
diff --git a/org.jacoco.doc/docroot/doc/ant.html b/org.jacoco.doc/docroot/doc/ant.html
index 4da2fff..78cd89a 100644
--- a/org.jacoco.doc/docroot/doc/ant.html
+++ b/org.jacoco.doc/docroot/doc/ant.html
@@ -28,7 +28,10 @@
<a href="#agent"><code>agent</code></a>,
<a href="#dump"><code>dump</code></a> and
<a href="#merge"><code>merge</code></a>. Reports in different formats are
- creates with the <a href="#report"><code>report</code></a> task.
+ creates with the <a href="#report"><code>report</code></a> task. For
+ <a href="offline.html">offline instrumentation</a> the task
+ <a href="#instrument"><code>instrument</code></a> can be used to prepare class
+ files.
</p>
<p class="hint">
@@ -694,6 +697,43 @@
</tbody>
</table>
+
+<h2><a name="instrument">Task <code>instrument</code></a></h2>
+
+<p>
+ This task is used for <a href="offline.html">offline instrumentation</a> of
+ class files. The task takes a set of class files and writes instrumented
+ versions to a specified location.
+</p>
+
+<pre class="source lang-xml linenums">
+<jacoco:instrument destfile="target/classes-instr">
+ <fileset dir="target/classes" includes="**/*.class"/>
+</jacoco:instrument>
+</pre>
+
+<p>
+ The task definition can contain any number of resource collection types and
+ has the following mandatory attribute:
+</p>
+
+<table class="coverage">
+ <thead>
+ <tr>
+ <td>Attribute</td>
+ <td>Description</td>
+ <td>Default</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td><code>destdir</code></td>
+ <td>Directory location to write the instrumented class files to.</td>
+ <td><i>none (required)</i></td>
+ </tr>
+ </tbody>
+</table>
+
</div>
<div class="footer">
<span class="right"><a href="@jacoco.home.url@">JaCoCo</a> @qualified.bundle.version@</span>
diff --git a/org.jacoco.doc/docroot/doc/index.html b/org.jacoco.doc/docroot/doc/index.html
index aa3cffb..7250e24 100644
--- a/org.jacoco.doc/docroot/doc/index.html
+++ b/org.jacoco.doc/docroot/doc/index.html
@@ -38,10 +38,12 @@
<ul>
<li><a href="ant.html">Ant Tasks</a></li>
- <li><a href="examples/build/build.xml">Ant Usage Example</a></li>
+ <li><a href="examples/build/build.xml">Ant Usage Example</a> -
+ <a href="examples/build/build-offline.xml">Offline Example</a></li>
<li><a href="maven.html">Maven Plug-in</a></li>
<li><a href="examples/build/pom.xml">Maven Usage Example</a></li>
<li><a href="agent.html">Java Agent</a></li>
+ <li><a href="offline.html">Offline Instrumentation</a></li>
<li><a href="faq.html">FAQ</a></li>
<li><a href="support.html">Support and Feedback</a></li>
</ul>
diff --git a/org.jacoco.doc/docroot/doc/offline.html b/org.jacoco.doc/docroot/doc/offline.html
new file mode 100644
index 0000000..ca73750
--- /dev/null
+++ b/org.jacoco.doc/docroot/doc/offline.html
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <link rel="stylesheet" href=".resources/doc.css" charset="UTF-8" type="text/css" />
+ <link rel="shortcut icon" href=".resources/report.gif" type="image/gif" />
+ <title>JaCoCo - Offline Instrumentation</title>
+</head>
+<body>
+
+<div class="breadcrumb">
+ <a href="../index.html" class="el_report">JaCoCo</a> >
+ <a href="index.html" class="el_group">Documentation</a> >
+ <span class="el_source">Offline Instrumentation</span>
+</div>
+<div id="content">
+
+<h1>Offline Instrumentation</h1>
+
+<p>
+ One of the main benefits of JaCoCo is the Java agent, which instruments
+ classes on-the-fly. This simplifies code coverage analysis a lot as no
+ pre-instrumentation and classpath tweaking is required. However, there can be
+ situations where on-the-fly instrumentation is not suitable, for example:
+</p>
+<ul>
+ <li>Runtime environments that do not support Java agents.</li>
+ <li>Deployments where it is not possible to configure JVM options.</li>
+ <li>Bytecode needs to be converted for another VM like the Android Dalvik VM.</li>
+ <li>Conflicts with other agents that do dynamic classfile transformation.</li>
+</ul>
+
+<p>
+ For such scenarios class files can be pre-instrumented with JaCoCo, for
+ example with the <a href="ant.html#instrument"><code>instrument</code></a>
+ Ant task. At runtime the pre-instrumented classes needs be on the classpath
+ instead of the original classes. In addition <code>jacocoagent.jar</code> must
+ be put on the classpath.
+</p>
+
+<h2>Configuration</h2>
+<p>
+ In offline mode the JaCoCo runtime can be configured with Java System
+ properties. The same set of properties as for the
+ <a href="agent.html">agent</a> is available, but prefixed with
+ "<code>jacoco.</code>". For example the location of the <code>*.exec</code>
+ file can be configured with the system property
+ "<code>jacoco.destfile</code>".
+</p>
+
+<h2>Class Loading and Initialization</h2>
+<p>
+ Unlike with on-the-fly instrumentation offline instrumented classes get a
+ direct dependency on the JaCoCo runtime. Therefore
+ <code>jacocoagent.jar</code> has to be on the classpath and accessible by the
+ instrumented classes. The proper location for <code>jacocoagent.jar</code>
+ might depend on your deployment scenario. The first instrumented class loaded
+ will trigger the initialization of the JaCoCo runtime. If no instrumented
+ class is loaded the JaCoCo runtime will not get started at all.
+</p>
+
+<h2>Using Pre-Instrumented Classes With the Java Agent</h2>
+<p>
+ It is possible to also use offline-instrumented classes with the JaCoCo Java
+ agent. In this case the configuration is taken from the agent options. The
+ agent must be configured in a way that pre-instrumented classes are excluded,
+ e.g. with <code>excludes=*</code>. Otherwise it will result in error messages
+ on the console if the agent instruments such classes again.
+</p>
+
+<h2>Execution Data Collection</h2>
+<p>
+ If <code>jacocoagent.jar</code> is used on the classpath it will collect
+ execution data the same way as used as a <a href="agent.html">Java agent</a>.
+ Depending on the <code>output</code> configuration execution data can be
+ collected via a remote connection or is written to the file system when the
+ JVM terminates. For the latter it is required that e.g. a <code>java</code>
+ task is executed with <code>fork="true"</code>.
+</p>
+
+<h2>Report Generation</h2>
+<p>
+ Based on the collected <code>*.exec</code> files reports can be created the
+ same way as for execution data collected with the Java agent. Note that for
+ report generation the original class files have to be supplied, not the
+ instrumented copies.
+</p>
+
+</div>
+<div class="footer">
+ <span class="right"><a href="@jacoco.home.url@">JaCoCo</a> @qualified.bundle.version@</span>
+ <a href="license.html">Copyright</a> © @copyright.years@ Mountainminds GmbH & Co. KG and Contributors
+</div>
+
+</body>
+</html>
diff --git a/org.jacoco.examples/build/build-offline.xml b/org.jacoco.examples/build/build-offline.xml
new file mode 100644
index 0000000..c3b56e1
--- /dev/null
+++ b/org.jacoco.examples/build/build-offline.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Copyright (c) 2009, 2012 Mountainminds GmbH & Co. KG and Contributors
+ All rights reserved. This program and the accompanying materials
+ are made available under the terms of the Eclipse Public License v1.0
+ which accompanies this distribution, and is available at
+ http://www.eclipse.org/legal/epl-v10.html
+
+ Contributors:
+ Marc R. Hoffmann - initial API and implementation
+-->
+
+<project name="Example Ant Build with JaCoCo Offline Instrumentation" default="rebuild" xmlns:jacoco="antlib:org.jacoco.ant">
+
+ <description>
+ Example Ant build file that demonstrates how a JaCoCo coverage report
+ can be itegrated into an existing build in three simple steps.
+ </description>
+
+ <property name="src.dir" location="./src/main/java" />
+ <property name="result.dir" location="./target" />
+ <property name="result.classes.dir" location="${result.dir}/classes" />
+ <property name="result.classes.instr.dir" location="${result.dir}/classes-instr" />
+ <property name="result.report.dir" location="${result.dir}/site/jacoco" />
+ <property name="result.exec.file" location="${result.dir}/jacoco.exec" />
+
+ <!-- Step 1: Import JaCoCo Ant tasks -->
+ <taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
+ <classpath path="../../../lib/jacocoant.jar" />
+ </taskdef>
+
+ <target name="clean">
+ <delete dir="${result.dir}" />
+ </target>
+
+ <target name="compile">
+ <mkdir dir="${result.classes.dir}" />
+ <javac srcdir="${src.dir}" destdir="${result.classes.dir}" debug="true" includeantruntime="false" />
+ </target>
+
+ <target name="instrument" depends="compile">
+ <!-- Step 2: Instrument class files -->
+ <jacoco:instrument destdir="${result.classes.instr.dir}">
+ <fileset dir="${result.classes.dir}" includes="**/*.class" />
+ </jacoco:instrument>
+ </target>
+
+
+ <target name="test" depends="instrument">
+ <!-- Step 3: Run tests with instrumented classes -->
+ <java classname="org.jacoco.examples.parser.Main" fork="true">
+ <!-- jacocoagent.jar must be on the classpath -->
+ <classpath>
+ <pathelement path="../../../lib/jacocoagent.jar"/>
+ <pathelement path="${result.classes.instr.dir}" />
+ </classpath>
+ <!-- Agent is configured with system properties -->
+ <sysproperty key="jacoco.destfile" file="${result.exec.file}"/>
+ <arg value="2 * 3 + 4"/>
+ <arg value="2 + 3 * 4"/>
+ <arg value="(2 + 3) * 4"/>
+ <arg value="2 * 2 * 2 * 2"/>
+ <arg value="1 + 2 + 3 + 4"/>
+ <arg value="2 * 3 + 2 * 5"/>
+ </java>
+ </target>
+
+ <target name="report" depends="test">
+ <!-- Step 4: Create coverage report -->
+ <jacoco:report>
+
+ <!-- This task needs the collected execution data and ... -->
+ <executiondata>
+ <file file="${result.exec.file}" />
+ </executiondata>
+
+ <!-- the class files and optional source files ... -->
+ <structure name="JaCoCo Ant Example">
+ <classfiles>
+ <fileset dir="${result.classes.dir}" />
+ </classfiles>
+ <sourcefiles encoding="UTF-8">
+ <fileset dir="${src.dir}" />
+ </sourcefiles>
+ </structure>
+
+ <!-- to produce reports in different formats. -->
+ <html destdir="${result.report.dir}" />
+ <csv destfile="${result.report.dir}/report.csv" />
+ <xml destfile="${result.report.dir}/report.xml" />
+ </jacoco:report>
+ </target>
+
+ <target name="rebuild" depends="clean,compile,instrument,test,report" />
+
+</project>
\ No newline at end of file
diff --git a/org.jacoco.examples/build/build.xml b/org.jacoco.examples/build/build.xml
index 6422d77..bddd5b9 100644
--- a/org.jacoco.examples/build/build.xml
+++ b/org.jacoco.examples/build/build.xml
@@ -54,7 +54,7 @@
</target>
<target name="report" depends="test">
- <!-- Step 3. Create coverage report -->
+ <!-- Step 3: Create coverage report -->
<jacoco:report>
<!-- This task needs the collected execution data and ... -->