Tag Archives: groovy

Ant hooks using Groovy, INIX, and XMLTask

I revisit the Ant Hook scripts using Groovy and change it to use INIX file for hook storage and use XML transform via XMLTask to insert the hook feature into a legacy build script.

In a prior post, Ant hooks using Groovy, continued, I used the Ant BuildListener interface to add a hooks feature that invokes a Groovy script mapped to build events. Then I implemented an “around advise” capability.

Many issues with this approach, but two stand out.

Drawbacks
  1. Existing Ant scripts must be modified to add this in.
  2. Hook scripts are now a bunch of files that must be managed.
Approach
  1. Hook scripts are stored in INIX files
  2. A meta build step is used to insert the hook feature into a build script.

INIX storage
Below, the Groovy hook scripts from the previous blog post are stored in the sections of a INIX file (a form of INI file). The section begin tag is composed of a unique path, optional fragment identifier, and a query string. Just like a URL. The query string indicates when this hook is applied and if the target in the script should be skipped (applicable for ‘before’ hooks). The last section in the file will be discussed later.

hooks.inix
[>hook/root/compile?when=after,skip=false]
    println " hook: root,{target=${event.target.name},when=post,event=$event}"
[<]

[>hook/demo1/deploy?when=before,skip=true]
    println "  hook: {project=${event.project.name},target=${event.target.name},when=pre,event=$event}"
[<]

[>hook/demo1/compile?when=before,skip=false]
    println "  hook: {project=${event.project.name},target=${event.target.name},when=pre,event=$event}"
[<]

[>fragment]
    <path id="libs">    
        <fileset dir="lib">
            <include 
                name="groovy-all-2.2.1.jar" />
        </fileset> 
        
        <pathelement location="src/main/groovy"/>
    </path>    
     
    <!-- Groovy library -->
    <taskdef name="groovy"
       classname="org.codehaus.groovy.ant.Groovy"
       classpathref="libs"/> 
    
    <!-- sets a BuildListener to the project -->
    <scriptdef name="set-listener" 
        language="Groovy"
        classpathref="libs" 
        src="src/main/groovy/com/octodecillion/util/ant/Hook.groovy"> 
        <attribute name="path"/>
    </scriptdef>
     
    <!-- install the listener -->
    <set-listener path="hooks.inix"/>   
     
[<fragment]

Ant build file transform
In the above inix file, the last section contains the XML fragment of the build script hook support. We read in this section and use Groovy’s Templating support to replace any values. Thus, by running a metabuild or bootstrap process we can take that fragment and insert it into a target Ant script.

Since an Ant script is XML, an XML transform is used by the XMLTask Ant task. This task allows us to indicate with an XPath expression where to insert the fragment. In a Groovy blog post, we of course, will use Groovy to do this via the Groovy AntBuilder. Below, the InsertHooks class implements this.

InsertHooks.groovy
package com.octodecillion.util.ant

import static com.octodecillion.util.inix.Inix.EventType.END

import com.octodecillion.util.inix.Inix

import org.apache.tools.ant.*

import static groovy.io.FileType.FILES

/**
 * Insert the XML into Ant script to enable hooks.
 *   
 * @author josef betancourt
 */
class InsertHooks{

    def ant
    def DEBUG = false
    static final String srcFilePath='build.xml'
    static final String destFilePath="build-hooked.xml"
    static final String INIXFILE = 'hooks.inix'    
    static final String XMLTASK = 'com.oopsconsultancy.xmltask.ant.XmlTask'
    
    static main(args){
        new InsertHooks().execute()
    }
    
    /** An Ant task entry point */
    public void execute() throws BuildException{
        def ant = new AntBuilder()
        
        try {
            
            def fragment = loadFragment(INIXFILE)
            if(!fragment){
                throw new BuildException("'fragment' from $INIXFILE is invalid")
            }        
            
            def engine = new groovy.text.SimpleTemplateEngine()
            def template = engine.createTemplate(fragment)             
            def xml = template.make([hookFilePath:INIXFILE])
              
            ant.path(id: "path") {
                fileset(dir: 'lib') {
                   include(name: "**/xml*.jar")
                }
            }
     
            ant.taskdef(name:'xmltask',classname:
                XMLTASK,
                classpathref: 'path')
             
            def xpath = '(//target)[1]' 
            ant.xmltask(source:srcFilePath,dest:destFilePath, 
                expandEntityReferences:false,report:false){
                insert(position:"before",path:xpath,xml:xml)                 
            }
                 
            new File(destFilePath).eachLine{
                println it
            }
            
        } catch (Exception e) {
            e.printStackTrace()
            throw new BuildException(e.getMessage(), e)
        }
    }
    
    def loadFragment(String path){
        def text = ''
        def inix = new Inix(path)
        def theEvent = inix.next()
         
        while(theEvent && theEvent != Inix.EventType.END ){
            Inix.Event event = inix.getEvent()

            if(event.isSection("fragment")){
                text = event.text
                break
            }
            
            theEvent = inix.next()
        }
        
        return text
    }    
    
}

// end Script

Hooking Up
Now the target Ant script has the ability to invoke the script below via a ScriptDef. The script wires in the Ant BuildListener interface implementation, and then loads the hooks from the inix file. When the Ant script is run, the BuildListener’s invokeTargetHook method is invoked. This in turn executes the correct hook scripts.

Hook.groovy
package com.octodecillion.util.ant

import groovy.transform.TypeChecked;
import groovy.transform.TypeCheckingMode;

import java.util.List;
import java.util.Map;
import java.util.regex.Pattern

import com.octodecillion.util.inix.Inix

import org.apache.tools.ant.BuildEvent
import org.apache.tools.ant.BuildException
import org.apache.tools.ant.Project
import org.apache.tools.ant.SubBuildListener;

import static groovy.io.FileType.FILES

// wire in the listener
def path = binding.attributes.get('path')
if(!path){
    throw new BuildException("'path' to hook inix not set")
}

def listener = new HookListener(project,path)
listener.project = project
project.addBuildListener(listener)

// end wiring

/**
 * Ant build listener that invokes groovy hook scripts.
 *  
 * @author josef betancourt
 *
 */
@TypeChecked
class HookListener implements SubBuildListener {
    Project project
    boolean DEBUG = false
    
    /**                          */
    def HookListener(Project project, String path){
        this.project = project
        loadInix(path)                
    }    
    
    /** load scripts in inix file */
    def loadInix(String path){
        debugln("load inix")
        def inix = new Inix()
        inix.reader = new BufferedReader(
            new FileReader(new File(path)))
         
        def theEvent = inix.next()
        def found = false
         
        while(theEvent && theEvent != Inix.EventType.END ){
            def event = inix.getEvent()

            if(isHook(event)){
                found = true
                def key = [event.path[2],((String)(event.params['when'])).
                    toUpperCase()].join('/')
                    
                String txt = event.text
                String skString = event.params['skip']
                boolean sk = (skString.compareTo('true')==0 ? true : false)
                debugln "key=$key, ${event.params['skip']}, skip=$sk"
                    
                def node = new HookNode(txt, sk)
            
                def prj = event.path[1]                
                if(!hooks[prj]){
                    hooks[prj] = [:]
                }    
                
                hooks[prj].put(key,node);            
            }
            
            theEvent = inix.next()
        }
        
        dumpHooks()        
        
    }

