/*******************************************************************************
* Copyright (c) 2009, eXXcellent solutions gmbh and others
* The code, documentation and other materials contained herein have been
* licensed under the Eclipse Public License - v 1.0 by the copyright holder
* listed above, as the Initial Contributor under such license. The text of
* such license is available at www.eclipse.org.
*
* Contributors:
* Achim Demelt - initial API and implementation
* Matthias Kappeller - Bug 321064 - No JUnit TestReport created for huge report files
*******************************************************************************/
package org.eclipse.buckminster.junit.internal;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Locale;
import org.eclipse.debug.core.model.IStreamMonitor;
import org.eclipse.jdt.junit.model.ITestCaseElement;
import org.eclipse.jdt.junit.model.ITestElement;
import org.eclipse.jdt.junit.model.ITestRunSession;
import org.eclipse.jdt.junit.model.ITestSuiteElement;
import org.eclipse.jdt.junit.model.ITestElement.FailureTrace;
import org.eclipse.jdt.junit.model.ITestElement.Result;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.AttributesImpl;
/**
* Serializes a given {@link ITestRunSession} into an ant-junit-like XML file.
* Most of the code is copied from JDT JUnit's TestRunSessionSerializer class,
* but it only uses public API and tries to conform to the (unspecified)
* ant-junit format.
*/
public class ResultSerializer implements XMLReader {
private static final String EMPTY = ""; //$NON-NLS-1$
private static final String CDATA = "CDATA"; //$NON-NLS-1$
private static final NumberFormat timeFormat = new DecimalFormat("0.0##", new DecimalFormatSymbols(Locale.US)); //$NON-NLS-1$ // not localized, parseable by Double.parseDouble(..)
private static final Attributes NO_ATTS = new AttributesImpl();
private ITestRunSession testRunSession;
private ContentHandler contentHandler;
private ErrorHandler errorHandler;
private TestListener testListener;
private IStreamMonitor[] stdOut;
private IStreamMonitor[] stdErr;
private boolean terseXML;
private boolean flatXML;
private String suiteStack = ""; //$NON-NLS-1$
public ResultSerializer(TestListener listener, IStreamMonitor[] stdout, IStreamMonitor[] stderr, boolean terseXML, boolean flatXML) {
if (listener.getTestRunSession() == null) {
throw new IllegalArgumentException(Messages.ResultSerializer_No_Test_Session);
}
this.testListener = listener;
this.testRunSession = listener.getTestRunSession();
this.stdOut = stdout;
this.stdErr = stderr;
this.terseXML = terseXML;
this.flatXML = flatXML;
}
@Override
public ContentHandler getContentHandler() {
return contentHandler;
}
@Override
public DTDHandler getDTDHandler() {
return null;
}
@Override
public EntityResolver getEntityResolver() {
return null;
}
@Override
public ErrorHandler getErrorHandler() {
return errorHandler;
}
@Override
public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
return false;
}
@Override
public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
return null;
}
@Override
public void parse(InputSource input) throws IOException, SAXException {
if (contentHandler == null)
throw new SAXException("ContentHandler missing"); //$NON-NLS-1$
contentHandler.startDocument();
handleTestRun();
contentHandler.endDocument();
}
@Override
public void parse(String systemId) throws IOException, SAXException {
// ignore
}
@Override
public void setContentHandler(ContentHandler handler) {
this.contentHandler = handler;
}
@Override
public void setDTDHandler(DTDHandler handler) {
// ignore
}
@Override
public void setEntityResolver(EntityResolver resolver) {
// ignore
}
@Override
public void setErrorHandler(ErrorHandler handler) {
this.errorHandler = handler;
}
@Override
public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
// ignore
}
@Override
public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
// ignore
}
private void addCDATA(AttributesImpl atts, String name, String value) {
atts.addAttribute(EMPTY, EMPTY, name, CDATA, value);
}
private void addFailure(ITestElement testElement) throws SAXException {
FailureTrace failureTrace = testElement.getFailureTrace();
if (failureTrace != null) {
AttributesImpl failureAtts = new AttributesImpl();
String failureKind = testElement.getTestResult(false) == Result.ERROR ? IXMLTags.NODE_ERROR : IXMLTags.NODE_FAILURE;
startElement(failureKind, failureAtts);
String expected = failureTrace.getExpected();
String actual = failureTrace.getActual();
if (expected != null) {
startElement(IXMLTags.NODE_EXPECTED, NO_ATTS);
contentHandler.characters(expected.toCharArray(), 0, expected.length());
endElement(IXMLTags.NODE_EXPECTED);
}
if (actual != null) {
startElement(IXMLTags.NODE_ACTUAL, NO_ATTS);
contentHandler.characters(actual.toCharArray(), 0, actual.length());
endElement(IXMLTags.NODE_ACTUAL);
}
String trace = failureTrace.getTrace();
contentHandler.characters(trace.toCharArray(), 0, trace.length());
endElement(failureKind);
}
}
private void endElement(String name) throws SAXException {
contentHandler.endElement(EMPTY, name, name);
}
private void handleTestElement(ITestElement testElement) throws SAXException {
if (testElement instanceof ITestSuiteElement) {
ITestSuiteElement testSuiteElement = (ITestSuiteElement) testElement;
if (flatXML) {
suiteStack = suiteStack + testSuiteElement.getSuiteTypeName() + "$"; //$NON-NLS-1$
} else {
AttributesImpl atts = new AttributesImpl();
addCDATA(atts, IXMLTags.ATTR_NAME, testSuiteElement.getSuiteTypeName());
if (!Double.isNaN(testSuiteElement.getElapsedTimeInSeconds()))
addCDATA(atts, IXMLTags.ATTR_TIME, timeFormat.format(testSuiteElement.getElapsedTimeInSeconds()));
startElement(IXMLTags.NODE_TESTSUITE, atts);
addFailure(testElement);
}
ITestElement[] children = testSuiteElement.getChildren();
for (int i = 0; i < children.length; i++) {
handleTestElement(children[i]);
}
if (flatXML) {
suiteStack = suiteStack.substring(0, suiteStack.lastIndexOf(testSuiteElement.getSuiteTypeName()));
} else {
endElement(IXMLTags.NODE_TESTSUITE);
}
} else if (testElement instanceof ITestCaseElement) {
ITestCaseElement testCaseElement = (ITestCaseElement) testElement;
AttributesImpl atts = new AttributesImpl();
String testClassName = testCaseElement.getTestClassName();
if (flatXML) {
if (suiteStack.endsWith(testClassName + "$")) //$NON-NLS-1$)
testClassName = suiteStack.substring(0, suiteStack.length() - 1);
else
testClassName = suiteStack + testClassName;
}
addCDATA(atts, IXMLTags.ATTR_NAME, testCaseElement.getTestMethodName());
addCDATA(atts, IXMLTags.ATTR_CLASSNAME, testClassName);
if (!Double.isNaN(testCaseElement.getElapsedTimeInSeconds()))
addCDATA(atts, IXMLTags.ATTR_TIME, timeFormat.format(testCaseElement.getElapsedTimeInSeconds()));
if (testCaseElement.getTestResult(false) == ITestElement.Result.IGNORED)
addCDATA(atts, IXMLTags.ATTR_IGNORED, Boolean.TRUE.toString());
startElement(IXMLTags.NODE_TESTCASE, atts);
addFailure(testElement);
endElement(IXMLTags.NODE_TESTCASE);
} else {
throw new IllegalStateException(String.valueOf(testElement));
}
}
private void handleTestRun() throws SAXException {
AttributesImpl atts = new AttributesImpl();
addCDATA(atts, IXMLTags.ATTR_NAME, testRunSession.getTestRunName());
addCDATA(atts, IXMLTags.ATTR_TESTS, String.valueOf(testListener.getOverallCount()));
addCDATA(atts, IXMLTags.ATTR_ERRORS, String.valueOf(testListener.getErrorCount()));
addCDATA(atts, IXMLTags.ATTR_FAILURES, String.valueOf(testListener.getFailureCount()));
addCDATA(atts, IXMLTags.ATTR_IGNORED, String.valueOf(testListener.getIgnoreCount()));
if (!Double.isNaN(testRunSession.getElapsedTimeInSeconds()))
addCDATA(atts, IXMLTags.ATTR_TIME, timeFormat.format(testRunSession.getElapsedTimeInSeconds()));
startElement(flatXML ? IXMLTags.NODE_TESTSUITE : IXMLTags.NODE_TESTSUITES, atts);
for (ITestElement element : testRunSession.getChildren())
handleTestElement(element);
if (!terseXML) {
writeStdOut();
writeStdErr();
}
endElement(flatXML ? IXMLTags.NODE_TESTSUITE : IXMLTags.NODE_TESTSUITES);
}
private void startElement(String name, Attributes atts) throws SAXException {
contentHandler.startElement(EMPTY, name, name, atts);
}
private void writeStdErr() throws SAXException {
for (IStreamMonitor sm : stdErr) {
if (sm == null)
continue;
String contents = sm.getContents();
if (contents.length() > 0) {
startElement(IXMLTags.NODE_SYSTEM_ERR, NO_ATTS);
contentHandler.characters(contents.toCharArray(), 0, contents.length());
endElement(IXMLTags.NODE_SYSTEM_ERR);
}
}
}
private void writeStdOut() throws SAXException {
for (IStreamMonitor sm : stdOut) {
if (sm == null)
continue;
String contents = sm.getContents();
if (contents.length() > 0) {
startElement(IXMLTags.NODE_SYSTEM_OUT, NO_ATTS);
contentHandler.characters(contents.toCharArray(), 0, contents.length());
endElement(IXMLTags.NODE_SYSTEM_OUT);
}
}
}
}