Ant transforms using Groovy template scriptdef

April 9, 2012

Ant provides powerful transformation capability. For example, the copy task can include filters that replace tokens in the text. There is also the XSLT task to perform more complex XML based transforms.

Sometimes you may need more dynamic or procedural transforms for non-xml based templates. While this can be done in Ant, they should not be; Ant should really only be used in a declarative manner. One can do this outside of Ant using custom solutions or use the various templating frameworks, such as Velocity and Freemarker, which provide Ant plugins.

Another alternative is the Groovy language which has always provided text processing in the form of GStrings and templates. These are used in various solutions such as Groovlets and Groovy Server Pages. Below I show how Groovy templates can be easily used in Ant.

listing 1 shows a meeting agenda text template with a list of topics. Note that it has a GString interpolated string using “$” character. It also, analogous to Java Server Pages, uses a scriptlet with <% and %> to execute code. Yes I know, scriptlets are evil.

List 1, template1.txt, a template:

Hello ${name}!
Agenda is:
<% 
   subject.split(',').each{
%>- $it 
<% }	
%>

In listing 2 below, an Ant script invokes the “template” task to transform the template in listing 1.

Listing 2, build.xml, use template task:

<project default="init">
	
<import file="tasks.xml"/>
	
<target name="init" 
  xmlns:jb="http://josefbetancourt.wordpress.com">
  <jb:template 
	src="template1.txt" 
	dest="temp.txt" 
	property="result" 
	failOnError="true">
		
	<property name="name"    
		value="world"/>
	<property name="subject" 
		value="beans,GStrings,templates"/>
		
  </jb:template>
	
  <echo>${result}</echo>
	
</target>	
</project>

Listing 3 shows an example run.

Listing 3, example use:

init:
Saving result to temp.txt
     [echo] Hello world!
     [echo]     Agenda is:
     [echo]     - beans
     [echo]     - GStrings
     [echo]     - templates
     [echo]

BUILD SUCCESSFUL

Listing 4 is the implementation. It is a simple scriptdef using Groovy as the script language. The Groovy code is inline but could have been in external file using the “src” attribute of the scriptdef declaration.

Listing 4, tasks.xml, the scriptdef implementation:

<project>
<!-- config stuff not shown -->

<!--
Render a Groovy template using properties to create the binding 
 -->	
<scriptdef name="template" 
  language="Groovy" 
  classpathref="groovy.lib" 
  uri="http://josefbetancourt.wordpress.com">
	
  <attribute name="src"/>   <!--  required -->
  <attribute name="dest"/>  <!--  optional -->
  <attribute name="property"/> <!--  optional -->
  <attribute name="failOnError"/> <!--  optional, true -->
  <element name="property" type="property"/> <!--  required -->
	
<![CDATA[
def SRC= 'src';
def PROPERTY = 'property'
def DEST = 'dest'
def FAILONERROR = 'failonerror'
		
try{
	// convert element properties to map
	def map = [:]
	elements.get(PROPERTY).each{
		map.put(it.name,it.value)
	}
			
	// render the template
	String result = new groovy.text.GStringTemplateEngine()
	  .createTemplate(new File(attributes.get(SRC))
	  .getText())
	  .make(map).toString()
	
	def prop = attributes.get(PROPERTY)
	if(prop){ // save to property?
		project.setNewProperty(prop, result)
	}
		
	def dest = attributes.get(DEST)
	if(dest){ // write to file?
		project.log("Saving to $dest",project.MSG_INFO)
		new File(dest).write(result)
	}
}catch(Exception ex){
	def f = attributes.get(FAILONERROR)
	def failOnError = f ? f : true;

	if(failOnError == 'true'){
		throw ex;
	}		
}			
]]>
</scriptdef>  <!-- end name="template" -->

</project>

Summary
Shown was an example of using Groovy templates in an Ant task. A simple scriptdef was created to allow reuse in Ant scripts.

Extensions
Kind of begs the question, since scriptlets have a bad rep, should there instead be a way of using taglibs in the templates. I think Grails implemented Groovy taglibs.

References


Jenkins CI Server is great

March 16, 2012