    /** invoked by Ant build */
    @Override
    public void targetStarted(BuildEvent event) {
        die("targetStarted invoked with null event", !event)
        invokeTargetHook(event, When.BEFORE)
    }
    
    /** invoked by Ant build */
    @Override
    public void targetFinished(BuildEvent event) {
        die("targetFinished invoked with null event", !event)
        invokeTargetHook(event, When.AFTER)
    }

    /** Invoke the target's hook script */
    def invokeTargetHook(BuildEvent event, When when){
        def b = new Binding()
        b.setProperty("event",event)
        b.setProperty("hook",this)
        
        def shell = new GroovyShell(b)
        
        def hookName = "${event.target.name}/$when"
        def pHook = hooks[event.project.name][hookName]
        def rHook = hooks['root'][hookName]    
        debugln("invokeTargetHook: $hookName\npHook:  $pHook\nrHook:  $rHook")
            
        boolean skipSet = false
        
        if(pHook){
            skipSet = pHook.skip    
            debugln("skipSet=$skipSet")
            shell.evaluate(pHook.text)
            
            if(!override && rHook){
                skipSet = skipSet ? skipSet : rHook.skip
                shell.evaluate(rHook.text)
            }            
            
        }else if(rHook){
            skipSet = rHook.skip            
            shell.evaluate(rHook.text)        
        }
        
        if( skipSet && (pHook || rHook) && (when == When.BEFORE) ){
            createSkipforTarget(event)
        }        
    } 

    /**   */
    private createSkipforTarget(BuildEvent event) {
        debugln "setting skip: ${event.target.name}_skipTarget"
        event.project.setProperty("${event.target.name}_skipTarget", "true")
        event.target.setUnless("${event.target.name}_skipTarget")
    }
    
    /** throw exception if flg is true */
    private die(Object msgObject, boolean flg){
        if(flg){
            throw new IllegalArgumentException(String.valueOf(msgObject))            
        }        
    }    
    
    @TypeChecked(TypeCheckingMode.SKIP)
    private isHook(ev){        
        ev.path && ev.path[0] == 'hook'        
    }
    
    private dumpHooks() {
        if(!DEBUG){
            return            
        }
        
        hooks.each{
            it.each{ node ->
                debugln(node)
            }
        }        
    }
    
    private debugln(Object msg){
        if(DEBUG){
            println(msg)
        }
    }
    
    String TARGETHOOK = "target"
    def override = true;
    
    enum When{
        BEFORE('before'),AFTER('after')
        String name
        
        When(s) {this.name = s}
    }
    
    private class HookNode {
        String text
        boolean skip
        public HookNode(String text, boolean skip){
            this.text = text
            this.skip = skip
        }
        
        def String toString() {return "s:$skip"};
    }
    
    Map<String, Map<String,HookNode>> hooks = [:]
    
    //@formatter:off
    @Override
    public void subBuildFinished(BuildEvent event) {}
    @Override
    public void subBuildStarted(BuildEvent event) {}
    @Override
    public void buildFinished(BuildEvent event) {}
    @Override
    public void buildStarted(BuildEvent event) {}
    @Override
    public void messageLogged(BuildEvent event) {}
    @Override
    public void taskFinished(BuildEvent event) {}
    @Override
    public void taskStarted(BuildEvent event) {}
    //@formatter:on
} // end class HookListener
// end Script

Demo
Below is a non-hooked Ant script.

build.xml
<project name="demo1" default="build" basedir=".">

<target name="compile">
    <echo>******* 'compile': Hello compile world!</echo>  
</target> 

<target name="deploy">
    <echo>******* 'deploy': Deploying ... ${skipSet}</echo>
</target>

<target name="build" depends="compile,deploy">
    <echo>******* 'build': Building ... </echo>
</target>

</project>

When we run the InsertHooks scripts, a new Ant script is created, build-hooked.xml. The console output (for demonstration) is:

Output

build-hooked.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project basedir="." default="build" name="demo1">

    <path id="libs">
<fileset dir="lib">
<include name="groovy-all-2.2.1.jar"/>
</fileset>

<pathelement location="src/main/groovy"/>
</path>

<!-- Groovy library -->
<taskdef classname="org.codehaus.groovy.ant.Groovy" classpathref="libs" name="groovy"/>

<!-- sets a BuildListener to the project -->
<scriptdef classpathref="libs" language="Groovy" name="set-listener" src="src/main/groovy/com/octodecillion/util/ant/Hook.groovy">
<attribute name="path"/>
</scriptdef>

<!-- install the listener -->
<set-listener path="hooks.inix"/>

<target name="compile">
    <echo>******* 'compile': Hello compile world!</echo>  
    </target> 
    
    <target name="deploy">
        <echo>******* 'deploy': Deploying ... ${skipSet}</echo>
    </target>
    
    <target depends="compile,deploy" name="build">
        <echo>******* 'build': Building ... </echo>
    </target>

</project>

Then we subsequently run the build-hooked.xml script, the console output is shown below. Note that the “deploy” target is skipped, only the output of the hook will show.

Buildfile: C:\Users\jbetancourt\workspace-4.3\AntAroundAdvice2\build-hooked.xml
compile:
  hook: {project=demo1,target=compile,when=pre,event=org.apache.tools.ant.BuildEvent}
     [echo] ******* 'compile': Hello compile world!
 hook: root,{target=compile,when=post,event=org.apache.tools.ant.BuildEvent}
deploy:
  hook: {project=demo1,target=deploy,when=pre,event=org.apache.tools.ant.BuildEvent}
build:
     [echo] ******* 'build': Building ... 
BUILD SUCCESSFUL
Total time: 3 seconds

Summary
Shown was an improvement of the Groovy Ant Hooks idea. If nothing else, shown was some Groovy code that used AntBuilder, XMLTask, and Inix file reading.

The actual use case for the need of a Ant hook is probably very narrow, such as a build server that reuses legacy Ant build scripts, or the targeting of legacy build to new requirements.

Further reading

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Groovy implementation of INIX file format

About to write another extension of Ant hook scripts and discovered that the file format I needed was too complex. So, in this post I present another implementation in Groovy.

Update: changed the URL of this blog post.

In two prior posts I presented a very simple metadata file storage approach. Pretty much using the INI file format as an external “heredoc”. I now call this an INIX format. Btw, this file extension is already being used by the Adobe InDesign product.

In the example below, the section is uniquely identified with a path, and the query string supplies parameters to any client accessing the data.

[>hook/root/compile?when=after,skip=false]
    println " hook: root,{target=${event.target.name},when=post,event=$event}"
[<]

The terminal tag could have also been written as: [<hook/root/compile]

