Copyright © 2001-2006 Vlad Roubtsov
Table of Contents
EMMA is a tool for measuring coverage of Java software. Such a tool is essential for detecting dead code and verifying which parts of your application are actually exercised by your test suite and interactive use.
EMMA's design strives for several, very elusive in their combination, goals:
report rich coverage analysis data without introducing significant overhead during either build or execution time
be useful in team development environments, while at the same time enabling fast individual develop-test cycle
support quick development and testing of small standalone Java applications as well as scale up to massive enterprise sotfware suites containing thousands of Java classes
EMMA differs from other coverage tools in its extreme orientation towards fast iterative develop-test style of writing software. JVM Profiler Interface (JVMPI)-based tools do not require an instrumented source build, but the runtime overhead of running with JVMPI on is empirically known to be very high and results in depressingly slow testsuite runs. For tools based on source code instrumentation, having to wait for a full source code rebuild just to check coverage metrics is not something a normal developer wants to do several times during a day. EMMA's goal is to be so unintrusive that frequent daily checking of coverage numbers becomes second nature to every developer on the team, if not a completely automatic byproduct of every test run.
This section introduces EMMA command line tools. It starts with an example of how to run an application so that coverage instrumentation is performed on-the-fly, as the classes are loaded by the JVM, and then repeats the same process by breaking it into distinct instrumentation/execution/reporting steps.
The source code located in examples/src
directory of EMMA distribution is used for all examples in this
tutorial.
Section 3, “Getting Started (ANT)” works with the same sample application through Apache ANT build tool. All of EMMA's command-line functionality is available via EMMA's ANT tasks and there is some duplication of content between this and the following sections, so you might want to skip ahead if you expect to obtain coverage through ANT builds exclusively. You should also skip ahead if you've already gone through "Getting Started" steps on EMMA's web site.
The recommended way to install EMMA command.line tools is to
make emma.jar an extension library for your Java Runtime Environment (JRE).
This is accomplished simply by copying emma.jar to
<your jre dir>/lib/ext/
directory for
whichever JRE you use from command line.
EMMA distribution neither depends on any external Java libraries
nor includes them inside EMMA jars. This means installing EMMA as a JRE
extension will not pollute your classpath with unknown versions of Java
libraries that you might be using elsewhere in your work. This also has the
advantage of setting up emma.jar
as a trusted Java
library, which is useful for collecting coverage in security sensitive
configurations.
Still, if you are wary of adding a third-party library as a
standard JRE extension, just make sure that all your EMMA command line
invocations add emma.jar
to the JVM classpath:
>java -cp .../lib/emma.jar <emma or emmarun command>
Assuming you are in the examples
directory
of EMMA distribution, start by compiling the example source code:
>mkdir out >javac -d out -g src/*.java src/search/*.java
You can now run the main demo driver:
>java -cp out Main main(): running doSearch()... main(): done
To run the same program with coverage data collection, just insert emmarun in front of your program's main class name:
>java emmarun -cp out Main main(): running doSearch()... main(): done EMMA: writing [txt] report to [...coverage.txt] ...
The default text coverage report is generated in the current directory:
[EMMA v2.0.3611 report, generated Sun Jan 11 14:18:08 CST 2004] ------------------------------------------------------------------------------- OVERALL COVERAGE SUMMARY: [class, %] [method, %] [block, %] [line, %] [name] 100% (3/3) 100% (7/7) 95% (116/122) 100% (29/29) all classes OVERALL STATS SUMMARY: total packages: 2 total classes: 3 total methods: 7 total executable files: 3 total executable lines: 29 COVERAGE BREAKDOWN BY PACKAGE: [class, %] [method, %] [block, %] [line, %] [name] 100% (2/2) 100% (4/4) 91% (64/70) 100% (18/18) search 100% (1/1) 100% (3/3) 100% (52/52) 100% (11/11) default package -------------------------------------------------------------------------------
Code coverage has never been easier! This on-the-fly
instrumentation mode is handy for light-weight testing of
main()
test methods, individual classes, and small- to-
mid-size programs. emmarun also works well with Swing
applications.
By default, emmarun generates a
plain-text report only. The default report's depth is all
which means to show the overall coverage summary followed by breakdown by
package. You can increase the default depth to include package and source
file summaries. This and many other aspects of EMMA report generation can be
configured using command line
(-D<report property>=<value>
) or an EMMA
configuration file. See Chapter3, EMMA Property Reference
in the reference manual for full details on EMMA configuration. Also, all EMMA command line tools
provide usage information in response to -h
option.
emmarun application runner uses an
instrumenting classloader to add bytecode instrumentation to Java classes as
they are being loaded by the JVM. For efficiency reasons,
emmarun does not scan your entire classpath before it
starts running. This has the side effect of only reporting on the classes
that got loaded by the application. If your intent is to base coverage
metrics on the full set of classes in the classpath, you can use the
-f
option.
Although it was not the case with this tutorial's sample code, chances are your application has third-party library dependencies and you are not interested in their coverage metrics. There are two ways to handle this:
List third-party libraries in the JVM's classpath, not
emmarun's -cp
option:
>java -cp ...somelib.jar emmarun -cp out Main
Use a coverage filter to make sure that only the classes of interest are instrumented:
>java emmarun -cp out;...somelib.jar -ix +Main,+search.* Main
Further reading. This has been a quick intro to EMMA's on-the-fly command line instrumentation mode. For further details see Section2, <emmajava>/emmarun in the reference manual.
As convenient as the on-the-fly mode is, in many cases it is not sufficient. For example, running a commercial J2EE container in a custom instrumenting classloader is practically impossible. Certain (bad) coding practices also fail for code executing in a custom classloader. Finally, in large scale development there is a common need to collect and merge coverage data from multiple execution runs and multiple JVM processes.
This is where separate instrument/execute/report phases are a necessity. This section repeats the previous exercise using emma command line tool, which provides several subcommands for managing EMMA's offline code coverage analysis: instr, merge, and report (there is also a run equivalent of emmarun for scripting convenience).
If you skipped the previous exercise, you need to compile the sample source code at this time:
>cd ...samples >mkdir out >javac -d out -g src/*.java src/search/*.java
Now, you are going to instrument classes produced by javac (it's kind of a second compilation phase). EMMA can do "in place" instrumentation (overwrite mode), whereby the classes and jar files are overwritten with their instrumented versions. However, this is easy to abuse, so for now we are going to be careful and create a separate directory for coverage-instrumented output:
>mkdir outinstr >java emma instr -d outinstr -ip out EMMA: processing instrumentation path ... EMMA: instrumentation path processed in 116 ms EMMA: [3 class(es) instrumented, 0 resource(s) copied] EMMA: metadata merged into [...coverage.em] {in 31 ms}
Several instrumented classes have been added to
outinstr
directory and class
metadata has been dumped into a
coverage.em
file. Note that instr
accepts a regular classpath string (containing archives and class
directories) as input: this is very convenient for makefile integration
because existing makefiles tend to already have macros for various
classpaths in a project.
Unlike some other tools, EMMA's design is based around filtering for the right set of classes at instrumentation time. Doing so allows EMMA to scale to enterprise size projects and ensures the best performance throughout all three offline stages: instrumentation, execution, and reporting. To select a subset of classes to be processed, you could give instr several -ix options, each containing a comma-separated list of inclusion/exlusion patterns. See Section6.2, Coverage filters in the reference manual for full details.
Now the instrumented application can be run. In a real-world project you might be packaging the instrumented classes in an archive to be deployed in an application server, but in this tutorial we just load the classes from disk:
>java -cp outinstr;out Main EMMA: collecting runtime coverage data ... main(): running doSearch()... main(): done EMMA: runtime coverage data merged into [...coverage.ec] {in 32 ms}
When the JVM exits, EMMA runtime dumps the runtime
coverage profile into a coverage.ec
file
(the precise names and locations of all files created by EMMA are
configurable, the above is just EMMA's default behavior).
And finally, we combine the class metadata and runtime coverage profile to produce a plain text and an HTML reports:
>java emma report -r txt,html -in coverage.em -in coverage.ec EMMA: 2 file(s) read and merged in 43 ms EMMA: writing [txt] report to [...coverage.txt] ... EMMA: writing [html] report to [...coverage/index.html] ...
Note that there could be several instrumentation and execution stages and report will happily merge all of the results in memory before generating the reports. To merge all files on disk (for maintenance and disk storage reasons) you can use merge:
>java emma merge -in coverage.em -in coverage.ec -out coverage.es EMMA: processing input files ... EMMA: 2 file(s) read and merged in 42 ms EMMA: merged/compacted data written to [...coverage.es] {in 58 ms}
In EMMA's offline mode, you are in complete control of mixing and
matching metadata and coverage data from different application runs. You can
instruct all tools to merge all types of data in the same file or keep everything in
separate file repositories. EMMA tools default to
merge=true
output file mode for metadata and runtime
coverage data, but properties exist to alter this behavior. See Chapter3, EMMA Property Reference in the reference manual for further details.
Further reading. This has been a quick intro to EMMA's offline instrumentation tools for command line. For further details read the reference manual starting with Section3, <instr>/instr.
This section introduces EMMA ANT tasks. It starts with an example
of how to run an application from ANT so that coverage instrumentation is
performed on-the-fly, as the classes are loaded by the JVM, and then repeats
the same process by breaking it into distinct
instrumentation/execution/reporting steps. The ANT logic sequence will be
equivalent to what was described previously, in Section 2, “Getting Started (command line)”. Additionally, the ANT build.xml
files
created below will have logic for enabling and disabling coverage through
ANT command line.
The source code located in examples/src
directory of EMMA distribution is used for all examples in this tutorial.
EMMA distribution also includes two sample ANT buildfiles,
build-onthefly.xml
and
build-offline.xml
, that correspond to the two
approaches to collecting coverage. Although it would have been possible to
keep everything in a single file, using distinct files allows for more
clarity.
EMMA ANT tasks can be deployed in any of the many ways available
for custom ANT taskdefs. This tutorial assumes that you have an EMMA
distribution directory that contains both emma.jar
and
emma_ant.jar
. The latter archive is what contains EMMA
taskdefs, however, both archives need to be visible to ANT. The easiest way
to accomplish this is with the following definitions in your
build.xml
:
<!-- directory that contains emma.jar and emma_ant.jar: --> <property name="emma.dir" value="${basedir}/../lib" /> <path id="emma.lib" > <pathelement location="${emma.dir}/emma.jar" /> <pathelement location="${emma.dir}/emma_ant.jar" /> </path> <taskdef resource="emma_ant.properties" classpathref="emma.lib" />
Let's create a simple ANT build file for the source code in
examples/src
. Add this after EMMA task
definitions:
<!-- root directory for the example source code: --> <property name="src.dir" value="${basedir}/src" /> <!-- javac class output directory: --> <property name="out.dir" value="${basedir}/out" /> <target name="init" > <mkdir dir="${out.dir}" /> <path id="run.classpath" > <pathelement location="${out.dir}" /> </path> </target> <target name="compile" depends="init" description="compiles the example source code" > <javac debug="on" srcdir="${src.dir}" destdir="${out.dir}" /> </target> <target name="run" depends="init, compile" description="runs the examples" > <java classname="Main" classpathref="run.classpath" > </java> </target>
You can now compile and run the example:
>ant run Buildfile: build.xml init: [mkdir] Created dir: .../examples/out compile: [javac] Compiling 4 source files to .../examples/out run: [java] main(): running doSearch()... [java] main(): done BUILD SUCCESSFUL Total time: 5 seconds
<emmajava> is an EMMA extension of ANT
stock <java> task that is an ANT adapter to the
same instrumenting application runner as used by EMMA
emmarun command line tool. Upgrading your build to do
code coverage on-the-fly is very easy: just replace <java> tags with
<emmajava> tags for tasks that run your application or test cases.
Don't worry, your build is not now in a permanent coverage-enabled mode:
<emmajava> becomes a pass-through to the normal
<java> when its enabled
attribute
is set to false
:
<target name="emma" description="turns on EMMA's on-the-fly instrumentation mode" > <property name="emma.enabled" value="true" /> </target> <target name="run" depends="init, compile" description="runs the examples" > <emmajava enabled="${emma.enabled}" libclasspathref="emma.lib" classname="Main" classpathref="run.classpath" > </emmajava> </target>
Now, whenever you insert emma before any run targets on ANT's command line, you enable on-the-fly coverage instrumentation and reporting (but ANT commands without emma continue to function as before):
>ant emma run Buildfile: build.xml emma: init: [mkdir] Created dir: .../examples/out compile: [javac] Compiling 4 source files to .../examples/out run: [emmajava] main(): running doSearch()... [emmajava] main(): done [emmajava] EMMA: writing [txt] report to [.../coverage.txt] ... BUILD SUCCESSFUL Total time: 7 seconds
The default text coverage report is generated in the current directory:
[EMMA v2.0.3611 report, generated Sun Jan 11 14:18:08 CST 2004] ------------------------------------------------------------------------------- OVERALL COVERAGE SUMMARY: [class, %] [method, %] [block, %] [line, %] [name] 100% (3/3) 100% (7/7) 95% (116/122) 100% (29/29) all classes OVERALL STATS SUMMARY: total packages: 2 total classes: 3 total methods: 7 total executable files: 3 total executable lines: 29 COVERAGE BREAKDOWN BY PACKAGE: [class, %] [method, %] [block, %] [line, %] [name] 100% (2/2) 100% (4/4) 91% (64/70) 100% (18/18) search 100% (1/1) 100% (3/3) 100% (52/52) 100% (11/11) default package -------------------------------------------------------------------------------
By default, <emmajava> generates a
plain-text report only. The default report's depth is all
which means to show the overall coverage summary followed by breakdown by
package. You can increase the default depth to include package and source
file summaries. This and many other aspects of EMMA report generation can be
configured using various attributes and nested elements that
<emmajava> adds to <java>.
These extensions only take effect when the task is in enabled state and have
no impact on the build otherwise. See Chapter3, EMMA Property Reference
in the reference manual for full details on EMMA configuration.
<emmajava> application runner uses an
instrumenting classloader to add bytecode instrumentation to Java classes as
they are being loaded by the JVM. For efficiency reasons,
<emmajava> does not scan your entire classpath
before it starts running. This has the side effect of only reporting on the
classes that got loaded by the application. If your intent is to base
coverage metrics on the full set of classes in the classpath, you can set
fullmetadata
task attribute to true
. Here
is an example that also adds some extra reports and makes sure the HTML
report generator has access to the source code by setting
sourcepath="${src.dir}"
:
<!-- output directory used for EMMA coverage reports: --> <property name="coverage.dir" value="${basedir}/coverage" /> <target name="run" depends="init, compile" description="runs the examples" > <emmajava enabled="${emma.enabled}" libclasspathref="emma.lib" fullmetadata="yes" sourcepath="${src.dir}" classname="Main" classpathref="run.classpath" > <txt outfile="${coverage.dir}/coverage.txt" /> <xml outfile="${coverage.dir}/coverage.xml" /> <html outfile="${coverage.dir}/coverage.html" /> </emmajava> </target>
Although this was not the case with this tutorial's sample code, chances are your application has third-party library dependencies and you are not interested in their coverage metrics. There are two ways to handle this with <emmajava>:
If these techniques are not sufficient (e.g., you need to exclude testcases from coverage and they are in the same Java packages as the application code and do not follow a sensible naming pattern), you can always switch to offline instrumentation as described next. Offline instrumentation does not keep everything in memory and ultimately gives you much more control over what gets instrumented.
To summarize, an existing build.xml
can be
converted to use EMMA's on-the-fly instrumentation mode by following these
steps:
add EMMA task definitions
replace the necessary invocations of <java> with <emmajava>
configure coverage paths and inclusion/exclusion filters
configure coverage reports
make sure there is a way to turn coverage instrumentation off
Futher reading. This has been a quick intro to EMMA's on-the-fly ANT instrumentation mode. For further details see Section2, <emmajava>/emmarun in the reference manual.
As convenient as the on-the-fly mode is, in many cases it is not sufficient. For example, running a commercial J2EE container in a custom instrumenting classloader is practically impossible. Certain (bad) coding practices also fail for code executing in a custom classloader. Finally, in large scale development there is a common need to collect and merge coverage data from multiple execution runs and multiple JVM processes.
This is where separate instrument/execute/report phases are a necessity. This section repeats the previous exercise using <emma> ANT task, which provides several subtasks for managing offline instrumentation: <instr>, <merge>, and <report>. In a typical ANT build each <emma> tag acts as a container for an arbitrary sequence of sub-tags. This design allows for a simple form of build flow control, whereby entire sequences of EMMA commands can be disabled at a single point.
Let's go back to the starting point of the previous section and
assume that you have a build.xml
file with EMMA tasks
imported and the following build infrastructure created:
<!-- root directory for the example source code: --> <property name="src.dir" value="${basedir}/src" /> <!-- javac class output directory: --> <property name="out.dir" value="${basedir}/out" /> <!-- output directory used for EMMA work files and coverage reports: --> <property name="coverage.dir" value="${basedir}/coverage" /> <target name="init" > <mkdir dir="${out.dir}" /> <path id="run.classpath" > <pathelement location="${out.dir}" /> </path> </target> <target name="compile" depends="init" description="compiles the example source code" > <javac debug="on" srcdir="${src.dir}" destdir="${out.dir}" /> </target> <target name="run" depends="init, compile" description="runs the examples" > <java classname="Main" classpathref="run.classpath" > </target>
In a real world project the actual application could be either an end user application or your test framework driver. Adding offline coverage instrumentation and reporting to this build is not much harder than it was in the command line tools case, in Section 2.3, “Offline mode: separating instrumentation and execution”. All you need to do is sandwich <java> (or your test framework driver, or anything that can run on Java classes) between EMMA's <instr> and <report>:
<target name="emma" description="turns on EMMA instrumentation/reporting" > <property name="emma.enabled" value="true" /> <!-- EMMA instr class output directory: --> <property name="out.instr.dir" value="${basedir}/outinstr" /> <mkdir dir="${out.instr.dir}" /> </target> <target name="run" depends="init, compile" description="runs the examples" > <emma enabled="${emma.enabled}" > <instr instrpathref="run.classpath" destdir="${out.instr.dir}" metadatafile="${coverage.dir}/metadata.emma" merge="true" /> </emma> <java classname="Main" fork="true" > <classpath> <pathelement location="${out.instr.dir}" /> <path refid="run.classpath" /> <path refid="emma.lib" /> </classpath> <jvmarg value="-Demma.coverage.out.file=${coverage.dir}/coverage.emma" /> <jvmarg value="-Demma.coverage.out.merge=true" /> </java> <emma enabled="${emma.enabled}" > <report sourcepath="${src.dir}" > <fileset dir="${coverage.dir}" > <include name="*.emma" /> </fileset> <txt outfile="${coverage.dir}/coverage.txt" /> <html outfile="${coverage.dir}/coverage.html" /> </report> </emma> </target>
When EMMA instrumentation is enabled via
emma.enabled
build property, the sequence of logic here is
as follows:
Again, there is a
emma helper target to set | |
EMMA's offline
instrumentor executes and copies instrumented classes into
| |
The application runs with instrumented classes. | |
In version 2.0, EMMA's runtime coverage data is dumped by a JVM exit handler and for this to happen <java> needs to be forked. | |
As in the command line
case, the instrumented classes need to be first in the classpath. Note that
this only happens when | |
For certainty, <java> is configured with an explicit filename that accepts runtime coverage profile data. | |
Each time you run, do you
want to accumulate coverage data or discard previous results? The
default is the former, which is what is forced explicitly
above. Note, however, that you could set | |
EMMA report processor executes. | |
The report processor combines the class metadata and runtime coverage profile to produce a couple of reports. |
EMMA can do "in place" instrumentation (overwrite mode), whereby the classes and jar files are overwritten with their instrumented versions. However, this is easy to abuse, so the build above is being careful and creates a separate directory for coverage-instrumented output.
Let's see the new build in action. Again, code coverage is turned on only if emma target appears on ANT command line before any run targets:
>ant emma run Buildfile: build.xml emma: init: [mkdir] Created dir: .../examples/out compile: [javac] Compiling 4 source files to .../examples/out run: [emma.instr] processing instrumentation path ... [emma.instr] instrumentation path processed in 200 ms [emma.instr] [3 class(es) instrumented, 0 resource(s) copied] [emma.instr] metadata merged into [.../coverage/metadata.emma] {in 31 ms} [java] EMMA: collecting runtime coverage data ... [java] main(): running doSearch()... [java] main(): done [java] EMMA: runtime coverage data written to [.../coverage/coverage.emma] {in 15 ms} [emma.report] 2 file(s) read and merged in 0 ms [emma.report] writing [txt] report to [.../coverage/coverage.txt] ... [emma.report] writing [html] report to [.../coverage/coverage.html] ... BUILD SUCCESSFUL Total time: 7 seconds
Unlike some other tools, EMMA's design is based around filtering for the right set of classes at instrumentation time. Doing so allows EMMA to scale to enterprise size projects and ensures the best performance throughout all three offline stages: instrumentation, execution, and reporting. To select a subset of classes to be processed, you can use two complementary techniques:
segment your ANT path elements well, do not lump
everything into just one path
. Specifically, keep
third-party libraries, your application classes, and testcase classes in
separate path elements. It will be easy to combine them when needed and code
coverage will be easier because you will have just the right path element to
use as <instr>'s instrpath
attribute or nested element: your application classes.
additionally, you can give <instr> several <filter> nested elements, each potentially containing a list of class name inclusion/exclusion patterns. This will allow you to zoom in on just the right part of your application. And adding an ANT command-line override for one of those will allow every developer on the team to narrow things down to their own module. See the exact ANT syntax for specifying coverage filters in the reference manual.
Note that your build can contain several instrumentation and execution stages and <report> will happily merge all of the results in memory before generating the reports. To merge all files on disk (for maintenance and disk storage reasons) you can use <merge> sub-task:
<target name="merge" description="demonstrates dump file merging" > <emma> <merge outfile="${coverage.dir}/session.emma" > <fileset dir="${coverage.dir}" > <include name="*.emma" /> </fileset> </merge> </emma> </target>In EMMA's offline mode, you are in complete control of mixing and matching metadata and coverage data from different application runs. You can instruct all tools to merge all data in the same file or keep everything in separate file repositories. EMMA tools default to
merge=true
output file mode for metadata and runtime
coverage data, but properties exist to alter this behavior. See Chapter3, EMMA Property Reference in the reference manual for
full details on EMMA configuration.To summarize, an existing build.xml
can be
converted to use EMMA's offline instrumentation mode by following these steps:
add EMMA task definitions
add the necessary <instr> tasks, making sure that the application classes are instrumented before they are used at runtime
configure classpaths and coverage inclusion/exclusion filters such that only your application classes, not third-party libraries or testcases, are instrumented
make sure the instrumented classes will be prepended to your application's runtime classpath (that is, if you are not packaging them as EJBs, a web app, etc instead)
add a <report> task that aggregates class metadata and runtime coverage profiles for reporting as needed
make sure there is a way to turn coverage instrumentation
on and off (you can use either the existing ANT solutions for that or the
enabled
attribute on all EMMA tasks)
Further reading. This has been a quick intro to EMMA's offline instrumentation tools for ANT. For further details read the reference manual starting with Section3, <instr>/instr.