Finally got a Jenkins server installed. Had a host of system issues, like communicating to our source code repo.

Jenkins is a joy to use. Well, it is not perfect, what is? Like, I need to pass the user’s name that invoked a build via Jenkins to the target DOS script (yea, Windows) that eventually invokes the legacy Ant scripts. A quick Google search shows that this is asked in various ways, but no answers. For example, here or here. Hmmmm.

Anyway, now comes a trial use, to see if it is what we really need and can we manage it to do what we will want. With 400 plugins, I don’t see how it could lack. Plus, I’m sure I can use the Groovy plugin to cobble something up. Jenkins even includes a Groovy Console. Finally, there is a road map for possible migration of legacy Ant scripts to Gradle using the Gradle Plugin.

I take back my past snarky comment. Jenkins is not just a pretty face on Cron.

BTW, is there some Wiki law that says a wiki shall never ever have a link to the parent project? If you get tossed into a wiki by following a link, invariably you will click in agony at links that should go to the real home. Instead, you have to edit the URL in the address bar. Since I never curse, I can’t write “wtf”.

Off Topic
Was watching the Easyb intro video. BDD is interesting. Definitely “should” is a better then “test”. With so many great tools why are products still bug ridden?

More stuff

  1. Jenkins home page
  2. Continuous integration Not a very good Wikipedia article
  3. Continuous Integration Much better
  4. Continuous Integration in Agile Software Development
  5. Hooking into the Jenkins(Hudson) API
  6. Five Cool Things You Can Do With Groovy Scripts
  7. Parameterized Builds in Jenkins – choosing subversion folders
  8. Groovy Console
  9. Groovy plugin
  10. Switching to Jenkins–Download and Install Artifact Script for Tester
  11. Gradle Plugin

How to fail an Ant Groovy Scriptdef task

February 18, 2012

While you can certainly just throw an exception in a scriptdef to stop an Ant script that is using a scriptdef, perhaps it is best to use Ant’s Exit task. This allows Ant to do whatever it has to do as it handles the resulting BuildException.

The exit task is used in an Ant script as “fail”. To use ‘fail’ in a scriptdef you just use the predefined ‘self’ variable that references the scriptdef instance. This is illustrated in listing 1 below.

An alternative to the scriptdef ant task is to use the ‘groovy’ task that comes with the Groovy environment. This is perhaps the simplest approach when you don’t need the full structure that the scriptdef task makes available to a script. The scriptdef task allows one to pass in Ant XML elements and attributes.

Listing 1

<project name="example" default="demo">

  <!-- reference the groovy libs -->
  <path id= "libs" >
    <fileset dir= "../lib">
      <include name="groovy/groovy-all-1.8.6.jar" />
    </fileset>
  </path>

  <!-- An inline scriptdef that throws an exception -->
  <scriptdef name="solveProblem" 
        language="Groovy" classpathref="libs">

     self.fail("intentionally failed")	 

  </scriptdef>

  <!-- a call of the scriptdef -->
  <target name="demo">
	<solveProblem/>
  </target>
</project>

A run …

>ant -f fail.xml
Buildfile: fail.xml

demo:

BUILD FAILED
fail.xml:15: intentionally failed

Total time: 1 second

Further reading


Archive files using Ant from Groovy

September 12, 2011

In Ant The zip task takes a fileset but only creates one archive. How do you zip files into individual archives?

Use case
One use case for this is log files. Sooner rather then later, every system must deal with the growth of log files. One of the best utilities for handling this is logrotate. This is available on *nix systems. There are probably native versions that do similar tasks in Windows, but I did not locate any ‘free as in beer’ ones.

Approach
Since I’m working with Ant, on Windoze, and can’t install cygwin or other system, will use Groovy to get around Ant limitations. This will not be an attempt to recreate logrotate, of course.

As in a previous post, we can use the Groovy AntBuilder and iterate a fileset to repeatably call an Ant task. In this case we’ll be invoking the zip task. Note that the same approach could be used to create a scriptdef to do this directly in an Ant build script. This would be more usable since we could declaratively define the fileset, etc.

Example

First the configuration. In listing 1, we use a JSON configuration file. No reason, I’m just tired of Property files.

