blob: 73668cc2e6cede3109d9b0f61a3987b66df43149 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2017 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Brock Janiczak (brockj@tpg.com.au)
* - https://bugs.eclipse.org/bugs/show_bug.cgi?id=102236: [JUnit] display execution time next to each test
*******************************************************************************/
package org.eclipse.jdt.internal.junit.model;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Locale;
import java.util.stream.Collectors;
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.XMLReader;
import org.xml.sax.helpers.AttributesImpl;
import org.eclipse.jdt.junit.model.ITestElement;
import org.eclipse.jdt.junit.model.ITestElement.FailureTrace;
import org.eclipse.jdt.junit.model.ITestElement.ProgressState;
import org.eclipse.jdt.junit.model.ITestElement.Result;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.core.IJavaProject;
public class TestRunSessionSerializer implements XMLReader {
private static final String EMPTY= ""; //$NON-NLS-1$
private static final String CDATA= "CDATA"; //$NON-NLS-1$
private static final Attributes NO_ATTS= new AttributesImpl();
private final TestRunSession fTestRunSession;
private ContentHandler fHandler;
private ErrorHandler fErrorHandler;
private final NumberFormat timeFormat= new DecimalFormat("0.0##", new DecimalFormatSymbols(Locale.US)); //$NON-NLS-1$ // not localized, parseable by Double.parseDouble(..)
/**
* @param testRunSession the test run session to serialize
*/
public TestRunSessionSerializer(TestRunSession testRunSession) {
Assert.isNotNull(testRunSession);
fTestRunSession= testRunSession;
}
@Override
public void parse(InputSource input) throws IOException, SAXException {
if (fHandler == null)
throw new SAXException("ContentHandler missing"); //$NON-NLS-1$
fHandler.startDocument();
handleTestRun();
fHandler.endDocument();
}
private void handleTestRun() throws SAXException {
AttributesImpl atts= new AttributesImpl();
addCDATA(atts, IXMLTags.ATTR_NAME, fTestRunSession.getTestRunName());
IJavaProject project= fTestRunSession.getLaunchedProject();
if (project != null)
addCDATA(atts, IXMLTags.ATTR_PROJECT, project.getElementName());
addCDATA(atts, IXMLTags.ATTR_TESTS, fTestRunSession.getTotalCount());
addCDATA(atts, IXMLTags.ATTR_STARTED, fTestRunSession.getStartedCount());
addCDATA(atts, IXMLTags.ATTR_FAILURES, fTestRunSession.getFailureCount());
addCDATA(atts, IXMLTags.ATTR_ERRORS, fTestRunSession.getErrorCount());
addCDATA(atts, IXMLTags.ATTR_IGNORED, fTestRunSession.getIgnoredCount());
String includeTags= fTestRunSession.getIncludeTags();
if (includeTags != null && !includeTags.trim().isEmpty()) {
addCDATA(atts, IXMLTags.ATTR_INCLUDE_TAGS, includeTags);
}
String excludeTags= fTestRunSession.getExcludeTags();
if (excludeTags != null && !excludeTags.trim().isEmpty()) {
addCDATA(atts, IXMLTags.ATTR_EXCLUDE_TAGS, excludeTags);
}
startElement(IXMLTags.NODE_TESTRUN, atts);
TestRoot testRoot= fTestRunSession.getTestRoot();
ITestElement[] topSuites= testRoot.getChildren();
for (int i= 0; i < topSuites.length; i++) {
handleTestElement(topSuites[i]);
}
endElement(IXMLTags.NODE_TESTRUN);
}
private void handleTestElement(ITestElement testElement) throws SAXException {
if (testElement instanceof TestSuiteElement) {
TestSuiteElement testSuiteElement= (TestSuiteElement) testElement;
AttributesImpl atts= new AttributesImpl();
// Need to store the full #getTestName instead of only the #getSuiteTypeName for test factory methods
addCDATA(atts, IXMLTags.ATTR_NAME, testSuiteElement.getTestName());
if (! Double.isNaN(testSuiteElement.getElapsedTimeInSeconds()))
addCDATA(atts, IXMLTags.ATTR_TIME, timeFormat.format(testSuiteElement.getElapsedTimeInSeconds()));
if (testElement.getProgressState() != ProgressState.COMPLETED || testElement.getTestResult(false) != Result.UNDEFINED)
addCDATA(atts, IXMLTags.ATTR_INCOMPLETE, Boolean.TRUE.toString());
if (testSuiteElement.getDisplayName() != null) {
addCDATA(atts, IXMLTags.ATTR_DISPLAY_NAME, testSuiteElement.getDisplayName());
}
String[] paramTypes= testSuiteElement.getParameterTypes();
if (paramTypes != null) {
String paramTypesStr= Arrays.stream(paramTypes).collect(Collectors.joining(",")); //$NON-NLS-1$
addCDATA(atts, IXMLTags.ATTR_PARAMETER_TYPES, paramTypesStr);
}
if (testSuiteElement.getUniqueId() != null) {
addCDATA(atts, IXMLTags.ATTR_UNIQUE_ID, testSuiteElement.getUniqueId());
}
startElement(IXMLTags.NODE_TESTSUITE, atts);
addFailure(testSuiteElement);
ITestElement[] children= testSuiteElement.getChildren();
for (int i= 0; i < children.length; i++) {
handleTestElement(children[i]);
}
endElement(IXMLTags.NODE_TESTSUITE);
} else if (testElement instanceof TestCaseElement) {
TestCaseElement testCaseElement= (TestCaseElement) testElement;
AttributesImpl atts= new AttributesImpl();
addCDATA(atts, IXMLTags.ATTR_NAME, testCaseElement.getTestMethodName());
addCDATA(atts, IXMLTags.ATTR_CLASSNAME, testCaseElement.getClassName());
if (! Double.isNaN(testCaseElement.getElapsedTimeInSeconds()))
addCDATA(atts, IXMLTags.ATTR_TIME, timeFormat.format(testCaseElement.getElapsedTimeInSeconds()));
if (testElement.getProgressState() != ProgressState.COMPLETED)
addCDATA(atts, IXMLTags.ATTR_INCOMPLETE, Boolean.TRUE.toString());
if (testCaseElement.isIgnored())
addCDATA(atts, IXMLTags.ATTR_IGNORED, Boolean.TRUE.toString());
if (testCaseElement.isDynamicTest()) {
addCDATA(atts, IXMLTags.ATTR_DYNAMIC_TEST, Boolean.TRUE.toString());
}
if (testCaseElement.getDisplayName() != null) {
addCDATA(atts, IXMLTags.ATTR_DISPLAY_NAME, testCaseElement.getDisplayName());
}
String[] paramTypes= testCaseElement.getParameterTypes();
if (paramTypes != null) {
String paramTypesStr= Arrays.stream(paramTypes).collect(Collectors.joining(",")); //$NON-NLS-1$
addCDATA(atts, IXMLTags.ATTR_PARAMETER_TYPES, paramTypesStr);
}
if (testCaseElement.getUniqueId() != null) {
addCDATA(atts, IXMLTags.ATTR_UNIQUE_ID, testCaseElement.getUniqueId());
}
startElement(IXMLTags.NODE_TESTCASE, atts);
addFailure(testCaseElement);
endElement(IXMLTags.NODE_TESTCASE);
} else {
throw new IllegalStateException(String.valueOf(testElement));
}
}
private void addFailure(TestElement testElement) throws SAXException {
FailureTrace failureTrace= testElement.getFailureTrace();
if (testElement.isAssumptionFailure()) {
startElement(IXMLTags.NODE_SKIPPED, NO_ATTS);
if (failureTrace != null) {
addCharacters(failureTrace.getTrace());
}
endElement(IXMLTags.NODE_SKIPPED);
} else if (failureTrace != null) {
AttributesImpl failureAtts= new AttributesImpl();
// addCDATA(failureAtts, IXMLTags.ATTR_MESSAGE, xx);
// addCDATA(failureAtts, IXMLTags.ATTR_TYPE, xx);
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);
addCharacters(expected);
endElement(IXMLTags.NODE_EXPECTED);
}
if (actual != null) {
startElement(IXMLTags.NODE_ACTUAL, NO_ATTS);
addCharacters(actual);
endElement(IXMLTags.NODE_ACTUAL);
}
String trace= failureTrace.getTrace();
addCharacters(trace);
endElement(failureKind);
}
}
private void startElement(String name, Attributes atts) throws SAXException {
fHandler.startElement(EMPTY, name, name, atts);
}
private void endElement(String name) throws SAXException {
fHandler.endElement(EMPTY, name, name);
}
private static void addCDATA(AttributesImpl atts, String name, int value) {
addCDATA(atts, name, Integer.toString(value));
}
private static void addCDATA(AttributesImpl atts, String name, String value) {
atts.addAttribute(EMPTY, EMPTY, name, CDATA, value);
}
private void addCharacters(String string) throws SAXException {
string= escapeNonUnicodeChars(string);
fHandler.characters(string.toCharArray(), 0, string.length());
}
/**
* Replaces all non-Unicode characters in the given string.
*
* @param string a string
* @return string with Java-escapes
* @since 3.6
*/
private static String escapeNonUnicodeChars(String string) {
StringBuffer buf= null;
for (int i= 0; i < string.length(); i++) {
char ch= string.charAt(i);
if (!(ch == 9 || ch == 10 || ch == 13 || ch >= 32)) {
if (buf == null) {
buf= new StringBuffer(string.substring(0, i));
}
buf.append("\\u"); //$NON-NLS-1$
String hex= Integer.toHexString(ch);
for (int j= hex.length(); j < 4; j++)
buf.append('0');
buf.append(hex);
} else if (buf != null) {
buf.append(ch);
}
}
if (buf != null) {
return buf.toString();
}
return string;
}
@Override
public void setContentHandler(ContentHandler handler) {
this.fHandler= handler;
}
@Override
public ContentHandler getContentHandler() {
return fHandler;
}
@Override
public void setErrorHandler(ErrorHandler handler) {
fErrorHandler= handler;
}
@Override
public ErrorHandler getErrorHandler() {
return fErrorHandler;
}
// ignored:
@Override
public void parse(String systemId) throws IOException, SAXException {
}
@Override
public void setDTDHandler(DTDHandler handler) {
}
@Override
public DTDHandler getDTDHandler() {
return null;
}
@Override
public void setEntityResolver(EntityResolver resolver) {
}
@Override
public EntityResolver getEntityResolver() {
return null;
}
@Override
public void setProperty(java.lang.String name, java.lang.Object value) {
}
@Override
public Object getProperty(java.lang.String name) {
return null;
}
@Override
public void setFeature(java.lang.String name, boolean value) {
}
@Override
public boolean getFeature(java.lang.String name) {
return false;
}
}