| /******************************************************************************* |
| * Copyright (c) 2007, 2017 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * 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.Arrays; |
| 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, Integer.valueOf(line))); |
| } |
| } |
| if (Thread.interrupted()) |
| throw new OperationCanceledException(); |
| |
| switch (qName) { |
| case 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); |
| String includeTags= attributes.getValue(IXMLTags.ATTR_INCLUDE_TAGS); |
| if (includeTags != null && includeTags.trim().length() > 0) { |
| fTestRunSession.setIncludeTags(includeTags); |
| } |
| String excludeTags= attributes.getValue(IXMLTags.ATTR_EXCLUDE_TAGS); |
| if (excludeTags != null && excludeTags.trim().length() > 0) { |
| fTestRunSession.setExcludeTags(excludeTags); |
| } |
| //TODO: read counts? |
| |
| } else { |
| fTestRunSession.reset(); |
| } |
| fTestSuite= fTestRunSession.getTestRoot(); |
| break; |
| // support Ant's 'junitreport' task; create suite from NODE_TESTSUITE |
| case IXMLTags.NODE_TESTSUITES: |
| break; |
| case 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$ |
| String displayName= attributes.getValue(IXMLTags.ATTR_DISPLAY_NAME); |
| String paramTypesStr= attributes.getValue(IXMLTags.ATTR_PARAMETER_TYPES); |
| String[] paramTypes; |
| if (paramTypesStr != null && !paramTypesStr.trim().isEmpty()) { |
| paramTypes= paramTypesStr.split(","); //$NON-NLS-1$ |
| Arrays.parallelSetAll(paramTypes, i -> paramTypes[i].trim()); |
| } else { |
| paramTypes= null; |
| } String uniqueId= attributes.getValue(IXMLTags.ATTR_UNIQUE_ID); |
| if (uniqueId != null && uniqueId.trim().isEmpty()) { |
| uniqueId= null; |
| } fTestSuite= (TestSuiteElement) fTestRunSession.createTestElement(fTestSuite, getNextId(), suiteName, true, 0, false, displayName, paramTypes, uniqueId); |
| readTime(fTestSuite, attributes); |
| fNotRun.push(Boolean.valueOf(attributes.getValue(IXMLTags.ATTR_INCOMPLETE))); |
| break; |
| } |
| // not interested |
| case IXMLTags.NODE_PROPERTIES: |
| case IXMLTags.NODE_PROPERTY: |
| break; |
| case IXMLTags.NODE_TESTCASE: |
| { |
| String name= attributes.getValue(IXMLTags.ATTR_NAME); |
| String classname= attributes.getValue(IXMLTags.ATTR_CLASSNAME); |
| String testName= name + '(' + classname + ')'; |
| boolean isDynamicTest= Boolean.valueOf(attributes.getValue(IXMLTags.ATTR_DYNAMIC_TEST)).booleanValue(); |
| String displayName= attributes.getValue(IXMLTags.ATTR_DISPLAY_NAME); |
| String paramTypesStr= attributes.getValue(IXMLTags.ATTR_PARAMETER_TYPES); |
| String[] paramTypes; |
| if (paramTypesStr != null && !paramTypesStr.trim().isEmpty()) { |
| paramTypes= paramTypesStr.split(","); //$NON-NLS-1$ |
| Arrays.parallelSetAll(paramTypes, i -> paramTypes[i].trim()); |
| } else { |
| paramTypes= null; |
| } String uniqueId= attributes.getValue(IXMLTags.ATTR_UNIQUE_ID); |
| if (uniqueId != null && uniqueId.trim().isEmpty()) { |
| uniqueId= null; |
| } fTestCase= (TestCaseElement) fTestRunSession.createTestElement(fTestSuite, getNextId(), testName, false, 0, isDynamicTest, displayName, paramTypes, uniqueId); |
| fNotRun.push(Boolean.valueOf(attributes.getValue(IXMLTags.ATTR_INCOMPLETE))); |
| fTestCase.setIgnored(Boolean.valueOf(attributes.getValue(IXMLTags.ATTR_IGNORED)).booleanValue()); |
| readTime(fTestCase, attributes); |
| break; |
| } |
| case IXMLTags.NODE_ERROR: |
| //TODO: multiple failures: https://bugs.eclipse.org/bugs/show_bug.cgi?id=125296 |
| fStatus= Status.ERROR; |
| fFailureBuffer= new StringBuffer(); |
| break; |
| case IXMLTags.NODE_FAILURE: |
| //TODO: multiple failures: https://bugs.eclipse.org/bugs/show_bug.cgi?id=125296 |
| fStatus= Status.FAILURE; |
| fFailureBuffer= new StringBuffer(); |
| break; |
| case IXMLTags.NODE_EXPECTED: |
| fInExpected= true; |
| fExpectedBuffer= new StringBuffer(); |
| break; |
| case IXMLTags.NODE_ACTUAL: |
| fInActual= true; |
| fActualBuffer= new StringBuffer(); |
| break; |
| // not interested |
| case IXMLTags.NODE_SYSTEM_OUT: |
| case IXMLTags.NODE_SYSTEM_ERR: |
| break; |
| case 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'); |
| } |
| break; |
| default: |
| 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 { |
| switch (qName) { |
| // OK |
| case IXMLTags.NODE_TESTRUN: |
| break; |
| // OK |
| case IXMLTags.NODE_TESTSUITES: |
| break; |
| case IXMLTags.NODE_TESTSUITE: |
| handleTestElementEnd(fTestSuite); |
| fTestSuite= fTestSuite.getParent(); |
| //TODO: end suite: compare counters? |
| break; |
| // OK |
| case IXMLTags.NODE_PROPERTIES: |
| case IXMLTags.NODE_PROPERTY: |
| break; |
| case IXMLTags.NODE_TESTCASE: |
| handleTestElementEnd(fTestCase); |
| fTestCase= null; |
| break; |
| case IXMLTags.NODE_FAILURE: |
| case IXMLTags.NODE_ERROR: |
| { |
| TestElement testElement= fTestCase; |
| if (testElement == null) |
| testElement= fTestSuite; |
| handleFailure(testElement); |
| break; |
| } |
| case IXMLTags.NODE_EXPECTED: |
| fInExpected= false; |
| if (fFailureBuffer != null) { |
| // skip whitespace from before <expected> and <actual> nodes |
| fFailureBuffer.setLength(0); |
| } |
| break; |
| case IXMLTags.NODE_ACTUAL: |
| fInActual= false; |
| if (fFailureBuffer != null) { |
| // skip whitespace from before <expected> and <actual> nodes |
| fFailureBuffer.setLength(0); |
| } |
| break; |
| // OK |
| case IXMLTags.NODE_SYSTEM_OUT: |
| case IXMLTags.NODE_SYSTEM_ERR: |
| break; |
| case 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); |
| } break; |
| } |
| default: |
| handleUnknownNode(qName); |
| break; |
| } |
| } |
| |
| 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; |
| } |
| } |