What do we want to test?
Code layout
Simple example: code got compiled
Harder example: JAR file
The nuclear option
Testing build logic
Discussion
Not the code:
<junit ...>
Build artifacts (e.g. JAR files, created files)
Build logic:
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"/>
We want to check that a <javac ...> task worked
We just check that a .class file was created
<!-- 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
<!-- 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>
<!-- 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>
<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"
/>
<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>
<!-- 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>
If ant tasks just won't do, drop into <script>
You ain't gonna like it
<script language="javascript"><![CDATA[
system.launchMissiles(); // Muhahahaha
]]></script>
Expect bad error messages
Targets succeed or fail under certain conditions
Indirect dependencies are as expected
Testing a unit of Ant logic (e.g. a macrodef)
<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>
We want to run ant's dependency resolution without doing anything.
Ant has no support for this(!)
Luckily we have the nuclear option
<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>
<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>
We need fake versions of e.g. <jar>, <copy>, <javac>
Fakes track what happened
We make assertions about what fake tasks were run
<!-- fake-tasks.xml -->
<macrodef name="jar">
<attribute name="destfile"/>
<sequential>
<property name="jar.was.run" value="yes"/>
</sequential>
</macrodef>
<!-- build.xml -->
<include file="fake-tasks.xml" optional="true"/>
<target name="targetA">
<jar destfile="foo.jar"/>
</target>
<!-- 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.