/*******************************************************************************
* Copyright (c) 2004, 2010 BREDEX GmbH.
* 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:
* BREDEX GmbH - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.jubula.client.core.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.eclipse.jubula.client.core.ClientTest;
import org.eclipse.jubula.client.core.businessprocess.ITestResultEventListener;
import org.eclipse.jubula.client.core.i18n.Messages;
import org.eclipse.jubula.client.core.propertytester.NodePropertyTester;
import org.eclipse.jubula.tools.internal.constants.StringConstants;
import org.eclipse.jubula.tools.internal.objects.event.TestErrorEvent;
import org.eclipse.osgi.util.NLS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* class for creation of resultNodes
* @author BREDEX GmbH
* @created 21.04.2005
*/
public class TestResultNode {
/**
* Status if not yet tested
*/
public static final int NOT_YET_TESTED = 0;
/**
* Status if test is successful
*/
public static final int SUCCESS = 1;
/**
* Status if test is not successful
*/
public static final int ERROR = 2;
/**
* Status if no verify must do.
*/
public static final int NO_VERIFY = 3;
/**
* Status if test is not tested
*/
public static final int NOT_TESTED = 4;
/**
* Error in child
*/
public static final int ERROR_IN_CHILD = 5;
/**
* Status if currently being tested
*/
public static final int TESTING = 6;
/**
* Status if not successful, but will be retried later
*/
public static final int RETRYING = 7;
/**
* Status if test is successful after 1 or more retries
*/
public static final int SUCCESS_RETRY = 8;
/**
* Status if test was aborted due to internal AutServer errors
*/
public static final int ABORT = 9;
/** Status if Condition failed */
public static final int CONDITION_FAILED = 10;
/** Status if an infinite loop was encountered */
public static final int INFINITE_LOOP = 11;
/**
* Status if the test was skipped
*/
public static final int SKIPPED = 20;
/**
* Status if the test is successful but contains only skipped test cases
*/
public static final int SUCCESS_ONLY_SKIPPED = 21;
/** the logger */
private static final Logger LOG =
LoggerFactory.getLogger(TestResultNode.class);
/** separator for Parameter values */
private static final String SEPARATOR = ", "; //$NON-NLS-1$
/** length of Parameter value separator string */
private static final int SEPARATOR_LEN = SEPARATOR.length();
/** */
private static final String NEGATED = "Negated"; //$NON-NLS-1$
/**
* index for Tree Tracker
*/
private int m_childIndex = -1;
/**
* The status
*/
private int m_status = 0;
/**
* Time of Teststep Execution
*/
private Date m_timestamp = null;
/**
* <code>m_screenshot</code> the screenshot in case of a test error event
*/
private byte[] m_screenshot = null;
/**
* errorEvent, indicated from server
*/
private TestErrorEvent m_event;
/**
* <code>m_node</code> associated node in testexecution tree
*/
private INodePO m_node;
/** the Component Name name for this result node */
private String m_componentName;
/** the Component Name type for this result node */
private String m_componentType;
/**
* wether the Node acts as a JUnit testsuite
* true = this testResultNode wll be used as a testsuite
* false = testResultNode is treated as the kind of node it is normally
*/
private boolean m_isJunitTestSuite;
/**
* <code>m_resultNodeList</code> childList
*/
private List < TestResultNode > m_resultNodeList =
new ArrayList < TestResultNode > ();
/**
* <code>m_parent</code> parent resultNode
*/
private TestResultNode m_parent;
/**
* Parameters used during test execution for the corresponding keyword.
*/
private List<TestResultParameter> m_parameters =
new LinkedList<TestResultParameter>();
/** the name of the Action that caused this Test Result */
private String m_actionName;
/**
* The listener
*/
private List < ITestResultEventListener > m_listener =
new ArrayList < ITestResultEventListener > ();
/**
* flag indicating whether the backing node / keyword for this result
* can be found
*/
private boolean m_hasBackingNode;
/**
* <code>m_omHeuristicEquivalence</code>
*/
private double m_omHeuristicEquivalence = -1.0d;
/**
* <code>m_noOfSimilarComponents</code>
*/
private int m_noOfSimilarComponents = -1;
/** the task Id of the result node */
private String m_taskId;
/** logging string*/
private String m_commandLog;
/**
* Constructor
*
* @param hasBackingNode <code>true</code> if the backing node for the
* result can be found. Otherwise, <code>false</code>.
* @param node The Test Execution node (i.e. Test Suite, Test Case,
* Test Step, etc.) associated with this result. If this value
* is <code>null</code>, the <code>fallbackName</code>
* will be used for display purposes, and there will be no
* reference to a Test Execution node.
* @param parent The parent Test Result node. May be <code>null</code>, in
* which case this node is the root of a Test Result tree.
*/
public TestResultNode(boolean hasBackingNode, INodePO node,
TestResultNode parent) {
m_node = node;
m_parent = parent;
if (m_parent != null) {
m_parent.addChild(this);
}
m_status = NOT_YET_TESTED;
m_event = null;
m_hasBackingNode = hasBackingNode;
m_isJunitTestSuite = node.isJUnitTestSuite();
}
/**
* Constructor
*
* @param node The Test Execution node (i.e. Test Suite, Test Case,
* Test Step, etc.) associated with this result.
* @param parent The parent Test Result node. May be <code>null</code>, in
* which case this node is the root of a Test Result tree.
*/
public TestResultNode(INodePO node, TestResultNode parent) {
this(node, parent, -1);
}
/**
* @param node
* associated node in testexecution tree
* @param parent
* parent resultNode (in case of testsuite null)
* @param pos
* inserts this into the parents child list; if a negative
* position is given its added to the child list
*/
public TestResultNode(INodePO node, TestResultNode parent, int pos) {
m_node = node;
m_parent = parent;
if (m_parent != null) {
if (pos > -1) {
m_parent.addChildAtPosition(pos, this);
} else {
m_parent.addChild(this);
}
}
m_status = NOT_YET_TESTED;
m_event = null;
m_hasBackingNode = true;
m_taskId = NodePropertyTester.getTaskIdforNode(node);
m_isJunitTestSuite = node.isJUnitTestSuite();
}
/**
* add a child to resultNode and set actual resultNode as parent
* @param resultNode resultNode to add
*/
public void addChild(TestResultNode resultNode) {
m_resultNodeList.add(resultNode);
resultNode.m_parent = this;
}
/**
* add a child to resultNode and set actual resultNode as parent
* @param resultNode resultNode to add
* @param pos
* position where to add
*/
public void addChildAtPosition(int pos, TestResultNode resultNode) {
m_resultNodeList.add(pos, resultNode);
resultNode.m_parent = this;
}
/**
* @return Returns the node.
*/
public INodePO getNode() {
return m_node;
}
/**
* @return Returns the parent.
*/
public TestResultNode getParent() {
return m_parent;
}
/**
* @return Returns the resultNodeList.
*/
public List < TestResultNode > getResultNodeList() {
return m_resultNodeList;
}
/**
* @return Return the name of the execTestCase
*/
public String getName() {
if (m_node != null) {
return m_node.getName();
}
return StringConstants.LEFT_INEQUALITY_SING
+ Messages.TestResultNodeGUINoNode
+ StringConstants.RIGHT_INEQUALITY_SING;
}
/**
* Adds the listener
* @param listener ITestResultEventListener
*/
public void addTestResultChangedListener(
ITestResultEventListener listener) {
if (!m_listener.contains(listener)) {
m_listener.add(listener);
}
}
/**
* Remove the listener from ResultCap
* @param listener to be removed
*/
public void removeTestResultChangedListener(
ITestResultEventListener listener) {
m_listener.remove(listener);
}
/**
* Trigger the listeners
* @param res the changed TestResultNode
*/
private void fireTestResultChanged(TestResultNode res) {
Iterator<ITestResultEventListener> iter = m_listener.iterator();
while (iter.hasNext()) {
Object obj = iter.next();
ITestResultEventListener item = (ITestResultEventListener) obj;
item.testResultChanged(res);
}
}
/**
* Updates the parent;
*
* @param pos
* index
* @param node
* TestResultNode
*/
public void updateResultNode(int pos, TestResultNode node) {
for (ITestResultEventListener item : m_listener) {
item.testResultNodeUpdated(this, pos, node);
}
}
/**
* @return Returns the status.
*/
public int getStatus() {
return m_status;
}
/**
* @return Returns the event.
*/
public TestErrorEvent getEvent() {
return m_event;
}
/**
* Set the results from an execution
*
* @param status
* Specifies the kind of result
* @param event
* On a CAP this may be the event from the server.
* <code>null</code> is allowed.
*/
public void setResult(int status, TestErrorEvent event) {
boolean changed = m_status != status
|| (m_event != null && !m_event.equals(event))
|| (m_event == null && event != null);
if (changed) {
m_status = status;
m_event = event;
if (m_status == TESTING && m_timestamp == null) {
m_timestamp = new Date();
}
}
if (isError(status) && status != CONDITION_FAILED
&& status != INFINITE_LOOP) {
if (getParent() != null) { // NOPMD by al on 3/19/07 1:37 PM
getParent().setResult(ERROR_IN_CHILD, null);
}
}
if (changed) {
fireTestResultChanged(this);
}
}
/**
* @param status to be checked
* @return true if the status means an error condition
*/
private boolean isError(int status) {
return status == ERROR || status == ERROR_IN_CHILD
|| status == NO_VERIFY || status == NOT_TESTED || status == ABORT
|| status == CONDITION_FAILED || status == INFINITE_LOOP;
}
/**
* {@inheritDoc}
*/
public String toString() {
return new ToStringBuilder(this).append(getName()).append(getStatus())
.toString();
}
/**
* @return Returns the childIndex.
*/
public int getNextChildIndex() {
m_childIndex++;
return m_childIndex;
}
/**
*
* @return String to a status
*/
public String getStatusString() {
return getStatusString(m_status);
}
/**
* @param status the status
* @return String to a status
*/
public static String getStatusString(int status) {
switch (status) {
case ERROR :
return Messages.TestResultNodeStepfailed;
case ERROR_IN_CHILD :
return Messages.TestResultNodeErrorInChildren;
case NOT_YET_TESTED :
return Messages.TestResultNodeNotYetTested;
case SUCCESS :
return Messages.TestResultNodeSuccessfullyTested;
case TESTING :
return Messages.TestResultNodeTesting;
case RETRYING :
return Messages.TestResultNodeRetrying;
case SUCCESS_RETRY :
return Messages.TestResultNodeSuccessRetry;
case ABORT :
return Messages.TestResultNodeAbort;
case CONDITION_FAILED:
return Messages.TestResultNodeConditionFailed;
case INFINITE_LOOP:
return Messages.TestResultNodeInfiniteLoop;
case SKIPPED :
return Messages.TestResultNodeSkipped;
case SUCCESS_ONLY_SKIPPED :
return Messages.TestResultNodeSuccessOnlySkipped;
default :
break;
}
return Messages.TestResultNodeUnknown;
}
/**
*
* @return the receiver's Parameters.
*/
public List<TestResultParameter> getParameters() {
return Collections.unmodifiableList(m_parameters);
}
/**
*
* @param parameter The Parameter to add.
*/
public void addParameter(TestResultParameter parameter) {
m_parameters.add(parameter);
}
/**
*
* @param componentName The Component Name name to set. This must be a name,
* <b>not</b> a GUID.
*/
public void setComponentName(String componentName) {
m_componentName = componentName;
}
/**
*
* @return the Component Name name for this result node.
* This is a name, <b>not</b> a GUID.
*/
public String getComponentName() {
return m_componentName;
}
/**
*
* @param componentType The Component Name type to set.
* This is a human-readable component type.
*/
public void setComponentType(String componentType) {
m_componentType = componentType;
}
/**
*
* @return the Component Name type for this result node.
* This is a human-readable component type.
*/
public String getComponentType() {
return m_componentType;
}
/**
*
* @param actionName The name of the executed Action that caused this
* Test Result. May be <code>null</code>, which means that
* the Test Result was not caused by any Action
* (e.g. Test Case Reference, as opposed to Test Step).
*/
public void setActionName(String actionName) {
m_actionName = actionName;
}
/**
*
* @return The name of the executed Action that caused this Test Result,
* or <code>null</code> if the Test Result was not caused by any
* Action (e.g. Test Case Reference, as opposed to Test Step).
*/
public String getActionName() {
return m_actionName;
}
/**
*
* @return the time at which the keyword execution corresponding to the
* receiver began, or <code>null</code> if the receiver does not
* correspond to an executed keyword.
*/
public Date getTimeStamp() {
return m_timestamp;
}
/**
* @param timestamp the timestamp to set
*/
public void setTimestamp(Date timestamp) {
m_timestamp = timestamp;
}
/**
* @param screenshot the screenshot to set
*/
public void setScreenshot(byte[] screenshot) {
m_screenshot = screenshot;
}
/**
* @return the screenshot
*/
public byte[] getScreenshot() {
return m_screenshot;
}
/**
*
* @return <code>true</code> if the backing node for the
* result can be found. Otherwise, <code>false</code>.
*/
public boolean hasBackingNode() {
return m_hasBackingNode;
}
/**
* @param omHeuristicEquivalence the omHeuristicEquivalence to set
*/
public void setOmHeuristicEquivalence(double omHeuristicEquivalence) {
m_omHeuristicEquivalence = omHeuristicEquivalence;
}
/**
* @return the omHeuristicEquivalence
*/
public double getOmHeuristicEquivalence() {
return m_omHeuristicEquivalence;
}
/**
* @param noOfSimilarComponents the noOfSimilarComponents to set
*/
public void setNoOfSimilarComponents(int noOfSimilarComponents) {
m_noOfSimilarComponents = noOfSimilarComponents;
}
/**
* @return the noOfSimilarComponents
*/
public int getNoOfSimilarComponents() {
return m_noOfSimilarComponents;
}
/**
*
* @return the sibling immediately following the receiver in the
* receiver's parent's child list, or <code>null</code> if the
* receiver is the last element in this list.
*/
private TestResultNode getNextSibling() {
TestResultNode parent = getParent();
if (parent != null) {
List<TestResultNode> siblingList = parent.getResultNodeList();
int nodeIndex = siblingList.indexOf(this);
if (nodeIndex == -1 && !ClientTest.instance().isTrimming()) {
LOG.error(NLS.bind(
Messages.ParentChildInconsistency, getName()));
} else {
int nextSiblingIndex = nodeIndex + 1;
if (siblingList.size() > nextSiblingIndex) {
return siblingList.get(nextSiblingIndex);
}
}
}
return null;
}
/**
*
* @return the {@link TestResultNode} corresponding to the Keyword that was
* executed after the receiver's Keyword, or <code>null</code> if
* the receiver's Keyword was the last executed element in a
* Test Suite.
*/
private TestResultNode getNextExecutedNode() {
TestResultNode nextSibling = getNextSibling();
TestResultNode currentNode = this;
// having a timestamp indicates that the keyword was executed
while ((nextSibling == null || nextSibling.getTimeStamp() == null)
&& currentNode.getParent() != null) {
currentNode = currentNode.getParent();
nextSibling = currentNode.getNextSibling();
}
if (nextSibling != null && nextSibling.getTimeStamp() == null) {
// corner case: Last executed Keyword, but not the last Keyword
// in the Test Suite (e.g. test aborted).
return null;
}
return nextSibling;
}
/**
*
* @param testEndTime The time at which the Test Suite execution containing
* the receiver was ended. May be <code>null</code> if
* the Test Suite end time is not available.
* @return the duration of the receiver's execution (in milliseconds),
* or <code>-1</code> if the duration cannot be calculated
* (e.g. the Keyword was not executed, or the keyword was the last
* executed and no Test Suite end time is available).
*/
public long getDuration(Date testEndTime) {
Date start = getTimeStamp();
if (start != null) {
TestResultNode nextExecutedNode = getNextExecutedNode();
if (nextExecutedNode != null) {
Date end = nextExecutedNode.getTimeStamp();
return end.getTime() - start.getTime();
}
// Receiver was the last executed node in the Test Suite Execution,
// so use Test Suite end time (if available) to determine duration.
if (testEndTime != null) {
return testEndTime.getTime() - start.getTime();
}
}
// keyword was not executed, so duration cannot be calculated
return -1;
}
/**
* @return a human readable type description for the given node
*/
public String getTypeOfNode() {
INodePO node = getNode();
if (node instanceof IEventExecTestCasePO) {
return Messages.TestResultNodeTypeEventTestCase;
} else if (node instanceof ITestCasePO) {
return Messages.TestResultNodeTypeTestCase;
} else if (node instanceof ICapPO) {
return Messages.TestResultNodeTypeTestStep;
} else if (node instanceof ITestSuitePO) {
return Messages.TestResultNodeTypeTestSuite;
} else if (node instanceof ICommentPO) {
return Messages.TestResultNodeTypeComment;
} else if (node instanceof IConditionalStatementPO) {
return Messages.TestResultNodeTypeCondition;
} else if (node instanceof IDoWhilePO) {
return Messages.TestResultNodeTypeDoWhile;
} else if (node instanceof IWhileDoPO) {
return Messages.TestResultNodeTypeWhileDo;
} else if (node instanceof IAbstractContainerPO) {
return Messages.TestResultNodeTypeContainer;
} else if (node instanceof IIteratePO) {
return Messages.TestResultNodeTypeIterate;
}
return Messages.TestResultNodeTypeUnknown;
}
/**
* @return a human readable parameter description for the given node
*/
public String getParameterDescription() {
StringBuilder paramValueBuilder = new StringBuilder();
List<TestResultParameter> parameters = getParameters();
// use index based loop to avoid ConcurrentModificationException
boolean isCondStruct = getNode() instanceof ICondStructPO;
for (int index = 0; index < parameters.size(); index++) {
TestResultParameter parameter = parameters.get(index);
if (!isCondStruct) {
paramValueBuilder
.append(StringUtils.defaultString(parameter.getValue()))
.append(SEPARATOR);
} else {
if (Boolean.parseBoolean(parameter.getValue())) {
paramValueBuilder.append(NEGATED).append(SEPARATOR);
}
}
}
if (paramValueBuilder.length() > 0) {
int builderLength = paramValueBuilder.length();
paramValueBuilder.delete(builderLength - SEPARATOR_LEN,
builderLength);
paramValueBuilder.insert(0, " ["); //$NON-NLS-1$
paramValueBuilder.append("]"); //$NON-NLS-1$
return paramValueBuilder.toString();
}
return StringConstants.EMPTY;
}
/**
* @return the taskId
*/
public String getTaskId() {
return m_taskId;
}
/**
* @param taskId the taskId to set
*/
public void setTaskId(String taskId) {
m_taskId = taskId;
}
/**
* @return the log from the command or null
*/
public String getCommandLog() {
return m_commandLog;
}
/**
*
* @param commandLog sets the log from a command
*/
public void setCommandLog(String commandLog) {
this.m_commandLog = commandLog;
}
/** Removes all children of the node */
public void removeChildren() {
m_resultNodeList.clear();
m_childIndex = -1;
}
/**
* @return wether the Node acts as a JUnit testsuite
*/
public boolean isJunitTestSuite() {
return m_isJunitTestSuite;
}
/**
* @param isJunitTestSuite wether the Node acts as a JUnit testsuite
* true = the Node is used as a JUnitTestSuite
*/
public void setJunitTestSuite(boolean isJunitTestSuite) {
m_isJunitTestSuite = isJunitTestSuite;
}
}