2.3. Offline mode: separating instrumentation and execution

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.

Instrumenting just the right classes

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 1outinstr;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).

Don't forget the classpath


Notice an important detail: the instrumented classes must appear first in the classpath. Also, instr does not copy non-executable items like interfaces and resource bundles, so you need to have the original out behind outinstr for everything to work.

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.