Listing 1, JSON configuration file

{ "about":
    {
    	"project":"archivelogs",
    	"author":"jbetancourt",
    	"date":"20110909T2156"    	
    },
  "config":
	{
		"basedir":"test/logs",
		"archivedir":"test/logs/archive",
		"includes":"*.log",
		"propertyName":"found.files",
		"when":"before",
		"update":"true",
		"days":"1",
		"level":"9"
	}
}

In listing 2, the main entry point calls the config loader and then the ‘archiveLogs’ method.

The fileset created with the AntBuilder uses an Ant ‘date’ selector to find the files that are x amount of days old. Then the resulting fileset is scanned in a loop, invoking the zip task as we go and then deleting the just zipped file. It is assumed here that the target file name has descriptive information, like a date. And, we are not adding a “rotate” counter to the name for future logrotate like behavior.

Listing 2, Groovy archive source

package ant

import org.apache.tools.ant.*
import java.text.*
import groovy.json.JsonSlurper

/**
 * @author jbetancourt
 */
class ArchiveLogs {
	def format = "yyMMdd'T'kkmm"
	def df = new SimpleDateFormat(format)

	/** entry point */
	static main(args) {
		def als = new ArchiveLogs()	
		
		def p = (
		  (args) ?
		  args[0] : "test/conf/archivelogs.json")
				
		def map = als.loadConfiguration(p)
		als.archiveLogs(map)
	}

	/**  */
	def archiveLogs(conf) {
		def base = conf.base
		def targetDateString = df.format(
			(new Date() - Integer.parseInt(conf.days))
		)
		
		def ant = new AntBuilder()		
		ant.fileset( 
			dir:conf.basedir, 
			includes:conf.includes, 
			id:conf.propertyName){			
				date( 
					datetime:targetDateString,
					when:conf.when,
					pattern:format)
		}	
		
		def ref = ant.project.getReference(
			 conf.propertyName)
		
		ref.each{ fileResource ->
			File file = fileResource.getFile()
			def name = file.getName()
			
			ant.zip(
				basedir:conf.basedir,
				destfile:
				 "${conf.archivedir}/${name}.zip",
				update:conf.update,
				level:conf.level,
				includes:name
			)
			
			ant.delete(verbose:"true",file:file.getPath())			
		}		
		
	}	
	
	/** load json file */
	def loadConfiguration(String path){
		def slurper = new JsonSlurper()
		def obj = slurper.parse(
			 new File(path).newReader())
		
		if(obj.config.debug == 'true'){
			obj.config.each{
				println it
			}		
		}
		
		return obj.config
		
	}
}

Caveats
This is, of course, a simplistic approach. For example, what if the file is being used when we attempt to delete it? Or we fail to zip the file, but continue to delete it? And, this source, being an example, does not have any error handling.

Alternatives
The ant-contribs project has looping support, foreach. Directly using the Java util packages for archiving, such as java.util.zip. Or, the libraries found in, for example, the Apache commons project.

Further Reading


“cafe”, egberto gismonti


Ant InputHandler GUI Using Groovy

August 16, 2011

Intro

With Apache Ant you can get input from the user using the “input” task. When you run Ant in a console window, the DefaultInputHandler is used. This handler just uses the console’s error and input streams.

When you run Ant in a graphical IDE, a Graphical User Interface based input handler may be used. This handler will use the graphics toolkit (on Eclipse that would be SWT) to pop up dialog boxes.


GUI in console?

Can you use GUI input dialogs when running in a command shell? Yes, and it is very easy. (Of, this is true if a gui environment is available to Java runtime, etc.).

First you create an InputHandler subclass that will be used with the Ant command option “-inputhandler class name”. If you use Ant 1.8* you can use a nested “inputputhandler” task in the “input” task within the build script.

Probably a much better approach is to just create a new custom Ant task, “ginput” for this, or even use a macrodef or scriptdef that uses Groovy inline or from a script file.

Another alternative is to use a browser interface for the Ant input. See “Java’s HTTP Server for browser-based Groovy app” for an example of using an embedded server to invoke browser UI.

