Tuesday, November 22, 2011

When a JUnit Report Has Too Many Files

When a junitreport generated by Ant starts failing with a "Too many open files" error, it's time to update your XSLT processor.

I recently found this out at work. I maintain my project's nightly test suite, and the email script that automatically lets the team know when a build fails. Lately it had been failing every night. When I would scroll down to the bottom of the log file, the error would have happened after running all of the unit tests. I did some experiments and figured out that the system would trigger the error when it had opened 1024 files. It seemed strange, because Ant uses Apache Xalan for XSLT tasks including junitreport, and the Xalan documentation that I had on hand claimed that the redirect:write extension tag used by the junitreport template would automatically close the open file handles. Another test using a recursive xsl:call-template proved that it was in fact leaving the files open forever.

The fix took a few steps, and gave me a lot of insight into how junitreport works under the hood. It starts by concatenating all of the individual TEST-*.xml files together into one big TESTS-TestSuites.xml file. The report subtag inside the junitreport tag is completely optional. If you leave the report tag out, all you'll get is this master XML file. The report tag then triggers the XSLT processing of the master XML file using one of Ant's templates in the ANT_HOME/etc directory, e.g. junit-frames.xsl. You can get more control by using the XSLT task instead of report, but I found that the class path and factory control attributes did not work. I ended up having to use a java task with xalan.jar on the classpath, and feed it the -IN, -XSL command line arguments.

This allowed me to process with our latest available version of xalan, which doesn't suffer from the bug. However, now the redirect:write extension and the Ant StringUtils.replace method did not work. I looked through Xalan's documentation to find out how to set up namespaces, and used the xalan:component and xalan:script tags to get the redirect extension and my own replacement for string replace working. Now I no longer get false positives for build failures!