Friday, August 9, 2013

Comparing feature.xml Files With XMLUnit

Recently, I worked on a releng tool that compares two build results of an Eclipse-based product and reports the differences. Depending on how the build is modelled, there are metadata files, which content changes with each build, e.g. version timestamps in feature.xml. In order to avoid polluting the report with such changes, I wanted to filter them.

So, I needed a way to compare feature.xml files, but yet ignoring differences found in some xml attributes like "version", "download-size" and "install-size". Luckily, I stumbled upon XMLUnit, which saved me lots of efforts.

XMLUnit was designed as a helper for asserting test results for code that produces XML output. But it's also turns to be a great helper for any use case that involves comparison of XML data.

Comparing two XML files is as easy as creating a new Diff object and calling any of the two test methods: identical() and similar(). In my case, similar() worked better.

FileReader xml1 = ...
FileReader xml2 = ...
Diff xmlDiff = new Diff(xml1, xml2);
xmlDiff.similar();
view raw XMLDiff.java hosted with ❤ by GitHub
The above snippet compares the XML files, but it does not ignore the attributes I am not interested of. Customizing the comparison mechanism of XMLUnit is very easy by overriding the so called DifferenceListener with own implementation.

xmlDiff.overrideDifferenceListener(
new IgnoreVariableAttributesDifferenceListener());
In my own implementation I simply check if the found difference is related to any of the three attributes I want to ignore. If this is the case then I simply tell XMLUnit to treat these difference as "similar".

public class IgnoreVariableAttributesDifferenceListener implements
DifferenceListener {
private static final List<String> IGNORE_ATTRS = Arrays
.asList(new String[] { "version", "download-size", "install-size" });
@Override
public int differenceFound(Difference difference) {
if (isIgnoredAttributeDifference(difference)) {
return RETURN_IGNORE_DIFFERENCE_NODES_SIMILAR;
} else {
return RETURN_ACCEPT_DIFFERENCE;
}
}
@Override
public void skippedComparison(Node control, Node test) {
// nothing to do
}
private boolean isIgnoredAttributeDifference(Difference difference) {
return difference.getId() == DifferenceConstants.ATTR_VALUE_ID
&& IGNORE_ATTRS.contains(difference.getControlNodeDetail()
.getNode().getNodeName());
}
}
And this is everything you need to get the job done.