Groovy?
Why Groovy for this? Just to be able to use the SwingBuilder that potentially could make the Gui creation much simpler. One can envision a full blown form being used for cases where Ant is used outside its traditional use-case.

GUI InputHandler subclass

Below in listing 1, there is an example Groovy source: AntGuiInputHandler.groovy

Much harder is actually getting Ant to use this handler.

Build file

First lets create an Ant build file that will get some input:

<project name="AntGui" default="all" basedir=".">
<target name="all">

    <input message="What is your name?"
       addproperty="got.response"/>		
    <echo>${got.response}>/echo>		
</target>

</project>

Compiling
Now, compile the Groovy source as follows:

groovyc AntGuiInputHandler.groovy

Running
There are many ways to actually run the Ant build. The simplest but not the best way is to set up your CLASSPATH and then run Ant as usual, for example:

C:\temp\AntGui&gt;set classpath=.;\java\groovy\embeddable\groovy-all-1.8.1.jar

C:\temp\AntGui&gt;ant -inputhandler AntGuiInputHandler
Buildfile: C:\temp\AntGui\build.xml

all:
[input] prompt=What is your name?
[input] Josef
[echo] Josef from Groovy InputHandler

BUILD SUCCESSFUL
Total time: 11 seconds

The resulting input dialog is:

Ant GUI InputHandler prompt

Listing 1
Note: I currently have no Swing mojo, so this is just reuse of code snippets. BTW, SwingBuilder could use more docs or examples.

import java.awt.event.WindowEvent
import javax.swing.ActionPropertyChangeListener;

import groovy.swing.SwingBuilder;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.input.InputHandler;
import org.apache.tools.ant.input.InputRequest;
import org.apache.tools.ant.input.MultipleChoiceInputRequest;

import javax.swing.*

/**
 * @author jbetancourt
 *
 */
class AntGuiInputHandler implements InputHandler {

	/* (non-Javadoc)
	 * @see org.apache.tools.ant.input.InputHandler#handleInput(org.apache.tools.ant.input.InputRequest)
	 */
	@Override
	public void handleInput(InputRequest request) throws BuildException {
		def response = ''
		println "prompt=${request.getPrompt()}"
		
		if(request instanceof org.apache.tools.ant.input.MultipleChoiceInputRequest){
			response = 	getMultipleChoiceInput(request)
		}else{
			response = getTextInput(request)
		}
		
		println response
		request.setInput("${response} from Groovy InputHandler");
	}
	
	String getTextInput(InputRequest request){
		//def swingBuilder = new SwingBuilder()
		def response = ''
                // how to do this with the builder?
		response = JOptionPane.showInputDialog(
                   null, 'Project Name','Enter name', JOptionPane.OK_OPTION)

		return response	
		
	}
	
	
	String getMultipleChoiceInput(InputRequest request){
		def swingBuilder = new SwingBuilder()
		def req = (MultipleChoiceInputRequest)request;
		
		def choices = req.getChoices()
		def defaultValue = req.getDefaultValue()		
		def prompt = request.getPrompt()
		
	    def pane = swingBuilder.optionPane(
                  message:prompt, 
                  selectionValues:choices as Object[], 
                  optionType:JOptionPane.CLOSED_OPTION)
		def dialog = pane.createDialog(null, 'dialog')
		dialog.show()
		
		def response = pane.getInputValue()
		
		println "input: ${response}"		
		
		return response
	}
}

Pretty groovy use of Groovy!

Previously posted at: “Ant InputHandler GUI Using Groovy

Updates
2011-08-16T2159: Just stumbled upon the AntForm project. If I knew about it, I wouldn’t even have tried doing the above. Oh well.


A Groovy powered blog?

August 13, 2011

Check out the technologies being used at Guillaume Laforge new blog. Very impressive and looks great.

Maybe that blog system can be made into a project so that others can use it too?

Update

  1. Seems he is not too keen on Google’s new price structure: Google App Engine’s new pricing model
  2. Not Groovy related, but another post on web hosting strategies using services, like GitHub, S3, etc.: Strategy: Run a Scalable, Available, and Cheap Static Site on S3 or GitHub

