/******************************************************************************* * Copyright (c) 2013 Rene Schneider, GEBIT Solutions GmbH 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 *******************************************************************************/ package de.gebit.integrity.runner.callbacks.xml; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.Serializable; import java.io.StringWriter; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Stack; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.transform.OutputKeys; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.xml.type.internal.DataValue.Base64; import org.eclipse.xtext.nodemodel.ICompositeNode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import org.jdom.Attribute; import org.jdom.CDATA; import org.jdom.Content; import org.jdom.DocType; import org.jdom.Document; import org.jdom.Element; import org.jdom.IllegalDataException; import org.jdom.JDOMException; import org.jdom.ProcessingInstruction; import org.jdom.Text; import org.jdom.input.SAXBuilder; import org.jdom.output.Format; import org.jdom.output.XMLOutputter; import org.jdom.transform.JDOMSource; import com.google.inject.Inject; import de.gebit.integrity.dsl.Call; import de.gebit.integrity.dsl.ConstantEntity; import de.gebit.integrity.dsl.MethodReference; import de.gebit.integrity.dsl.Suite; import de.gebit.integrity.dsl.SuiteDefinition; import de.gebit.integrity.dsl.SuiteReturn; import de.gebit.integrity.dsl.SuiteStatement; import de.gebit.integrity.dsl.SuiteStatementWithResult; import de.gebit.integrity.dsl.TableTest; import de.gebit.integrity.dsl.TableTestRow; import de.gebit.integrity.dsl.Test; import de.gebit.integrity.dsl.ValueOrEnumValueOrOperationCollection; import de.gebit.integrity.dsl.VariableAssignment; import de.gebit.integrity.dsl.VariableEntity; import de.gebit.integrity.dsl.VariableOrConstantEntity; import de.gebit.integrity.dsl.VariantDefinition; import de.gebit.integrity.dsl.VisibleComment; import de.gebit.integrity.dsl.VisibleDivider; import de.gebit.integrity.dsl.VisibleMultiLineTitleComment; import de.gebit.integrity.dsl.VisibleSingleLineTitleComment; import de.gebit.integrity.exceptions.MethodNotFoundException; import de.gebit.integrity.fixtures.ExtendedResultFixture.ExtendedResult; import de.gebit.integrity.fixtures.ExtendedResultFixture.ExtendedResultHTML; import de.gebit.integrity.fixtures.ExtendedResultFixture.ExtendedResultImage; import de.gebit.integrity.fixtures.ExtendedResultFixture.ExtendedResultText; import de.gebit.integrity.operations.UnexecutableException; import de.gebit.integrity.parameter.conversion.ConversionContext; import de.gebit.integrity.parameter.conversion.UnresolvableVariableHandling; import de.gebit.integrity.parameter.resolving.ParameterResolver; import de.gebit.integrity.parameter.resolving.TableTestParameterResolveMethod; import de.gebit.integrity.parameter.variables.VariableManager; import de.gebit.integrity.remoting.transport.enums.TestRunnerCallbackMethods; import de.gebit.integrity.runner.IntegrityRunnerModule; import de.gebit.integrity.runner.TestModel; import de.gebit.integrity.runner.callbacks.AbstractTestRunnerCallback; import de.gebit.integrity.runner.callbacks.TestFormatter; import de.gebit.integrity.runner.console.intercept.ConsoleInterceptionAggregator; import de.gebit.integrity.runner.console.intercept.Intercept; import de.gebit.integrity.runner.console.intercept.InterceptedLine; import de.gebit.integrity.runner.results.SuiteResult; import de.gebit.integrity.runner.results.SuiteSummaryResult; import de.gebit.integrity.runner.results.call.CallResult; import de.gebit.integrity.runner.results.call.CallResult.UpdatedVariable; import de.gebit.integrity.runner.results.test.TestComparisonFailureResult; import de.gebit.integrity.runner.results.test.TestComparisonResult; import de.gebit.integrity.runner.results.test.TestComparisonSuccessResult; import de.gebit.integrity.runner.results.test.TestExceptionSubResult; import de.gebit.integrity.runner.results.test.TestExecutedSubResult; import de.gebit.integrity.runner.results.test.TestResult; import de.gebit.integrity.runner.results.test.TestSubResult; import de.gebit.integrity.utils.IntegrityDSLUtil; import de.gebit.integrity.utils.VersionUtil; /** * Test runner callback which writes to an XML result file. This runner may optionally add an XHTML transform to the * file which allows to render the results in a nice, readable layout in any good browser. * * * @author Rene Schneider - initial API and implementation * */ public class XmlWriterTestCallback extends AbstractTestRunnerCallback { /** * The XML document that will be created. */ protected Document document; /** * The file in which to serialize the document. */ protected File outputFile; /** * The title of the result document. */ protected String title; /** * The timestamp of execution start. */ protected long executionStartTime; /** * Counter used to generate unique IDs for a lot of XML elements. */ protected long idCounter; /** * How the XML -> XHTML transform shall be handled. */ protected TransformHandling transformHandling; /** * Whether the next title comment is to be treated as a suite title. */ protected boolean nextTitleCommentIsSuiteTitle; /** * The classloader to use. */ @Inject protected ClassLoader classLoader; /** * The parameter resolver to use. */ @Inject protected ParameterResolver parameterResolver; /** * The variable manager to use. */ @Inject protected VariableManager variableManager; /** * The test formatter to use. */ @Inject protected TestFormatter testFormatter; /** * The interception service used to intercept console output. */ @Inject protected ConsoleInterceptionAggregator consoleInterceptor; /** * Whether console output shall be captured. */ protected boolean captureConsoleOutput; /** * The stack of elements. */ protected Stack<Element> currentElement = new Stack<Element>(); /** * In case of an abortion, the message is stored here to be used later. */ protected String abortMessage; /** * This prefix is used to mark temporary attributes (these are to be stripped before elements are serialized for the * final result). */ protected static final String TEMPORARY_ATTRIBUTE_PREFIX = "TEMP_"; /** The Constant ROOT_ELEMENT. */ protected static final String ROOT_ELEMENT = "integrity"; /** The Constant TEST_RUN_NAME_ATTRIBUTE. */ protected static final String TEST_RUN_NAME_ATTRIBUTE = "name"; /** The Constant TEST_RUN_TIMESTAMP. */ protected static final String TEST_RUN_TIMESTAMP = "timestamp"; /** The Constant TEST_RUN_TIMESTAMP_ISO8601. */ protected static final String TEST_RUN_TIMESTAMP_ISO8601 = "isotimestamp"; /** The Constant TEST_RUN_DURATION. */ protected static final String TEST_RUN_DURATION = "duration"; /** The Constant TEST_RUN_ABORT_MESSAGE_ATTRIBUTE. */ protected static final String TEST_RUN_ABORT_MESSAGE_ATTRIBUTE = "abortMessage"; /** The Constant VARIANT_ELEMENT. */ protected static final String VARIANT_ELEMENT = "variant"; /** The Constant VARIANT_NAME_ATTRIBUTE. */ protected static final String VARIANT_NAME_ATTRIBUTE = "name"; /** The Constant VARIANT_DESCRIPTION_ATTRIBUTE. */ protected static final String VARIANT_DESCRIPTION_ATTRIBUTE = "description"; /** The Constant SUITE_ELEMENT. */ protected static final String SUITE_ELEMENT = "suite"; /** The Constant SUITE_NAME_ATTRIBUTE. */ protected static final String SUITE_NAME_ATTRIBUTE = "name"; /** The Constant SUITE_TITLE_ATTRIBUTE. */ protected static final String SUITE_TITLE_ATTRIBUTE = "title"; /** The Constant VARIABLE_DEFINITION_COLLECTION_ELEMENT. */ protected static final String VARIABLE_DEFINITION_COLLECTION_ELEMENT = "variables"; /** The Constant RETURN_VARIABLE_ASSIGNMENT_COLLECTION_ELEMENT. */ protected static final String RETURN_VARIABLE_ASSIGNMENT_COLLECTION_ELEMENT = "returns"; /** The Constant STATEMENT_COLLECTION_ELEMENT. */ protected static final String STATEMENT_COLLECTION_ELEMENT = "statements"; /** The Constant SETUP_COLLECTION_ELEMENT. */ protected static final String SETUP_COLLECTION_ELEMENT = "setup"; /** The Constant TEARDOWN_COLLECTION_ELEMENT. */ protected static final String TEARDOWN_COLLECTION_ELEMENT = "teardown"; /** The Constant TEST_ELEMENT. */ protected static final String TEST_ELEMENT = "test"; /** The Constant TABLETEST_ELEMENT. */ protected static final String TABLETEST_ELEMENT = "tabletest"; /** The Constant CALL_ELEMENT. */ protected static final String CALL_ELEMENT = "call"; /** The Constant RESULT_ELEMENT. */ protected static final String RESULT_ELEMENT = "result"; /** The Constant RESULT_COLLECTION_ELEMENT. */ protected static final String RESULT_COLLECTION_ELEMENT = "results"; /** The Constant EXTENDED_RESULT_ELEMENT_TYPE_ATTRIBUTE. */ protected static final String EXTENDED_RESULT_ELEMENT_TITLE_ATTRIBUTE = "title"; /** The Constant EXTENDED_RESULT_TEXT_ELEMENT. */ protected static final String EXTENDED_RESULT_TEXT_ELEMENT = "extResultText"; /** The Constant EXTENDED_RESULT_IMAGE_ELEMENT. */ protected static final String EXTENDED_RESULT_IMAGE_ELEMENT = "extResultImage"; /** The Constant EXTENDED_RESULT_HTML_ELEMENT. */ protected static final String EXTENDED_RESULT_HTML_ELEMENT = "extResultHTML"; /** The Constant EXTENDED_RESULT_IMAGE_ELEMENT_TYPE_ATTRIBUTE. */ protected static final String EXTENDED_RESULT_IMAGE_ELEMENT_TYPE_ATTRIBUTE = "type"; /** The Constant EXTENDED_RESULT_IMAGE_ELEMENT_WIDTH_ATTRIBUTE. */ protected static final String EXTENDED_RESULT_IMAGE_ELEMENT_WIDTH_ATTRIBUTE = "width"; /** The Constant EXTENDED_RESULT_IMAGE_ELEMENT_HEIGHT_ATTRIBUTE. */ protected static final String EXTENDED_RESULT_IMAGE_ELEMENT_HEIGHT_ATTRIBUTE = "height"; /** The Constant EXTENDED_RESULT_COLLECTION_ELEMENT. */ protected static final String EXTENDED_RESULT_COLLECTION_ELEMENT = "extResults"; /** The Constant VARIABLE_UPDATE_ELEMENT. */ protected static final String VARIABLE_UPDATE_ELEMENT = "variableUpdate"; /** The Constant VARIABLE_UPDATE_PARAMETER_NAME_ATTRIBUTE. */ protected static final String VARIABLE_UPDATE_PARAMETER_NAME_ATTRIBUTE = "parameter"; /** The Constant COMPARISON_ELEMENT. */ protected static final String COMPARISON_ELEMENT = "comparison"; /** The Constant COMPARISON_COLLECTION_ELEMENT. */ protected static final String COMPARISON_COLLECTION_ELEMENT = "comparisons"; /** The Constant COMPARISON_NAME_ATTRIBUTE. */ protected static final String COMPARISON_NAME_ATTRIBUTE = "name"; /** The Constant VARIABLE_NAME_ATTRIBUTE. */ protected static final String VARIABLE_NAME_ATTRIBUTE = "name"; /** The Constant VARIABLE_NAME_ATTRIBUTE. */ protected static final String VARIABLE_TARGET_ATTRIBUTE = "target"; /** The Constant VARIABLE_ELEMENT. */ protected static final String VARIABLE_ELEMENT = "variable"; /** The Constant VARIABLE_ASSIGNMENT_ELEMENT. */ protected static final String VARIABLE_ASSIGNMENT_ELEMENT = "variableAssign"; /** The Constant COMMENT_ELEMENT. */ protected static final String COMMENT_ELEMENT = "comment"; /** The Constant COMMENT_TEXT_ATTRIBUTE. */ protected static final String COMMENT_TEXT_ATTRIBUTE = "text"; /** The Constant COMMENT_TYPE_ATTRIBUTE. */ protected static final String COMMENT_TYPE_ATTRIBUTE = "type"; /** The Constant COMMENT_TYPE_SUITETITLE. */ protected static final String COMMENT_TYPE_SUITETITLE = "suitetitle"; /** The Constant COMMENT_TYPE_TITLE. */ protected static final String COMMENT_TYPE_TITLE = "title"; /** The Constant DIVIDER_ELEMENT. */ protected static final String DIVIDER_ELEMENT = "divider"; /** The Constant DIVIDER_TEXT_ATTRIBUTE. */ protected static final String DIVIDER_TEXT_ATTRIBUTE = "text"; /** The Constant PARAMETER_COLLECTION_ELEMENT. */ protected static final String PARAMETER_COLLECTION_ELEMENT = "parameters"; /** The Constant PARAMETER_ELEMENT. */ protected static final String PARAMETER_ELEMENT = "parameter"; /** The Constant PARAMETER_NAME_ATTRIBUTE. */ protected static final String PARAMETER_NAME_ATTRIBUTE = "name"; /** The Constant PARAMETER_VALUE_ATTRIBUTE. */ protected static final String PARAMETER_VALUE_ATTRIBUTE = "value"; /** The Constant VARIABLE_VALUE_ATTRIBUTE. */ protected static final String VARIABLE_VALUE_ATTRIBUTE = "value"; /** The Constant RESULT_EXPECTED_VALUE_ATTRIBUTE. */ protected static final String RESULT_EXPECTED_VALUE_ATTRIBUTE = "expectedValue"; /** The Constant RESULT_REAL_VALUE_ATTRIBUTE. */ protected static final String RESULT_REAL_VALUE_ATTRIBUTE = "value"; /** The Constant RESULT_TYPE_ATTRIBUTE. */ protected static final String RESULT_TYPE_ATTRIBUTE = "type"; /** The Constant RESULT_TYPE_SUCCESS. */ protected static final String RESULT_TYPE_SUCCESS = "success"; /** The Constant RESULT_TYPE_FAILURE. */ protected static final String RESULT_TYPE_FAILURE = "failure"; /** The Constant RESULT_TYPE_EXCEPTION. */ protected static final String RESULT_TYPE_EXCEPTION = "exception"; /** The Constant RESULT_EXCEPTION_MESSAGE_ATTRIBUTE. */ protected static final String RESULT_EXCEPTION_MESSAGE_ATTRIBUTE = "exceptionMessage"; /** The Constant RESULT_EXCEPTION_TRACE_ATTRIBUTE. */ protected static final String RESULT_EXCEPTION_TRACE_ATTRIBUTE = "exceptionTrace"; /** The Constant EXECUTION_DURATION_ATTRIBUTE. */ protected static final String EXECUTION_DURATION_ATTRIBUTE = "duration"; /** The Constant SUCCESS_COUNT_ATTRIBUTE. */ protected static final String SUCCESS_COUNT_ATTRIBUTE = "successCount"; /** The Constant FAILURE_COUNT_ATTRIBUTE. */ protected static final String FAILURE_COUNT_ATTRIBUTE = "failureCount"; /** The Constant EXCEPTION_COUNT_ATTRIBUTE. */ protected static final String EXCEPTION_COUNT_ATTRIBUTE = "exceptionCount"; /** The Constant TEST_EXCEPTION_COUNT_ATTRIBUTE. */ protected static final String TEST_EXCEPTION_COUNT_ATTRIBUTE = "testExceptionCount"; /** The Constant CALL_EXCEPTION_COUNT_ATTRIBUTE. */ protected static final String CALL_EXCEPTION_COUNT_ATTRIBUTE = "callExceptionCount"; /** The Constant FIXTURE_DESCRIPTION_ATTRIBUTE. */ protected static final String FIXTURE_DESCRIPTION_ATTRIBUTE = "description"; /** The Constant TEST_NAME_ELEMENT. */ protected static final String TEST_NAME_ELEMENT = "name"; /** The Constant CALL_NAME_ELEMENT. */ protected static final String CALL_NAME_ELEMENT = "name"; /** The Constant FIXTURE_METHOD_ATTRIBUTE. */ protected static final String FIXTURE_METHOD_ATTRIBUTE = "fixture"; /** The Constant FORK_NAME_ATTRIBUTE. */ protected static final String FORK_NAME_ATTRIBUTE = "forkName"; /** The Constant FORK_DESCRIPTION_ATTRIBUTE. */ protected static final String FORK_DESCRIPTION_ATTRIBUTE = "forkDescription"; /** The Constant ID_ATTRIBUTE. */ protected static final String ID_ATTRIBUTE = "id"; /** The constant LINE_NUMBER_ATTRIBUTE. */ protected static final String LINE_NUMBER_ATTRIBUTE = "line"; /** The constant VERSION_ATTRIBUTE. */ protected static final String VERSION_ATTRIBUTE = "version"; /** The Constant CONSOLE_ELEMENT. */ protected static final String CONSOLE_ELEMENT = "console"; /** The Constant CONSOLE_LINECOUNT_ATTRIBUTE. */ protected static final String CONSOLE_LINECOUNT_ATTRIBUTE = "lines"; /** The Constant CONSOLE_TRUNCATED_ATTRIBUTE. */ protected static final String CONSOLE_TRUNCATED_ATTRIBUTE = "truncated"; /** The Constant CONSOLE_TEMP_STARTTIME_ATTRIBUTE. */ protected static final String CONSOLE_TEMP_STARTTIME_ATTRIBUTE = TEMPORARY_ATTRIBUTE_PREFIX + "starttime"; /** The Constant CONSOLE_TEMP_ENDTIME_ATTRIBUTE. */ protected static final String CONSOLE_TEMP_ENDTIME_ATTRIBUTE = TEMPORARY_ATTRIBUTE_PREFIX + "endtime"; /** The Constant CONSOLE_LINE_STDOUT_ELEMENT. */ protected static final String CONSOLE_LINE_STDOUT_ELEMENT = "out"; /** The Constant CONSOLE_LINE_STDERR_ELEMENT. */ protected static final String CONSOLE_LINE_STDERR_ELEMENT = "err"; /** The Constant CONSOLE_LINE_TEXT_ATTRIBUTE. */ protected static final String CONSOLE_LINE_TEXT_ATTRIBUTE = "text"; /** The Constant CONSOLE_LINE_TEMP_TIME_ATTRIBUTE. */ protected static final String CONSOLE_LINE_TEMP_TIME_ATTRIBUTE = TEMPORARY_ATTRIBUTE_PREFIX + "time"; /** The Constant CONSOLE_LINE_SOURCE_ATTRIBUTE. */ protected static final String CONSOLE_LINE_SOURCE_ATTRIBUTE = "source"; /** * Maximum number of lines of console output that is added to a single test/call. */ protected static final int MAX_CONSOLE_LINES = 10000; /** * Maximum size of a single console line. */ protected static final int MAX_CONSOLE_LINE_SIZE = 1000; /** * Default stack size for the XSLT transform thread. */ protected static final int TRANSFORM_THREAD_STACK_SIZE_DEFAULT = 10 * 1024 * 1024; /** * System property name for overriding transform thread stack size. */ protected static final String SYSPARAM_TRANSFORM_THREAD_STACK_SIZE = "integrity.transform.stacksize"; /** * The time format used to format execution times. */ protected static final DecimalFormat EXECUTION_TIME_FORMAT = new DecimalFormat("0.000"); /** * The generally used date format (coarse-grained date/time). */ protected static final DateFormat DATE_FORMAT = new SimpleDateFormat(); /** * The generally used timestamp format (fine-grained date/time). */ protected static final DateFormat TIMESTAMP_FORMAT = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM); static { if (TIMESTAMP_FORMAT instanceof SimpleDateFormat) { // This allows the use of the default, locale-specific date formats, but with integration of millisecond // precision. This little hack wouldn't be necessary if Java would allow to specify that I want milliseconds // to be added when retrieving the date format in the first place. SimpleDateFormat tempTimestampFormat = (SimpleDateFormat) TIMESTAMP_FORMAT; String tempPattern = tempTimestampFormat.toPattern(); if (!tempPattern.contains("SSSS")) { tempPattern = tempPattern.replace("ss", "ss.SSSS"); } tempTimestampFormat.applyPattern(tempPattern); } } /** * The ISO-standardized date format (this is mostly added to the XML to allow for easy transformation into a * JUnit-compatible result XML. */ protected static final SimpleDateFormat ISO_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); static { EXECUTION_TIME_FORMAT.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ENGLISH)); } /** * <<<<<<< HEAD ======= System property name to enable XML Writer Stack Trace output. */ protected static final String SYSPARAM_ENABLE_TRACE_OUTPUT = "integrity.xmlwriter.trace"; /** * Whether stack tracing is enabled. See the {@link #stackPeek()}, {@link #stackPop()} and * {@link #stackPush(Element)} method for details. */ protected boolean isTracingEnabled = Boolean.getBoolean(SYSPARAM_ENABLE_TRACE_OUTPUT); /** * >>>>>>> v0.15.x_bugfix Creates a new instance. * * @param aClassLoader * the classloader to use * @param anOutputFile * the file to write the result into * @param aTitle * the title of the result * @param aTransformHandling * how the XML -> XHTML transform shall be handled * @param aCaptureConsoleFlag * whether stdout and stderr shall be captured * */ public XmlWriterTestCallback(ClassLoader aClassLoader, File anOutputFile, String aTitle, TransformHandling aTransformHandling, boolean aCaptureConsoleFlag) { classLoader = aClassLoader; outputFile = anOutputFile; title = aTitle; transformHandling = aTransformHandling != null ? aTransformHandling : TransformHandling.EXECUTE_TRANSFORM; captureConsoleOutput = aCaptureConsoleFlag; } /** * Gets the xslt stream. * * @return the xslt stream */ protected InputStream getXsltStream() { return getClass().getClassLoader().getResourceAsStream("resource/xhtml.xslt"); } /** * On execution start. * * @param aModel * the a model * @param aVariant * the a variant */ @Override public void onExecutionStart(TestModel aModel, VariantDefinition aVariant) { if (captureConsoleOutput) { consoleInterceptor.startIntercept(); } Element tempRootElement = new Element(ROOT_ELEMENT); if (aVariant != null) { Element tempVariantElement = new Element(VARIANT_ELEMENT); tempVariantElement.setAttribute(VARIANT_NAME_ATTRIBUTE, aVariant.getName()); if (aVariant.getDescription() != null) { tempVariantElement.setAttribute(VARIANT_DESCRIPTION_ATTRIBUTE, aVariant.getDescription()); } tempRootElement.addContent(tempVariantElement); } tempRootElement.addContent(new Element(VARIABLE_DEFINITION_COLLECTION_ELEMENT)); tempRootElement.setAttribute(TEST_RUN_NAME_ATTRIBUTE, title); tempRootElement.setAttribute(TEST_RUN_TIMESTAMP, DATE_FORMAT.format(new Date())); tempRootElement.setAttribute(TEST_RUN_TIMESTAMP_ISO8601, ISO_DATE_FORMAT.format(new Date())); addVersion(tempRootElement); document = new Document(tempRootElement); stackPush(tempRootElement); if (!isFork()) { if (transformHandling == TransformHandling.EMBED_TRANSFORM) { try { Document tempTransform = new SAXBuilder().build(getXsltStream()); tempRootElement.addContent(0, tempTransform.getRootElement().detach()); DocType tempDocType = new DocType("doc"); tempDocType.setInternalSubset("<!ATTLIST xsl:stylesheet\nid ID #REQUIRED>"); document.setDocType(tempDocType); HashMap<String, String> tempProcessingInstructionMap = new HashMap<String, String>(2); tempProcessingInstructionMap.put("type", "text/xsl"); tempProcessingInstructionMap.put("href", "#xhtmltransform"); ProcessingInstruction tempProcessingInstruction = new ProcessingInstruction("xml-stylesheet", tempProcessingInstructionMap); document.addContent(0, tempProcessingInstruction); } catch (JDOMException exc) { exc.printStackTrace(); } catch (IOException exc) { exc.printStackTrace(); } } } executionStartTime = System.nanoTime(); } /** * On suite start. * * @param aSuite * the a suite */ @Override public void onSuiteStart(Suite aSuite) { Element tempSuiteElement = new Element(SUITE_ELEMENT); addId(tempSuiteElement); addLineNumber(tempSuiteElement, aSuite); tempSuiteElement.setAttribute(SUITE_NAME_ATTRIBUTE, IntegrityDSLUtil.getQualifiedSuiteName(aSuite.getDefinition())); addCurrentTime(tempSuiteElement); tempSuiteElement.addContent(new Element(SETUP_COLLECTION_ELEMENT)); tempSuiteElement.addContent(new Element(VARIABLE_DEFINITION_COLLECTION_ELEMENT)); tempSuiteElement.addContent(new Element(STATEMENT_COLLECTION_ELEMENT)); tempSuiteElement.addContent(new Element(RETURN_VARIABLE_ASSIGNMENT_COLLECTION_ELEMENT)); tempSuiteElement.addContent(new Element(TEARDOWN_COLLECTION_ELEMENT)); if (getForkInExecution() != null) { tempSuiteElement.setAttribute(FORK_NAME_ATTRIBUTE, getForkInExecution().getName()); if (getForkInExecution().getDescription() != null) { tempSuiteElement.setAttribute(FORK_DESCRIPTION_ATTRIBUTE, getForkInExecution().getDescription()); } } // The XML output has a notion of a "suite title", which is a freely choosable title given to a suite. This // title is derived from the first title comment in a suite. But since those comments can be used for multiple // purposes, a special logic is applied to determine whether the first title in a suite shall be treated as // suite title. If one of the following circumstances is met, this is the case: // 1. The first statement in a suite is a title comment, and it is the only title comment in the suite. // 2. The first statement is a title comment, directly followed by another title comment. // In both of these cases, the first comment is assumed to be a suite title. In all other cases the logic // assumes that the title comments are just simple titles used to structure a suite internally. boolean tempFirstStatementWasTitleComment = false; boolean tempSecondStatementWasTitleComment = false; int tempTitleCommentCount = 0; int tempStatementCount = 0; for (SuiteStatement tempStatement : aSuite.getDefinition().getStatements()) { tempStatementCount++; if ((tempStatement instanceof VisibleSingleLineTitleComment) || (tempStatement instanceof VisibleMultiLineTitleComment)) { tempTitleCommentCount++; if (tempStatementCount == 1) { tempFirstStatementWasTitleComment = true; } else if (tempStatementCount == 2) { tempSecondStatementWasTitleComment = true; } } } if ((tempFirstStatementWasTitleComment && tempTitleCommentCount == 1) || (tempFirstStatementWasTitleComment && tempSecondStatementWasTitleComment)) { nextTitleCommentIsSuiteTitle = true; } if (!isDryRun()) { if (isFork()) { sendElementsToMaster(TestRunnerCallbackMethods.SUITE_START, tempSuiteElement); } internalOnSuiteStart(tempSuiteElement); } } /** * Internal version of {@link #onSuiteStart(Suite)}. * * @param aSuiteElement * the a suite element */ protected void internalOnSuiteStart(Element aSuiteElement) { Element tempParentStatementElement = stackPeek().getChild(STATEMENT_COLLECTION_ELEMENT); if (tempParentStatementElement == null) { stackPeek().addContent(aSuiteElement); } else { tempParentStatementElement.addContent(aSuiteElement); } stackPush(aSuiteElement); } /** * On setup start. * * @param aSetupSuite * the a setup suite */ @Override public void onSetupStart(SuiteDefinition aSetupSuite) { Element tempSetupElement = new Element(SUITE_ELEMENT); addId(tempSetupElement); addLineNumber(tempSetupElement, aSetupSuite); tempSetupElement.setAttribute(SUITE_NAME_ATTRIBUTE, IntegrityDSLUtil.getQualifiedSuiteName(aSetupSuite)); addCurrentTime(tempSetupElement); tempSetupElement.addContent(new Element(SETUP_COLLECTION_ELEMENT)); tempSetupElement.addContent(new Element(VARIABLE_DEFINITION_COLLECTION_ELEMENT)); tempSetupElement.addContent(new Element(STATEMENT_COLLECTION_ELEMENT)); if (!isDryRun()) { if (isFork()) { sendElementsToMaster(TestRunnerCallbackMethods.SETUP_START, tempSetupElement); } internalOnSetupStart(tempSetupElement); } } /** * Internal version of {@link #onSetupStart(SuiteDefinition)}. * * @param aSetupElement * the a setup element */ protected void internalOnSetupStart(Element aSetupElement) { stackPeek().getChild(SETUP_COLLECTION_ELEMENT).addContent(aSetupElement); stackPush(aSetupElement); } /** * On setup finish. * * @param aSetupSuite * the a setup suite * @param aResult * the a result */ @Override public void onSetupFinish(SuiteDefinition aSetupSuite, SuiteResult aResult) { Element tempSuiteResultElement = new Element(RESULT_ELEMENT); addSuiteSummaryResultToElement(tempSuiteResultElement, aResult); if (!isDryRun()) { if (isFork()) { sendElementsToMaster(TestRunnerCallbackMethods.SETUP_FINISH, tempSuiteResultElement); } internalOnSetupFinish(tempSuiteResultElement); } } /** * Adds the given suite summary result totals to the given result element as attributes. * * @param anElement * the XML element * @param aResult * the result object */ protected void addSuiteSummaryResultToElement(Element anElement, SuiteSummaryResult aResult) { if (aResult != null) { anElement.setAttribute(EXECUTION_DURATION_ATTRIBUTE, nanoTimeToString(aResult.getExecutionTime())); anElement.setAttribute(SUCCESS_COUNT_ATTRIBUTE, Integer.toString(aResult.getTestSuccessCount())); anElement.setAttribute(FAILURE_COUNT_ATTRIBUTE, Integer.toString(aResult.getTestFailCount())); anElement.setAttribute(EXCEPTION_COUNT_ATTRIBUTE, Integer.toString(aResult.getExceptionCount())); anElement.setAttribute(TEST_EXCEPTION_COUNT_ATTRIBUTE, Integer.toString(aResult.getTestExceptionCount())); anElement.setAttribute(CALL_EXCEPTION_COUNT_ATTRIBUTE, Integer.toString(aResult.getCallExceptionCount())); } } /** * Internal version of {@link #onSetupFinish(SuiteDefinition, SuiteResult)}. * * @param aSuiteResultElement * the a suite result element */ protected void internalOnSetupFinish(Element aSuiteResultElement) { stackPop().addContent(aSuiteResultElement); } /** * On test start. * * @param aTest * the a test */ @Override public void onTestStart(Test aTest) { addConsoleOutput(null); // clear the console interceptor Element tempTestElement = new Element(TEST_ELEMENT); addId(tempTestElement); addLineNumber(tempTestElement, aTest); tempTestElement.setAttribute(TEST_NAME_ELEMENT, aTest.getDefinition().getName()); try { tempTestElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, testFormatter.testToHumanReadableString(aTest, null)); } catch (ClassNotFoundException exc) { tempTestElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, exc.getMessage()); exc.printStackTrace(); } catch (UnexecutableException exc) { tempTestElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, exc.getMessage()); exc.printStackTrace(); } catch (InstantiationException exc) { tempTestElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, exc.getMessage()); exc.printStackTrace(); } catch (MethodNotFoundException exc) { tempTestElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, exc.getMessage()); exc.printStackTrace(); } tempTestElement.setAttribute(FIXTURE_METHOD_ATTRIBUTE, IntegrityDSLUtil.getQualifiedNameOfFixtureMethod(aTest.getDefinition().getFixtureMethod())); addCurrentTime(tempTestElement); if (!isDryRun()) { if (isFork()) { sendElementsToMaster(TestRunnerCallbackMethods.TEST_START, tempTestElement); } internalOnTestStart(tempTestElement); } } /** * Internal version of {@link #onTestStart(Test)}. * * @param aTestElement * the a test element */ protected void internalOnTestStart(Element aTestElement) { Element tempCollectionElement = stackPeek().getChild(STATEMENT_COLLECTION_ELEMENT); tempCollectionElement.addContent(aTestElement); stackPush(aTestElement); } /** * On table test start. * * @param aTest * the a test */ @Override public void onTableTestStart(TableTest aTest) { addConsoleOutput(null); // clear the console interceptor Element tempTestElement = new Element(TABLETEST_ELEMENT); addId(tempTestElement); addLineNumber(tempTestElement, aTest); tempTestElement.setAttribute(TEST_NAME_ELEMENT, aTest.getDefinition().getName()); try { tempTestElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, testFormatter.tableTestToHumanReadableString(aTest, new ConversionContext().withUnresolvableVariableHandlingPolicy( UnresolvableVariableHandling.RESOLVE_TO_UNRESOLVABLE_OBJECT))); } catch (ClassNotFoundException exc) { tempTestElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, exc.getMessage()); exc.printStackTrace(); } catch (UnexecutableException exc) { tempTestElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, exc.getMessage()); exc.printStackTrace(); } catch (InstantiationException exc) { tempTestElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, exc.getMessage()); exc.printStackTrace(); } catch (MethodNotFoundException exc) { tempTestElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, exc.getMessage()); exc.printStackTrace(); } tempTestElement.setAttribute(FIXTURE_METHOD_ATTRIBUTE, IntegrityDSLUtil.getQualifiedNameOfFixtureMethod(aTest.getDefinition().getFixtureMethod())); addCurrentTime(tempTestElement); try { Map<String, Object> tempParameterMap = parameterResolver.createParameterMap(aTest, null, TableTestParameterResolveMethod.ONLY_COMMON, true, UnresolvableVariableHandling.RESOLVE_TO_NULL_VALUE); Element tempParameterCollectionElement = new Element(PARAMETER_COLLECTION_ELEMENT); for (Entry<String, Object> tempEntry : tempParameterMap.entrySet()) { Element tempParameterElement = new Element(PARAMETER_ELEMENT); tempParameterElement.setAttribute(PARAMETER_NAME_ATTRIBUTE, tempEntry.getKey()); tempParameterElement.setAttribute(PARAMETER_VALUE_ATTRIBUTE, valueConverter .convertValueToFormattedString(tempEntry.getValue(), false, null).toFormattedString()); tempParameterCollectionElement.addContent(tempParameterElement); } tempTestElement.addContent(tempParameterCollectionElement); } catch (InstantiationException exc) { exc.printStackTrace(); } catch (ClassNotFoundException exc) { exc.printStackTrace(); } catch (UnexecutableException exc) { exc.printStackTrace(); } if (!isDryRun()) { if (isFork()) { sendElementsToMaster(TestRunnerCallbackMethods.TABLE_TEST_START, tempTestElement); } internalOnTableTestStart(tempTestElement); } } /** * Internal version of {@link #onTableTestStart(TableTest)}. * * @param aTestElement * the a test element */ protected void internalOnTableTestStart(Element aTestElement) { Element tempCollectionElement = stackPeek().getChild(STATEMENT_COLLECTION_ELEMENT); tempCollectionElement.addContent(aTestElement); stackPush(aTestElement); Element tempResultCollectionElement = new Element(RESULT_COLLECTION_ELEMENT); stackPush(tempResultCollectionElement); } /** * On test finish. * * @param aTest * the a test * @param aResult * the a result */ @Override public void onTestFinish(Test aTest, TestResult aResult) { Element tempResultCollectionElement = new Element(RESULT_COLLECTION_ELEMENT); if (aResult.getExecutionTime() != null) { tempResultCollectionElement.setAttribute(EXECUTION_DURATION_ATTRIBUTE, nanoTimeToString(aResult.getExecutionTime())); } tempResultCollectionElement.setAttribute(SUCCESS_COUNT_ATTRIBUTE, Integer.toString(aResult.getSubTestSuccessCount())); tempResultCollectionElement.setAttribute(FAILURE_COUNT_ATTRIBUTE, Integer.toString(aResult.getSubTestFailCount())); tempResultCollectionElement.setAttribute(EXCEPTION_COUNT_ATTRIBUTE, Integer.toString(aResult.getSubTestExceptionCount())); Map<String, Object> tempParameterMap = new HashMap<String, Object>(); try { tempParameterMap = parameterResolver.createParameterMap(aTest, true, UnresolvableVariableHandling.RESOLVE_TO_NULL_VALUE); } catch (InstantiationException exc) { exc.printStackTrace(); } catch (ClassNotFoundException exc) { exc.printStackTrace(); } catch (UnexecutableException exc) { exc.printStackTrace(); } onAnyKindOfSubTestFinish(aTest.getDefinition().getFixtureMethod(), aTest, tempResultCollectionElement, aResult.getSubResults().get(0), tempParameterMap, tempParameterMap); if (!isDryRun()) { Element tempExtendedResultElement = createExtendedResultElement(aResult.getExtendedResults()); if (isFork()) { addConsoleOutput(tempResultCollectionElement); sendElementsToMaster(TestRunnerCallbackMethods.TEST_FINISH, tempResultCollectionElement, tempExtendedResultElement); } internalOnTestFinish(tempResultCollectionElement, tempExtendedResultElement); } } /** * Internal version of {@link #onTestFinish(Test, TestResult)}. * * @param aResultCollectionElement * the a result collection element */ protected void internalOnTestFinish(Element aResultCollectionElement, Element anExtendedResultElement) { addConsoleOutput(aResultCollectionElement); Element tempTestElement = stackPop(); tempTestElement.addContent(aResultCollectionElement); if (anExtendedResultElement != null) { tempTestElement.addContent(anExtendedResultElement); } } /** * Creates an extended result element. * * @param someExtendedResults * the results to add */ protected Element createExtendedResultElement(List<ExtendedResult> someExtendedResults) { if (someExtendedResults != null && someExtendedResults.size() > 0) { Element tempExtendedResultCollection = new Element(EXTENDED_RESULT_COLLECTION_ELEMENT); for (ExtendedResult tempExtendedResult : someExtendedResults) { Element tempResultElement = null; if (tempExtendedResult instanceof ExtendedResultText) { tempResultElement = new Element(EXTENDED_RESULT_TEXT_ELEMENT); tempResultElement.addContent(new CDATA(((ExtendedResultText) tempExtendedResult).getText())); } else if (tempExtendedResult instanceof ExtendedResultImage) { tempResultElement = new Element(EXTENDED_RESULT_IMAGE_ELEMENT); tempResultElement.addContent( new Text(Base64.encode(((ExtendedResultImage) tempExtendedResult).getEncodedImage()))); tempResultElement.setAttribute(EXTENDED_RESULT_IMAGE_ELEMENT_TYPE_ATTRIBUTE, ((ExtendedResultImage) tempExtendedResult).getType().getMimeType()); tempResultElement.setAttribute(EXTENDED_RESULT_IMAGE_ELEMENT_WIDTH_ATTRIBUTE, Integer.toString(((ExtendedResultImage) tempExtendedResult).getWidth())); tempResultElement.setAttribute(EXTENDED_RESULT_IMAGE_ELEMENT_HEIGHT_ATTRIBUTE, Integer.toString(((ExtendedResultImage) tempExtendedResult).getHeight())); } else if (tempExtendedResult instanceof ExtendedResultHTML) { tempResultElement = new Element(EXTENDED_RESULT_HTML_ELEMENT); tempResultElement.addContent(new CDATA(((ExtendedResultHTML) tempExtendedResult).getHypertext())); } if (tempResultElement != null) { if (tempExtendedResult.getTitle() != null) { tempResultElement.setAttribute(EXTENDED_RESULT_ELEMENT_TITLE_ATTRIBUTE, tempExtendedResult.getTitle()); } tempExtendedResultCollection.addContent(tempResultElement); } } return tempExtendedResultCollection; } return null; } /** * On table test row start. * * @param aTableTest * the a table test * @param aRow * the a row */ @Override public void onTableTestRowStart(TableTest aTableTest, TableTestRow aRow) { // nothing to do here } /** * On table test row finish. * * @param aTableTest * the a table test * @param aRow * the a row * @param aSubResult * the a sub result */ @Override public void onTableTestRowFinish(TableTest aTableTest, TableTestRow aRow, TestSubResult aSubResult) { if (!isDryRun()) { Map<String, Object> tempParameterMapForText = new HashMap<String, Object>(); Map<String, Object> tempParameterMap = new HashMap<String, Object>(); try { // The parameters to be displayed in the result table shall only include the line-individual ones tempParameterMap = parameterResolver.createParameterMap(aTableTest, aRow, TableTestParameterResolveMethod.ONLY_INDIVIDUAL, true, UnresolvableVariableHandling.RESOLVE_TO_NULL_VALUE); // For the textual test result description, we need all values, including the common ones tempParameterMapForText = parameterResolver.createParameterMap(aTableTest, aRow, TableTestParameterResolveMethod.COMBINED, true, UnresolvableVariableHandling.RESOLVE_TO_NULL_VALUE); } catch (InstantiationException exc) { exc.printStackTrace(); } catch (ClassNotFoundException exc) { exc.printStackTrace(); } catch (UnexecutableException exc) { exc.printStackTrace(); } onAnyKindOfSubTestFinish(aTableTest.getDefinition().getFixtureMethod(), aTableTest, stackPeek(), aSubResult, tempParameterMapForText, tempParameterMap); } } /** * On table test finish. * * @param aTableTest * the a table test * @param aResult * the a result */ @Override public void onTableTestFinish(TableTest aTableTest, TestResult aResult) { Element tempResultCollectionElement = stackPeek(); if (aResult.getExecutionTime() != null) { tempResultCollectionElement.setAttribute(EXECUTION_DURATION_ATTRIBUTE, nanoTimeToString(aResult.getExecutionTime())); } tempResultCollectionElement.setAttribute(SUCCESS_COUNT_ATTRIBUTE, Integer.toString(aResult.getSubTestSuccessCount())); tempResultCollectionElement.setAttribute(FAILURE_COUNT_ATTRIBUTE, Integer.toString(aResult.getSubTestFailCount())); tempResultCollectionElement.setAttribute(EXCEPTION_COUNT_ATTRIBUTE, Integer.toString(aResult.getSubTestExceptionCount())); if (!isDryRun()) { Element tempExtendedResultElement = createExtendedResultElement(aResult.getExtendedResults()); if (isFork()) { addConsoleOutput(tempResultCollectionElement); sendElementsToMaster(TestRunnerCallbackMethods.TABLE_TEST_FINISH, tempResultCollectionElement, tempExtendedResultElement); } internalOnTableTestFinish(tempResultCollectionElement, tempExtendedResultElement); } } /** * Internal version of {@link #onTableTestFinish(TableTest, TestResult)}. * * @param aResultCollectionElement * the a result collection element * @param anExtendedResultElement * the extended result element */ protected void internalOnTableTestFinish(Element aResultCollectionElement, Element anExtendedResultElement) { stackPop(); // remove result collection element from stack first addConsoleOutput(aResultCollectionElement); Element tempTestElement = stackPop(); if (anExtendedResultElement != null) { tempTestElement.addContent(anExtendedResultElement); } tempTestElement.addContent(aResultCollectionElement); } /** * Used to write sub-test results. * * @param aMethod * the method executed * @param aStatement * the statement currently being executed * @param aResultCollectionElement * the result element * @param aSubResult * the sub-result to write * @param aParameterMapForText * the parameters to be used for text string generation * @param aParameterMap * the parameters */ protected void onAnyKindOfSubTestFinish(MethodReference aMethod, SuiteStatementWithResult aStatement, Element aResultCollectionElement, TestSubResult aSubResult, Map<String, Object> aParameterMapForText, Map<String, Object> aParameterMap) { Element tempTestResultElement = new Element(RESULT_ELEMENT); if (aSubResult.getExecutionTime() != null) { tempTestResultElement.setAttribute(EXECUTION_DURATION_ATTRIBUTE, nanoTimeToString(aSubResult.getExecutionTime())); } Element tempParameterCollectionElement = new Element(PARAMETER_COLLECTION_ELEMENT); for (Entry<String, Object> tempEntry : aParameterMap.entrySet()) { Element tempParameterElement = new Element(PARAMETER_ELEMENT); tempParameterElement.setAttribute(PARAMETER_NAME_ATTRIBUTE, tempEntry.getKey()); tempParameterElement.setAttribute(PARAMETER_VALUE_ATTRIBUTE, valueConverter .convertValueToFormattedString(tempEntry.getValue(), false, null).toFormattedString()); tempParameterCollectionElement.addContent(tempParameterElement); } tempTestResultElement.addContent(tempParameterCollectionElement); try { tempTestResultElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, testFormatter.fixtureMethodToHumanReadableString(aMethod, aStatement, aParameterMapForText, null)); } catch (ClassNotFoundException exc) { tempTestResultElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, exc.getMessage()); exc.printStackTrace(); } catch (MethodNotFoundException exc) { tempTestResultElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, exc.getMessage()); exc.printStackTrace(); } if (aSubResult instanceof TestExceptionSubResult) { tempTestResultElement.setAttribute(RESULT_TYPE_ATTRIBUTE, RESULT_TYPE_EXCEPTION); String tempMessage = ((TestExceptionSubResult) aSubResult).getException().getMessage(); if (tempMessage != null) { tempTestResultElement.setAttribute(RESULT_EXCEPTION_MESSAGE_ATTRIBUTE, tempMessage); } tempTestResultElement.setAttribute(RESULT_EXCEPTION_TRACE_ATTRIBUTE, stackTraceToString(((TestExceptionSubResult) aSubResult).getException())); } else if (aSubResult instanceof TestExecutedSubResult) { if (aSubResult.wereAllComparisonsSuccessful()) { tempTestResultElement.setAttribute(RESULT_TYPE_ATTRIBUTE, RESULT_TYPE_SUCCESS); } else { tempTestResultElement.setAttribute(RESULT_TYPE_ATTRIBUTE, RESULT_TYPE_FAILURE); } Element tempComparisonCollectionElement = new Element(COMPARISON_COLLECTION_ELEMENT); for (Entry<String, TestComparisonResult> tempEntry : aSubResult.getComparisonResults().entrySet()) { Element tempComparisonResultElement = new Element(COMPARISON_ELEMENT); if (tempEntry.getKey().length() > 0) { tempComparisonResultElement.setAttribute(COMPARISON_NAME_ATTRIBUTE, tempEntry.getKey()); } // Either there is an expected value, or if there isn't, "true" is the default ValueOrEnumValueOrOperationCollection tempExpectedValue = tempEntry.getValue().getExpectedValue(); boolean tempExpectedIsNestedObject = containsNestedObject(tempExpectedValue); tempComparisonResultElement .setAttribute(RESULT_EXPECTED_VALUE_ATTRIBUTE, valueConverter .convertValueToFormattedString( (tempExpectedValue == null ? true : tempExpectedValue), false, new ConversionContext() .withComparisonResult(tempEntry.getValue().getResult())) .toFormattedString()); if (tempEntry.getValue().getActualValue() != null) { tempComparisonResultElement.setAttribute(RESULT_REAL_VALUE_ATTRIBUTE, convertResultValueToFormattedStringGuarded(tempEntry.getValue().getActualValue(), aSubResult, tempExpectedIsNestedObject, new ConversionContext().withComparisonResult(tempEntry.getValue().getResult())) .toFormattedString()); } if (tempEntry.getValue() instanceof TestComparisonSuccessResult) { tempComparisonResultElement.setAttribute(RESULT_TYPE_ATTRIBUTE, RESULT_TYPE_SUCCESS); } else if (tempEntry.getValue() instanceof TestComparisonFailureResult) { tempComparisonResultElement.setAttribute(RESULT_TYPE_ATTRIBUTE, RESULT_TYPE_FAILURE); } tempComparisonCollectionElement.addContent(tempComparisonResultElement); } tempTestResultElement.addContent(tempComparisonCollectionElement); } aResultCollectionElement.addContent(tempTestResultElement); } /** * On call start. * * @param aCall * the a call */ @Override public void onCallStart(Call aCall) { addConsoleOutput(null); // clear the console interceptor Element tempCallElement = new Element(CALL_ELEMENT); addId(tempCallElement); addLineNumber(tempCallElement, aCall); tempCallElement.setAttribute(CALL_NAME_ELEMENT, aCall.getDefinition().getName()); try { tempCallElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, testFormatter.callToHumanReadableString(aCall, null)); } catch (ClassNotFoundException exc) { tempCallElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, exc.getMessage()); exc.printStackTrace(); } catch (UnexecutableException exc) { tempCallElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, exc.getMessage()); exc.printStackTrace(); } catch (InstantiationException exc) { tempCallElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, exc.getMessage()); exc.printStackTrace(); } catch (MethodNotFoundException exc) { tempCallElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, exc.getMessage()); exc.printStackTrace(); } tempCallElement.setAttribute(FIXTURE_METHOD_ATTRIBUTE, IntegrityDSLUtil.getQualifiedNameOfFixtureMethod(aCall.getDefinition().getFixtureMethod())); addCurrentTime(tempCallElement); Map<String, Object> tempParameterMap = null; try { tempParameterMap = parameterResolver.createParameterMap(aCall, true, UnresolvableVariableHandling.RESOLVE_TO_NULL_VALUE); } catch (ClassNotFoundException exc) { exc.printStackTrace(); } catch (UnexecutableException exc) { exc.printStackTrace(); } catch (InstantiationException exc) { exc.printStackTrace(); } Element tempParameterCollectionElement = new Element(PARAMETER_COLLECTION_ELEMENT); if (tempParameterMap != null) { for (Entry<String, Object> tempParameter : tempParameterMap.entrySet()) { Element tempParameterElement = new Element(PARAMETER_ELEMENT); tempParameterElement.setAttribute(PARAMETER_NAME_ATTRIBUTE, tempParameter.getKey()); tempParameterElement.setAttribute(PARAMETER_VALUE_ATTRIBUTE, valueConverter .convertValueToFormattedString(tempParameter.getValue(), false, null).toFormattedString()); tempParameterCollectionElement.addContent(tempParameterElement); } } tempCallElement.addContent(tempParameterCollectionElement); if (!isDryRun()) { if (isFork()) { sendElementsToMaster(TestRunnerCallbackMethods.CALL_START, tempCallElement); } internalOnCallStart(tempCallElement); } } /** * Internal version of {@link #onCallStart(Call)}. * * @param aCallElement * the a call element */ protected void internalOnCallStart(Element aCallElement) { Element tempCollectionElement = stackPeek().getChild(STATEMENT_COLLECTION_ELEMENT); tempCollectionElement.addContent(aCallElement); stackPush(aCallElement); } /** * On call finish. * * @param aCall * the a call * @param aResult * the a result */ @Override public void onCallFinish(Call aCall, CallResult aResult) { Element tempCallResultElement = null; if (aResult != null) { tempCallResultElement = new Element(RESULT_ELEMENT); if (aResult.getExecutionTime() != null) { tempCallResultElement.setAttribute(EXECUTION_DURATION_ATTRIBUTE, nanoTimeToString(aResult.getExecutionTime())); } if (aResult instanceof de.gebit.integrity.runner.results.call.SuccessResult) { tempCallResultElement.setAttribute(RESULT_TYPE_ATTRIBUTE, RESULT_TYPE_SUCCESS); de.gebit.integrity.runner.results.call.SuccessResult tempResult = (de.gebit.integrity.runner.results.call.SuccessResult) aResult; for (UpdatedVariable tempUpdatedVariable : tempResult.getUpdatedVariables()) { Element tempVariableUpdateElement = new Element(VARIABLE_UPDATE_ELEMENT); tempVariableUpdateElement.setAttribute(VARIABLE_NAME_ATTRIBUTE, tempUpdatedVariable.getTargetVariable().getName()); if (tempUpdatedVariable.getParameterName() != null) { tempVariableUpdateElement.setAttribute(VARIABLE_UPDATE_PARAMETER_NAME_ATTRIBUTE, tempUpdatedVariable.getParameterName()); } tempVariableUpdateElement.setAttribute(VARIABLE_VALUE_ATTRIBUTE, convertResultValueToFormattedStringGuarded(tempUpdatedVariable.getValue(), aResult, false, null).toFormattedString()); tempCallResultElement.addContent(tempVariableUpdateElement); } } else if (aResult instanceof de.gebit.integrity.runner.results.call.ExceptionResult) { tempCallResultElement.setAttribute(RESULT_TYPE_ATTRIBUTE, RESULT_TYPE_EXCEPTION); String tempExceptionMessage = ((de.gebit.integrity.runner.results.call.ExceptionResult) aResult) .getException().getMessage(); tempCallResultElement.setAttribute(RESULT_EXCEPTION_MESSAGE_ATTRIBUTE, tempExceptionMessage != null ? tempExceptionMessage : "null"); tempCallResultElement.setAttribute(RESULT_EXCEPTION_TRACE_ATTRIBUTE, stackTraceToString( ((de.gebit.integrity.runner.results.call.ExceptionResult) aResult).getException())); } } if (!isDryRun()) { Element tempExtendedResultElement = createExtendedResultElement(aResult.getExtendedResults()); if (isFork()) { addConsoleOutput(tempCallResultElement); sendElementsToMaster(TestRunnerCallbackMethods.CALL_FINISH, tempCallResultElement, tempExtendedResultElement); } internalOnCallFinish(tempCallResultElement, tempExtendedResultElement); } } /** * Internal version of {@link #onCallFinish(Call, CallResult)}. * * @param aCallResultElement * the a call result element * @param anExtendedResultElement * the extended result element */ protected void internalOnCallFinish(Element aCallResultElement, Element anExtendedResultElement) { if (aCallResultElement != null) { addConsoleOutput(aCallResultElement); stackPeek().addContent(aCallResultElement); } if (anExtendedResultElement != null) { stackPeek().addContent(anExtendedResultElement); } stackPop(); } /** * On tear down start. * * @param aTearDownSuite * the a tear down suite */ @Override public void onTearDownStart(SuiteDefinition aTearDownSuite) { Element tempTearDownElement = new Element(SUITE_ELEMENT); addId(tempTearDownElement); addLineNumber(tempTearDownElement, aTearDownSuite); tempTearDownElement.setAttribute(SUITE_NAME_ATTRIBUTE, IntegrityDSLUtil.getQualifiedSuiteName(aTearDownSuite)); addCurrentTime(tempTearDownElement); tempTearDownElement.addContent(new Element(VARIABLE_DEFINITION_COLLECTION_ELEMENT)); tempTearDownElement.addContent(new Element(STATEMENT_COLLECTION_ELEMENT)); if (!isDryRun()) { if (isFork()) { sendElementsToMaster(TestRunnerCallbackMethods.TEAR_DOWN_START, tempTearDownElement); } internalOnTearDownStart(tempTearDownElement); } } /** * Internal version of {@link #onTearDownStart(SuiteDefinition)}. * * @param aTearDownElement * the a tear down element */ protected void internalOnTearDownStart(Element aTearDownElement) { stackPeek().getChild(TEARDOWN_COLLECTION_ELEMENT).addContent(aTearDownElement); stackPush(aTearDownElement); } /** * On tear down finish. * * @param aTearDownSuite * the a tear down suite * @param aResult * the a result */ @Override public void onTearDownFinish(SuiteDefinition aTearDownSuite, SuiteResult aResult) { Element tempSuiteResultElement = new Element(RESULT_ELEMENT); addSuiteSummaryResultToElement(tempSuiteResultElement, aResult); if (!isDryRun()) { if (isFork()) { sendElementsToMaster(TestRunnerCallbackMethods.TEAR_DOWN_FINISH, tempSuiteResultElement); } internalOnTearDownFinish(tempSuiteResultElement); } } /** * Internal version of {@link #onTearDownFinish(SuiteDefinition, SuiteResult)}. * * @param aSuiteResultElement * the a suite result element */ protected void internalOnTearDownFinish(Element aSuiteResultElement) { stackPop().addContent(aSuiteResultElement); } /** * On suite finish. * * @param aSuite * the a suite * @param aResult * the a result */ @Override public void onSuiteFinish(Suite aSuite, SuiteSummaryResult aResult) { Element tempSuiteResultElement = new Element(RESULT_ELEMENT); addSuiteSummaryResultToElement(tempSuiteResultElement, aResult); if (!isDryRun()) { if (isFork()) { sendElementsToMaster(TestRunnerCallbackMethods.SUITE_FINISH, tempSuiteResultElement); } internalOnSuiteFinish(tempSuiteResultElement); } } /** * Internal version of {@link #onSuiteFinish(Suite, SuiteResult)}. * * @param aSuiteResultElement * the a suite result element */ protected void internalOnSuiteFinish(Element aSuiteResultElement) { stackPop().addContent(aSuiteResultElement); } /** * On execution finish. * * @param aModel * the a model * @param aResult * the a result */ @Override public void onExecutionFinish(TestModel aModel, SuiteSummaryResult aResult) { if (captureConsoleOutput) { consoleInterceptor.stopIntercept(); } Element tempElement = stackPop(); tempElement.setAttribute(TEST_RUN_DURATION, nanoTimeToString(System.nanoTime() - executionStartTime)); if (abortMessage != null) { tempElement.setAttribute(TEST_RUN_ABORT_MESSAGE_ATTRIBUTE, abortMessage); } if (!isFork()) { long tempStart = System.nanoTime(); stripTemporaryAttributes(document.getRootElement()); final FileOutputStream tempOutputStream; try { tempOutputStream = new FileOutputStream(outputFile); } catch (FileNotFoundException exc) { exc.printStackTrace(); return; } try { if (transformHandling == TransformHandling.EXECUTE_TRANSFORM) { System.out.print("Transforming Integrity Result XML to HTML..."); // Transform the XML to XHTML and output that (this actually contains a copy of the original XML // result tree in an invisible element!) Thread tempThread = new Thread(Thread.currentThread().getThreadGroup(), new Runnable() { @Override public void run() { transformResult(tempOutputStream); } }, "Integrity XSLT Transform Thread", determineTransformThreadStackSize()); tempThread.start(); while (tempThread.isAlive()) { try { tempThread.join(); } catch (InterruptedException exc) { // ignored } } } else { // Output the XML (with XSLT inlined or not) System.out.print("Writing Result XML..."); XMLOutputter tempSerializer = new XMLOutputter(Format.getPrettyFormat()); tempSerializer.output(document, tempOutputStream); } long tempTime = System.nanoTime() - tempStart; System.out.println("done in " + ((double) (tempTime / 1000000) / 1000.0) + " seconds!"); } catch (IOException exc) { exc.printStackTrace(); } finally { try { tempOutputStream.close(); } catch (IOException exc) { // ignore } } } } /** * Determines the stack size used for the XSLT transform thread. XSLT transformation can require large stack sizes, * since XSLT is a functional language and relies heavily on recursion. * * @return */ protected int determineTransformThreadStackSize() { String tempStackSize = System.getProperty(SYSPARAM_TRANSFORM_THREAD_STACK_SIZE); if (tempStackSize != null) { try { return Integer.parseInt(tempStackSize); } catch (NumberFormatException exc) { exc.printStackTrace(); } } return TRANSFORM_THREAD_STACK_SIZE_DEFAULT; } /** * Performs the XSLT transformation and writes the result HTML file into the provided target stream. * * @param aTargetStream */ protected void transformResult(FileOutputStream aTargetStream) { try { if (System.getProperty("javax.xml.transform.TransformerFactory") == null) { // Explicitly specify the JRE-bundled XSLT transformer if nothing else was specified via the // system property, so we at least know for sure what to expect System.setProperty("javax.xml.transform.TransformerFactory", "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl"); } TransformerFactory tempTransformerFactory = TransformerFactory.newInstance(); Transformer tempTransformer = tempTransformerFactory.newTransformer(new StreamSource(getXsltStream())); tempTransformer.setOutputProperty(OutputKeys.METHOD, "html"); tempTransformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); Source tempSource = new JDOMSource(document); /* * There is a problem with the XML source data that is copied to the transformed HTML result (into the * "xmldata" element): since the output method for the serializer is set to HTML, it seems to inevitably * output '<' and '>' in attributes as characters, which is no problem for HTML, but strict XML requires * those to be replaced by their corresponding entities. I could solve this by outputting strict XML, but * that renders the output unrenderable by browsers :-( well, for some reason I don't fully understand at * least, I'm no browser developer. To solve this, the following very ugly hack replaces the characters by * their entities in all attribute values inside the xmldata section on a character stream level. That makes * the xmldata content valid XML and thus conveniently parseable for example by a SAX parser (just be sure * to stop the parsing when reaching the end of that section, because the HTML afterwards definitely doesn't * parse as XML!), while keeping viewability on all browsers. It should not have any negative side-effects, * apart from being disgusting and everything, but well, this can still be replaced by a more elegant * solution if someone comes up with one. */ StreamResult tempResult = new StreamResult(new FilterOutputStream(aTargetStream) { private final char[] triggerOpenTagName = new char[] { 'x', 'm', 'l', 'd', 'a', 't', 'a' }; private final char[] triggerCloseTagName = new char[] { '/', 'x', 'm', 'l', 'd', 'a', 't', 'a' }; private static final char TRIGGER_TAG_START = '<'; private static final char TRIGGER_TAG_END = '<'; private static final char TRIGGER_ATTRIBUTE = '"'; private boolean insideXmlPart; private boolean insideAttribute; private boolean pastXmlPart; private int tagPosition; @Override public void write(int aByte) throws IOException { char tempChar = (char) aByte; if (!pastXmlPart) { if (!insideAttribute) { if (tempChar == TRIGGER_TAG_START) { tagPosition = 0; } else if (tempChar == TRIGGER_TAG_END) { tagPosition = -1; } else if (tagPosition >= 0) { if (insideXmlPart && tempChar == TRIGGER_ATTRIBUTE) { insideAttribute = true; } else { tagPosition++; if (insideXmlPart) { if (tagPosition < triggerCloseTagName.length - 1) { if (tempChar != triggerCloseTagName[tagPosition]) { tagPosition = 0; } } else if (tagPosition == triggerCloseTagName.length - 1) { insideXmlPart = false; pastXmlPart = true; tagPosition = 0; } } else { if (tagPosition < triggerOpenTagName.length - 1) { if (tempChar != triggerOpenTagName[tagPosition]) { tagPosition = 0; } } else if (tagPosition == triggerOpenTagName.length - 1) { insideXmlPart = true; pastXmlPart = false; tagPosition = 0; } } } } } else { if (insideXmlPart) { if (tempChar == TRIGGER_ATTRIBUTE) { insideAttribute = false; } else { if (tempChar == '<') { super.write("<".getBytes("UTF-8")); return; } else if (tempChar == '>') { super.write(">".getBytes("UTF-8")); return; } } } } } super.write(aByte); } @Override public void write(byte[] someBytes, int anOffset, int aLength) throws IOException { if (!pastXmlPart) { super.write(someBytes, anOffset, aLength); } else { out.write(someBytes, anOffset, aLength); } } }); tempTransformer.transform(tempSource, tempResult); } catch (TransformerConfigurationException exc) { exc.printStackTrace(); } catch (TransformerException exc) { exc.printStackTrace(); } } /** * On variable definition. * * @param aDefinition * the a definition * @param aSuite * the a suite * @param anInitialValue * the an initial value */ @Override public void onVariableDefinition(VariableEntity aDefinition, SuiteDefinition aSuite, Object anInitialValue) { onVariableDefinitionInternal(aDefinition, aSuite, anInitialValue); } /** * On constant definition. * * @param aDefinition * the a definition * @param aSuite * the a suite * @param aValue * the a value * @param aParameterizedFlag * the a parameterized flag */ @Override public void onConstantDefinition(ConstantEntity aDefinition, SuiteDefinition aSuite, Object aValue, boolean aParameterizedFlag) { // constants are handled like variables (for now...) onVariableDefinitionInternal(aDefinition, aSuite, aValue); } @Override public void onVariableAssignment(VariableAssignment anAssignment, VariableEntity aDefinition, SuiteDefinition aSuite, Object aValue) { Element tempVariableAssignmentElement = new Element(VARIABLE_ASSIGNMENT_ELEMENT); addId(tempVariableAssignmentElement); addLineNumber(tempVariableAssignmentElement, anAssignment); addCurrentTime(tempVariableAssignmentElement); tempVariableAssignmentElement.setAttribute(FIXTURE_DESCRIPTION_ATTRIBUTE, testFormatter.variableAssignmentToHumanReadableString(anAssignment, null)); tempVariableAssignmentElement.setAttribute(VARIABLE_NAME_ATTRIBUTE, IntegrityDSLUtil.getQualifiedVariableEntityName(aDefinition, false)); tempVariableAssignmentElement.setAttribute(VARIABLE_VALUE_ATTRIBUTE, valueConverter .convertValueToFormattedString(aValue, false, new ConversionContext().withUnresolvableVariableHandlingPolicy( UnresolvableVariableHandling.RESOLVE_TO_UNRESOLVABLE_OBJECT)) .toFormattedString()); if (!isDryRun()) { if (isFork()) { sendElementsToMaster(TestRunnerCallbackMethods.VARIABLE_ASSIGNMENT, tempVariableAssignmentElement); } internalOnVariableAssignment(tempVariableAssignmentElement); } } /** * Internal version of {@link #onVariableAssignment(VariableEntity, SuiteDefinition, Object)}. * * @param aVariableElement */ protected void internalOnVariableAssignment(Element aVariableAssignmentElement) { Element tempCollectionElement = stackPeek().getChild(STATEMENT_COLLECTION_ELEMENT); tempCollectionElement.addContent(aVariableAssignmentElement); } @Override public void onReturnVariableAssignment(SuiteReturn aReturn, VariableEntity aSource, VariableEntity aTarget, Suite aSuite, Object aValue) { Element tempVariableElement = new Element(VARIABLE_ELEMENT); tempVariableElement.setAttribute(VARIABLE_NAME_ATTRIBUTE, IntegrityDSLUtil.getQualifiedVariableEntityName(aSource, false)); tempVariableElement.setAttribute(VARIABLE_TARGET_ATTRIBUTE, IntegrityDSLUtil.getQualifiedVariableEntityName(aTarget, false)); tempVariableElement.setAttribute(VARIABLE_VALUE_ATTRIBUTE, valueConverter.convertValueToFormattedString(aValue, false, null).toFormattedString()); if (!isDryRun()) { if (isFork()) { sendElementsToMaster(TestRunnerCallbackMethods.RETURN_ASSIGNMENT, tempVariableElement); } internalOnReturnVariableAssignment(tempVariableElement); } } /** * Internal version of {@link #onReturnVariableAssignment(SuiteReturn, Suite, Object)}. * * @param aReturnAssignmentElement */ protected void internalOnReturnVariableAssignment(Element aReturnAssignmentElement) { Element tempCollectionElement = stackPeek().getChild(RETURN_VARIABLE_ASSIGNMENT_COLLECTION_ELEMENT); tempCollectionElement.addContent(aReturnAssignmentElement); } @Override public void onAbortExecution(String anAbortExecutionMessage, String anAbortExecutionStackTrace) { abortMessage = anAbortExecutionMessage; } /** * On variable definition internal. * * @param aDefinition * the a definition * @param aSuite * the a suite * @param anInitialValue * the an initial value */ private void onVariableDefinitionInternal(VariableOrConstantEntity aDefinition, SuiteDefinition aSuite, Object anInitialValue) { Element tempVariableElement = new Element(VARIABLE_ELEMENT); tempVariableElement.setAttribute(VARIABLE_NAME_ATTRIBUTE, IntegrityDSLUtil.getQualifiedVariableEntityName(aDefinition, false)); if (anInitialValue != null) { tempVariableElement.setAttribute(VARIABLE_VALUE_ATTRIBUTE, valueConverter.convertValueToFormattedString(anInitialValue, false, null).toFormattedString()); } if (!isDryRun()) { if (isFork()) { sendElementsToMaster(TestRunnerCallbackMethods.VARIABLE_DEFINITION, tempVariableElement); } internalOnVariableDefinition(tempVariableElement); } } /** * Internal version of {@link #onVariableDefinition(VariableEntity, SuiteDefinition, Object)}. * * @param aVariableElement * the a variable element */ protected void internalOnVariableDefinition(Element aVariableElement) { Element tempCollectionElement = stackPeek().getChild(VARIABLE_DEFINITION_COLLECTION_ELEMENT); tempCollectionElement.addContent(aVariableElement); } /** * The pattern for URL detection. */ protected static final Pattern URL_PATTERN = Pattern.compile("(.*?)((?:(?:\\w+://)|(?:\\./))\\S+)(.*)"); /** * The pattern for Markdown-style URL detection. */ protected static final Pattern MARKDOWN_URL_PATTERN = Pattern .compile("(.*?)\\[(.*?)\\]\\(((?:(?:\\w+://)|(?:\\./)).+?)\\)(.*)"); /** * Parses a comment into a list of {@link Content} elements. This takes care of URLs embedded in the comment. * * @param aCommment * the a commment * @return the list */ protected List<Content> parseComment(String aCommment) { List<Content> tempList = new ArrayList<Content>(); tempList.add(new Text(aCommment)); outer: while (true) { int i; for (i = 0; i < tempList.size(); i++) { Content tempElement = tempList.get(i); if (tempElement instanceof Text) { List<Content> tempInnerList = detectMarkdownURLs(((Text) tempElement).getText()); if (tempInnerList.size() > 1) { tempList.remove(i); tempList.addAll(i, tempInnerList); break; } tempInnerList = detectSimpleURLs(((Text) tempElement).getText()); if (tempInnerList.size() > 1 || (tempInnerList.size() == 1 && !(tempInnerList.get(0) instanceof Text))) { tempList.remove(i); tempList.addAll(i, tempInnerList); break; } } } if (i >= tempList.size()) { break outer; } } return tempList; } /** * Finds simple URLs in the given text and parses all into XML elements. * * @param aText * the text to parse * @return a list of XML elements, with the URLs converted to anchor tags */ protected List<Content> detectSimpleURLs(String aText) { List<Content> tempElementList = new ArrayList<Content>(); String tempTextLeft = aText; Matcher tempMatcher = URL_PATTERN.matcher(tempTextLeft); while (tempMatcher.matches()) { String tempPrefix = tempMatcher.group(1); String tempUrl = tempMatcher.group(2); String tempSuffix = tempMatcher.group(3); if (tempPrefix != null && tempPrefix.length() > 0) { tempElementList.add(new Text(tempPrefix)); } Element tempAnchorElement = new Element("a"); tempAnchorElement.setAttribute("href", tempUrl); tempAnchorElement.setText(tempUrl); tempElementList.add(tempAnchorElement); tempTextLeft = tempSuffix; tempMatcher = URL_PATTERN.matcher(tempTextLeft); } if (tempTextLeft != null && tempTextLeft.length() > 0) { tempElementList.add(new Text(tempTextLeft)); } return tempElementList; } /** * Finds Markdown-style URLs in the given text and parses all into XML elements. * * @param aText * the text to parse * @return a list of XML elements, with the URLs converted to anchor tags */ protected List<Content> detectMarkdownURLs(String aText) { List<Content> tempElementList = new ArrayList<Content>(); String tempTextLeft = aText; Matcher tempMatcher = MARKDOWN_URL_PATTERN.matcher(tempTextLeft); while (tempMatcher.matches()) { String tempPrefix = tempMatcher.group(1); String tempName = tempMatcher.group(2); String tempUrl = tempMatcher.group(3); String tempSuffix = tempMatcher.group(4); if (tempPrefix != null && tempPrefix.length() > 0) { tempElementList.add(new Text(tempPrefix)); } Element tempAnchorElement = new Element("a"); tempAnchorElement.setAttribute("href", tempUrl); tempAnchorElement.setText(tempName); tempElementList.add(tempAnchorElement); tempTextLeft = tempSuffix; tempMatcher = URL_PATTERN.matcher(tempTextLeft); } if (tempTextLeft != null && tempTextLeft.length() > 0) { tempElementList.add(new Text(tempTextLeft)); } return tempElementList; } /** * On visible comment. * * @param aCommentText * the a comment text * @param anIsTitle * the an is title * @param aCommentElement * the a comment element */ @Override public void onVisibleComment(String aCommentText, boolean anIsTitle, VisibleComment aCommentElement) { Element tempCommentElement = new Element(COMMENT_ELEMENT); addId(tempCommentElement); addLineNumber(tempCommentElement, aCommentElement); tempCommentElement.addContent(parseComment(aCommentText)); if (anIsTitle) { tempCommentElement.setAttribute(COMMENT_TYPE_ATTRIBUTE, nextTitleCommentIsSuiteTitle ? COMMENT_TYPE_SUITETITLE : COMMENT_TYPE_TITLE); nextTitleCommentIsSuiteTitle = false; } if (!isDryRun()) { if (isFork()) { sendElementsToMaster(TestRunnerCallbackMethods.VISIBLE_COMMENT, tempCommentElement); } internalOnVisibleComment(tempCommentElement); } } /** * On visible divider. * * @param aDividerText * the a divider text * @param aDividerElement * the a divider element */ @Override public void onVisibleDivider(String aDividerText, VisibleDivider aDividerElement) { Element tempCommentElement = new Element(DIVIDER_ELEMENT); addId(tempCommentElement); addLineNumber(tempCommentElement, aDividerElement); tempCommentElement.setAttribute(DIVIDER_TEXT_ATTRIBUTE, aDividerText); if (!isDryRun()) { if (isFork()) { sendElementsToMaster(TestRunnerCallbackMethods.VISIBLE_DIVIDER, tempCommentElement); } internalOnVisibleDivider(tempCommentElement); } } /** * Internal version of {@link #onVisibleComment(String)}. * * @param aCommentElement * the a comment element */ protected void internalOnVisibleComment(Element aCommentElement) { Element tempCollectionElement = stackPeek().getChild(STATEMENT_COLLECTION_ELEMENT); tempCollectionElement.addContent(aCommentElement); if (COMMENT_TYPE_SUITETITLE.equals(aCommentElement.getAttributeValue(COMMENT_TYPE_ATTRIBUTE))) { Element tempSuiteElement = stackFind(SUITE_ELEMENT); tempSuiteElement.setAttribute(SUITE_TITLE_ATTRIBUTE, new XMLOutputter().outputString(aCommentElement.getContent())); } } /** * Internal version of {@link #onVisibleDivider(String)}. * * @param aDividerElement * the divider element */ protected void internalOnVisibleDivider(Element aDividerElement) { Element tempCollectionElement = stackPeek().getChild(STATEMENT_COLLECTION_ELEMENT); tempCollectionElement.addContent(aDividerElement); } /** * Formats a stack trace into a single string with line-breaks. * * @param anException * the exception from which to get the stack trace * @return the stack trace as a string */ protected static String stackTraceToString(Throwable anException) { String tempResult = null; StringWriter tempStringWriter = null; PrintWriter tempPrintWriter = null; try { tempStringWriter = new StringWriter(); tempPrintWriter = new PrintWriter(tempStringWriter); anException.printStackTrace(tempPrintWriter); tempResult = tempStringWriter.toString(); } finally { try { if (tempPrintWriter != null) { tempPrintWriter.close(); } if (tempStringWriter != null) { tempStringWriter.close(); } } catch (IOException exc) { // nothing to do, since this cannot happen } } return tempResult; } /** * Converts a nanosecond time value into a string, according to {@link #EXECUTION_TIME_FORMAT}. * * @param aNanosecondValue * the time * @return the formatted string */ protected static String nanoTimeToString(long aNanosecondValue) { return EXECUTION_TIME_FORMAT.format(((double) aNanosecondValue) / 1000000.0); } /** * Sends an XML element to the master {@link XmlWriterTestCallback}. * * @param aMethod * the method from which this is called * @param someElements * the elements to send */ protected void sendElementsToMaster(TestRunnerCallbackMethods aMethod, Element... someElements) { // System.out.println("FORK OUT: " + aMethod); Serializable[] tempClones = null; if (someElements != null) { tempClones = new Serializable[someElements.length]; for (int i = 0; i < someElements.length; i++) { if (someElements[i] != null) { tempClones[i] = (Serializable) someElements[i].clone(); } } } sendToMaster(aMethod, tempClones); } /** * On message from fork. * * @param aMethod * the a method * @param someObjects * the some objects */ @Override public void onMessageFromFork(TestRunnerCallbackMethods aMethod, Serializable... someObjects) { Element tempFirstElement = (Element) someObjects[0]; // one element will always be provided // System.out.println("FORK IN: " + aMethod); // dispatch message to matching internal... method switch (aMethod) { case SUITE_START: internalOnSuiteStart(tempFirstElement); break; case SETUP_START: internalOnSetupStart(tempFirstElement); break; case SETUP_FINISH: internalOnSetupFinish(tempFirstElement); break; case TEST_START: internalOnTestStart(tempFirstElement); break; case TABLE_TEST_START: internalOnTableTestStart(tempFirstElement); break; case TEST_FINISH: internalOnTestFinish(tempFirstElement, (Element) someObjects[1]); break; case TABLE_TEST_FINISH: internalOnTableTestFinish(tempFirstElement, (Element) someObjects[1]); break; case CALL_START: internalOnCallStart(tempFirstElement); break; case CALL_FINISH: internalOnCallFinish(tempFirstElement, (Element) someObjects[1]); break; case TEAR_DOWN_START: internalOnTearDownStart(tempFirstElement); break; case TEAR_DOWN_FINISH: internalOnTearDownFinish(tempFirstElement); break; case SUITE_FINISH: internalOnSuiteFinish(tempFirstElement); break; case VARIABLE_DEFINITION: internalOnVariableDefinition(tempFirstElement); break; case VARIABLE_ASSIGNMENT: internalOnVariableAssignment(tempFirstElement); break; case RETURN_ASSIGNMENT: internalOnReturnVariableAssignment(tempFirstElement); break; case VISIBLE_COMMENT: internalOnVisibleComment(tempFirstElement); break; case VISIBLE_DIVIDER: internalOnVisibleDivider(tempFirstElement); break; default: return; } } /** * On callback processing start. */ @Override public void onCallbackProcessingStart() { if (captureConsoleOutput) { consoleInterceptor.pauseIntercept(); } } /** * On callback processing end. */ @Override public void onCallbackProcessingEnd() { if (captureConsoleOutput) { consoleInterceptor.resumeIntercept(); } } /** * Adds the console output. Can also be called without an element. In that case, it just retrieves the current * intercept job from the interceptor. Since that automatically starts a new interception job, it is used as a means * to reset the interception at the start of a call or test.<br> * <br> * This method is able to deal with elements which already contain console output. In such a case, the existing * elements' {@link #CONSOLE_TEMP_STARTTIME_ATTRIBUTE} and {@link #CONSOLE_TEMP_ENDTIME_ATTRIBUTE} as well as the * existing lines' {@link #CONSOLE_LINE_TEMP_TIME_ATTRIBUTE} attributes are used to merge newly added lines with the * existing lines. This mechanism is designed to allow merging of console data from forks with data collected on the * master. * * @param anElement * the element, or null if the interception result is not to be added to an element */ @SuppressWarnings("unchecked") void addConsoleOutput(Element anElement) { if (captureConsoleOutput) { Intercept tempIntercept = consoleInterceptor.retrieveIntercept(); if (anElement != null) { long tempLowerTimeBound; long tempUpperTimeBound; int tempLineCount; int tempTruncatedCount; boolean tempMerged; Element tempLineElements = anElement.getChild(CONSOLE_ELEMENT); if (tempLineElements == null) { tempLineElements = new Element(CONSOLE_ELEMENT); anElement.addContent(tempLineElements); tempMerged = false; tempLineCount = 0; tempTruncatedCount = 0; tempLowerTimeBound = tempIntercept.getStartTimestamp(); tempLineElements.setAttribute(CONSOLE_TEMP_STARTTIME_ATTRIBUTE, Long.toString(tempLowerTimeBound)); tempUpperTimeBound = tempIntercept.getEndTimestamp(); tempLineElements.setAttribute(CONSOLE_TEMP_ENDTIME_ATTRIBUTE, Long.toString(tempUpperTimeBound)); } else { tempMerged = true; tempLineCount = Integer.parseInt(tempLineElements.getAttributeValue(CONSOLE_LINECOUNT_ATTRIBUTE)); tempTruncatedCount = Integer .parseInt(tempLineElements.getAttributeValue(CONSOLE_TRUNCATED_ATTRIBUTE)); tempLowerTimeBound = Long .parseLong(tempLineElements.getAttributeValue(CONSOLE_TEMP_STARTTIME_ATTRIBUTE)); tempUpperTimeBound = Long .parseLong(tempLineElements.getAttributeValue(CONSOLE_TEMP_ENDTIME_ATTRIBUTE)); } int tempEarliestPossiblePosition = 0; for (int i = 0; i < tempIntercept.getLines().size(); i++) { InterceptedLine tempLine = tempIntercept.getLines().get(i); // First check if we do even need to add this line if (tempLowerTimeBound > tempLine.getTimestamp()) { // This line is older than our timeframe of interest and thus skipped continue; } if (tempUpperTimeBound < tempLine.getTimestamp()) { // This line is newer than our timeframe of interest. The following lines will be even newer, // so at this point we can abort processing break; } if (tempLineCount >= MAX_CONSOLE_LINES) { tempTruncatedCount++; continue; } // Okay, the line needs to be added Element tempLineElement = new Element( tempLine.isStdErr() ? CONSOLE_LINE_STDERR_ELEMENT : CONSOLE_LINE_STDOUT_ELEMENT); String tempText = tempLine.getText(); tempText.replace("\t", " "); if (tempText.length() > MAX_CONSOLE_LINE_SIZE) { tempText = tempText.substring(0, MAX_CONSOLE_LINE_SIZE) + "... (" + (tempText.length() - MAX_CONSOLE_LINE_SIZE) + " CHARS TRUNCATED)"; } // This time attribute does not actually go into the final result! It's just used to merge fork // and master lines on the master after receiving test/call results from a fork. tempLineElement.setAttribute(CONSOLE_LINE_TEMP_TIME_ATTRIBUTE, Long.toString(tempLine.getTimestamp())); if (isFork()) { // At the moment, there is only one valid value for the source attribute: "fork" denotes a line // that was created on a fork. If no source is given, the line came from the master. tempLineElement.setAttribute(CONSOLE_LINE_SOURCE_ATTRIBUTE, "fork"); } try { tempLineElement.setAttribute(CONSOLE_LINE_TEXT_ATTRIBUTE, tempText); } catch (IllegalDataException exc) { exc.printStackTrace(); tempLineElement.setAttribute(CONSOLE_LINE_TEXT_ATTRIBUTE, "LINE TRUNCATED: IllegalDataException"); } if (tempMerged) { // We need to find the right position to add this element in. The mechanism used here should be // a bit faster than any generic sorting, but it is based on the assumption that all existing // data is already well-ordered, and any new data is also well-ordered, so they just need to // be merged. while (tempEarliestPossiblePosition < tempLineElements.getChildren().size()) { Element tempChild = (Element) tempLineElements.getChildren() .get(tempEarliestPossiblePosition); if (Long.parseLong(tempChild.getAttributeValue(CONSOLE_LINE_TEMP_TIME_ATTRIBUTE)) > tempLine .getTimestamp()) { // The new line is to be added right before the current earliest possible position break; } else { tempEarliestPossiblePosition++; } } tempLineElements.getChildren().add(tempEarliestPossiblePosition, tempLineElement); tempEarliestPossiblePosition++; } else { // This is simple: just add it at the end! tempLineElements.addContent(tempLineElement); } tempLineCount++; } tempLineElements.setAttribute(CONSOLE_LINECOUNT_ATTRIBUTE, Integer.toString(tempLineCount)); tempLineElements.setAttribute(CONSOLE_TRUNCATED_ATTRIBUTE, Integer.toString(tempTruncatedCount)); if (!isFork() && tempLineCount == 0) { // If we're on the master, we can scrap console elements without any lines. Forks need to keep these // to be sent to the master, since the master might have some lines to be merged into these empty // elements. anElement.removeContent(tempLineElements); } } } } /** * Adds the ID attribute to the element and increments the ID counter for the next element. * * @param anElement * the element to add an ID to */ protected void addId(Element anElement) { anElement.setAttribute(ID_ATTRIBUTE, Long.toString(idCounter)); idCounter++; } /** * Adds the version number of the test runner bundle to the given element. * * @param anElement * the element to add the version to */ protected void addVersion(Element anElement) { String tempVersion = VersionUtil.getBundleVersionString(IntegrityRunnerModule.class); if (tempVersion != null) { anElement.setAttribute(VERSION_ATTRIBUTE, tempVersion); } } /** * Adds the line number to the element where the given {@link EObject} starts. * * @param anElement * the element to add the number * @param anObject * the object to find */ protected void addLineNumber(Element anElement, EObject anObject) { ICompositeNode tempNode = NodeModelUtils.getNode(anObject); if (tempNode != null) { int tempLine = tempNode.getStartLine(); anElement.setAttribute(LINE_NUMBER_ATTRIBUTE, Integer.toString(tempLine)); } } /** * Adds a timestamp based on the current time to the provided element. * * @param anElement * the element to add the timestamp to */ protected void addCurrentTime(Element anElement) { anElement.setAttribute(TEST_RUN_TIMESTAMP, TIMESTAMP_FORMAT.format(new Date())); } /** * Strips any temporary attributes (recognized by the prefix {@link #TEMPORARY_ATTRIBUTE_PREFIX}) from an element * hierarchy. * * @param anElement * the an element */ protected void stripTemporaryAttributes(Element anElement) { @SuppressWarnings("unchecked") Iterator<Attribute> tempAttributeIterator = anElement.getAttributes().iterator(); // Strip the temporary attributes from the current element... while (tempAttributeIterator.hasNext()) { if (tempAttributeIterator.next().getName().startsWith(TEMPORARY_ATTRIBUTE_PREFIX)) { tempAttributeIterator.remove(); } } // ...then recurse down the element tree for (Object tempContent : anElement.getContent()) { if (tempContent instanceof Element) { stripTemporaryAttributes((Element) tempContent); } } } /** * Pops an element from the stack. * * @return the element */ protected Element stackPop() { Element tempElement = currentElement.pop(); if (isTracingEnabled) { System.out.println("--> XMLWRITER STACK POP: " + getStringForElement(tempElement) + " FROM " + Thread.currentThread().getName()); } return tempElement; } /** * Pushes an element on the stack. * * @param anElement * the an element */ protected void stackPush(Element anElement) { if (isTracingEnabled) { System.out.println("--> XMLWRITER STACK PUSH: " + getStringForElement(anElement) + " FROM " + Thread.currentThread().getName()); } currentElement.push(anElement); } /** * Peeks onto the stack. * * @return the element */ protected Element stackPeek() { Element tempElement = currentElement.peek(); if (isTracingEnabled) { System.out.println("--> XMLWRITER STACK PEEK: " + getStringForElement(tempElement) + " FROM " + Thread.currentThread().getName()); } return tempElement; } /** * <<<<<<< HEAD ======= Converts the provided element into a string with the element name and all its attributes. * This is for debug purposes only, NOT for writing actual XML content! * * @param anElement * the element to stringify * @return the string representation of the element */ protected String getStringForElement(Element anElement) { if (anElement == null) { return "null"; } StringBuilder tempBuilder = new StringBuilder(); tempBuilder.append("<"); tempBuilder.append(anElement.getName()); tempBuilder.append(" "); @SuppressWarnings("unchecked") Iterator<Attribute> tempAttributeIterator = anElement.getAttributes().iterator(); while (tempAttributeIterator.hasNext()) { Attribute tempAttribute = tempAttributeIterator.next(); tempBuilder.append(" "); tempBuilder.append(tempAttribute.getName()); tempBuilder.append("=\""); tempBuilder.append(tempAttribute.getValue()); tempBuilder.append("\""); } tempBuilder.append(">"); return tempBuilder.toString(); } /** * >>>>>>> v0.15.x_bugfix Finds a given element in the stack. The first (topmost) element is returned. The stack is * not altered. * * @param anElementName * the name of the element to find * @return the element or null if none was found */ protected Element stackFind(String anElementName) { for (int i = currentElement.size() - 1; i >= 0; i--) { Element tempElement = currentElement.get(i); if (anElementName.equals(tempElement.getName())) { return tempElement; } } return null; } }