The change from the previous INIX format is in the section header format. It is now more like a URL: [>path#fragment-id?query-string] The query string is not following the full URL spec, it uses ‘&’ to separate entries. Also, commas are used as in Groovy Object Notation (GRON).

Example
In a blog post, “Ant hooks using Groovy, INIX, and XMLTask“, I show how an inix file can be used to store multiple Groovy scripts to be used as Ant hooks.

Update Feb 5, 2014: Just noticed that Git also uses the INI file subsection approach, for example [section “subsection”]. See “CONFIGURATION FILE” section here.
 

Implementation
Source code available at Github: https://gist.github.com/josefbetancourt/7701645

Listing 2, Implementation

Test class
Note that there are not enough tests and the implementation code has not been reviewed.

Listing 3, Test class

The test data is:

Listing 4, data file

Grammar
A possible grammar follows, but has not been ‘checked’ by attempted use of a parser generator like Antlr.

section  : '[>' path ('#' fragment)? ('?' args)? ']' data '[<' path? ']';
path     :  NAME ('/' NAME)*;
fragment :  NAME;
args     :  (NAME=NAME (',' NAME=NAME)*)?;
data     :  (ANYTHING CRLF)*;
NAME     :  ('a'..'z' | 'A'..'Z')('a' .. 'z' | 'A'..'Z'|'0'..'9'|'_');
TODO:

  1. Now that the query string is being used, we can add “import” of sections. A section ID beginning with ‘@’, will reuse the contents of another section. Any content in the destination section will be appended to the imported content of the source section. Import here does not mean a namespace concern as in Java imports.
  2. Allow multiple params per param key: ?measure=21,measure=34,measure=90. Or better yet, just allow array in arg string: measure=[21,34,90],color=red

 

Further Reading

  1. INI file
  2. Data File Metaformats
  3. Here document
  4. JSON configuration file format
  5. Groovy Object Notation (GrON) for Data
    Interchange
  6. Cloanto Implementation of INI File Format
  7. http://groovy.codehaus.org/Tutorial+5+-+Capturing+regex+groups
  8. URI
  9. Designing a simple file format
  10. The Universal Design Pattern
Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Ant hooks using Groovy, continued

The Ant Hook scripts using Groovy implementation is extended with the ability to also skip target execution. Thus, an “around” advice AOP like or filter.

In the last post, Ant Hooks using Groovy Script via Scriptdef, we used the Ant BuildListener interface to add a hooks feature that invokes a Groovy script mapped to build events. Now we implement a true “around advice”. Admittingly kludgy, but ….

Approach
Since the hook script has the Ant Event object, we have access to the Target object currently about to be executed. Target has a convenient method to allow target skip:

public void setUnless(java.lang.String property):  
 Sets the "unless" condition to test on execution. 
 if the property is set, the task will not execute. 

Demo
The demo Ant build script is modified with two new targets, ‘deploy’ and ‘build’. Also, the default target is changed to ‘build’.

<project name="demo1" default="build" basedir=".">
    ............ see original code .....
     
    <target name="compile">
        <echo>Hello compile world!</echo>  
    </target> 

    <target name="deploy">
        <echo>Deploying ... ${skipSet}</echo>
    </target>
    
    <target name="build" depends="compile,deploy">
        <echo>Building ... </echo>
    </target>

</project>

A new hook script to filter the ‘deploy’ target and skip it is added.
demo1/deploy_targetStarted.groovy

println "  hook: {project=${event.project.name},target=${event.target.name},when=pre,event=$event}"
hook.skipTarget(event)

Listener implementation
The actual listener is slightly modified to test for the skip property. If found the ‘unLess’ method is used on the target and the target is skipped.

/**
 * Ant build listener that invokes groovy hook scripts.
 *  
 * @author josef betancourt
 *
 */
class HookListener implements SubBuildListener {
    
    ...... see prior post ......

    /** Invoke the 'started' or 'finished' root or target hook script */
    def evokeTargetHook(BuildEvent event, When when){
        def b = new Binding()
        b.event=event
        b.hook=this

        def shell = new GroovyShell(b)
        
        def hookName = "${event.target.name}_${TARGETHOOK}${when.name}"
        
        def hooked = false
        
        def stored = projectHooks[hookName]        
        if(stored){
            hooked = true
            shell.evaluate(stored)
        }else{ 
            def hook = rootHooks[hookName]            
            if(hook){
                hooked = true
                shell.evaluate(hook)
            }
        }        
        
        if( hooked && (when == When.STARTED) ){
            def skipSet = event.project.getProperty(
                 "${event.target.name}_skipTarget")

            if(skipSet){
                event.project.setProperty(skipSet, "true")
                event.target.setUnless(skipSet)
            }            
        }
    }    

    /** invoked from a hook script */
    static void skipTarget(BuildEvent event) {
        def name = event.target.name
        def project = event.project
        project.setProperty(name + "_skipTarget", 'set')
    }

    ...... see prior post ......
}

    ...... see prior post ......

Output

Buildfile: C:\Users\jbetancourt\workspace-4.3\AntAroundAdvice\build.xml
compile:
  hook: {project=demo1,target=compile,when=pre,event=org.apache.tools.ant.BuildEvent}
     [echo] Hello compile world!
 hook: root,{target=compile,when=post,event=org.apache.tools.ant.BuildEvent}
deploy:
  hook: {project=demo1,target=deploy,when=pre,event=org.apache.tools.ant.BuildEvent}
build:
     [echo] Building ... 
BUILD SUCCESSFUL

Further reading

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Ant hooks using Groovy scripts via Scriptdef

Ant BuildListener interface is used to add a hooks feature that invokes a Groovy script mapped to build events.

One way of managing or customizing a complex Ant build is to use hook scripts that are executed at defined points in the Ant build life cycle. A use case for this is the reuse of build scripts in multiple build environments, like a build server vs a local workstation build, another is to add extra auditing.

Approach
We create a listener that take the project name and the current running target’s name to locate a matching hook file. This file contains a Groovy script to be invoked. Further, we allow a global hook script folder that contains hook scripts that would be applied if there are no matching hook scripts in a project level folder, named after the Ant build script project name.

Of course, this is Groovy specific, but it would be very easy to make this into a more generic hook invocation facility.

Similar ideas
Version control systems use the concept of hook scripts. Before a commit, for example, a hook script can ensure that the commit log contains the correct metadata. In AOP we can advise at ‘before’ and ‘after’ pointcuts.

Ant listeners
An Ant listener will be alerted to:

  • build started
  • build finished
  • target started
  • target finished
  • task started
  • task finished
  • message logged

In this demo we use only the target events.

Implementation
In the Ant script below, we install a listener before any ant targets are invoked. A scriptdef defines the listener as a script file.

Demo build script

<project name="demo1" default="compile" basedir=".">

    <path id="libs">    
        <fileset dir="lib">
            <include 
                name="groovy-all-1.8.6.jar" />
        </fileset>         
    </path>    
     
    <!-- Groovy library -->
    <taskdef name="groovy"
       classname="org.codehaus.groovy.ant.Groovy"
       classpathref="libs"/> 
    
    <!-- sets a BuildListener to the project -->
    <scriptdef name="set-listener" 
        language="Groovy"
        classpathref="libs" 
        src="src/main/groovy/com/octodecillion/ant/Hook.groovy"> 
    </scriptdef>
     
    <!-- install the listener -->
    <set-listener/>   
     
    <target name="compile">
        <echo>Hello compile world!</echo>  
    </target> 

</project>

Now we want to use the following actual Groovy hook scripts. The start target hook is defined at the project level, and the finished target hook is defined at the root level.

demo1/compile_targetStarted.groovy

println "hook: {project=${event.project.name},target=${event.target.name},when=pre,event=$event}"

root/compile_targetFinished.groovy

println "hook: root,{target=${event.target.name},when=post,event=$event}"

Listener implementation
The actual listener implementation is shown below. It uses the build event object to find the project name and target name. Then it searches for the matching hook script, _listenerMethod[Started | Finished].groovy, in the folder that matches the project name “demo1” here. If a hook script is not found, it searches for a matching hook script in the root folder. The found script is then evaluated.

In the example, the hooks/demo1 folder contains file: compile_targetStarted.groovy. Thus, when the Ant runtime invokes the targetStarted listener method on the ‘compile’ target, the listener will invoke the targetStarted hook script.

package com.octodecillion.ant

import org.apache.tools.ant.BuildEvent
import org.apache.tools.ant.Project
import org.apache.tools.ant.SubBuildListener;
import static groovy.io.FileType.FILES

/**
 * Ant build listener that invokes groovy hook scripts.
 *  
 * @author josef betancourt
 *
 */
class HookListener implements SubBuildListener {
    def project
    Map rootHooks = [:]
    Map projectHooks = [:] 
    
    String TARGETHOOK = "target"
    
    enum When{
        STARTED('Started'),FINISHED('Finished')
        String name
        
        When(s) {this.name = s}
    }
    
    /**                          */
    def HookListener(project){
        this.project = project
        
        // cache the root hooks
        new File("hooks/root").eachFileMatch FILES, ~/.*\.groovy/, 
            { file ->
                rootHooks.put(getBaseName(file.name), file.text)
            }
            
        // cache the project hooks
        new File("hooks/$project.name").eachFileMatch FILES, ~/.*\.groovy/,
            { file ->
                projectHooks.put(getBaseName(file.name), file.text)
            }
            
    }    
    
    @Override
    public void targetFinished(BuildEvent event) {
        evokeTargetHook(event, When.FINISHED)        
    }

    @Override
    public void targetStarted(BuildEvent event) {
        evokeTargetHook(event, When.STARTED)    
    }
    
    /** Invoke the 'started' or 'finished' root or target hook script */
    def evokeTargetHook(BuildEvent event, When when){
        def b = new Binding()
        b.event=event
        def shell = new GroovyShell(b)
        
        def hookName = "${event.target.name}_${TARGETHOOK}${when.name}"        

        def stored = projectHooks[hookName]        
        if(stored){
            shell.evaluate(stored)
        }else{ 
            // use the cached root hooks if found       
            def hook = rootHooks[hookName]            
            if(hook){
                shell.evaluate(hook)
            }
        }    
    }    

    /**                          */
    private String getBaseName(fileName){
        fileName.replaceFirst(~/\.[^\.]+$/, '')        
    }
    
    /**                          */
    private String getSuffix(fileName){
        def parts = fileName.split("\\.")
        parts.size() > 0 ? parts[parts.size()-1] : ''        
    }    
   
    //@formatter:off
    @Override
    public void subBuildFinished(BuildEvent event) {}
    @Override
    public void subBuildStarted(BuildEvent event) {}
    @Override
    public void buildFinished(BuildEvent event) {}
    @Override
    public void buildStarted(BuildEvent event) {}
    @Override
    public void messageLogged(BuildEvent event) {}
    @Override
    public void taskFinished(BuildEvent event) {}
    @Override
    public void taskStarted(BuildEvent event) {}
    //@formatter:on
}

// wire in the listener
def listener = new HookListener(project)
listener.project = project
project.addBuildListener(listener)

// end Script

Output

C:\Users\jbetancourt\workspace-4.3\AntAroundAdvice>ant
Buildfile: C:\Users\jbetancourt\workspace-4.3\AntAroundAdvice\build.xml

compile:
hook: {project=demo1,target=compile,when=pre,event=org.apache.tools.ant.BuildEvent}
     [echo] Hello compile world!
hook: root,{target=compile,when=post,event=org.apache.tools.ant.BuildEvent}

BUILD SUCCESSFUL
Total time: 1 second

Around Advice
While possibly useful, a further powerful potential is to allow the bypass of an invocation of a target altogether. One way of doing this is to use a library like XMLTask to modify the build script. This would have to be done before the script is parsed by Ant of course.

Alternative
One issue with described approach is that the target Ant build scripts must be modified (very minor) to hook in the hooks feature. Using the Ant API itself to add targets and change the target dependency chains might be possible. So is changing Ant itself using AspectJ is also possible.

Of course if very complex scenarios are necessary to manage legacy builds, it may be time to replace Ant with something like Gradle.

Project Layout
The project layout used to the test this hook approach is shown below:

|   .classpath
|   .gitignore
|   .project
|   build.xml
|   tree.txt
|   
+---.settings
|       org.eclipse.jdt.core.prefs
|       org.eclipse.jdt.groovy.core.prefs
|       
+---docs
|       .gitignore
|       Backup of docs.wbk
|       docs.docx
|       
+---hooks
|   +---demo1
|   |       compile_targetStarted.groovy
|   |       
|   \---root
|           compile_targetFinished.groovy
|           
+---lib
|       ant-antlr.jar
|       ant.jar
|       groovy-all-1.8.6.jar
|       groovy-all-2.2.0-rc-1.jar
|       
+---src
|   \---main
|       \---groovy
|           \---com
|               \---octodecillion
|                   \---ant
|                           Hook.groovy
|                           

Further reading

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Use bound script param in Groovy gstring

In Groovy you can pass in variables using a Binding when evaluating an expression using GroovyShell.

The code below illustrates this. Except the last use case. An attempt is made to access the bound variable in a gstring directly. Apparently it is only accessible by the actual binding.

b1 = new Binding([name:'world']);

def result = new GroovyShell(b1).evaluate("'Hello ' + name")
assert 'Hello world' == result

result = new GroovyShell(b1).evaluate("'Hello ${b1.name}'")
assert 'Hello world' == result

result = new GroovyShell(b1).evaluate("\"Hello $b1.name\"")
assert 'Hello world' == result

result = new GroovyShell(b1).evaluate("println name; 'Hello world'")
assert 'Hello world' == result

result = new GroovyShell(b1).evaluate("\"Hello $name\"")
assert 'Hello world' == result

println "after ${count++}"

Executing the above results in:

world
Caught: groovy.lang.MissingPropertyException: No such property: name for class: BindTest
groovy.lang.MissingPropertyException: No such property: name for class: BindTest
	at BindTest.run(BindTest.groovy:15)

My puzzler is puzzled. Probably one of those intricacies of scoping?

Further reading
Passing parameters into Groovy script using Binding class

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Notes on using Groovy in Ant Scriptdef

Ant build scripts can define new tasks in a script language using ‘scriptdef‘. The following are a few notes I’m collecting on using the Groovy language with scriptdef.

Since I don’t do this often I have to re-read the info on how to use it. Thus, I’d thought I create a post where I collect this info. for future use.

Groovy Ant task
Groovy already supplies a Groovy Ant script task: org.codehaus.groovy.ant.Groovy.
To use, you define declare it in the Ant script:

<path id="libs">
    <fileset dir="lib">
        <include name="*.jar" />
    </fileset>
</path>

<taskdef name="groovy" 
      classname="org.codehaus.groovy.ant.Groovy" 
      classpathref="libs"/>

Then use it:

<groovy>
   println "Hello World"
</groovy>

The bindings supplied with the Groovy Ant task are:

Name Description
ant an instance of AntBuilder that knows about the current ant project
project the current ant project
properties a Map of ant properties
target the owning target that invoked this groovy script
task the wrapping task, can access anything needed in org.apache.tools.ant.Task
args command line arguments, if any

ScriptDef task
The ScriptDef task in Ant just allows the creation of custom tasks using a scripting language, unlike the Taskdef task. I’m sure there are other approaches, perhaps for many situations, a taskdef that uses the groovy task within it is doable.

Example

Listing 1, example ant script
<?xml version="1.0" encoding="UTF-8"?>
<project name="project" default="demo1" basedir=".">
	<path id="libs">
	    <fileset dir="lib">
	    	<include name="*.jar" />
	    </fileset>
	</path>
	 
	<!-- Groovy library -->
	<taskdef name="groovy" 
		classname="org.codehaus.groovy.ant.Groovy" 
		classpathref="libs"/>
	 
    <scriptdef name="groovyInfo" language="groovy" 
    	classpathref="libs"
    	src="src/ScriptInfo.groovy">
    	
    	<attribute name="attr1"/>
    	<element name="fileset" type="fileset"/>
    	
 	</scriptdef>
	
    <!-- target: default -->
    <target name="demo1" description="description">
    	
    	<groovyInfo attr1="anAttrib">
    		<fileset dir="src"/>
    		<fileset dir="lib"/>    		
    	</groovyInfo>
        
    </target>

</project>

Hmmm. I’m thinking of perhaps creating a Groovy utility that one can just use in a Scriptdef to give the same bindings as the Groovy Ant task.

Listing 1, ScriptInfo.groovy source
println "project: ${project ?: ''}"
println "properties: ${properties ?: ''}"
println "Object:\n ${dump()}"
println "The binding:\n ${properties['binding'].dump()}"

int indent = 1
println "\n\n\nProperties"
binding.getProperties().each{ p ->
	if(p.value instanceof Map){
		println ' ' * indent + p.key + ":"
		indent ++
		p.value.each{ entry ->
			println '  ' * indent + "${entry}"
		}
		indent--
	}else{
		println "${' ' * indent}prop: $p"
	}
}

println "\nDirect access of attribute"
println  "\tattribute attr1=" + binding.attributes.get('attr1')

println "Direct access to elements"
elements.fileset.each{ fs ->
  fs.each{
    println "\t$it"
  }
}

The result of running the above Ant script is shown below:

demo1:
 project: org.apache.tools.ant.Project@5213fce3
 properties: [class:class Script1, binding:org.codehaus.groovy.jsr223.GroovyScriptEngineImpl$1@11a7a000]
 Object:
  <Script1@4d129d08 binding=org.codehaus.groovy.jsr223.GroovyScriptEngineImpl$1@11a7a000>
 The binding:
  <org.codehaus.groovy.jsr223.GroovyScriptEngineImpl$1@11a7a000 val$ctx=javax.script.SimpleScriptContext@668ce272 this$0=org.codehaus.groovy.jsr223.GroovyScriptEngineImpl@2b66def7 variables=[project:org.apache.tools.ant.Project@5213fce3, context:javax.script.SimpleScriptContext@668ce272, self:org.apache.tools.ant.taskdefs.optional.script.ScriptDefBase@6eb84063, attributes:[attr1:anAttrib], elements:[fileset:[BindTest.class;BindTest.groovy;ScriptInfo.groovy, groovy-all-2.2.1.jar]], out:java.io.PrintWriter@28b5c5f]>
 
 
 
 Properties
  prop: class=class org.codehaus.groovy.jsr223.GroovyScriptEngineImpl$1
  variables:
     project=org.apache.tools.ant.Project@5213fce3
     context=javax.script.SimpleScriptContext@668ce272
     self=org.apache.tools.ant.taskdefs.optional.script.ScriptDefBase@6eb84063
     attributes={attr1=anAttrib}
     elements={fileset=[BindTest.class;BindTest.groovy;ScriptInfo.groovy, groovy-all-2.2.1.jar]}
     out=java.io.PrintWriter@28b5c5f
 
 Direct access of attribute
 	attribute attr1=anAttrib
 Direct access to elements
 	C:\Users\jbetancourt\workspace-4.3\ScriptdefInfo\src\BindTest.class
 	C:\Users\jbetancourt\workspace-4.3\ScriptdefInfo\src\BindTest.groovy
 	C:\Users\jbetancourt\workspace-4.3\ScriptdefInfo\src\ScriptInfo.groovy
 	C:\Users\jbetancourt\workspace-4.3\ScriptdefInfo\lib\groovy-all-2.2.1.jar
BUILD SUCCESSFUL
Total time: 1 second

About Ant
Ant is the breakthrough build system for the Java platform. Since there are now many other build systems, such as Maven and Gradle, Ant could be seen as a legacy build system, in some sense. JSON and the resurgence of scripting languages is changing the view of an build language in XML.
From the Ant homepage:

“Apache Ant is a Java library and command-line tool whose mission is to drive processes described in build files as targets and extension points dependent upon each other. The main known usage of Ant is the build of Java applications. Ant supplies a number of built-in tasks allowing to compile, assemble, test and run Java applications. Ant can also be used effectively to build non Java applications, for instance C or C++ applications. More generally, Ant can be used to pilot any type of process which can be described in terms of targets and tasks. ”

About Groovy
From the Groovy home page we have:

  • is an agile and dynamic language for the Java Virtual Machine
  • builds upon the strengths of Java but has additional power features inspired by languages like Python, Ruby and Smalltalk
  • makes modern programming features available to Java developers with almost-zero learning curve
  • provides the ability to statically type check and statically compile your code for robustness and performance
  • supports Domain-Specific Languages and other compact syntax so your code becomes easy to read and maintain
  • makes writing shell and build scripts easy with its powerful processing primitives, OO abilities and an Ant DSL
  • increases developer productivity by reducing scaffolding code when developing web, GUI, database or console applications
  • simplifies testing by supporting unit testing and mocking out-of-the-box
  • seamlessly integrates with all existing Java classes and libraries
  • compiles straight to Java bytecode so you can use it anywhere you can use Java

No, they didn’t pay me to advertise Groovy. 🙂

Further Reading

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Use type checked or compile static for Groovy scripts?

When you create a ‘script’ in Groovy, the type check or static compilation annotations cannot be applied to the script itself?
Updates

Background
Groovy has two features that improve it’s use as a “normal” application language: static type checking and static compilation. The first gives compile time feedback of issues instead of at runtime due to the dynamic nature of Groovy. The second slightly improves performance (in lieu of full JDK 7 “invoke dynamic”).

@TypeChecked and @CompileStatic can applied to a method or a class.

Groovy Scripts
Using Groovy as a dynamic scripting language is still very important, and the most direct use is to just write Groovy code in a text file. This sounds strange since Groovy is a scripting language, but a Groovy Script is a plain script that does not have a class declaration. They can have methods and nested classes. Internally Groovy compiles this text file to a standard JVM class that extends Groovy’s Script class, the original source script is wrapped in a ‘run’ method, and a main method is added as a convenient entry point. There are more details, of course.

An example groovy script:

println "Hello world!"

Scripts that start out small can grow to be very large and complex. Perhaps it would be useful to allow this script compilation to be statically type checked or statically compiled? From a web search attempt this does not seem to be a concern, however, P. Niederwieser has made this proposal.

Work around?
Since a script is just a Groovy class, one can just directly create a Groovy class with a main method. Example:

import groovy.transform.TypeChecked;

//@TypeChecked
class Hello {
	static int x = 1
	
	def run(msg){
		println "Hello $msgx"	
	}
	
	static main(args) {
		new Hello().run(x)
	
	}

}

When this is run we get:

Caught: groovy.lang.MissingPropertyException: No such property: msgx for class: Hello
Possible solutions: x
groovy.lang.MissingPropertyException: No such property: msgx for class: Hello
Possible solutions: x
        at Hello.run(Hello.groovy:8)
        at Hello$run.call(Unknown Source)
        at Hello.main(Hello.groovy:12)

Not too bad, at least we get some hints as to what the problem is. If we instead compile the Groovy script we get:

groovyc Hello.groovy

That’s right, nothing. Our script is compiled into Hello.class, and we won’t know there is a problem until run-time. Note that, when we ran the script, Groovy performed the compilation at run time, then executed the script.

If we uncomment the type check annotation and compile or just run the script with groovy Hello.groovy, we get:

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
Hello.groovy: 8: [Static type checking] - The variable [msgx] is undeclared.
 @ line 8, column 19.
          println "Hello $msgx"
               ^
1 error

This is the same behavior in an IDE. In Eclipse, with the annotation, the error is flagged and the mouse-over hover contains: Groovy:[Static type checking] – The variable [msgx] is undeclared.

Summary
Does Groovy need CompileStatic or TypeChecked on scripts? From the above illustration, type checking is useful. Yes, everyone checks their scripts, except when they don’t.

Addendum
Yup, didn’t go into compile static. That would involve micro-benchmarks which are difficult to do correctly.

Updates
Feb 26, 2014: Cédric Champeau, on Feb 9, 2014, has updated the nabble thread with info that this can indeed be done:

“It is possible to use @TypeChecked or @CompileStatic on a script, using the compiler configuration (see http://groovy.codehaus.org/Advanced+compiler+configuration, “static compilation by default“).”
 

References

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Use truthy falsy booleans in Java

Some scripting languages such as PHP, JavaScript, and Groovy allow the use of object reference in a conditional context. They have a standard boolean coercion for certain objects or references, “truthy” and “falsy” values. Could this be useful in Java?

Lists for: Groovy, JavaScript, Python, Ruby, Clojure, Perl

For example, we can run this inline script in the Groovy language:

groovy -e "if(![]) println 'Hello empty array!'"
Hello empty array!

or,

groovy -e "println( [] ? '' : 'Hello empty array!')"
Hello empty array!

What this did is take the empty array, [], and coerced it to a boolean value, then we tested that value for false.

The equivalent in Java could be:

String[] ary = new String[0];
if(ary.length == 0){
   System.out.println("Hello empty array");
}

In Java we have to directly access a property or invoke a method to create a boolean value. In some scripting languages, an array is false if it is null or empty. This simplifies the use of scripting and reduces the syntactic clutter. Of course, this also can introduce very subtle issues for the unwary.

Boolean coercion in Java

What if we could have an asBoolean type of conversion in Java, like:

If( !valid(new Object[0])){
   System.out.println("Hello empty array");
}

Some possible method names are: true(), false(), truth(), valid(), asBoolean(), and so forth. I used “valid”, since it is not really about truth, but about whether a value is ready for use.

One truthy and falsy list for Java could be:

  • Object: false if null
  • Collection: false if empty or null
  • Maps: false if empty or null
  • Boolean: false if false
  • Iterators: false if there are no further elements
  • Strings: non-empty are true
  • Numbers: non-zero are true
  • Float: non-null are true

Implementation
Below is a simplistic implementation of a Truth class that provides truthy support in Java. A full implementation would have to take into account a lot of things, such as wrapped objects, multidimensional arrays, and so forth.
Example use:

import static com.octodecillion.util.Truth.valid;

List value = new ArrayList();
if(valid(value)){
     // do stuff with value
} 

Unfortunately in the implementation below, the test for null and the object’s condition, such as whether a list is empty, mixes two very different object states. Thus, in certain situations, further tests are required, or the idiomatic Java coding style retained.

Listing 1, Simple Java implementation of truthyness
package com.octodecillion.util;

import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertThat;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import mockit.Expectations;

import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * Coerce Objects to boolean value in script-like manner.
 * <p>
 * <ul>
 * <li>Object: false if null</li>
 * <li>Collection: false if empty</li>
 * <li>Map: false if empty</li>
 * <li>Iterators: false if there are no further elements</li>
 * <li>Enumeration: false if there are no further elements</li>
 * <li>Strings: empty are false</li>
 * <li>Float: null is false</li>
 * <li>Numbers: non-zero are true</li>
 * </ul>
 * 
 * @author <a href="http://octodecillion.com/">Josef Betancourt</a>
 * 
 */
public class Truth {

	/** non-public since this is a static utility class */
	private Truth() {
		// singleton utility
	}

	public static boolean valid(String ref) {
		return isNull(ref) ? false : ref.length() != 0;
	}	

	public static boolean valid(Collection<?> ref) {
		return isNull(ref) ? false : !ref.isEmpty();
	}

	public static <E> boolean valid(Set<E> ref) {
		return isNull(ref) ? false : !ref.isEmpty();
	}

	public static <E> boolean valid(Enumeration<E> ref) {
		return isNull(ref) ? false : !ref.hasMoreElements();
	}

	public static <E> boolean valid(Iterator<E> ref) {
		return isNull(ref) ? false : !ref.hasNext();
	}

	public static boolean valid(Map<?, ?> ref) {
		return isNull(ref) ? false : !ref.isEmpty();
	}

	public static boolean valid(Boolean ref) {
		return isNull(ref) ? false : ref.booleanValue();
	}
	
	public static boolean valid(Integer ref) {
		return isNull(ref) ? false : ref != 0;
	}

	public static boolean valid(byte ref) {
		return ref != 0;
	}
	
	public static boolean valid(Float ref){
		return ref != 0;
	}

	public static boolean valid(Byte ref) {
		return isNull(ref) ? false : ref != 0;
	}

	public static boolean valid(long ref) {
		return ref != 0;
	}

	public static boolean valid(Long ref) {
		return isNull(ref) ? false : ref != 0;
	}

	public static boolean valid(BigDecimal ref) {
		return isNull(ref) ? false : ref.compareTo(BigDecimal.ZERO) != 0;
	}

	public static boolean valid(char ref) {
		return ref != 0;
	}

	public static boolean valid(double ref) {
		return ref != 0;
	}

	public static boolean valid(short ref) {
		return ref != 0;
	}

	private static boolean isNull(Object obj) {
		return obj == null;
	}

	/**
	 * Test if Object is true from a scripting standpoint.
	 * <p>
	 * @param ref an object reference
	 * @return true if object in not null or is not "empty"
	 * @throws Throwable
	 */
	public static boolean valid(Object ref){
		if (ref == null) {
		  return false;
		}

		Class<? extends Object> actualClass = ref.getClass();
		String className = actualClass.getName();

		if (className.startsWith("[")) {
			return validArray(ref);
		}
		
		return true;
	}
	
	public static boolean validArray(Object ref) {
		// TODO
		throw new UnsupportedOperationException("array coercion not supported");
	}	
	
	/**
	 * 
	 * Tests of {@link Truth}.
	 * 
	 * Uses nested JUnit testing approach advocated by Ben J. Christensen in <a
	 * href=
	 * "http://benjchristensen.com/2011/10/23/junit-tests-as-inner-classes/"
	 * >"JUnit Tests as Inner Classes"</a>
	 * 
	 * @author jbetancourt
	 * 
	 */
	@RunWith(JUnit4.class)
	public static class TruthTest {
		@Test
		public final void truth_empty_string() throws Throwable {
			String actual = "";
			assertThat(valid(actual), is(false));
		}
		
		@Test
		public final void truth_nonempty_string() throws Throwable {
			String actual = "x";
			assertThat(valid(actual), is(true));
		}
		
		@Test
		public final void truth_null_object() throws Throwable {
			Object obj = null;
			assertThat(valid(obj),is(false));
		}

		@Test
		public final void truth_not_null_object() throws Throwable {
			Object obj = new Object();
			assertThat(valid(obj),is(true));
		}

		@Test
		public final void truth_Byte() {
			Byte b = new Byte((byte) 0);
			assertThat(valid(b),is(false));
		}
		
		@Test
		public final void truth_Integer() {
			Integer num = new Integer(0);
			assertThat(valid(num),is(false));
		}
		
		@Test
		public final void truth_nonzero_Integer() {
			Integer num = new Integer(47);
			assertThat(valid(num),is(true));
		}
		
		@Test
		public final void zero_float(){
			Float fl = +0.0f;
			boolean actual = valid(fl);
			assertThat(valid(actual), is(false));
		}

		@Test
		public final void truth_Boolean() {
			Boolean bool = Boolean.TRUE;
			boolean actual = valid(bool);
			assertThat(actual, is(true));
		}

		@Test
		public final void null_Boolean() {
			Boolean bool = null;
			assertThat(valid(bool), not(true));
		}

		@Test
		public final void truth_Boolean_false() {
			Boolean bool = Boolean.FALSE;
			boolean actual = valid(bool);
			assertThat(actual, not(true));
		}
		
		@Test
		public final void truth_list() throws Throwable {			
			final List<String> lst = new ArrayList<String>();
			boolean actual = valid(lst);
			assertThat(actual, is(false));			
		}

		@SuppressWarnings("static-access")
		@Test
		public final void truth_empty_list() throws Throwable {			
			final List<String> lst = new ArrayList<String>();
			
			new Expectations() {
				Truth truth;
				{
					truth.valid((Collection<?>)lst); minTimes = 1;
				}
			};			
			
			boolean actual = Truth.valid(lst);
			assertThat(actual, is(false));
		}

		@Test
		public final void truth_empty_map() throws Throwable {
			@SuppressWarnings("rawtypes")
			Map map = new HashMap();
			boolean actual = valid(map);
			assertThat(actual, not(true));
		}	
		
		@Test
		public final void primitive_byte(){
			int x = 3;
			boolean actual = valid(x);
			assertThat(actual, is(true));		
		}
		
		@Test
		@Ignore
		public final void two_dimensional_array() throws Throwable{
			int[][] ref = new int[2][4];
			boolean actual = valid(ref);
			assertThat(actual, is(true));		
		}
		
		@Test
		@Ignore
		public final void truth_array_empty() throws Throwable {
			int[] ary = new int[3];
			boolean actual = valid(ary);
			assertThat(actual, is(true));
		}
		
	}
}

Scripting language support for truthy and falsey values


Here is the Groovy language coercion support

  • Object: false if null
  • Collection: false if empty
  • Maps: false if empty
  • Iterators: false if there are no further elements
  • Strings: non-empty are true
  • Numbers: non-zero are true


In JavaScript we have:

  • false // obviously
  • 0 // The only falsy number
  • “” // the empty string
  • null
  • undefined
  • NaN
  • Numbers: non-zero are true


In Python:

  • False: false
  • None: false
  • 0 : false
  • “” : false
  • empty containers: false


In Ruby

  • false: false
  • nil: false
  • true:true
  • -1:true
  • 0:true
  • 0.0:true
  • “”:true
  • “not empty”:true
  • []:true
  • {x:1}:true
  • Object.new:true


In Clojure:

  • nil: false
  • false: false


Perl uses

  • 0 # converts to “0” so it’s false;
  • “” # is empty string so it’s false;
  • “0.0” # not “0” or “” so it’s true
  • 0.0 # computes to 0 and is then converted to “0” so false
  • undef # evaluates to “” so it’s false — note, you may get a warni+ng “use of uninitialized value” if you are using -w
  • 2-3+1 # computes to 0 which is converted to “0” so it is false

Links

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Parsing files using Groovy closures

At work I recently created a Groovy automation script that parsed FTP log files and generated a web based report. This script was then added as a scheduled job in the open-source Jenkins continuous integration server. Groovy support is built in to Jenkins.

Groovy certainly made this very easy (from a Java perspective). The script used the Groovy GDK (extensions to Java’s classes) and iteration via closures, first of the target folder, and then on each file’s lines. A regexp was used to grab the info. Finally a Groovy Builder was used to create the HTML page.

After looking at the solution I thought it could be simplified if there was support for this pattern in some kind of template or API. A sample approach is shown below. Of course, this is only applicable for line oriented data files. When data spans multiple lines one must use more stateful approaches or parsing tools.

Updates

  • Sep 23, 2013: Version of this code at a Gist here.
  • Sep 23, 2013: Changed method signature of gather at folder. But, perhaps, the gather at file level does not need the filter closure.

References

  1. Groovy
  2. Closures in Groovy
  3. Builders in Groovy
  4. File in Groovy JDK
  5. Regular Expressions in Groovy
Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Testing Groovy with Nested Test Classes

The Groovy language allows testing with inline code, it provides the built-in JUnit, Mock, stubs, and other support. Presented here are two simple ways of embedding a JUnit test class inside a Groovy class.

In a prior post, “List sorting using topological ordering of a digraph“, I used this embedded test approach in the Java coding. But this embedding did not work when I tried it with a Groovy script I was working on. I revisited this technique to see how to do with Groovy.

Note that I am interested in directly running a class or script and having the embedded JUnit test run. One could of course just invoke the embedded test class using a JUnit runner. If it is possible to run the nested class, why have the class or script runnable as a test? Good question.

Running nested test directly:

GroovyEmbeddedTests\src>java -Dtest.script=true -cp .;..\bin;..\lib\junit-3.8.1.jar;\java\groovy\embeddable\groovy-all-2.1.2.jar;\java\groovy\lib\asm-4.0.jar  junit.textui.TestRunner com.octodecillion.testing.Example3
 
I was reading a JavaScript programming book the other day. The author talked about statistics that show most JavaScript is untested, i.e., has no tests. I bet that extends to other uses of other scripting languages: the only test is to use them and see if they still do what they should. (BTW, for testing JavaScript, Jasmine is a nice framework.)

Nested Tests?
Should JUnit type test classes be embedded within the class or script system under test (SUT)? About the only clear advantage is that instead of two files you have one.

In terms of deployment, Java one has the option of shipping only the non-test compiled classes. If one is shipping ‘script’ source itself, that is much harder to do.

There are some ‘human’ advantages to embedded tests. For one they are in one’s face, not another file that the average developer won’t even look at. See Ben Christensen’s blog post for further discussion.

Approach
Groovy classes
To run nested test classes we use the approach that Groovy uses in its GroovyShell class. This class is pretty neat; the runScriptOrMainOrTestOrRunnable method can run a Class, Runnable, Script, JUnit3, JUnit4, and even wash dishes. The private method that runs a JUnit test is what I needed, so I copied it into a new EmbeddedTestUtil class, shown in listing 4.

Groovy Scripts
While running nested tests is easy in Groovy classes, Scripts are very different. The Groovy compiler compiles the Groovy script into a Java class where all the ‘script’ code is put into the run() method, and a main entry point is added to invoke that run method. There are other issues of course, such as bindings, field declarations, and so forth.

One way of running nested tests in a script is to signal this desire with a system property. If that property is set, the test class is run via the appropriate JUnit runner. These tests can themselves run the target script by invoking the run method.

Examples
In listing 1, the Example1 class contains a nested ExampleTest1 JUnit 3 test class. To run this test class, we add a ‘main’ entry point to the top level class. This entry point will instantiate the test class and then use JUnit’s test runner to invoke it’s implicit ‘run’ method.

Listing 1, Groovy class with nested test
package com.octodecillion.testing

import junit.framework.TestCase;
import junit.textui.TestRunner
import org.codehaus.groovy.runtime.InvokerHelper;

/**  */
class Example1 {
	String name;
	
	/** Nested JUnit3 test class */
	static class ExampleTest1 extends GroovyTestCase{
		private Example1 ex
		
		protected void setUp(){
			ex = new Example1()
			ex.name = "hello"
		}
		
		public void test1(){
			println "in test1 ...."
			assert(ex.name.equals("hello"))
		}
		
	}
	
	/**
	 * Run the {@link Example$ExampleTest} tests.
	 * 
	 */	
	static main(args) {		
		EmbeddedTestUtil.runJUnit3Test(Example1.ExampleTest1)
	}

}

 

An alternative to a nested test class, we can simply put the test class within the same class source file as in listing 2.

Listing 2, Groovy class source file with inline test
package com.octodecillion.testing

import junit.framework.TestCase;
import junit.textui.TestRunner
import org.codehaus.groovy.runtime.InvokerHelper;

/**
 */
class Example2 {
	String name;
	
	/** Run {@link ExampleTest2} tests. */
	static main(args) {
		EmbeddedTestUtil.runJUnit3Test(ExampleTest2)
	}

}

/** Inline JUnit3 test class */
class ExampleTest2 extends GroovyTestCase{
	private Example2 ex
	
	protected void setUp(){
		ex = new Example2()
		ex.name = "hello"
	}
	
	public void test1(){
		println "in test1 ...."
		assert(ex.name.equals("hello"))
	}
	
}

 

In listing 3 we attempt to test a Groovy script.

Listing 3, Groovy script with nested test
/**  */
package com.octodecillion.testing

import junit.framework.TestCase;
import junit.textui.TestRunner
import org.codehaus.groovy.runtime.InvokerHelper;
import java.util.concurrent.*

if(System.getProperty("test.script")){	
	Executors.newSingleThreadExecutor().execute(new Runnable(){
		void run() {
			EmbeddedTestUtil.runJUnit3Test(ExampleTest3)
		}	
	})

	return;
}

String name;

println "Hello world!"

/** Nested JUnit3 test class */
class ExampleTest3 extends GroovyTestCase{
	def Example3 ex
	
	protected void setUp(){
		System.clearProperty("test.script")
		ex = new Example3()
		ex.name = "hello"
	}
	
	public void test1(){
		println "in test1 ...."
		assert(ex.name.equals("hello"))
	}
	
	/** Run the script */
	public void test2(){
		println "in test2 ...."
		
		ByteArrayOutputStream baos = new ByteArrayOutputStream()
		def myOut = System.out
		def pout = new PrintStream(baos)
		System.setOut(pout)
		ex.run()
		System.setOut(myOut)
		pout.flush();
		def actual = baos.toString()
		
		assert(actual.contains("Hello world!"))
	}
	
}

 

We copied some of the JUnit support built into Groovy into a new utility class shown in listing 4.

Listing 4, EmbeddedTestUtil class
package com.octodecillion.testing

import junit.framework.TestCase;
import junit.textui.TestRunner
import org.codehaus.groovy.runtime.InvokerHelper;

/** 
 * Invoke embedded test classes.
 * 
 * Based on code from groovy.lang.GroovyShell.
 * See http://groovy.codehaus.org/gapi/groovy/lang/GroovyShell.html
 * @author j. betancourt
 */
class EmbeddedTestUtil {

	/**
	 * Run the JUnit 3 test.
	 * 
	 * Copied the approach used in 
	 * groovy.lang.GroovyShell.runJUnit3Test(Class scriptClass)
	 * @see https://github.com/groovy/groovy-core/blob/master/src/main/groovy/lang/GroovyShell.java
	 */	
	static runJUnit3Test(Class clazz) {		
		try {	
			
            Object testSuite = InvokerHelper.
				invokeConstructorOf("junit.framework.TestSuite", clazz);			
            InvokerHelper.invokeStaticMethod(
				"junit.textui.TestRunner", "run", testSuite);
        } catch (ClassNotFoundException e) {
            throw new GroovyRuntimeException(
				"Failed to run the unit test. JUnit is not on the Classpath.", e);
        }
	}
	
	/**
	 * Run the JUnit 4 test.
	 * 
	 * Copied the approach used in 
	 * groovy.lang.GroovyShell.runJUnit4Test(Class scriptClass)
	 * @see https://github.com/groovy/groovy-core/blob/master/src/main/groovy/lang/GroovyShell.java
	 */	
	static runJUnit4Test(Class clazz, def loader) {	
		def junit4Utils = "org.codehaus.groovy.vmplugin.v5.JUnit4Utils"	
		try {			
            return InvokerHelper.invokeStaticMethod(junit4Utils,"realRunJUnit4Test", [clazz, loader]);
        } catch (ClassNotFoundException e) {
            throw new GroovyRuntimeException(
				"Failed to run the unit test. JUnit is not on the Classpath.", e);
        }
	}
	
	private EmbeddedTestUtil(){
		// 
	}	
	
}

 

In listing 5 below, I run the examples in a Windows shell.

Listing 5, run of tests in Windows shell
src>groovy -cp . com\octodecillion\testing\Example1
.in test1 ....

Time: 0.023

OK (1 test)


src>groovy -cp . com\octodecillion\testing\Example2
.in test1 ....

Time: 0.018

OK (1 test)

src>groovy -cp . -Dtest.script=true com\octodecillion\testing\Example3
.in test1 ....
.in test2 ....

Time: 0.047

OK (2 tests)
</div>

 

Conclusion
Presented was a possible alternative to Groovy test source code structuring using nested test classes. Classes and scripts were given as examples. Perhaps a more powerful approach is to use more high-level test frameworks, such as Behavior Driven Development (with for example, JBehave). This would allow the target class or script to also contain its own executable specification.

Environment
Eclipse Kepler (4.3)
Groovy 2.1.2
Java JDK 1.7.0_25
Windows 7 64bit

Further Reading

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.