Links

Other Links

 

 

 


Updates
- 2011-08-14T19:29 -
I gave a quick look at Gaelyk. At the first test run on local system I got:

WARNING: failed Server@98f352: java.net.BindException: Address already in use: bind
************************************************
Could not open the requested socket: Address already in use: bind
Try overriding --address and/or --port.

Which seems to indicate some kind of command line option switches. So, tried using –port 9080. No good, unknown command option.

Ok, its not one of those things you just flip a switch and your done, you have to pour thru docs from a whole bunch of stuff, RTFM!

Found the answer on the gradle-gae-plugin page. I just added httpPort = 9080 to the gae closure in the build.gradle script. It works!

Hmmm. I like convention over configuration, but the convention is that one will invariably change the port number, thus, that configuration should be explicit.

 

<eop/>


Transform XML with Groovy and XMLTask

July 21, 2010

(originally created 15 July 2007)

Abstract

Presented is an example using Groovy’s AntBuilder to invoke XMLTask to transform an XML file.  Also shown is how to unit test using the XMLUnit framework.

Jump to script
Jump to testing

Background

This is my third program used for learning Groovy. How it came about? I had to do an XML transform, change a flag value in an element. After looking at very cryptic sed, grep, awk, bash approaches, I decided that a naive Java program would be good enough to get this out the door ASAP, a simple state machine to traverse the file. This is a Java shop, so if I get run over by a truck, anyone could maintain it. So, I coded a Java class that performed the string replace. Not so unusual, of course, plenty of applications do this, like parse RSS feeds and so forth.

XML transform

The problem with a programmatic string replace of XML is that it is not semantically coherent. One is changing a tree structured data structure using a flat text based approach. Sure it works now, but changes to the data structure may break it. Plus, XML aware tools would provide better coding and testing. Thus, though we shipped my simple transformer I still was thinking about this; maybe next maintenace cycle I could replace it with something more robust. Should I have used XSLT or some other XML based approach, SAX, DOM, StAX, XQuery, JDOM, XOM? Forgot to mention that other preexisting scripts were already manipulating the XML files as text, so transforming and creating XML output that changes the ‘text’ layout could have broken existing processes.

After thinking about it I finally felt that XMLTask would offer the most direct approach. Essentially, this is just a single line, which in Ant would be:

<replace path="//UNIT[@name=\'widgetB\']/VALUE/@value" withText="${newValue}"/>

Software used

  • Groovy 1.5
  • XMLTask 1.15.1
  • XMLUnit 1.1
  • JUnit 3.8.1
  • Ant 1.7
  • Java 1.5
  • GroovyIDE plugin for Eclipse 1.0.1

Requirements

Some requirements that illustrate why the Groovy AntBuilder was chosen:

  • Not change the XML text file except for the specific target node.  (don’t remember if this was true.  7/21/10)
  • Easy to write.
    • Uses mainstream language (Java, but…)
    • Compact, using scripting flavor
    • Plenty of docs on the language
    • Easy to test and debug
  • Command line driven;
  • Cross platform. Used on Windows and Linux, so no Bash or PowerShell scripting
  • No cygwin, just because it is not on each machine
  • Easy to maintain. That ruled out a one-line Perl script or monstrous Bash script with sed, awk, here documents, etc.
  • Reusable
    • Can be copied and used for other tasks. (Note, don’t worry about extensibility).
  • Performance.  Not a concern in this case.

Replace Script

As shown in listing 2, this is amazingly small. Sure there are other frameworks and libraries that are even more powerful, but this is within an existing framework, Ant, so its available as part of larger processes.

Not shown here is the hours I wasted trying to get XMLCatalog support to work so that the DOCTYPE could be handled properly. I’m sure if other XML technologies such as namespaces or Entities were being used, that would have also caused aggravations. Fortunaely, in this case, using XMLTask’s ‘entity’ element got around the custom ‘classpath’ path being used here. I left this in this example in case someone has the same issue. I saw a bunch of forum pleas for help with this.

Listing 2 script

