/******************************************************************************* * 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.ui.editors; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import javax.persistence.EntityManager; import org.apache.commons.lang.StringUtils; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.jface.action.GroupMarker; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; import org.eclipse.jface.viewers.DecorationContext; import org.eclipse.jface.viewers.IDecorationContext; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jubula.client.core.businessprocess.TestresultSummaryBP; import org.eclipse.jubula.client.core.model.ICommentPO; import org.eclipse.jubula.client.core.model.ICondStructPO; import org.eclipse.jubula.client.core.model.IConditionalStatementPO; import org.eclipse.jubula.client.core.model.INodePO; import org.eclipse.jubula.client.core.model.IParameterDetailsPO; import org.eclipse.jubula.client.core.model.ITestResultAdditionPO; import org.eclipse.jubula.client.core.model.ITestResultPO; import org.eclipse.jubula.client.core.model.ITestResultSummaryPO; import org.eclipse.jubula.client.core.model.ITestSuitePO; import org.eclipse.jubula.client.core.model.NodeMaker; import org.eclipse.jubula.client.core.model.PoMaker; import org.eclipse.jubula.client.core.model.TestResultNode; import org.eclipse.jubula.client.core.model.TestResultParameter; import org.eclipse.jubula.client.core.persistence.GeneralStorage; import org.eclipse.jubula.client.core.persistence.Persistor; import org.eclipse.jubula.client.core.persistence.TestResultPM; import org.eclipse.jubula.client.ui.Plugin; import org.eclipse.jubula.client.ui.constants.CommandIDs; import org.eclipse.jubula.client.ui.constants.Constants; import org.eclipse.jubula.client.ui.constants.ContextHelpIds; import org.eclipse.jubula.client.ui.i18n.Messages; import org.eclipse.jubula.client.ui.provider.DecoratingCellLabelProvider; import org.eclipse.jubula.client.ui.provider.contentprovider.TestResultTreeViewContentProvider; import org.eclipse.jubula.client.ui.provider.labelprovider.TestResultTreeViewLabelProvider; import org.eclipse.jubula.client.ui.utils.CommandHelper; import org.eclipse.jubula.client.ui.utils.OpenViewUtils; import org.eclipse.jubula.client.ui.views.IJBPart; import org.eclipse.jubula.client.ui.views.ITreeViewerContainer; import org.eclipse.jubula.client.ui.views.NonSortedPropertySheetPage; import org.eclipse.jubula.tools.internal.constants.StringConstants; import org.eclipse.jubula.tools.internal.i18n.I18n; import org.eclipse.jubula.tools.internal.objects.event.TestErrorEvent; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Menu; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.IWorkbenchActionConstants; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.part.EditorPart; import org.eclipse.ui.progress.IProgressService; import org.eclipse.ui.views.properties.IPropertySheetPage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Viewer for Test Results associated with a Test Result Summary. * * @author BREDEX GmbH * @created May 17, 2010 */ public class TestResultViewer extends EditorPart implements ISelectionProvider, ITreeViewerContainer, IAdaptable, IJBPart { /** Constant: Editor ID */ public static final String EDITOR_ID = "org.eclipse.jubula.client.ui.editors.TestResultViewer"; //$NON-NLS-1$ /** * ID of the decoration context property for Test Suite end time. * The value of the property is a {@link java.util.Date}. */ public static final String DECORATION_CONTEXT_SUITE_END_TIME_ID = "org.eclipse.jubula.client.ui.editors.TestResultViewer.testSuiteEndTime"; //$NON-NLS-1$ /** the logger */ private static final Logger LOG = LoggerFactory.getLogger(TestResultViewer.class); /** * Operation to gather Test Result information from the database and use * that information to construct a Test Result tree. * * @author BREDEX GmbH * @created May 18, 2010 */ public static final class GenerateTestResultTreeOperation implements IRunnableWithProgress { /** * Reverse lookup for test error event IDs. This is necessary because the * values stored in the database are internationalized, whereas most of the * time we really need the ID itself. */ private static Map<String, String> eventIdReverseLookup = new HashMap<String, String>(); static { //FIXME NLS eventIdReverseLookup.put(I18n.getString( TestErrorEvent.ID.COMPONENT_NOT_FOUND), TestErrorEvent.ID.COMPONENT_NOT_FOUND); eventIdReverseLookup.put(I18n.getString( TestErrorEvent.ID.CONFIGURATION_ERROR), TestErrorEvent.ID.CONFIGURATION_ERROR); eventIdReverseLookup.put(I18n.getString( TestErrorEvent.ID.ACTION_ERROR), TestErrorEvent.ID.ACTION_ERROR); eventIdReverseLookup.put(I18n.getString( TestErrorEvent.ID.VERIFY_FAILED), TestErrorEvent.ID.VERIFY_FAILED); } /** the database ID of the summary for which to generate the tree */ private Long m_summaryId; /** the root node of the created Test Result tree */ private TestResultNode m_rootNode; /** the manager for Test Result entities */ private EntityManager m_session; /** * Constructor * * @param summaryId The database ID of the summary for which to generate the * tree. * @param session The manager for Test Result entities. The caller is * responsible for managing the session (opening, * closing, etc.). Closing the session before or during * the operation will cause errors. */ public GenerateTestResultTreeOperation( Long summaryId, EntityManager session) { m_summaryId = summaryId; m_session = session; } /** * {@inheritDoc} */ public void run(IProgressMonitor pMonitor) { SubMonitor monitor = SubMonitor.convert(pMonitor, Messages.TestResultViewerDetailsLoadingJobName, 2); try { monitor.subTask(Messages. TestResultViewerDetailsLoading1SubTask); List<ITestResultPO> testResultList = TestResultPM .computeTestResultListForSummary(m_session, m_summaryId); monitor.worked(1); TestResultNode createdNode = null; Set<String> allGuids = new HashSet<String>(); for (ITestResultPO result : testResultList) { allGuids.add(result.getInternalKeywordGuid()); } monitor.subTask(NLS.bind( Messages.TestResultViewerDetailsLoading2SubTask, allGuids.size())); int remainingWork = testResultList.size(); SubMonitor sMonitor = SubMonitor.convert(monitor, Messages.TestResultViewerDetailsLoadingJobName, remainingWork); sMonitor.subTask(Messages. TestResultViewerDetailsLoading3SubTask); Stack<TestResultNode> parentNodeStack = new Stack<TestResultNode>(); for (ITestResultPO result : testResultList) { if (pMonitor.isCanceled()) { throw new OperationCanceledException(); } int keywordLevel = result.getKeywordLevel(); if (keywordLevel > parentNodeStack.size()) { parentNodeStack.push(createdNode); } else { while (keywordLevel < parentNodeStack.size()) { parentNodeStack.pop(); } } createdNode = new TestResultNode(false, generateBackingNode(result), parentNodeStack.isEmpty() ? null : parentNodeStack.peek()); List<IParameterDetailsPO> parameterList = result. getUnmodifiableParameterList(); setNegatedState(createdNode, parameterList); createdNode.setComponentName(result.getComponentName()); createdNode.setComponentType(result.getComponentType()); createdNode.setTaskId(result.getTaskId()); for (IParameterDetailsPO param : parameterList) { createdNode.addParameter( new TestResultParameter(param)); } createdNode.setResult(result.getInternalKeywordStatus(), generateTestErrorEvent(result)); createdNode.setScreenshot(result.getImage()); createdNode.setJunitTestSuite(result.getIsJUnitSuite()); setAdditionsToTestResultNode(createdNode, result); createdNode.setTimestamp(result.getTimestamp()); createdNode.setOmHeuristicEquivalence(result .getOmHeuristicEquivalence()); if (m_rootNode == null) { m_rootNode = createdNode; } sMonitor.worked(1); } } finally { pMonitor.done(); } } /** * sets the negated state for the html * @param createdNode the {@link TestResultNode} * @param parameterList the list of parameterDetails */ private void setNegatedState(TestResultNode createdNode, List<IParameterDetailsPO> parameterList) { INodePO theNode = createdNode.getNode(); if (theNode instanceof ICondStructPO && parameterList.size() > 0) { ((ICondStructPO) theNode).setNegate("true" //$NON-NLS-1$ .equals(parameterList.get(0).getParameterValue())); } } /** * * @param createdNode the created {@link TestResultNode} * @param result the {@link ITestResultPO} where we get the data from */ private void setAdditionsToTestResultNode(TestResultNode createdNode, ITestResultPO result) { for (ITestResultAdditionPO addition : result.getTestResultAdditions()) { if (addition.getType().equals( ITestResultAdditionPO.TYPE.OUT_AND_ERR)) { Object data = addition.getData(); if (data instanceof String) { createdNode.setCommandLog((String) data); } } if (addition.getType().equals(ITestResultAdditionPO. TYPE.JUNIT_TEST_SUITE)) { createdNode.setJunitTestSuite(true); } } } /** * * @return the root node of the Test Result tree generated by this * operation. Behavior when this method is called before the * operation is complete is undefined. */ public TestResultNode getRootNode() { return m_rootNode; } /** * Creates and returns a transient keyword suitable for backing the * given result. * * @param result The result for which to generate a backing keyword. * @return a transient keyword that backs the given result, or * <code>null</code> if the keyword type is not recognized. */ private INodePO generateBackingNode(ITestResultPO result) { switch (result.getInternalKeywordType()) { case TestresultSummaryBP.TYPE_TEST_STEP: // FIXME zeb in order to construct a valid Test Step, we // sometimes use whitespace (" ") as a // placeholder. This works so far, as the only // validation is that the string is neither null // nor empty, but this may cause problems in // future. String componentName = !StringUtils.isEmpty(result .getComponentName()) ? result.getComponentName() : StringConstants.SPACE; return NodeMaker.createCapPO(result.getKeywordName(), componentName, result.getInternalComponentType() != null ? result .getInternalComponentType() : StringConstants.SPACE, result.getInternalActionName() != null ? result .getInternalActionName() : StringConstants.SPACE); case TestresultSummaryBP.TYPE_TEST_CASE: return NodeMaker.createTransientTestCasePO( result.getKeywordName(), result.getInternalKeywordGuid()); case TestresultSummaryBP.TYPE_TEST_SUITE: ITestSuitePO backingTestSuite = NodeMaker .createTestSuitePO(result.getKeywordName(), result.getInternalKeywordGuid()); ITestResultSummaryPO summary = m_session.find( PoMaker.getTestResultSummaryClass(), m_summaryId); backingTestSuite.setAut(PoMaker.createAUTMainPO(summary .getAutName())); return backingTestSuite; case TestresultSummaryBP.TYPE_COMMENT: ICommentPO backingComment = NodeMaker.createCommentPO( result.getKeywordName(), result.getInternalKeywordGuid()); return backingComment; case TestresultSummaryBP.TYPE_CONDITION: IConditionalStatementPO condition = NodeMaker .createConditionalStatementPO( result.getKeywordName(), result.getInternalKeywordGuid()); return condition; case TestresultSummaryBP.TYPE_DOWHILE: return NodeMaker.createDoWhilePO(result.getKeywordName(), result.getInternalKeywordGuid()); case TestresultSummaryBP.TYPE_WHILEDO: return NodeMaker.createWhileDoPO(result.getKeywordName(), result.getInternalKeywordGuid()); case TestresultSummaryBP.TYPE_ITERATE: return NodeMaker.createIteratePO(result.getKeywordName(), result.getInternalKeywordGuid()); case TestresultSummaryBP.TYPE_CONTAINER: return NodeMaker.createContainerPO(result.getKeywordName(), result.getInternalKeywordGuid()); default: return null; } } /** * * @param result The result for which to generate a test error event. * @return a test error event corresponding to the given result, or * <code>null</code> if the given result is not an error result. */ private TestErrorEvent generateTestErrorEvent(ITestResultPO result) { TestErrorEvent errorEvent = null; if (result.getInternalKeywordStatus() == TestResultNode.ERROR) { errorEvent = new TestErrorEvent( eventIdReverseLookup.get(result.getStatusType())); if (result.getStatusDescription() != null) { errorEvent.addProp( TestErrorEvent.Property.DESCRIPTION_KEY, result.getStatusDescription()); } if (result.getActualValue() != null) { errorEvent.addProp( TestErrorEvent.Property.ACTUAL_VALUE_KEY, result.getActualValue()); } if (result.getStatusOperator() != null) { errorEvent.addProp( TestErrorEvent.Property.OPERATOR_KEY, result.getStatusOperator()); } if (result.getExpectedValue() != null) { errorEvent.addProp( TestErrorEvent.Property.PATTERN_KEY, result.getExpectedValue()); } return errorEvent; } return null; } } /** the viewer */ private TreeViewer m_viewer; /** the manager for Test Result entities */ private EntityManager m_session; /** * whether the Test Results loaded in this viewer will be cached in the * master session */ private boolean m_cacheResults; /** * the root node */ private TestResultNode m_testResultRootNode; /** react on selection to open log view */ private ISelectionChangedListener m_selectionListener = new OpenViewUtils.TestResultNodeSelectionListener(); /** {@inheritDoc} */ public void doSave(IProgressMonitor monitor) { // "Save" not supported. Do nothing. } /** {@inheritDoc} */ public void doSaveAs() { // "Save as" not supported. Do nothing. } /** {@inheritDoc} */ public Object getAdapter(Class key) { if (key.equals(IPropertySheetPage.class)) { return new NonSortedPropertySheetPage(); } return super.getAdapter(key); } /** {@inheritDoc} */ public void init(IEditorSite site, IEditorInput input) throws PartInitException { if (input instanceof TestResultEditorInput) { setSite(site); setInput(input); setPartName(input.getName()); } else { throw new PartInitException(Messages.EditorInitCreateError); } } /** * Generates a Test Result tree and returns the root node of the generated * tree. * * @param summaryId The database ID of the summary for which to generate the * tree. * @return the root node of the generated Test Result tree. * * @throws InterruptedException if the operation was cancelled by the user. */ private TestResultNode generateTestResult(Long summaryId) throws InterruptedException { IProgressService progressService = getSite().getService( IProgressService.class); GenerateTestResultTreeOperation operation = new GenerateTestResultTreeOperation( summaryId, m_cacheResults ? GeneralStorage.getInstance() .getMasterSession() : m_session); try { progressService.busyCursorWhile(operation); } catch (InvocationTargetException e) { LOG.error(Messages.ErrorFetchingTestResultInformation + StringConstants.DOT, e); } catch (OperationCanceledException oce) { throw new InterruptedException(); } return operation.getRootNode(); } /** * * {@inheritDoc} */ public boolean isDirty() { return false; } /** * * {@inheritDoc} */ public boolean isSaveAsAllowed() { return false; } /** * {@inheritDoc} */ public void createPartControl(Composite parent) { TestResultEditorInput editorInput = (TestResultEditorInput)getEditorInput(); m_cacheResults = Plugin.getDefault().getPreferenceStore().getBoolean( Constants.PREF_KEY_CACHE_TEST_RESULTS); if (!m_cacheResults) { m_session = Persistor.instance().openSession(); } m_viewer = new TreeViewer(parent); ColumnViewerToolTipSupport.enableFor(getTreeViewer()); m_viewer.setContentProvider(new TestResultTreeViewContentProvider()); DecoratingCellLabelProvider labelProvider = new DecoratingCellLabelProvider( new TestResultTreeViewLabelProvider(), PlatformUI.getWorkbench() .getDecoratorManager().getLabelDecorator()); IDecorationContext decorationContext = labelProvider.getDecorationContext(); if (decorationContext instanceof DecorationContext) { ((DecorationContext)decorationContext).putProperty( DECORATION_CONTEXT_SUITE_END_TIME_ID, editorInput.getTestSuiteEndTime()); } m_viewer.setLabelProvider(labelProvider); getSite().setSelectionProvider(m_viewer); createContextMenu(m_viewer); m_viewer.setAutoExpandLevel(2); PlatformUI.getWorkbench().getHelpSystem().setHelp(m_viewer.getControl(), ContextHelpIds.RESULT_TREE_VIEW); try { setTestResultRootNode(generateTestResult( editorInput.getTestResultSummaryId())); m_viewer.setInput(new TestResultNode[] {getTestResultRootNode()}); m_viewer.addSelectionChangedListener(m_selectionListener); } catch (InterruptedException ie) { // Operation was cancelled by user m_viewer.getControl().dispose(); m_viewer = null; new Label(parent, SWT.NONE).setText( Messages.EditorsOpenEditorOperationCanceled); } } /** * @param viewer the tree viewer */ private void createContextMenu(TreeViewer viewer) { // Create menu manager. MenuManager menuMgr = new MenuManager(); menuMgr.setRemoveAllWhenShown(true); menuMgr.addMenuListener(new IMenuListener() { public void menuAboutToShow(IMenuManager mgr) { fillContextMenu(mgr); } }); // Create menu. Control viewerControl = viewer.getControl(); Menu menu = menuMgr.createContextMenu(viewerControl); viewerControl.setMenu(menu); // Register menu for extension. getSite().registerContextMenu(menuMgr, getTreeViewer()); } /** * @param mgr the menu manager */ private void fillContextMenu(IMenuManager mgr) { CommandHelper.createContributionPushItem(mgr, CommandIDs.EXPAND_TREE_ITEM_COMMAND_ID); mgr.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS)); } /** * * {@inheritDoc} */ public void setFocus() { if (m_viewer != null && !m_viewer.getControl().isDisposed()) { m_viewer.getControl().setFocus(); } } /** * {@inheritDoc} */ public void addSelectionChangedListener( ISelectionChangedListener listener) { m_viewer.addSelectionChangedListener(listener); } /** * {@inheritDoc} */ public ISelection getSelection() { return m_viewer.getSelection(); } /** * {@inheritDoc} */ public void removeSelectionChangedListener( ISelectionChangedListener listener) { if (m_viewer != null) { m_viewer.removeSelectionChangedListener(listener); } } /** * {@inheritDoc} */ public void setSelection(ISelection selection) { m_viewer.setSelection(selection); } /** * {@inheritDoc} */ public TreeViewer getTreeViewer() { return m_viewer; } @Override public void dispose() { super.dispose(); Persistor.instance().dropSession(m_session); removeSelectionChangedListener(m_selectionListener); } /** * @return the testResultRootNode */ public TestResultNode getTestResultRootNode() { return m_testResultRootNode; } /** * @param testResultRootNode the testResultRootNode to set */ private void setTestResultRootNode(TestResultNode testResultRootNode) { m_testResultRootNode = testResultRootNode; } }