The Kaptain on … stuff

26 Sep, 2010

Groovy inspect()/Eval for Externalizing Data

Posted by: TheKaptain In: Development

One of the things I love about Groovy is how easy it makes reading and writing text files. I’ve written Groovy scripts for everything from parsing log files for extracting timing information to finding (and replacing with selectors) in-line css blocks. Often there’s a piece of information extracted from a file that I want to keep for further examination, or just for future reference. The Groovy inspect() method provides a nice easy way to take simple results that are stored in variables and write them to a file. Then the Groovy Eval class provides convenience methods to parse that information back from the file in a single line. Here’s examples of test code that creates Lists and Maps, writes them out to a file and then asserts that evaluating the stored Groovy code results in the same data structures.

[groovy language=”true”]
private static final Closure RANDOM_STRING = RandomStringUtils.&randomAlphanumeric
private static final String TMP_DIR = System.getProperty(‘java.io.tmpdir’)

@Test
public void testSerializeListToFile()
{
List<String> accumulator = []
100.times { int i ->
accumulator << RANDOM_STRING(i + 1)
}
File file = new File("$TMP_DIR/inspectListTest.groovy")
file.deleteOnExit()
file << accumulator.inspect()
assertThat(accumulator, equalTo(Eval.me(file.text)))
}

@Test
public void testSerializeMapToFile()
{
Map<String, String> accumulator = [:]
100.times { int i ->
accumulator[RANDOM_STRING(i + 1)] = RANDOM_STRING(i + 1)
}
File file = new File("$TMP_DIR/inspectMapTest.groovy")
file.deleteOnExit()
file << accumulator.inspect()
assertThat(accumulator, equalTo(Eval.me(file.text)))
}
[/groovy]

Two little tidbits of goodness embedded in this example are the ability to capture a method as a closure, in this case RandomStringUtils.randomAlphanumeric(), and the File.deleteOnExit() method – which is only cool because I never noticed it in the API before and it turns out to be a great way to clean up after tests.
🙂

A particular usage of this technique I’ve been using lately has been sparked by a shift of tools at work. I’ve worked with the Atlassian stack of web applications for years now, and have always enjoyed the rich feedback Bamboo gives for build history. But now I’m using Hudson as the primary continuous integration tool and one of the things I’ve been sorely missing is the ‘Top 10 Longest Running Tests’. If you’re not familiar with this view in Bamboo, stroll on over to the Groovy build results page and click on the tab.
One of my present priorities at work is to speed up build times and rooting out slow or inefficient tests is one way to do this. It’s possible to elicit this information from JUnit test reports using xsl(see this link for an example) but I came up with a nice way to incorporate similar functionality into a Gradle build. Following the Gradle project-report conventions, this task simply uses the inspect() method to write out a results file in the ‘reportsDir’. I’ve captured this output(manually) over time to track the progress of speeding up test times, and the format makes it easy to read back in results and do deeper analysis and aggregation – such as the creation of csv reports and simple graphs for instance.

[groovy language=”true”]
tolerance = 2.0
task findLongRunningTests << {
description= "find all tests that take over $tolerance seconds to run"
String testDir = "${project.reportsDir}/tests".toString()
file(testDir).mkdirs()
File file = file("$testDir/longRunningTests.txt")
file.createNewFile()
BufferedWriter writer = file.newWriter()
writer << parseTestResults(‘**/TESTS-TestSuites.xml’, tolerance)
writer.close()
}

/**
* Read in xml files in the junit format and extract test names and times
* @includePattern ant pattern describing junit xml reports to inspect, recursively gathered from the rootDir
* @tolerance the number of seconds over which tests should be included in the report
*/
private String parseTestResults(includePattern, float tolerance)
{
def resultMap = [:]
fileTree(dir: rootDir, include: includePattern).files.each {
def testResult = new XmlSlurper().parse(it)
testResult.depthFirst().findAll {it.name() == ‘testcase’}.each { testcase ->
def key = [[email protected](), [email protected]()].join(‘:’)
def value = [email protected]() as float
resultMap[(key)] = value
}
}
return resultMap.findAll {it.value > tolerance}.sort {-it.value}.inspect()
}
[/groovy]

Note that this works recursively from the ‘rootDir’ of a Project, so it is effective for multi-project Gradle builds. Source code for this simple example is available on github if you want to check it out yourself.

Here’s a sample result drawn from examining some test output in the Gradle build itself. Only one of the tests executed in WrapperTest takes more than 2 seconds to execute.
[groovy language=”true”]
["org.gradle.api.tasks.wrapper.WrapperTest:testWrapper":2.37]
[/groovy]

3 Responses to "Groovy inspect()/Eval for Externalizing Data"

1 | Hans Dockter

January 17th, 2011 at 2:10 am

Avatar

Hi Kelly,

just wanted to point out (a little bit late :)) that you can also use test listeners for this. See: http://gradle.org/0.9.1/docs/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:afterTest(groovy.lang.Closure)

Cheers

Hans

2 | TheKaptain

January 17th, 2011 at 8:26 am

Avatar

Thanks Hans, and that’s exactly the way I’ve gone since writing this. Here’s the Closure that I’m using as part of a regular developer build:

def tolerance = 2000
final Closure testLogger = { descr, result ->
def time = result.endTime - result.startTime
if (time > tolerance)
{
logger.warn("Test: ${descr.className}.${descr.name} is long running: ${time / 1000} s")
}
}

3 | Hassan Jawad

October 16th, 2011 at 2:19 pm

Avatar

Thanks awesome information love this post

Comment Form