Recently I migrated from CruiseControl to Hudson as our CI build server. One thing I found lacking in Hudson was to send rich text email containing information regarding changelist details, test result etc. The email extender plugin do provide few variables to be inserted into the mail but that is very limiting.So I modified the plugin to send HTML email which were generated using Groovy‘s SimpleTemplate.
In the template the AbstractBuild object was exposed as a binding. Using that one can compose email containing some changelist info, test stats etc. Further the Groovy template can be used to write email content in any form, look and feel as one wants. This post would explain how to write a simple groovy script to display build related information
A patch has been submitted to Hudson as REF2175. In this post I would try to explain how this feature can be put to use.
Plugin Configuration
The updated plugin now provides new fields for adding the mail template. In the plugin template you can add any groovy based script which would then be passed to the groovy template engine. Also you can enable weather the email is of type text/html or text/plain.
All the variables provided by the email-ext plugin earlier are still available for use as bindings
Mail Content
For the mail content we would use the Groovy’s SimpleTemplate. The complete template can be accessed from here. The email generated has following main sections.
- General Information
- Committed changelist
- Test Result summary
- Build logs
Lets see few of these in details.
Collecting Changelist
In the template we first collect the changelist information from the build object. For more details refer to the properties exposed by the AbstractBuild. So here we collect all the changelists from lastSuccesfulBuild. Later we would use this to display the changelist contents in details
<%
def lastSuccesfulBuild = build.previousNotFailedBuild
def failed = build.result != hudson.model.Result.SUCCESS
def currResult = build.result
def prevResult = build.previousBuild?.result ?: null
def consecutiveSuccess = currResult == hudson.model.Result.SUCCESS && prevResult == hudson.model.Result.SUCCESS
def builds = []
def changes = []
def count = 0
if(consecutiveSuccess){
builds << build
def changeItems = build.changeSet.items
count += changeItems.length
changes += changeItems as List
}else{
while(lastSuccesfulBuild){
builds << lastSuccesfulBuild
def changeItems = lastSuccesfulBuild.changeSet.items
count += changeItems.length
changes += changeItems as List
lastSuccesfulBuild = lastSuccesfulBuild.nextBuild
}
}
%>
Displaying the Changelist details
Now we would iterate over the changes to display the details. The groovy code is highlighted below. In my case the SCM repository is Perforce. So the changelist object is PerforceChangeLogSet and hence the properties accessed are exposed by it
<tr>
<td><b>Modifications since last successful build: (${count}) </b><hr size="2" width="100%" align="center"/></td>
</tr>
<tr>
<td>
<table width="100%" border="0">
<% changes.each { item ->
def cl = item.change
%>
<tr>
<td style="font-size:10pt; font-family:Arial, Helvetica, sans-serif">
<a href="http://perforceserver/cgi-bin/p4describe.pl?@${cl.changeNumber}">${cl.changeNumber}</a>
by ${cl.user}@${cl.workspace} on ${cl.date}</td>
</tr>
<tr>
<td><pre style="font-size:8pt; font-family:Arial, Helvetica, sans-serif">${cl.description}</pre></td>
</tr>
<tr>
<td style="font-size:10pt; font-family:Arial, Helvetica, sans-serif">
<span style="font-size:10pt; font-family:Arial, Helvetica, sans-serif; "> Affected files:</span>
<ul style="font-size:8pt; font-family:Arial, Helvetica, sans-serif">
<% cl.files.each{ fileEntry -> %>
<li>[<a href="http://perforceserver/cgi-bin/p4browse.pl?@diff+${fileEntry.filename}+${fileEntry.revision}+{@action}">Diff</a>]
[<a href="http://perforceserver/cgi-bin/p4browse.pl?@filelog+${fileEntry.filename}">History</a>]
${fileEntry.action} ${fileEntry.filename} (${fileEntry.revision})
</li>
<% } %>
</ul>
<hr size="1" width="80%" align="left" />
</td>
</tr>
<% } %>
</table>
</td>
</tr>
This results in following output

Test Result display
I had a requirement to depict the test results in the mail. The result should display the testcases which have started failing seperately from the previous failing test.(I know all test should pass but in reality few test continue failing due to some reasons or other). So here we iterate over current test results and previous test results to determine the delta. Also we collect some data to create a link for the failing test
Segregating the test results in such a way brings the required attention to the test cases which had started failing
<%
if(build.testResultAction) {
def rootUrl = hudson.model.Hudson.instance.rootUrl
def testResult = build.testResultAction
def jobName = build.parent.name
def lastBuildSuccessRate = String.format("%.2f",(testResult.totalCount-testResult.result.failCount)*100f/(testResult.totalCount))
def lastBuildDuration = String.format("%.2f",(testResult.result.duration ))
def startedPassing = []
def startedFailing = []
def failing = []
def previousFailedTestCases = new HashSet()
def currentFailedTestCase = new HashSet()
if(build.previousBuild?.testResultAction){
build.previousBuild.testResultAction.failedTests.each {
previousFailedTestCases << it.simpleName +"." + it.safeName
}
}
testResult.failedTests.each{tr ->
def packageName = tr.packageName
def className = tr.simpleName
def testName = tr.safeName
def displayName = className+"."+testName
currentFailedTestCase << displayName
def url = "${rootUrl}job/$jobName/lastBuild/testReport/$packageName/$className/$testName"
if(tr.age == 1){
startedFailing << [displayName:displayName,url:url,age:1]
} else{
failing << [displayName:displayName,url:url,age:tr.age]
}
}
startedPassing = previousFailedTestCases - currentFailedTestCase
startedFailing = startedFailing.sort {it.displayName}
failing = failing.sort {it.displayName}
startedPassing = startedPassing.sort()
%>
This data is then later displayed in list form as shown below
Conclusion
Using Groovy for generating the email content provides considerable flexibility. It exposes the Hudon data model (which is quite neatly designed) and makes it easy to extract information from it. Further it reduces the requirements to modify the plugin code considerably.
I find it more convenient over the xsl based approach used by CruiseControl. Below is a snapshot of the complete mail send to the users.
Update
I have uploaded the compile hpi file here. Just download it and then add it to your Hudson setup at Hudson -> Manage Hudson -> Manage Plugins -> Advanced