/**
 * Example of using Groovy AntBuilder to invoke XMLTask via Ant API.
 * @author Josef Betancourt
 * @date 20071205T23:14
 */

 def SOURCE='data/storage.xml'
 def DEST='target/storage-new.xml'
 def XPATH='//UNIT[@name=\'widgetB\']/VALUE/@value'
 def VALUE='Y'
 def SYSTEM = 'classpath://some/path/to/dtd/narly.dtd'
 def REMOTE = SYSTEM

 def ant = new AntBuilder()
 ant.sequential{
        path(id: "path") {
            fileset(dir: 'lib') {
                           include(name: "**/*.jar")
                  }
        }

        ant.taskdef(name:'xmltask',classname:
            'com.oopsconsultancy.xmltask.ant.XmlTask',
            classpathref: 'path')

        ant.xmltask(source:SOURCE,dest:DEST,
               expandEntityReferences:false,
               report:false,system:SYSTEM){
                 // don't use DTD spec'd in DOCTYPE
                 entity(remote:REMOTE,local:'')
                 replace(path:XPATH,withText:VALUE)
        }
 }
// end Replace.groovy script

How would have normal Ant have looked like? Not bad. In this case, the Ant script is just as small. The only advantage the Groovy approach would have, other then the avoidance of pointy brackets, is the potential to allow more programmatic manipulations. Of course, I had problems getting XML Catalogs to work in Ant too. Here is my plea to the open source movement: if your not going to document it well, don’t bother. Minimally, there should be examples for all the use cases.

Listing 3 conventional Ant use


<taskdef name="xmltask" classname="com.oopsconsultancy.xmltask.ant.XmlTask"/>

<target name="transform" depends="init">
 <xmltask source="${inputFile}" dest="${outputFile}"
    expandEntityReferences="false" report="false"
      system="classpath://some/path/to/dtd/narly.dtd">
    <entity remote="classpath://some/path/to/dtd/narly.dtd"
         local=""/>
    <replace path="//UNIT[@name=\'widgetB\']/VALUE/@value"
         withText="${newValue}"/>
 </xmltask>
</target>
Using command Line arguments

Instead of hard coding the values, you can get them from the command line with something like the following, which uses Apache Common’s CLI.

Listing 4 command line arg parsing

 // === command line options handling
def cli = new CliBuilder()
cli.s(longOpt: 'source',"source file path",args:1,required:true)
cli.d(longOpt: 'dest',"destination file path",args:1,required:true)
cli.x(longOpt: 'xpath',"xpath expression",args:1,required:true)
cli.v(longOpt: 'value',"replacement string",args:1,required:true)

def options = cli.parse(args)
if(options.s){SOURCE = options.getOptionValue('s')}
if(options.d){ DEST = options.getOptionValue('d')}
if(options.x){ XPATH = options.getOptionValue('x')}
if(options.v){ VALUE = options.getOptionValue('v')}
//=== end command line options handling

Unit Testing

Ok the transform works. How do you know? Eyeballing the resulting XML files? If there are structural changes made to the XML file, will it still work?

Eyeballing the files is not reliable and cannot be automated. One way of testing the changes, is to just use tools such as ‘diff’, and testing the output. I tried that, worked great, until the actual QA testing. There were end-of-line differences in the files depending where you ran the transform and the initial source file. So that would have then required dos2unix or unix2dos to be part of the pipeline. Perhaps there is a switch to diff command to get by this, but I did not find it.

For testing I used JUnit and XMLUnit. I just subclassed Groovy’s JUnit Test subclass called GroovyTestCase.

The test data file is similar to the production data, but much smaller, of course.

Writing the tests was harder then writing the actual script. Fortunately XMLUnit has a very easy to use API.

As usual there were complications. Again, the DOCTYPE was killing the test and I could not get the XMLCatalog support working. My hack was to preprocess the source and output files and filter the DOCTYPE. Notice how small this method is. Straight Java would be pretty wordy.

/** read file, stripping doctype string */
 String filterDocType(path, docString) throws Exception{
           def input = new File(path);
           def writer = new StringWriter()
           input.filterLine(writer){
             it.replaceAll(DOCSTRING,{Object[]s -> ""})
           }
           return writer
 }

