Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'testsrunner/org.eclipse.cdt.testsrunner/src/org/eclipse/cdt/testsrunner/internal/ui/view/TestsHierarchyViewer.java')
-rw-r--r--testsrunner/org.eclipse.cdt.testsrunner/src/org/eclipse/cdt/testsrunner/internal/ui/view/TestsHierarchyViewer.java554
1 files changed, 554 insertions, 0 deletions
diff --git a/testsrunner/org.eclipse.cdt.testsrunner/src/org/eclipse/cdt/testsrunner/internal/ui/view/TestsHierarchyViewer.java b/testsrunner/org.eclipse.cdt.testsrunner/src/org/eclipse/cdt/testsrunner/internal/ui/view/TestsHierarchyViewer.java
new file mode 100644
index 00000000000..117a7b3beec
--- /dev/null
+++ b/testsrunner/org.eclipse.cdt.testsrunner/src/org/eclipse/cdt/testsrunner/internal/ui/view/TestsHierarchyViewer.java
@@ -0,0 +1,554 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Anton Gorenkov
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Anton Gorenkov - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.cdt.testsrunner.internal.ui.view;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.cdt.internal.ui.viewsupport.ColoringLabelProvider;
+import org.eclipse.cdt.testsrunner.internal.TestsRunnerPlugin;
+import org.eclipse.cdt.testsrunner.internal.ui.view.actions.CopySelectedTestsAction;
+import org.eclipse.cdt.testsrunner.internal.ui.view.actions.RedebugSelectedAction;
+import org.eclipse.cdt.testsrunner.internal.ui.view.actions.RelaunchSelectedAction;
+import org.eclipse.cdt.testsrunner.internal.ui.view.actions.RerunSelectedAction;
+import org.eclipse.cdt.testsrunner.internal.ui.view.actions.TestsHierarchyCollapseAllAction;
+import org.eclipse.cdt.testsrunner.internal.ui.view.actions.TestsHierarchyExpandAllAction;
+import org.eclipse.cdt.testsrunner.model.IModelVisitor;
+import org.eclipse.cdt.testsrunner.model.ITestCase;
+import org.eclipse.cdt.testsrunner.model.ITestItem;
+import org.eclipse.cdt.testsrunner.model.ITestMessage;
+import org.eclipse.cdt.testsrunner.model.ITestSuite;
+import org.eclipse.cdt.testsrunner.model.ITestingSession;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IMenuListener;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.StyledCellLabelProvider;
+import org.eclipse.jface.viewers.StyledString;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.dnd.Clipboard;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IViewSite;
+import org.eclipse.ui.actions.ActionFactory;
+
+/**
+ * Shows the tests hierarchy in a flat or hierarchical view.
+ */
+public class TestsHierarchyViewer {
+
+ /**
+ * The content provider for the tests hierarchy viewer.
+ */
+ private class TestTreeContentProvider implements ITreeContentProvider {
+
+ /**
+ * Utility class: recursively collects all the test cases of the
+ * specified test item.
+ *
+ * It is used for flat view of tests hierarchy.
+ */
+ private class TestCasesCollector implements IModelVisitor {
+
+ public List<ITestCase> testCases = new ArrayList<ITestCase>();
+
+ @Override
+ public void visit(ITestCase testCase) {
+ testCases.add(testCase);
+ }
+
+ @Override
+ public void visit(ITestMessage testMessage) {}
+ @Override
+ public void visit(ITestSuite testSuite) {}
+ @Override
+ public void leave(ITestSuite testSuite) {}
+ @Override
+ public void leave(ITestCase testCase) {}
+ @Override
+ public void leave(ITestMessage testMessage) {}
+ }
+
+ @Override
+ public Object[] getChildren(Object parentElement) {
+ return ((ITestItem) parentElement).getChildren();
+ }
+
+ @Override
+ public Object[] getElements(Object rootTestSuite) {
+ if (showTestsHierarchy) {
+ return getChildren(rootTestSuite);
+ } else {
+ TestCasesCollector testCasesCollector = new TestCasesCollector();
+ ((ITestItem)rootTestSuite).visit(testCasesCollector);
+ return testCasesCollector.testCases.toArray();
+ }
+ }
+
+ @Override
+ public Object getParent(Object object) {
+ return ((ITestItem) object).getParent();
+ }
+
+ @Override
+ public boolean hasChildren(Object object) {
+ return ((ITestItem) object).hasChildren();
+ }
+
+ @Override
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {}
+
+ @Override
+ public void dispose() {}
+ }
+
+ /**
+ * The label provider for the tests hierarchy viewer.
+ */
+ private class TestLabelProvider extends LabelProvider implements IStyledLabelProvider {
+
+ /** Images for the test cases with the different statuses. */
+ private Map<ITestItem.Status, Image> testCaseImages = new HashMap<ITestItem.Status, Image>();
+ {
+ testCaseImages.put(ITestItem.Status.NotRun, TestsRunnerPlugin.createAutoImage("obj16/test_notrun.gif")); //$NON-NLS-1$
+ testCaseImages.put(ITestItem.Status.Skipped, TestsRunnerPlugin.createAutoImage("obj16/test_skipped.gif")); //$NON-NLS-1$
+ testCaseImages.put(ITestItem.Status.Passed, TestsRunnerPlugin.createAutoImage("obj16/test_passed.gif")); //$NON-NLS-1$
+ testCaseImages.put(ITestItem.Status.Failed, TestsRunnerPlugin.createAutoImage("obj16/test_failed.gif")); //$NON-NLS-1$
+ testCaseImages.put(ITestItem.Status.Aborted, TestsRunnerPlugin.createAutoImage("obj16/test_aborted.gif")); //$NON-NLS-1$
+ }
+
+ /** Running test case image (overrides the test case status image). */
+ private Image testCaseRunImage = TestsRunnerPlugin.createAutoImage("obj16/test_run.gif"); //$NON-NLS-1$
+
+ /** Images for the test suites with the different statuses. */
+ private Map<ITestItem.Status, Image> testSuiteImages = new HashMap<ITestItem.Status, Image>();
+ {
+ // NOTE: There is no skipped-icon for test suite, but it seems it is not a problem
+ testSuiteImages.put(ITestItem.Status.NotRun, TestsRunnerPlugin.createAutoImage("obj16/tsuite_notrun.gif")); //$NON-NLS-1$
+ testSuiteImages.put(ITestItem.Status.Skipped, TestsRunnerPlugin.createAutoImage("obj16/tsuite_notrun.gif")); //$NON-NLS-1$
+ testSuiteImages.put(ITestItem.Status.Passed, TestsRunnerPlugin.createAutoImage("obj16/tsuite_passed.gif")); //$NON-NLS-1$
+ testSuiteImages.put(ITestItem.Status.Failed, TestsRunnerPlugin.createAutoImage("obj16/tsuite_failed.gif")); //$NON-NLS-1$
+ testSuiteImages.put(ITestItem.Status.Aborted, TestsRunnerPlugin.createAutoImage("obj16/tsuite_aborted.gif")); //$NON-NLS-1$
+ }
+
+ /** Running test suite image (overrides the test suite status image). */
+ private Image testSuiteRunImage = TestsRunnerPlugin.createAutoImage("obj16/tsuite_run.gif"); //$NON-NLS-1$
+
+ /** Small optimization: the last test item cache */
+ private ITestItem lastTestItemCache = null;
+
+ /** Small optimization: test path for the last test item is cache */
+ private String lastTestItemPathCache = null;
+
+
+ @Override
+ public Image getImage(Object element) {
+ Map<ITestItem.Status, Image> imagesMap = null;
+ Image runImage = null;
+ if (element instanceof ITestCase) {
+ imagesMap = testCaseImages;
+ runImage = testCaseRunImage;
+
+ } else if (element instanceof ITestSuite) {
+ imagesMap = testSuiteImages;
+ runImage = testSuiteRunImage;
+ }
+ if (imagesMap != null) {
+ ITestItem testItem = (ITestItem)element;
+ if (testingSession.getModelAccessor().isCurrentlyRunning(testItem)) {
+ return runImage;
+ }
+ return imagesMap.get(testItem.getStatus());
+ }
+
+ return null;
+ }
+
+ @Override
+ public String getText(Object element) {
+ ITestItem testItem = (ITestItem)element;
+ StringBuilder sb = new StringBuilder();
+ sb.append(testItem.getName());
+ if (!showTestsHierarchy) {
+ appendTestItemPath(sb, testItem);
+ }
+ if (showTime) {
+ sb.append(getTestingTimeString(element));
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public StyledString getStyledText(Object element) {
+ ITestItem testItem = (ITestItem)element;
+ StringBuilder labelBuf = new StringBuilder();
+ labelBuf.append(testItem.getName());
+ StyledString name = new StyledString(labelBuf.toString());
+ if (!showTestsHierarchy) {
+ appendTestItemPath(labelBuf, testItem);
+ name = StyledCellLabelProvider.styleDecoratedString(labelBuf.toString(), StyledString.QUALIFIER_STYLER, name);
+ }
+ if (showTime) {
+ String time = getTestingTimeString(element);
+ labelBuf.append(time);
+ name = StyledCellLabelProvider.styleDecoratedString(labelBuf.toString(), StyledString.COUNTER_STYLER, name);
+ }
+ return name;
+ }
+
+ /**
+ * Appends path to the parent of the specified test item. Also
+ * implements caching of the last path (cause the test item parent is
+ * often the same).
+ *
+ * @param sb string builder to append test item path to
+ * @param testItem specified test item
+ */
+ private void appendTestItemPath(StringBuilder sb, ITestItem testItem) {
+ ITestSuite testItemParent = testItem.getParent();
+ if (lastTestItemCache != testItemParent) {
+ lastTestItemCache = testItemParent;
+ lastTestItemPathCache = TestPathUtils.getTestItemPath(lastTestItemCache);
+ }
+ sb.append(MessageFormat.format(
+ UIViewMessages.TestsHierarchyViewer_test_path_format,
+ new Object[] { lastTestItemPathCache }
+ ));
+ }
+
+ /**
+ * Returns the execution time suffix for the test item.
+ *
+ * @param element test item
+ * @return execution time suffix
+ */
+ private String getTestingTimeString(Object element) {
+ return (element instanceof ITestItem)
+ ? MessageFormat.format(UIViewMessages.TestsHierarchyViewer_test_time_format, Double.toString(((ITestItem)element).getTestingTime()/1000.0))
+ : ""; //$NON-NLS-1$
+ }
+
+ }
+
+ /**
+ * Filters passed test cases and test suites.
+ */
+ private class FailedOnlyFilter extends ViewerFilter {
+
+ @Override
+ public boolean select(Viewer viewer, Object parentElement, Object element) {
+ return ((ITestItem)element).getStatus().isError();
+ }
+ }
+
+ /** Testing session to show hierarchy of. */
+ private ITestingSession testingSession;
+
+ /** Main widget. */
+ private TreeViewer treeViewer;
+
+ /** Specifies whether test items execution time should be shown in hierarchy. */
+ private boolean showTime = true;
+
+ /** Specifies whether tests hierarchy should be shown in flat or hierarchical view. */
+ private boolean showTestsHierarchy = true;
+
+ /** Failed only tree filter instance. Created on first demand. */
+ private FailedOnlyFilter failedOnlyFilter = null;
+
+ /** System clipboard access to provide copy operations. */
+ private Clipboard clipboard;
+
+ // Context menu actions
+ private Action expandAllAction;
+ private Action collapseAllAction;
+ private Action copyAction;
+ private RelaunchSelectedAction rerunAction;
+ private RelaunchSelectedAction redebugAction;
+
+
+ public TestsHierarchyViewer(Composite parent, IViewSite viewSite, Clipboard clipboard) {
+ this.clipboard = clipboard;
+ treeViewer = new TreeViewer(parent, SWT.V_SCROLL | SWT.MULTI);
+ treeViewer.setContentProvider(new TestTreeContentProvider());
+ treeViewer.setLabelProvider(new ColoringLabelProvider(new TestLabelProvider()));
+ initContextMenu(viewSite);
+ }
+
+ /**
+ * Initializes the viewer context menu.
+ *
+ * @param viewSite view
+ */
+ private void initContextMenu(IViewSite viewSite) {
+ expandAllAction = new TestsHierarchyExpandAllAction(treeViewer);
+ collapseAllAction = new TestsHierarchyCollapseAllAction(treeViewer);
+ copyAction = new CopySelectedTestsAction(treeViewer, clipboard);
+ rerunAction = new RerunSelectedAction(testingSession, treeViewer);
+ redebugAction = new RedebugSelectedAction(testingSession, treeViewer);
+
+ MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
+ menuMgr.addMenuListener(new IMenuListener() {
+ @Override
+ public void menuAboutToShow(IMenuManager manager) {
+ handleMenuAboutToShow(manager);
+ }
+ });
+ viewSite.registerContextMenu(menuMgr, treeViewer);
+ Menu menu = menuMgr.createContextMenu(treeViewer.getTree());
+ treeViewer.getTree().setMenu(menu);
+
+ menuMgr.add(copyAction);
+ menuMgr.add(new Separator());
+ menuMgr.add(rerunAction);
+ menuMgr.add(redebugAction);
+ menuMgr.add(new Separator());
+ menuMgr.add(expandAllAction);
+ menuMgr.add(collapseAllAction);
+
+ IActionBars actionBars = viewSite.getActionBars();
+ actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(), copyAction);
+ actionBars.updateActionBars();
+ }
+
+ /**
+ * Handles the context menu showing.
+ *
+ * @param manager context menu manager
+ */
+ private void handleMenuAboutToShow(IMenuManager manager) {
+ IStructuredSelection selection = (IStructuredSelection)treeViewer.getSelection();
+ boolean isRelaunchEnabledForSelection = !selection.isEmpty() &&
+ (testingSession.getTestsRunnerProviderInfo().isAllowedMultipleTestFilter() || (selection.size() == 1));
+ rerunAction.setEnabled(isRelaunchEnabledForSelection);
+ rerunAction.setTestingSession(testingSession);
+ redebugAction.setEnabled(isRelaunchEnabledForSelection);
+ redebugAction.setTestingSession(testingSession);
+ copyAction.setEnabled(!selection.isEmpty());
+
+ boolean hasAnything = treeViewer.getInput() != null;
+ expandAllAction.setEnabled(hasAnything);
+ collapseAllAction.setEnabled(hasAnything);
+ }
+
+ /**
+ * Sets the testing session to show.
+ *
+ * @param testingSession testing session or null to set default empty
+ * session
+ */
+ public void setTestingSession(ITestingSession testingSession) {
+ this.testingSession = testingSession;
+ treeViewer.setInput(testingSession != null ? testingSession.getModelAccessor().getRootSuite() : null);
+ }
+
+ /**
+ * Provides access to the main widget of the tests hierarchy viewer.
+ *
+ * @return main widget of the tests hierarchy viewer
+ */
+ public TreeViewer getTreeViewer() {
+ return treeViewer;
+ }
+
+ /**
+ * Move the selection to the next failed test case.
+ */
+ public void showNextFailure() {
+ showFailure(true);
+ }
+
+ /**
+ * Move the selection to the previous failed test case.
+ */
+ public void showPreviousFailure() {
+ showFailure(false);
+ }
+
+ /**
+ * Common implementation for movement the selection to the next or previous
+ * failed test case.
+ *
+ * @param next true if the next failed test case should be selected and false otherwise
+ */
+ private void showFailure(boolean next) {
+ IStructuredSelection selection = (IStructuredSelection) getTreeViewer().getSelection();
+ ITestItem selected = (ITestItem) selection.getFirstElement();
+ ITestItem failedItem;
+
+ if (selected == null) {
+ ITestItem rootSuite = (ITestItem)treeViewer.getInput();
+ // For next element we should also check its children, for previous shouldn't.
+ failedItem = findFailedImpl(rootSuite, null, next, next);
+ } else {
+ // For next element we should also check its children, for previous shouldn't.
+ failedItem = findFailedImpl(selected.getParent(), selected, next, next);
+ }
+
+ if (failedItem != null)
+ getTreeViewer().setSelection(new StructuredSelection(failedItem), true);
+ }
+
+ /**
+ * Returns the next or previous failed test case relatively to the
+ * <code>currItem</code> that should be a child of <code>parentItem</code>.
+ * If the such item was not found through the children, it steps up to the
+ * parent and continues search.
+ *
+ * @param parentItem parent test item to the current one
+ * @param currItem current item search should be started from or null if
+ * there is no any
+ * @param next true if the next failed test case should be looked for and
+ * false otherwise
+ * @param checkCurrentChild specifies whether the search should be also
+ * through the children for the current item
+ * @return found item or null
+ */
+ private ITestItem findFailedImpl(ITestItem parentItem, ITestItem currItem, boolean next, boolean checkCurrentChild) {
+ ITestItem result = findFailedChild(parentItem, currItem, next, checkCurrentChild);
+ if (result != null) {
+ return result;
+ }
+ // Nothing found at this level - try to step up
+ ITestSuite grandParentItem = parentItem.getParent();
+ if (grandParentItem != null) {
+ return findFailedImpl(grandParentItem, parentItem, next, false);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the next or previous failed test case relatively to the
+ * <code>currItem</code> that should be a child of <code>parentItem</code>.
+ * Note that unlike <code>findFailedImpl()</code> this method search only
+ * through the children items.
+ *
+ * @param parentItem parent test item to the current one
+ * @param currItem current item search should be started from or null if
+ * there is no any
+ * @param next true if the next failed test case should be looked for and
+ * false otherwise
+ * @param checkCurrentChild specifies whether the search should be also
+ * through the children for the current item
+ * @return found item or null
+ */
+ private ITestItem findFailedChild(ITestItem parentItem, ITestItem currItem, boolean next, boolean checkCurrentChild) {
+ ITestItem[] children = parentItem.getChildren();
+ boolean doSearch = (currItem == null);
+ int increment = next ? 1 : -1;
+ int startIndex = next ? 0 : children.length-1;
+ int endIndex = next ? children.length : -1;
+ for (int index = startIndex; index != endIndex; index += increment) {
+ ITestItem item = children[index];
+ // Check element
+ if (doSearch) {
+ if (item instanceof ITestCase && item.getStatus().isError()) {
+ return item;
+ }
+ }
+ // If children of current element should be checked we should enable search here (if necessary)
+ if (checkCurrentChild && item == currItem) {
+ doSearch = true;
+ }
+ // Search element's children
+ if (doSearch) {
+ ITestItem result = findFailedChild(item, null, next, checkCurrentChild);
+ if (result != null) {
+ return result;
+ }
+ }
+ // If children of current element should NOT be checked we should enable search here
+ if (!checkCurrentChild && item == currItem) {
+ doSearch = true;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns whether test items execution time should be shown in tests
+ * hierarchy.
+ *
+ * @return true if time should be shown and false otherwise
+ */
+ public boolean showTime() {
+ return showTime;
+ }
+
+ /**
+ * Sets whether test items execution time should be shown in tests
+ * hierarchy. Updates tests hierarchy viewer if the view is changed.
+ *
+ * @param showTime true if time is shown and false otherwise
+ */
+ public void setShowTime(boolean showTime) {
+ if (this.showTime != showTime) {
+ this.showTime = showTime;
+ getTreeViewer().refresh();
+ }
+ }
+
+ /**
+ * Sets whether only failed tests should be shown.
+ *
+ * @param showFailedOnly new filter state
+ */
+ public void setShowFailedOnly(boolean showFailedOnly) {
+ // Create filter on first demand
+ if (failedOnlyFilter == null) {
+ failedOnlyFilter = new FailedOnlyFilter();
+ }
+ if (showFailedOnly) {
+ getTreeViewer().addFilter(failedOnlyFilter);
+ } else {
+ getTreeViewer().removeFilter(failedOnlyFilter);
+ }
+ }
+
+ /**
+ * Returns whether tests hierarchy should be shown in flat or hierarchical
+ * mode.
+ *
+ * @return tests hierarchy view mode
+ */
+ public boolean showTestsHierarchy() {
+ return showTestsHierarchy;
+ }
+
+ /**
+ * Sets whether tests hierarchy should be shown in flat or hierarchical
+ * mode. Updates tests hierarchy viewer if the view is changed.
+ *
+ * @param showTestsHierarchy true if tests hierarchy is shown in
+ * hierarchical mode and false otherwise
+ */
+ public void setShowTestsHierarchy(boolean showTestsHierarchy) {
+ if (this.showTestsHierarchy != showTestsHierarchy) {
+ this.showTestsHierarchy = showTestsHierarchy;
+ getTreeViewer().refresh();
+ }
+ }
+
+}

Back to the top