This post describes a script I wrote to take XML Emma output and produce multi-package aggregated statistics. One of the drawbacks of Emma's HTML reporting is that it does not allow you to get aggregated coverage information across packages. For instance, if I have packages "com.foobar.sdk.interface" "com.foobar.sdk.impl", there's no automated way to get coverage information for all packages starting with "com.foobar.sdk". Most larger projects are logically grouped like this, so having these "superpackage" groupings is useful. My previous method of getting this was to cut-and-paste the HTML from a browser into a text file, run a Ruby script on it to convert it to CSV, import the CSV into Excel, and add the necessary formulas to the sheet to get the measurements I wanted. Having it simply printed out at the end of the Emma run is much simpler.
First, the setup of Emma. Inside the <report> tag, I put the following output descriptions:
<html outfile="${emma.coverage.dir}/foobar/coverage.html"
columns="name,class,method,block,line"
sort="+name,+class,+method,+block,+line" depth="method"/>
<xml outfile="${emma.coverage.dir}/foobar_coverage.xml"
columns="name,class,method,block,line"
sort="+name,+class,+method,+block,+line" depth="method"/>
These create both the full Emma HTML report and an XML document with the same results. After calling the report target that includes this, I then use the <groovy> Ant task to call a script which parses the Emma XML and produces some output.
<echo message="------------EMMA Summary----------------" />
<groovy src="${test.scripts.dir}/EmmaParser.groovy">
<arg value="${emma.coverage.dir}/rules_coverage.xml" />
<arg value="com.foobar.sdk:SDK,com.foobar.tools:SDK,com.foobar.engine:ENGINE,com.thirdparty:ENGINE"/>
</groovy>
<echo message="----------------------------------------" />
The format of the second argument is comma-delimited set of Java package prefixes and "superpackage" names for which we want aggregate coverage. In the above example, all packages that start with "com.foobar.sdk" and "com.foobar.tools" are grouped into the "SDK" aggregate, and "com.foobar.engine" and "com.thirdparty" are grouped into "ENGINE". For each superpackage, the total number of lines, number of lines covered, and percentage covered are printed.
Below is the groovy script which does the EMMA XML work. A few comments on it:
- The Groovy XmlParser class was a joy to use and vastly simplified accessing the XML document.
- The regex was the hardest part to get right. I most commonly write regexes in vim, which requires different escaping that Groovy. It involves both captures and parenthesis in the expression. In Groovy regexes, you escape the parens you want in the expression and don't escape the capture parens. This really tripped me up on the next groovy project after this one, where I reversed the meaning when looking at this regex.
- Closures are such a nice feature to have when parsing with XmlParser like this. Their use in iteration and assignment of local variables makes the code much shorter to read and understand.
The script:
def config = args[1]
def pkgmap = [:]
def spkgs = [:]
def cmap = [:]
def tmap = [:]
// split the config string by comma, then by colon
config.split(',').each { entry ->
(entry =~ /(.+):(.+)/).each { all, pkg, spkg ->
pkgmap[pkg] = spkg
spkgs[spkg] = ''
}
}
// init the package map
pkgmap.each { k, v -> cmap[v] = 0; tmap[v] = 0; }
// parse the report
def report = new XmlParser().parse(new File(filename))
// get the stats for the "line" coverage for each package
// packages are non-bundling, so pkg.foo does not contain stats for pkg.foo.bar
report.data[0].all[0].'package'.each() { pkg ->
pkgmap.each { pkgname, sname ->
if ((pkg.'@name').startsWith(pkgname) ) {
(pkg.coverage[3].'@value' =~ /d+%s+((d+.*d*)/(d+))/ ).each {
all, cov, total ->
cmap[sname] += Float.valueOf(cov)
tmap[sname] += Integer.valueOf(total)
}
}
}
}
// print summary stats for each super-package
spkgs.each { sname,x ->
if (tmap[sname] > 0) println "," + sname + "," +
String.format("%.2f",cmap[sname]*100/tmap[sname]) + "%," +
cmap[sname] + "," + tmap[sname]
else println "," + sname + ",0%,0,0"
}
Post a Comment