Techkriti

August 30, 2008

Using Groovy with Hudson to send rich text email

Filed under: Java — Chetan @ 7:27 pm

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

Email-Ext configuration

Email-Ext 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:&nbsp; (${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

Create a free website or blog at WordPress.com.