/******************************************************************************* * Copyright (c) 2000, 2004 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Common Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/cpl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Sebastian Davids - sdavids@gmx.de bugs 26754, 41228 *******************************************************************************/ package org.rubypeople.rdt.internal.testunit.ui; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Vector; import org.eclipse.core.runtime.IStatus; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; 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.swt.SWT; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.custom.CTabItem; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.rubypeople.rdt.core.RubyConventions; import org.rubypeople.rdt.testunit.ITestRunListener; /* * A view that shows the contents of a test suite as a tree. */ public class TestHierarchyTab extends TestRunTab implements IMenuListener { private Tree fTree; private TreeItem fCachedParent; private TreeItem[] fCachedItems; private TreeItem fLastParent; private List<TreeItem> fExecutionPath; private boolean fMoveSelection = false; /** * Helper used to resurrect test hierarchy */ private static class SuiteInfo { public int fTestCount; public TreeItem fTreeItem; public SuiteInfo(TreeItem treeItem, int testCount) { fTreeItem = treeItem; fTestCount = testCount; } } /** * Vector of SuiteInfo items */ private Vector<SuiteInfo> fSuiteInfos = new Vector<SuiteInfo>(); /** * Maps test Ids to TreeItems. */ private Map<String, TreeItem> fTreeItemMap = new HashMap<String, TreeItem>(); private TestUnitView fTestRunnerPart; private final Image fOkIcon = TestUnitView.createImage("obj16/testok.gif"); //$NON-NLS-1$ private final Image fErrorIcon = TestUnitView.createImage("obj16/testerr.gif"); //$NON-NLS-1$ private final Image fFailureIcon = TestUnitView.createImage("obj16/testfail.gif"); //$NON-NLS-1$ private final Image fHierarchyIcon = TestUnitView.createImage("obj16/testhier.gif"); //$NON-NLS-1$ private final Image fSuiteIcon = TestUnitView.createImage("obj16/tsuite.gif"); //$NON-NLS-1$ private final Image fSuiteErrorIcon = TestUnitView.createImage("obj16/tsuiteerror.gif"); //$NON-NLS-1$ private final Image fSuiteFailIcon = TestUnitView.createImage("obj16/tsuitefail.gif"); //$NON-NLS-1$ private final Image fTestIcon = TestUnitView.createImage("obj16/test.gif"); //$NON-NLS-1$ private final Image fTestRunningIcon = TestUnitView.createImage("obj16/testrun.gif"); //$NON-NLS-1$ private final Image fSuiteRunningIcon = TestUnitView.createImage("obj16/tsuiterun.gif"); //$NON-NLS-1$ private class ExpandAllAction extends Action { public ExpandAllAction() { setText(TestUnitMessages.ExpandAllAction_text); setToolTipText(TestUnitMessages.ExpandAllAction_tooltip); } public void run() { expandAll(); } } public TestHierarchyTab() {} public void createTabControl(CTabFolder tabFolder, Clipboard clipboard, TestUnitView runner) { fTestRunnerPart = runner; CTabItem hierarchyTab = new CTabItem(tabFolder, SWT.NONE); hierarchyTab.setText(getName()); hierarchyTab.setImage(fHierarchyIcon); Composite testTreePanel = new Composite(tabFolder, SWT.NONE); GridLayout gridLayout = new GridLayout(); gridLayout.marginHeight = 0; gridLayout.marginWidth = 0; testTreePanel.setLayout(gridLayout); GridData gridData = new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL); testTreePanel.setLayoutData(gridData); hierarchyTab.setControl(testTreePanel); hierarchyTab.setToolTipText(TestUnitMessages.HierarchyRunView_tab_tooltip); fTree = new Tree(testTreePanel, SWT.V_SCROLL | SWT.SINGLE); gridData = new GridData(GridData.FILL_BOTH | GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL); fTree.setLayoutData(gridData); initMenu(); addListeners(); } void disposeIcons() { fErrorIcon.dispose(); fFailureIcon.dispose(); fOkIcon.dispose(); fHierarchyIcon.dispose(); fTestIcon.dispose(); fTestRunningIcon.dispose(); fSuiteRunningIcon.dispose(); fSuiteIcon.dispose(); fSuiteErrorIcon.dispose(); fSuiteFailIcon.dispose(); } private void initMenu() { MenuManager menuMgr = new MenuManager(); menuMgr.setRemoveAllWhenShown(true); menuMgr.addMenuListener(this); Menu menu = menuMgr.createContextMenu(fTree); fTree.setMenu(menu); } private String getTestMethod() { return getTestInfo().getTestMethodName(); } private TestRunInfo getTestInfo() { TreeItem[] treeItems = fTree.getSelection(); if (treeItems.length == 0) return null; return ((TestRunInfo) treeItems[0].getData()); } private boolean isSuiteSelected() { TreeItem[] treeItems = fTree.getSelection(); if (treeItems.length != 1) return false; return treeItems[0].getItemCount() > 0; } private String getClassName() { return getTestInfo().getClassName(); } public String getSelectedTestId() { TestRunInfo testInfo = getTestInfo(); if (testInfo == null) return null; return testInfo.getTestId(); } public String getName() { return TestUnitMessages.HierarchyRunView_tab_title; } public void setSelectedTest(String testId) { TreeItem treeItem = findTreeItem(testId); if (treeItem != null) fTree.setSelection(new TreeItem[] { treeItem}); } public void startTest(String testId) { TreeItem treeItem = findTreeItem(testId); if (treeItem == null) return; TreeItem parent = treeItem.getParentItem(); if (fLastParent != parent) { updatePath(parent); fLastParent = parent; } setCurrentItem(treeItem); } private void updatePath(TreeItem parent) { List<TreeItem> newPath = new ArrayList<TreeItem>(); while (parent != null) { newPath.add(parent); parent = parent.getParentItem(); } Collections.reverse(newPath); // common path ListIterator<TreeItem> old = fExecutionPath.listIterator(); ListIterator<TreeItem> np = newPath.listIterator(); int c = 0; while (old.hasNext() && np.hasNext()) { if (old.next() != np.next()) break; c++; } // clear old path for (ListIterator<TreeItem> iter = fExecutionPath.listIterator(c); iter.hasNext();) refreshItem(iter.next(), false); // update new path for (ListIterator<TreeItem> iter = newPath.listIterator(c); iter.hasNext();) refreshItem(iter.next(), true); fExecutionPath = newPath; } private void refreshItem(TreeItem item, boolean onPath) { if (onPath) item.setImage(fSuiteRunningIcon); else { TestRunInfo info = getTestRunInfo(item); switch (info.getStatus()) { case ITestRunListener.STATUS_ERROR: item.setImage(fSuiteErrorIcon); break; case ITestRunListener.STATUS_FAILURE: item.setImage(fSuiteFailIcon); break; default: item.setImage(fSuiteIcon); } } } private void setCurrentItem(TreeItem treeItem) { treeItem.setImage(fTestRunningIcon); TreeItem parent = treeItem.getParentItem(); if (fTestRunnerPart.isAutoScroll()) { fTree.showItem(treeItem); while (parent != null) { if (parent.getExpanded()) break; parent.setExpanded(true); parent = parent.getParentItem(); } } } public void endTest(String testId) { TreeItem treeItem = findTreeItem(testId); if (treeItem == null) return; TestRunInfo testInfo = fTestRunnerPart.getTestInfo(testId); // fix for 61709 NPE in JUnit view plus strange behavior // the testInfo map can already be destroyed at this point if (testInfo == null) return; updateItem(treeItem, testInfo); if (fTestRunnerPart.isAutoScroll()) { fTree.showItem(treeItem); cacheItems(treeItem); collapsePassedTests(treeItem); } } private void cacheItems(TreeItem treeItem) { TreeItem parent = treeItem.getParentItem(); if (parent == fCachedParent) return; fCachedItems = parent.getItems(); fCachedParent = parent; } private void collapsePassedTests(TreeItem treeItem) { TreeItem parent = treeItem.getParentItem(); if (parent != null) { TreeItem[] items = null; if (parent == fCachedParent) items = fCachedItems; else items = parent.getItems(); if (isLast(treeItem, items)) { boolean ok = true; for (int i = 0; i < items.length; i++) { if (isFailure(items[i])) { ok = false; break; } } if (ok) { parent.setExpanded(false); collapsePassedTests(parent); } } } } private boolean isLast(TreeItem treeItem, TreeItem[] items) { return items[items.length - 1] == treeItem; } private void updateItem(TreeItem treeItem, TestRunInfo testInfo) { treeItem.setData(testInfo); if (testInfo.getStatus() == ITestRunListener.STATUS_OK) { treeItem.setImage(fOkIcon); return; } if (testInfo.getStatus() == ITestRunListener.STATUS_FAILURE) treeItem.setImage(fFailureIcon); else if (testInfo.getStatus() == ITestRunListener.STATUS_ERROR) treeItem.setImage(fErrorIcon); propagateStatus(treeItem, testInfo.getStatus()); } private void propagateStatus(TreeItem item, int status) { TreeItem parent = item.getParentItem(); TestRunInfo testRunInfo = getTestRunInfo(item); if (parent == null) return; TestRunInfo parentInfo = getTestRunInfo(parent); int parentStatus = parentInfo.getStatus(); if (status == ITestRunListener.STATUS_FAILURE) { if (parentStatus == ITestRunListener.STATUS_ERROR || parentStatus == ITestRunListener.STATUS_FAILURE) return; parentInfo.setStatus(ITestRunListener.STATUS_FAILURE); testRunInfo.setStatus(ITestRunListener.STATUS_FAILURE); } else { if (parentStatus == ITestRunListener.STATUS_ERROR) return; parentInfo.setStatus(ITestRunListener.STATUS_ERROR); testRunInfo.setStatus(ITestRunListener.STATUS_ERROR); } propagateStatus(parent, status); } private TestRunInfo getTestRunInfo(TreeItem item) { return (TestRunInfo) item.getData(); } public void activate() { fMoveSelection = false; testSelected(); } public void setFocus() { fTree.setFocus(); } public void aboutToStart() { fTree.removeAll(); fSuiteInfos.removeAllElements(); fTreeItemMap = new HashMap<String, TreeItem>(); fCachedParent = null; fCachedItems = null; fMoveSelection = false; fExecutionPath = new ArrayList<TreeItem>(); } private void testSelected() { fTestRunnerPart.handleTestSelected(getSelectedTestId()); } private void addListeners() { fTree.addSelectionListener(new SelectionListener() { public void widgetSelected(SelectionEvent e) { activate(); } public void widgetDefaultSelected(SelectionEvent e) { activate(); } }); fTree.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { disposeIcons(); } }); fTree.addMouseListener(new MouseAdapter() { public void mouseDoubleClick(MouseEvent e) { handleDoubleClick(e); } }); } void handleDoubleClick(MouseEvent e) { TestRunInfo testInfo= getTestInfo(); if (testInfo == null) return; IAction action = null; if (isSuiteSelected()) action= new OpenTestAction(fTestRunnerPart, getClassName()); else action= new OpenTestAction(fTestRunnerPart, getClassName(), getTestMethod()); if (action != null && action.isEnabled()) action.run(); } public void menuAboutToShow(IMenuManager manager) { if (fTree.getSelectionCount() > 0) { if (isSuiteSelected()) { manager.add(new OpenTestAction(fTestRunnerPart, getClassName())); manager.add(new Separator()); if (testClassExists(getClassName()) && !fTestRunnerPart.lastLaunchIsKeptAlive()) { manager.add(new RerunAction(fTestRunnerPart, getSelectedTestId(), getClassName(), null, ILaunchManager.RUN_MODE)); manager.add(new RerunAction(fTestRunnerPart, getSelectedTestId(), getClassName(), null, ILaunchManager.DEBUG_MODE)); } } else { manager.add(new OpenTestAction(fTestRunnerPart, getClassName(), getTestMethod(), true)); manager.add(new Separator()); manager.add(new RerunAction(fTestRunnerPart, getSelectedTestId(), getClassName(), getTestMethod(), ILaunchManager.RUN_MODE)); manager.add(new RerunAction(fTestRunnerPart, getSelectedTestId(), getClassName(), getTestMethod(), ILaunchManager.DEBUG_MODE)); } manager.add(new Separator()); manager.add(new ExpandAllAction()); } } private boolean testClassExists(String className) { IStatus status = RubyConventions.validateRubyTypeName(className); return status.isOK(); } public void newTreeEntry(String treeEntry) { // format: testId","testName","isSuite","testcount" String[] parts = treeEntry.split(","); String testId = parts[0]; String testName = parts[1]; Boolean isSuite = false; int testCount = 1; try { isSuite = Boolean.parseBoolean(parts[2]); testCount = Integer.parseInt(parts[3]); } catch (NumberFormatException e) { TestunitPlugin.log("Was unable to parse test count from full input: " + treeEntry); TestunitPlugin.log(e); } TestRunInfo testInfo = new TestRunInfo(testId, testName); TreeItem treeItem; while ((fSuiteInfos.size() > 0) && ((fSuiteInfos.lastElement()).fTestCount == 0)) { fSuiteInfos.removeElementAt(fSuiteInfos.size() - 1); } if (fSuiteInfos.size() == 0) { treeItem = new TreeItem(fTree, SWT.NONE); treeItem.setImage(fSuiteIcon); fSuiteInfos.addElement(new SuiteInfo(treeItem, testCount)); } else if (isSuite) { //$NON-NLS-1$ treeItem = new TreeItem((fSuiteInfos.lastElement()).fTreeItem, SWT.NONE); treeItem.setImage(fSuiteIcon); (fSuiteInfos.lastElement()).fTestCount -= 1; fSuiteInfos.addElement(new SuiteInfo(treeItem, testCount)); } else { treeItem = new TreeItem((fSuiteInfos.lastElement()).fTreeItem, SWT.NONE); treeItem.setImage(fTestIcon); (fSuiteInfos.lastElement()).fTestCount -= 1; mapTest(testInfo, treeItem); } treeItem.setText(testInfo.getTestMethodName()); treeItem.setData(testInfo); } private void mapTest(TestRunInfo info, TreeItem item) { fTreeItemMap.put(info.getTestId(), item); } private TreeItem findTreeItem(String testId) { Object o = fTreeItemMap.get(testId); if (o instanceof TreeItem) return (TreeItem) o; return null; } /* * @see ITestRunView#testStatusChanged(TestRunInfo, int) */ public void testStatusChanged(TestRunInfo newInfo) { Object o = fTreeItemMap.get(newInfo.getTestId()); if (o instanceof TreeItem) { updateItem((TreeItem) o, newInfo); return; } } /* * (non-Javadoc) * * @see org.eclipse.jdt.internal.junit.ui.ITestRunView#selectNext() */ public void selectNext() { TreeItem selection = getInitialSearchSelection(); if (!moveSelection(selection)) return; TreeItem failure = findFailure(selection, true, !isLeafFailure(selection)); if (failure != null) selectTest(failure); } /* * (non-Javadoc) * * @see org.eclipse.jdt.internal.junit.ui.ITestRunView#selectPrevious() */ public void selectPrevious() { TreeItem selection = getInitialSearchSelection(); if (!moveSelection(selection)) return; TreeItem failure = findFailure(selection, false, !isLeafFailure(selection)); if (failure != null) selectTest(failure); } private boolean moveSelection(TreeItem selection) { if (!fMoveSelection) { fMoveSelection = true; if (isLeafFailure(selection)) { selectTest(selection); return false; } } return true; } private TreeItem getInitialSearchSelection() { TreeItem[] treeItems = fTree.getSelection(); TreeItem selection = null; if (treeItems.length == 0) selection = fTree.getItems()[0]; else selection = treeItems[0]; return selection; } private boolean isFailure(TreeItem selection) { return !(getTestRunInfo(selection).getStatus() == ITestRunListener.STATUS_OK); } private boolean isLeafFailure(TreeItem selection) { boolean isLeaf = selection.getItemCount() == 0; return isLeaf && isFailure(selection); } private void selectTest(TreeItem selection) { fTestRunnerPart.showTest(getTestRunInfo(selection)); } private TreeItem findFailure(TreeItem start, boolean next, boolean includeNode) { TreeItem[] sib = findSiblings(start, next, includeNode); if (next) { for (int i = 0; i < sib.length; i++) { TreeItem failure = findFailureInTree(sib[i]); if (failure != null) return failure; } } else { for (int i = sib.length - 1; i >= 0; i--) { TreeItem failure = findFailureInTree(sib[i]); if (failure != null) return failure; } } TreeItem parent = start.getParentItem(); if (parent == null) return null; return findFailure(parent, next, false); } private TreeItem[] findSiblings(TreeItem item, boolean next, boolean includeNode) { TreeItem parent = item.getParentItem(); TreeItem[] children = null; if (parent == null) children = item.getParent().getItems(); else children = parent.getItems(); for (int i = 0; i < children.length; i++) { TreeItem item2 = children[i]; if (item2 == item) { TreeItem[] result = null; if (next) { if (!includeNode) { result = new TreeItem[children.length - i - 1]; System.arraycopy(children, i + 1, result, 0, children.length - i - 1); } else { result = new TreeItem[children.length - i]; System.arraycopy(children, i, result, 0, children.length - i); } } else { if (!includeNode) { result = new TreeItem[i]; System.arraycopy(children, 0, result, 0, i); } else { result = new TreeItem[i + 1]; System.arraycopy(children, 0, result, 0, i + 1); } } return result; } } return new TreeItem[0]; } private TreeItem findFailureInTree(TreeItem item) { if (item.getItemCount() == 0) { if (isFailure(item)) return item; } TreeItem[] children = item.getItems(); for (int i = 0; i < children.length; i++) { TreeItem item2 = findFailureInTree(children[i]); if (item2 != null) return item2; } return null; } protected void expandAll() { TreeItem[] treeItems = fTree.getSelection(); fTree.setRedraw(false); for (int i = 0; i < treeItems.length; i++) { expandAll(treeItems[i]); } fTree.setRedraw(true); } private void expandAll(TreeItem item) { item.setExpanded(true); TreeItem[] items = item.getItems(); for (int i = 0; i < items.length; i++) { expandAll(items[i]); } } public void aboutToEnd() { for (int i = 0; i < fExecutionPath.size(); i++) { refreshItem(fExecutionPath.get(i), false); } } }