The code below is showing the use of command line arguments, whereas the Replace.groovy code was not using them. I left them in here since my original code was using args, this testing shows how to create a command line arg array and invoke a Groovy script.

Listing 5 Replace Unit Test

/**
 * Unit testing the example of using Groovy AntBuilder to invoke XMLTask via Ant API.
 * @author Josef Betancourt
 * @date 20071205T23:14
 *
 * @see http://www.bytemycode.com/snippets/snippet/475/
 * @see http://www.oopsconsultancy.com/software/xmltask
 * @see http://groovy.codehaus.org/Using+Ant+from+Groovy
 *
 */

import org.custommonkey.xmlunit.*

/**
Run the Replace script and make sure only one change is made.
The file paths are relative to TEST_ROOT_PATH passed in
with -DTEST_ROOT_PATH=xxxxx
*/
class ReplaceTest extends GroovyTestCase {
 def DOCSTRING =
 '<!DOCTYPE STORE SYSTEM "classpath://some/path/to/dtd/narly.dtd">'
 def main
 def root
 def lineSeparator = System.getProperty("line.separator")
 def source='data/storage.xml'
 def dest='target/storage-new.xml'
 def args

 void setUp() throws Exception {
         super.setUp()
         main = new Replace()
         root = System.getProperty("TEST_ROOT_PATH")+
                            "/GroovyXMLTaskXMLUnit/"
         XMLUnit.setIgnoreWhitespace(true)
         args = ['-s', source,  '-d', dest, '-x',
             '//UNIT[@name=\'widgetB\']/VALUE/@value', '-v', 'Y']
             as String[]
 }

 void testReplaceScript() throws Exception {
          main.main(args);
          def input =root + source;
          def output = root + +dest;
          validateSingleChange(input, output, "N","Y");
 }

 /** Ensure only one change in XML at XPath */
 void validateSingleChange(final inFile, final outFile,
           value, newValue) throws Exception {
         def diff =
              new DetailedDiff(
                      new Diff(filterDocType(inFile,DOCSTRING),
                               filterDocType(outFile,DOCSTRING)
              )
         )

         def list = diff.getAllDifferences();
         if(list.size()==1){
                  for (dif in list) {
                   def v1 = dif.getControlNodeDetail().getValue();
                   def v2 = dif.getTestNodeDetail().getValue();
                   assertTrue("Failed to change '${value}'
                     to '${newValue}' \
                     using XPath:
                     '${dif.getControlNodeDetail()
                      .getXpathLocation()}'  \
                     control value is: $v1 test value is: $v2",
                     (v1.equals(value) && v2.equals(newValue)));
         }
       }else{
           fail("Expected 1 change, but had ${list.size()} changes.
                diff is: " + diff)
       }
 }

 /** Not really a unit test, but provides confidence in XMLUnit tool use */
 void testNoReplacement() throws Exception {
         main.main(args);
         def input =root + source;
         assertTrue("Should have been the same",
                new DetailedDiff(
                      new Diff(filterDocType(input,DOCSTRING),
                       filterDocType(input,DOCSTRING)
                )
         ).getAllDifferences().size()==0
      );
 }

 /** Another non-unit test, but provides confidence in XMLUnit tool use */
 void testTooManyChanges() throws Exception {
         main.main();
         def t = '(<UNIT name=".*?">)'
         def input =filterDocType(root+source,DOCSTRING)
         def output = input.replaceAll(t,{Object[]it ->
                        it[1]+'<extra>1</extra>'})
         def diff =new DetailedDiff(new Diff(input,output))

         assertTrue("Should have been more then one change",
            diff.getAllDifferences().size()>1)
 }

 /** read file, stripping doctype line */
 String filterDocType(path, docString) throws Exception{
           def input = new File(path);
           def writer = new StringWriter()
           input.filterLine(writer){!(it =~ docString)}
           return writer
 }
}

When run and no failures:


C:\home\projects\dev\GroovyXMLTaskXMLUnit>replacetest.cmd ... Time: 2.578
OK (3 tests)

Links

Groovy
XMLTask
XMLUnit
AntBuilder
Ant

Follow

Get every new post delivered to your Inbox.