blob: d1d17b7e8952c7c2ad76f1e19465daff1b6a61a0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011 Anton Gorenkov
* 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:
* Anton Gorenkov - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.testsrunner.internal.qttest;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.cdt.testsrunner.model.ITestItem;
import org.eclipse.cdt.testsrunner.model.ITestMessage.Level;
import org.eclipse.cdt.testsrunner.model.ITestModelUpdater;
import org.eclipse.cdt.testsrunner.model.ITestCase;
import org.eclipse.cdt.testsrunner.model.ITestMessage;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Parses the Qt Test XML log and notifies the Tests Runner Core about how the
* testing process is going.
*
* @note There is a terminology conflict between Qt Test library and Test
* Runner. Qt Test's "test case" is a "test suite" in Test Runner's terminology,
* Qt's "test function" is a "test case", the "incident" and "message" are
* "test messages". Be careful with it!
*/
public class QtXmlLogHandler extends DefaultHandler {
// Qt Test XML log tags
private static final String XML_NODE_TEST_CASE = "TestCase"; //$NON-NLS-1$
private static final String XML_NODE_TEST_FUNCTION = "TestFunction"; //$NON-NLS-1$
private static final String XML_NODE_INCIDENT = "Incident"; //$NON-NLS-1$
private static final String XML_NODE_MESSAGE = "Message"; //$NON-NLS-1$
private static final String XML_NODE_DESCRIPTION = "Description"; //$NON-NLS-1$
private static final String XML_NODE_ENVIRONMENT = "Environment"; //$NON-NLS-1$
private static final String XML_NODE_QTVERSION = "QtVersion"; //$NON-NLS-1$
private static final String XML_NODE_QTESTVERSION = "QTestVersion"; //$NON-NLS-1$
private static final String XML_NODE_BENCHMARK = "BenchmarkResult"; //$NON-NLS-1$
private static final String XML_NODE_DATATAG = "DataTag"; //$NON-NLS-1$
// Qt Test XML case statuses representation
private static final String XML_VALUE_INCIDENT_PASS = "pass"; //$NON-NLS-1$
private static final String XML_VALUE_INCIDENT_XFAIL = "xfail"; //$NON-NLS-1$
private static final String XML_VALUE_INCIDENT_FAIL = "fail"; //$NON-NLS-1$
private static final String XML_VALUE_INCIDENT_XPASS = "xpass"; //$NON-NLS-1$
private static final String XML_VALUE_INCIDENT_UNKNOWN = "??????"; //$NON-NLS-1$
// Qt Test XML log message levels representation
private static final String XML_VALUE_MESSAGE_WARN = "warn"; //$NON-NLS-1$
private static final String XML_VALUE_MESSAGE_SYSTEM = "system"; //$NON-NLS-1$
private static final String XML_VALUE_MESSAGE_QDEBUG = "qdebug"; //$NON-NLS-1$
private static final String XML_VALUE_MESSAGE_QWARN = "qwarn"; //$NON-NLS-1$
private static final String XML_VALUE_MESSAGE_QFATAL = "qfatal"; //$NON-NLS-1$
private static final String XML_VALUE_MESSAGE_SKIP = "skip"; //$NON-NLS-1$
private static final String XML_VALUE_MESSAGE_INFO = "info"; //$NON-NLS-1$
private static final String XML_VALUE_MESSAGE_UNKNOWN = "??????"; //$NON-NLS-1$
// Qt Test XML log attributes
private static final String XML_ATTR_TEST_CASE_NAME = "name"; //$NON-NLS-1$
private static final String XML_ATTR_TEST_FUNCTION_NAME = "name"; //$NON-NLS-1$
private static final String XML_ATTR_TYPE = "type"; //$NON-NLS-1$
private static final String XML_ATTR_FILE = "file"; //$NON-NLS-1$
private static final String XML_ATTR_LINE = "line"; //$NON-NLS-1$
private static final String XML_ATTR_BENCHMARK_METRIC = "metric"; //$NON-NLS-1$
private static final String XML_ATTR_BENCHMARK_VALUE = "value"; //$NON-NLS-1$
private static final String XML_ATTR_BENCHMARK_ITERATIONS = "iterations"; //$NON-NLS-1$
private static final String XML_ATTR_DATA_TAG = "tag"; //$NON-NLS-1$
/** Maps the string message level representation to the Tests Runner internal enum code. */
private static final Map<String, ITestMessage.Level> STRING_TO_MESSAGE_LEVEL;
static {
Map<String, ITestMessage.Level> aMap = new HashMap<String, ITestMessage.Level>();
aMap.put(XML_VALUE_MESSAGE_WARN, ITestMessage.Level.Warning);
aMap.put(XML_VALUE_MESSAGE_SYSTEM, ITestMessage.Level.Message);
aMap.put(XML_VALUE_MESSAGE_QDEBUG, ITestMessage.Level.Message);
aMap.put(XML_VALUE_MESSAGE_QWARN, ITestMessage.Level.Warning);
aMap.put(XML_VALUE_MESSAGE_QFATAL, ITestMessage.Level.FatalError);
aMap.put(XML_VALUE_MESSAGE_SKIP, ITestMessage.Level.Info);
aMap.put(XML_VALUE_MESSAGE_INFO, ITestMessage.Level.Info);
aMap.put(XML_VALUE_MESSAGE_UNKNOWN, ITestMessage.Level.FatalError);
// NOTE: Exception node is processed separately
STRING_TO_MESSAGE_LEVEL = Collections.unmodifiableMap(aMap);
}
/** Maps the string incident status representation to the test case status. */
private static final Map<String, ITestCase.Status> STRING_TO_TEST_STATUS;
static {
Map<String, ITestCase.Status> aMap = new HashMap<String, ITestCase.Status>();
aMap.put(XML_VALUE_INCIDENT_PASS, ITestCase.Status.Passed);
aMap.put(XML_VALUE_INCIDENT_XFAIL, ITestCase.Status.Failed);
aMap.put(XML_VALUE_INCIDENT_FAIL, ITestCase.Status.Failed);
aMap.put(XML_VALUE_INCIDENT_XPASS, ITestCase.Status.Failed);
aMap.put(XML_VALUE_INCIDENT_UNKNOWN, ITestCase.Status.Aborted);
// NOTE: Exception node is processed separately
STRING_TO_TEST_STATUS = Collections.unmodifiableMap(aMap);
}
/** Maps the string incident status representation to the test message level to log about it. */
private static final Map<String, ITestMessage.Level> STRING_INCIDENT_TO_MESSAGE_LEVEL;
static {
Map<String, ITestMessage.Level> aMap = new HashMap<String, ITestMessage.Level>();
aMap.put(XML_VALUE_INCIDENT_PASS, ITestMessage.Level.Info);
aMap.put(XML_VALUE_INCIDENT_XFAIL, ITestMessage.Level.Error);
aMap.put(XML_VALUE_INCIDENT_FAIL, ITestMessage.Level.FatalError);
aMap.put(XML_VALUE_INCIDENT_XPASS, ITestMessage.Level.Error);
aMap.put(XML_VALUE_INCIDENT_UNKNOWN, ITestMessage.Level.FatalError);
// NOTE: Exception node is processed separately
STRING_INCIDENT_TO_MESSAGE_LEVEL = Collections.unmodifiableMap(aMap);
}
/** Maps the metrics unit ids to the user readable names. */
private static final Map<String, String> XML_METRICS_TO_UNIT_NAME;
static {
Map<String,String> aMap = new HashMap<String, String>();
aMap.put("events", QtTestsRunnerMessages.QtXmlLogHandler_metrics_unit_events); //$NON-NLS-1$
aMap.put("callgrind", QtTestsRunnerMessages.QtXmlLogHandler_metrics_unit_instructions); //$NON-NLS-1$
aMap.put("walltime", QtTestsRunnerMessages.QtXmlLogHandler_metrics_unit_msec); //$NON-NLS-1$
aMap.put("cputicks", QtTestsRunnerMessages.QtXmlLogHandler_metrics_unit_ticks); //$NON-NLS-1$
// NOTE: Exception node is processed separately
XML_METRICS_TO_UNIT_NAME = Collections.unmodifiableMap(aMap);
}
/** The interface to notify the Tests Runner Core */
private ITestModelUpdater modelUpdater;
/** Stores the text between current XML tag. */
private String elementData;
/** Stores the text for currently parsed test message. */
private String messageText;
/** Stores the file name part of location for currently parsed test message. */
private String fileName;
/** Stores the line number part of location for currently parsed test message. */
private int lineNumber;
/** Stores the message level for currently parsed test message. */
private ITestMessage.Level messageLevel;
/** Stores the status for currently parsed test case. */
private ITestItem.Status testCaseStatus;
/** Stores the name for currently parsed test case. */
private String testCaseName;
/** Stores the currently parsing data tag. */
private String currentDataTag;
/** Stores the last parsed data tag. */
private String lastDataTag;
/** Stores whether the test case was already added (means Tests Runner Core notified). */
private boolean testCaseAdded;
QtXmlLogHandler(ITestModelUpdater modelUpdater) {
this.modelUpdater = modelUpdater;
}
/**
* Notifies about test case exiting (if it was entered).
*/
private void exitTestCaseIfNecessary() {
if (testCaseAdded) {
modelUpdater.setTestStatus(testCaseStatus);
modelUpdater.exitTestCase();
testCaseAdded = false;
}
}
/**
* Creates a new test case if a new data tag is met.
*/
private void createTestCaseIfNecessary() {
if (!lastDataTag.equals(currentDataTag)) {
exitTestCaseIfNecessary();
currentDataTag = lastDataTag;
String suffix = !currentDataTag.isEmpty() ? MessageFormat.format(QtTestsRunnerMessages.QtXmlLogHandler_datatag_format, currentDataTag) : ""; //$NON-NLS-1$
modelUpdater.enterTestCase(testCaseName+suffix);
testCaseAdded = true;
}
}
/**
* Adds a new test message if there is a text for it.
*/
private void addTestMessageIfNecessary() {
if (messageText != null) {
modelUpdater.addTestMessage(fileName, lineNumber, messageLevel, messageText);
}
}
/**
* Sets a new status for the currently parsing test case.
*
* @param newStatus new test status
*
* @note Passed status is set by default and should not be set explicitly.
* But in case of errors it should not override Failed or Skipped statuses.
*/
private void setCurrentTestCaseStatus(ITestItem.Status newStatus) {
// Passed status is set by default and should not be set explicitly.
// But in case of errors it should not override Failed or Skipped statuses.
if (newStatus != ITestItem.Status.Passed) {
testCaseStatus = newStatus;
}
}
/**
* Converts the metric unit ids to user readable names.
*
* @param benchmarkMetric metric unit id
* @return user readable name
* @throws SAXException if metric unit id is not known
*/
private String getUnitsByBenchmarkMetric(String benchmarkMetric) throws SAXException {
String units = XML_METRICS_TO_UNIT_NAME.get(benchmarkMetric);
if (units == null) {
logAndThrowError(MessageFormat.format(QtTestsRunnerMessages.QtXmlLogHandler_unknown_benchmarck_metric, benchmarkMetric));
}
return units;
}
/**
* Converts the message level string to the internal enumeration core.
*
* @param map map to use
* @param incidentTypeStr message level string
* @return message level code
* @throws SAXException if message level string is not known
*/
private ITestMessage.Level getMessageLevel(Map<String, ITestMessage.Level> map, String incidentTypeStr) throws SAXException {
Level result = map.get(incidentTypeStr);
if (result == null) {
logAndThrowError(MessageFormat.format(QtTestsRunnerMessages.QtXmlLogHandler_unknown_message_level, incidentTypeStr));
}
return result;
}
@Override
public void startElement(String namespaceURI, String localName, String qName, Attributes attrs) throws SAXException {
elementData = null;
if (qName == XML_NODE_TEST_CASE) {
// NOTE: Terminology mapping: Qt Test Case is actually a Test Suite
String testSuiteName = attrs.getValue(XML_ATTR_TEST_CASE_NAME);
modelUpdater.enterTestSuite(testSuiteName);
} else if (qName == XML_NODE_TEST_FUNCTION) {
// NOTE: Terminology mapping: Qt Test Function is actually a Test Case
testCaseName = attrs.getValue(XML_ATTR_TEST_FUNCTION_NAME);
currentDataTag = null;
lastDataTag = ""; //$NON-NLS-1$
testCaseAdded = false;
testCaseStatus = ITestItem.Status.Passed;
} else if (qName == XML_NODE_MESSAGE) {
String messageLevelStr = attrs.getValue(XML_ATTR_TYPE);
fileName = attrs.getValue(XML_ATTR_FILE);
lineNumber = Integer.parseInt(attrs.getValue(XML_ATTR_LINE).trim());
messageLevel = getMessageLevel(STRING_TO_MESSAGE_LEVEL, messageLevelStr);
messageText = null;
if (messageLevelStr.equals(XML_VALUE_MESSAGE_SKIP)) {
setCurrentTestCaseStatus(ITestCase.Status.Skipped);
}
} else if (qName == XML_NODE_INCIDENT) {
String strType = attrs.getValue(XML_ATTR_TYPE);
fileName = attrs.getValue(XML_ATTR_FILE);
lineNumber = Integer.parseInt(attrs.getValue(XML_ATTR_LINE).trim());
messageLevel = getMessageLevel(STRING_INCIDENT_TO_MESSAGE_LEVEL, strType);
messageText = null;
setCurrentTestCaseStatus(STRING_TO_TEST_STATUS.get(strType));
} else if (qName == XML_NODE_BENCHMARK) {
lastDataTag = attrs.getValue(XML_ATTR_DATA_TAG);
createTestCaseIfNecessary();
int benchmarkResultIteratations = Integer.parseInt(attrs.getValue(XML_ATTR_BENCHMARK_ITERATIONS).trim());
float benchmarkResultValue = Integer.parseInt(attrs.getValue(XML_ATTR_BENCHMARK_VALUE).trim());
String units = getUnitsByBenchmarkMetric(attrs.getValue(XML_ATTR_BENCHMARK_METRIC).trim());
modelUpdater.addTestMessage("", 0, ITestMessage.Level.Info, //$NON-NLS-1$
MessageFormat.format(QtTestsRunnerMessages.QtXmlLogHandler_benchmark_result_message,
benchmarkResultValue/benchmarkResultIteratations, units, benchmarkResultValue, benchmarkResultIteratations
)
);
} else if (qName == XML_NODE_DATATAG) {
lastDataTag = ""; //$NON-NLS-1$
} else if (qName == XML_NODE_DESCRIPTION
|| qName == XML_NODE_ENVIRONMENT
|| qName == XML_NODE_QTVERSION
|| qName == XML_NODE_QTESTVERSION) {
/* just skip, do nothing */
} else {
logAndThrowErrorForElement(qName);
}
}
@Override
public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
if (qName == XML_NODE_TEST_CASE) {
modelUpdater.exitTestSuite();
} else if (qName == XML_NODE_TEST_FUNCTION) {
createTestCaseIfNecessary();
exitTestCaseIfNecessary();
} else if (qName == XML_NODE_DATATAG) {
lastDataTag = elementData;
} else if (qName == XML_NODE_INCIDENT) {
createTestCaseIfNecessary();
addTestMessageIfNecessary();
} else if (qName == XML_NODE_MESSAGE) {
createTestCaseIfNecessary();
addTestMessageIfNecessary();
} else if (qName == XML_NODE_DESCRIPTION) {
messageText = elementData == null || elementData.isEmpty() ? "" : elementData; //$NON-NLS-1$
} else if (qName == XML_NODE_ENVIRONMENT
|| qName == XML_NODE_QTVERSION
|| qName == XML_NODE_QTESTVERSION
|| qName == XML_NODE_BENCHMARK) {
/* just skip, do nothing */
} else {
logAndThrowErrorForElement(qName);
}
elementData = null;
}
@Override
public void characters(char[] ch, int start, int length) {
StringBuilder sb = new StringBuilder();
for (int i = start; i < start + length; i++) {
sb.append(ch[i]);
}
elementData = sb.toString();
}
/**
* Throws the testing exception for the specified XML tag.
*
* @param tagName XML tag name
* @throws SAXException the exception that will be thrown
*/
private void logAndThrowErrorForElement(String tagName) throws SAXException {
logAndThrowError(
MessageFormat.format(QtTestsRunnerMessages.QtXmlLogHandler_wrong_tag_name, tagName)
);
}
/**
* Throws the testing exception with the specified message.
*
* @param message the reason
* @throws SAXException the exception that will be thrown
*/
private void logAndThrowError(String message) throws SAXException {
SAXException e = new SAXException(message);
QtTestsRunnerPlugin.log(e);
throw e;
}
@Override
public void warning(SAXParseException ex) throws SAXException {
QtTestsRunnerPlugin.log(ex);
}
@Override
public void error(SAXParseException ex) throws SAXException {
QtTestsRunnerPlugin.log(ex);
throw ex;
}
@Override
public void fatalError(SAXParseException ex) throws SAXException {
QtTestsRunnerPlugin.log(ex);
throw ex;
}
}