Everybody loves build.xml (test-driven Ant)

Andy Balaam

2013-02-01

Contents

What do we want to test?

Code layout

build.xml      - real build file
asserts.xml    - support code for tests
test-build.xml - actual tests

To run the tests:

ant -f test-build.xml test-name

Use asserts in test-build:

<!-- test-build.xml -->

<include file="asserts.xml"/>

Simple example: code got compiled

Simple example - test

<!-- test-build.xml -->

<target name="test-class-file-created">
    <assert-target-creates-file
        target="build"
        file="bin/my/package/ExampleFile.class"
    />
</target>

To run:

ant -f test-build.xml test-class-file-created

Simple example - assertions

<!-- asserts.xml -->

<macrodef name="assert-target-creates-file">
    <attribute name="target"/>
    <attribute name="file"/>
    <sequential>
        <delete file="@{file}" quiet="true"/>
        <subant antfile="build.xml" buildpath="." target="@{target}"/>
        <assert-file-exists file="@{file}"/>
    </sequential>
</macrodef>

Simple example - assertions (2)

<!-- asserts.xml -->

<macrodef name="assert-file-exists">
    <attribute name="file"/>
    <sequential>
        <echo message="Checking existence of file: @{file}"/>
        <fail message="File '@{file}' does not exist.">
            <condition>
                <not><available file="@{file}"/></not>
            </condition>
        </fail>
    </sequential>
</macrodef>

Harder example: JAR file

Harder example - test

<target name="test-jar-created-with-manifest">

    <assert-target-creates-file
        target="build"
        file="dist/MyProduct.jar"
    />
    <assert-file-in-jar-contains
        jarfile="dist/MyProduct.jar"
        filename="MANIFEST.MF"
        find="Main-Class: my.package.MyMain"
    />

Harder example - assertion

<macrodef name="assert-file-in-jar-contains">
    <attribute name="jarfile"/>
    <attribute name="filename"/>
    <attribute name="find"/>

    <sequential>
        <!-- ... insert checks that jar exists, and contains file -->

        <delete dir="${tmpdir}/unzip"/>
        <unzip src="@{jarfile}" dest="${tmpdir}/unzip"/>

        <!-- SEE NEXT SLIDE -->

        <delete dir="${tmpdir}/unzip"/>

    </sequential>
</macrodef>

Harder example - assertion (2)

<!-- Insert into previous slide -->

<fail message="@{jarfile}:@{filename} should contain @{find}">
    <condition>
        <resourcecount when="equal" count="0">
            <fileset dir="${tmpdir}/unzip">
                <and>
                    <filename name="**/@{filename}"/>
                    <contains text="@{find}"/>
                </and>
            </fileset>
        </resourcecount>
    </condition>
</fail>

The Nuclear Option

Testing build logic

Testing build logic - success and failure

<macrodef name="expect-failure">
    <attribute name="target"/>
    <sequential>
        <local name="ex.caught"/>
        <script language="javascript"><![CDATA[
        try {
            project.executeTarget( "@{target}" );
        } catch( e ) {
            project.setProperty( "ex.caught", "yes" )
        }
        ]]></script>
        <fail message="@{target} succeeded!!!" unless="ex.caught"/>
    </sequential>
</macrodef>

Testing dependencies

Testing deps - "dry run"

<target name="printCdeps">
    <script language="javascript"><![CDATA[

        var targs = project.getTargets().elements();
        while( targs.hasMoreElements() )
        {
            var targ = targs.nextElement();
            targ.setUnless( "DRY.RUN" );
        }
        project.setProperty( "DRY.RUN", "1" );
        project.executeTarget( "targetC" );

    ]]></script>
</target>

Testing deps - capturing the output

<target name="test-C-depends-on-A">
    <delete file="${tmpdir}/cdeps.txt"/>
    <ant
        target="printCdeps"
        output="${tmpdir}/cdeps.txt"
    />
    <fail message="Target A did not execute when we ran C!">
        <condition>
            <resourcecount when="equal" count="0">
                <fileset file="${tmpdir}/cdeps.txt">
                    <contains text="targetA:"/>
                </fileset>
            </resourcecount>
        </condition>
    </fail>
    <delete file="${tmpdir}/cdeps.txt"/>
</target>

Testing ant units

Testing ant units - fake tasks

<!-- fake-tasks.xml -->
<macrodef name="jar">
    <attribute name="destfile"/>
    <sequential>
        <property name="jar.was.run" value="yes"/>
    </sequential>
</macrodef>

Testing ant units - normalish targets

<!-- build.xml -->
<include file="fake-tasks.xml" optional="true"/>

<target name="targetA">
    <jar destfile="foo.jar"/>
</target>

Testing ant units - running tasks

<!-- test-build.xml -->
<include file="build.xml" as="build"/>

<target name="test-A-runs-jar" depends="build.targetA">
    <fail message="Didn't jar!" unless="jar.was.run"/>
</target>

Run it like this:

cp real-fake-tasks.xml fake-tasks.xml
ant -f test-build.xml test-A-runs-jar
rm fake-tasks.xml

Complains, but it works in my Ant.

Discussion