blob: eebbef37460315a6b0fe9f9c851181c9b190ce50 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2013 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
* Neale Upstone <neale@nealeupstone.com> - [JUnit] JUnit viewer doesn't recognise <skipped/> node - https://bugs.eclipse.org/bugs/show_bug.cgi?id=276068
*******************************************************************************/
package org.eclipse.jdt.internal.junit.model;
import java.util.Stack;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import org.eclipse.osgi.util.NLS;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.internal.junit.model.TestElement.Status;
public class TestRunHandler extends DefaultHandler {
/*
* TODO: validate (currently assumes correct XML)
*/
private int fId;
private TestRunSession fTestRunSession;
private TestSuiteElement fTestSuite;
private TestCaseElement fTestCase;
private Stack<Boolean> fNotRun= new Stack<>();
private StringBuffer fFailureBuffer;
private boolean fInExpected;
private boolean fInActual;
private StringBuffer fExpectedBuffer;
private StringBuffer fActualBuffer;
private Locator fLocator;
private Status fStatus;
private IProgressMonitor fMonitor;
private int fLastReportedLine;
public TestRunHandler() {
}
public TestRunHandler(IProgressMonitor monitor) {
fMonitor= monitor;
}
public TestRunHandler(TestRunSession testRunSession) {
fTestRunSession= testRunSession;
}
@Override
public void setDocumentLocator(Locator locator) {
fLocator= locator;
}
@Override
public void startDocument() throws SAXException {
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (fLocator != null && fMonitor != null) {
int line= fLocator.getLineNumber();
if (line - 20 >= fLastReportedLine) {
line -= line % 20;
fLastReportedLine= line;
fMonitor.subTask(NLS.bind(ModelMessages.TestRunHandler_lines_read, new Integer(line)));
}
}
if (Thread.interrupted())
throw new OperationCanceledException();
if (qName.equals(IXMLTags.NODE_TESTRUN)) {
if (fTestRunSession == null) {
String name= attributes.getValue(IXMLTags.ATTR_NAME);
String project= attributes.getValue(IXMLTags.ATTR_PROJECT);
IJavaProject javaProject= null;
if (project != null) {
IJavaModel javaModel= JavaCore.create(ResourcesPlugin.getWorkspace().getRoot());
javaProject= javaModel.getJavaProject(project);
if (! javaProject.exists())
javaProject= null;
}
fTestRunSession= new TestRunSession(name, javaProject);
//TODO: read counts?
} else {
fTestRunSession.reset();
}
fTestSuite= fTestRunSession.getTestRoot();
} else if (qName.equals(IXMLTags.NODE_TESTSUITES)) {
// support Ant's 'junitreport' task; create suite from NODE_TESTSUITE
} else if (qName.equals(IXMLTags.NODE_TESTSUITE)) {
String name= attributes.getValue(IXMLTags.ATTR_NAME);
if (fTestRunSession == null) {
// support standalone suites and Ant's 'junitreport' task:
fTestRunSession= new TestRunSession(name, null);
fTestSuite= fTestRunSession.getTestRoot();
}
String pack= attributes.getValue(IXMLTags.ATTR_PACKAGE);
String suiteName= pack == null ? name : pack + "." + name; //$NON-NLS-1$
fTestSuite= (TestSuiteElement) fTestRunSession.createTestElement(fTestSuite, getNextId(), suiteName, true, 0);
readTime(fTestSuite, attributes);
fNotRun.push(Boolean.valueOf(attributes.getValue(IXMLTags.ATTR_INCOMPLETE)));
} else if (qName.equals(IXMLTags.NODE_PROPERTIES) || qName.equals(IXMLTags.NODE_PROPERTY)) {
// not interested
} else if (qName.equals(IXMLTags.NODE_TESTCASE)) {
String name= attributes.getValue(IXMLTags.ATTR_NAME);
String classname= attributes.getValue(IXMLTags.ATTR_CLASSNAME);
fTestCase= (TestCaseElement) fTestRunSession.createTestElement(fTestSuite, getNextId(), name + '(' + classname + ')', false, 0);
fNotRun.push(Boolean.valueOf(attributes.getValue(IXMLTags.ATTR_INCOMPLETE)));
fTestCase.setIgnored(Boolean.valueOf(attributes.getValue(IXMLTags.ATTR_IGNORED)).booleanValue());
readTime(fTestCase, attributes);
} else if (qName.equals(IXMLTags.NODE_ERROR)) {
//TODO: multiple failures: https://bugs.eclipse.org/bugs/show_bug.cgi?id=125296
fStatus= Status.ERROR;
fFailureBuffer= new StringBuffer();
} else if (qName.equals(IXMLTags.NODE_FAILURE)) {
//TODO: multiple failures: https://bugs.eclipse.org/bugs/show_bug.cgi?id=125296
fStatus= Status.FAILURE;
fFailureBuffer= new StringBuffer();
} else if (qName.equals(IXMLTags.NODE_EXPECTED)) {
fInExpected= true;
fExpectedBuffer= new StringBuffer();
} else if (qName.equals(IXMLTags.NODE_ACTUAL)) {
fInActual= true;
fActualBuffer= new StringBuffer();
} else if (qName.equals(IXMLTags.NODE_SYSTEM_OUT) || qName.equals(IXMLTags.NODE_SYSTEM_ERR)) {
// not interested
} else if (qName.equals(IXMLTags.NODE_SKIPPED)) {
// before Ant 1.9.0: not an Ant JUnit tag, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=276068
// later: child of <suite> or <test>, see https://issues.apache.org/bugzilla/show_bug.cgi?id=43969
fStatus= Status.OK;
fFailureBuffer= new StringBuffer();
String message= attributes.getValue(IXMLTags.ATTR_MESSAGE);
if (message != null) {
fFailureBuffer.append(message).append('\n');
}
} else {
throw new SAXParseException("unknown node '" + qName + "'", fLocator); //$NON-NLS-1$//$NON-NLS-2$
}
}
private void readTime(TestElement testElement, Attributes attributes) {
String timeString= attributes.getValue(IXMLTags.ATTR_TIME);
if (timeString != null) {
try {
testElement.setElapsedTimeInSeconds(Double.parseDouble(timeString));
} catch (NumberFormatException e) {
}
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (fInExpected) {
fExpectedBuffer.append(ch, start, length);
} else if (fInActual) {
fActualBuffer.append(ch, start, length);
} else if (fFailureBuffer != null) {
fFailureBuffer.append(ch, start, length);
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName.equals(IXMLTags.NODE_TESTRUN)) {
// OK
} else if (qName.equals(IXMLTags.NODE_TESTSUITES)) {
// OK
} else if (qName.equals(IXMLTags.NODE_TESTSUITE)) {
handleTestElementEnd(fTestSuite);
fTestSuite= fTestSuite.getParent();
//TODO: end suite: compare counters?
} else if (qName.equals(IXMLTags.NODE_PROPERTIES) || qName.equals(IXMLTags.NODE_PROPERTY)) {
// OK
} else if (qName.equals(IXMLTags.NODE_TESTCASE)) {
handleTestElementEnd(fTestCase);
fTestCase= null;
} else if (qName.equals(IXMLTags.NODE_FAILURE) || qName.equals(IXMLTags.NODE_ERROR)) {
TestElement testElement= fTestCase;
if (testElement == null)
testElement= fTestSuite;
handleFailure(testElement);
} else if (qName.equals(IXMLTags.NODE_EXPECTED)) {
fInExpected= false;
if (fFailureBuffer != null) {
// skip whitespace from before <expected> and <actual> nodes
fFailureBuffer.setLength(0);
}
} else if (qName.equals(IXMLTags.NODE_ACTUAL)) {
fInActual= false;
if (fFailureBuffer != null) {
// skip whitespace from before <expected> and <actual> nodes
fFailureBuffer.setLength(0);
}
} else if (qName.equals(IXMLTags.NODE_SYSTEM_OUT) || qName.equals(IXMLTags.NODE_SYSTEM_ERR)) {
// OK
} else if (qName.equals(IXMLTags.NODE_SKIPPED)) {
TestElement testElement= fTestCase;
if (testElement == null)
testElement= fTestSuite;
if (fFailureBuffer != null && fFailureBuffer.length() > 0) {
handleFailure(testElement);
testElement.setAssumptionFailed(true);
} else if (fTestCase != null) {
fTestCase.setIgnored(true);
} else { // not expected
testElement.setAssumptionFailed(true);
}
} else {
handleUnknownNode(qName);
}
}
private void handleTestElementEnd(TestElement testElement) {
boolean completed= fNotRun.pop() != Boolean.TRUE;
fTestRunSession.registerTestEnded(testElement, completed);
}
private void handleFailure(TestElement testElement) {
if (fFailureBuffer != null) {
fTestRunSession.registerTestFailureStatus(testElement, fStatus, fFailureBuffer.toString(), toString(fExpectedBuffer), toString(fActualBuffer));
fFailureBuffer= null;
fExpectedBuffer= null;
fActualBuffer= null;
fStatus= null;
}
}
private String toString(StringBuffer buffer) {
return buffer != null ? buffer.toString() : null;
}
private void handleUnknownNode(String qName) throws SAXException {
//TODO: just log if debug option is enabled?
String msg= "unknown node '" + qName + "'"; //$NON-NLS-1$//$NON-NLS-2$
if (fLocator != null) {
msg += " at line " + fLocator.getLineNumber() + ", column " + fLocator.getColumnNumber(); //$NON-NLS-1$//$NON-NLS-2$
}
throw new SAXException(msg);
}
@Override
public void error(SAXParseException e) throws SAXException {
throw e;
}
@Override
public void warning(SAXParseException e) throws SAXException {
throw e;
}
private String getNextId() {
return Integer.toString(fId++);
}
/**
* @return the parsed test run session, or <code>null</code>
*/
public TestRunSession getTestRunSession() {
return fTestRunSession;
}
}