blob: 51077cc49bdd0218b1bde0c5d531f4d03f2007ea [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2006 IBM Corporation 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
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.junit.ui;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.TableItem;
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.AbstractTreeViewer;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.part.PageBook;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.ui.viewsupport.SelectionProviderMediator;
import org.eclipse.jdt.internal.junit.model.TestCaseElement;
import org.eclipse.jdt.internal.junit.model.TestElement;
import org.eclipse.jdt.internal.junit.model.TestRoot;
import org.eclipse.jdt.internal.junit.model.TestRunSession;
import org.eclipse.jdt.internal.junit.model.TestSuiteElement;
import org.eclipse.jdt.internal.junit.model.TestElement.Status;
public class TestViewer {
private final class TestSelectionListener implements ISelectionChangedListener {
public void selectionChanged(SelectionChangedEvent event) {
handleSelected();
}
}
private final class TestOpenListener extends SelectionAdapter {
public void widgetDefaultSelected(SelectionEvent e) {
handleDefaultSelected();
}
}
private final class FailuresOnlyFilter extends ViewerFilter {
public boolean select(Viewer viewer, Object parentElement, Object element) {
return select(((TestElement) element));
}
public boolean select(TestElement testElement) {
Status status= testElement.getStatus();
if (status.isErrorOrFailure())
return true;
else
return ! fTestRunSession.isRunning() && status == Status.RUNNING; // rerunning
}
}
private static class ReverseList extends AbstractList {
private final List fList;
public ReverseList(List list) {
fList= list;
}
public Object get(int index) {
return fList.get(fList.size() - index - 1);
}
public int size() {
return fList.size();
}
}
private class ExpandAllAction extends Action {
public ExpandAllAction() {
setText(JUnitMessages.ExpandAllAction_text);
setToolTipText(JUnitMessages.ExpandAllAction_tooltip);
}
public void run(){
fTreeViewer.expandAll();
}
}
private final FailuresOnlyFilter fFailuresOnlyFilter= new FailuresOnlyFilter();
private final TestRunnerViewPart fTestRunnerPart;
private final Clipboard fClipboard;
private PageBook fViewerbook;
private TreeViewer fTreeViewer;
private TestSessionTreeContentProvider fTreeContentProvider;
private TestSessionLabelProvider fTreeLabelProvider;
private TableViewer fTableViewer;
private TestSessionTableContentProvider fTableContentProvider;
private TestSessionLabelProvider fTableLabelProvider;
private SelectionProviderMediator fSelectionProvider;
private final Image fHierarchyIcon;
private int fLayoutMode;
private boolean fTreeHasFilter;
private boolean fTableHasFilter;
private TestRunSession fTestRunSession;
private boolean fTreeNeedsRefresh;
private boolean fTableNeedsRefresh;
private HashSet/*<TestElement>*/ fNeedUpdate;
private TestCaseElement fAutoScrollTarget;
private LinkedList/*<TestSuiteElement>*/ fAutoClose;
private HashSet/*<TestSuite>*/ fAutoExpand;
public TestViewer(Composite parent, Clipboard clipboard, TestRunnerViewPart runner) {
fTestRunnerPart= runner;
fClipboard= clipboard;
fHierarchyIcon= TestRunnerViewPart.createImage("obj16/testhier.gif"); //$NON-NLS-1$
parent.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
disposeIcons();
}
});
fLayoutMode= TestRunnerViewPart.LAYOUT_HIERARCHICAL;
createTestViewers(parent);
registerViewersRefresh();
initContextMenu();
}
private void createTestViewers(Composite parent) {
fViewerbook= new PageBook(parent, SWT.NULL);
fTreeViewer= new TreeViewer(fViewerbook, SWT.V_SCROLL | SWT.SINGLE);
fTreeViewer.setUseHashlookup(true);
fTreeContentProvider= new TestSessionTreeContentProvider();
fTreeViewer.setContentProvider(fTreeContentProvider);
fTreeLabelProvider= new TestSessionLabelProvider(fTestRunnerPart, TestRunnerViewPart.LAYOUT_HIERARCHICAL);
fTreeViewer.setLabelProvider(fTreeLabelProvider);
fTableViewer= new TableViewer(fViewerbook, SWT.V_SCROLL | SWT.H_SCROLL | SWT.SINGLE);
fTableViewer.setUseHashlookup(true);
fTableContentProvider= new TestSessionTableContentProvider();
fTableViewer.setContentProvider(fTableContentProvider);
fTableLabelProvider= new TestSessionLabelProvider(fTestRunnerPart, TestRunnerViewPart.LAYOUT_FLAT);
fTableViewer.setLabelProvider(fTableLabelProvider);
fSelectionProvider= new SelectionProviderMediator(new StructuredViewer[] { fTreeViewer, fTableViewer }, fTreeViewer);
fSelectionProvider.addSelectionChangedListener(new TestSelectionListener());
TestOpenListener testOpenListener= new TestOpenListener();
fTreeViewer.getTree().addSelectionListener(testOpenListener);
fTableViewer.getTable().addSelectionListener(testOpenListener);
fViewerbook.showPage(fTreeViewer.getTree());
}
private void initContextMenu() {
MenuManager menuMgr= new MenuManager("#PopupMenu"); //$NON-NLS-1$
menuMgr.setRemoveAllWhenShown(true);
menuMgr.addMenuListener(new IMenuListener() {
public void menuAboutToShow(IMenuManager manager) {
handleMenuAboutToShow(manager);
}
});
fTestRunnerPart.getSite().registerContextMenu(menuMgr, fSelectionProvider);
Menu menu= menuMgr.createContextMenu(fViewerbook);
fTreeViewer.getTree().setMenu(menu);
fTableViewer.getTable().setMenu(menu);
}
void handleMenuAboutToShow(IMenuManager manager) {
IStructuredSelection selection= (IStructuredSelection) fSelectionProvider.getSelection();
if (! selection.isEmpty()) {
TestElement testElement= (TestElement) selection.getFirstElement();
String testLabel= testElement.getTestName();
String className= testElement.getClassName();
if (testElement instanceof TestSuiteElement) {
manager.add(new OpenTestAction(fTestRunnerPart, testLabel));
manager.add(new Separator());
if (testClassExists(className) && !fTestRunnerPart.lastLaunchIsKeptAlive()) {
manager.add(new RerunAction(fTestRunnerPart, testElement.getId(), className, null, ILaunchManager.RUN_MODE));
manager.add(new RerunAction(fTestRunnerPart, testElement.getId(), className, null, ILaunchManager.DEBUG_MODE));
}
} else {
TestCaseElement testCaseElement= (TestCaseElement) testElement;
String testMethodName= testCaseElement.getTestMethodName();
manager.add(new OpenTestAction(fTestRunnerPart, className, testMethodName));
manager.add(new Separator());
if (fTestRunnerPart.lastLaunchIsKeptAlive()) {
manager.add(new RerunAction(fTestRunnerPart, testElement.getId(), className, testMethodName, ILaunchManager.RUN_MODE));
} else {
manager.add(new RerunAction(fTestRunnerPart, testElement.getId(), className, testMethodName, ILaunchManager.RUN_MODE));
manager.add(new RerunAction(fTestRunnerPart, testElement.getId(), className, testMethodName, ILaunchManager.DEBUG_MODE));
}
}
if (fLayoutMode == TestRunnerViewPart.LAYOUT_HIERARCHICAL) {
manager.add(new Separator());
manager.add(new ExpandAllAction());
}
}
if (fTestRunSession != null && fTestRunSession.getFailureCount() > 0) {
if (fLayoutMode != TestRunnerViewPart.LAYOUT_HIERARCHICAL)
manager.add(new Separator());
manager.add(new CopyFailureListAction(fTestRunnerPart, fClipboard));
}
manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS + "-end")); //$NON-NLS-1$
}
private boolean testClassExists(String className) {
IJavaProject project= fTestRunnerPart.getLaunchedProject();
try {
IType type= project.findType(className);
return type != null;
} catch (JavaModelException e) {
// fall through
}
return false;
}
public Control getTestViewerControl() {
return fViewerbook;
}
public synchronized void registerActiveSession(TestRunSession testRunSession) {
fTestRunSession= testRunSession;
registerAutoScrollTarget(null);
registerViewersRefresh();
}
void handleDefaultSelected() {
IStructuredSelection selection= (IStructuredSelection) fSelectionProvider.getSelection();
if (selection.size() != 1)
return;
TestElement testElement= (TestElement) selection.getFirstElement();
OpenTestAction action;
if (testElement instanceof TestSuiteElement) {
action= new OpenTestAction(fTestRunnerPart, testElement.getTestName());
} else if (testElement instanceof TestCaseElement){
TestCaseElement testCase= (TestCaseElement) testElement;
action= new OpenTestAction(fTestRunnerPart, testCase.getClassName(), testCase.getTestMethodName());
} else {
throw new IllegalStateException(String.valueOf(testElement));
}
if (action.isEnabled())
action.run();
}
private void handleSelected() {
IStructuredSelection selection= (IStructuredSelection) fSelectionProvider.getSelection();
TestElement testElement= null;
if (selection.size() == 1) {
testElement= (TestElement) selection.getFirstElement();
}
fTestRunnerPart.handleTestSelected(testElement);
}
void disposeIcons() {
fHierarchyIcon.dispose();
}
public synchronized void setShowFailuresOnly(boolean failuresOnly, int layoutMode) {
/*
* Management of fTreeViewer and fTableViewer
* ******************************************
* - invisible viewer is updated on registerViewerUpdate unless its f*NeedsRefresh is true
* - invisible viewer is not refreshed upfront
* - on layout change, new viewer is refreshed if necessary
* - filter only applies to "current" layout mode / viewer
*/
try {
fViewerbook.setRedraw(false);
IStructuredSelection selection= null;
boolean switchLayout= layoutMode != fLayoutMode;
if (switchLayout) {
selection= (IStructuredSelection) fSelectionProvider.getSelection();
if (layoutMode == TestRunnerViewPart.LAYOUT_HIERARCHICAL) {
if (fTreeNeedsRefresh) {
clearUpdateAndExpansion();
}
} else {
if (fTableNeedsRefresh) {
clearUpdateAndExpansion();
}
}
fLayoutMode= layoutMode;
fViewerbook.showPage(getActiveViewer().getControl());
}
//avoid realizing all TableItems, especially in flat mode!
StructuredViewer viewer= getActiveViewer();
if (failuresOnly) {
if (! getActiveViewerHasFilter()) {
setActiveViewerHasFilter(true);
if (getActiveViewerNeedsRefresh())
viewer.setInput(null);
viewer.addFilter(fFailuresOnlyFilter);
}
} else {
if (getActiveViewerHasFilter()) {
setActiveViewerHasFilter(false);
if (getActiveViewerNeedsRefresh())
viewer.setInput(null);
viewer.removeFilter(fFailuresOnlyFilter);
}
}
processChangesInUI();
if (selection != null) {
// workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=125708
// (ITreeSelection not adapted if TreePaths changed):
StructuredSelection flatSelection= new StructuredSelection(selection.toList());
fSelectionProvider.setSelection(flatSelection, true);
}
} finally {
fViewerbook.setRedraw(true);
}
}
private boolean getActiveViewerHasFilter() {
if (fLayoutMode == TestRunnerViewPart.LAYOUT_HIERARCHICAL)
return fTreeHasFilter;
else
return fTableHasFilter;
}
private void setActiveViewerHasFilter(boolean filter) {
if (fLayoutMode == TestRunnerViewPart.LAYOUT_HIERARCHICAL)
fTreeHasFilter= filter;
else
fTableHasFilter= filter;
}
private StructuredViewer getActiveViewer() {
if (fLayoutMode == TestRunnerViewPart.LAYOUT_HIERARCHICAL)
return fTreeViewer;
else
return fTableViewer;
}
private boolean getActiveViewerNeedsRefresh() {
if (fLayoutMode == TestRunnerViewPart.LAYOUT_HIERARCHICAL)
return fTreeNeedsRefresh;
else
return fTableNeedsRefresh;
}
private void setActiveViewerRefreshed() {
if (fLayoutMode == TestRunnerViewPart.LAYOUT_HIERARCHICAL)
fTreeNeedsRefresh= false;
else
fTableNeedsRefresh= false;
}
/**
* To be called periodically by the TestRunnerViewPart (in the UI thread).
*/
public void processChangesInUI() {
TestRoot testRoot;
if (fTestRunSession == null) {
registerViewersRefresh();
fTreeNeedsRefresh= false;
fTableNeedsRefresh= false;
fTreeViewer.setInput(null);
fTableViewer.setInput(null);
return;
}
testRoot= fTestRunSession.getTestRoot();
StructuredViewer viewer= getActiveViewer();
if (getActiveViewerNeedsRefresh()) {
clearUpdateAndExpansion();
setActiveViewerRefreshed();
viewer.setInput(testRoot);
} else {
Object[] toUpdate;
synchronized (this) {
toUpdate= fNeedUpdate.toArray();
fNeedUpdate.clear();
}
if (! fTreeNeedsRefresh && toUpdate.length > 0) {
if (fTreeHasFilter)
for (int i= 0; i < toUpdate.length; i++)
updateElementInTree((TestElement) toUpdate[i]);
else {
HashSet toUpdateWithParents= new HashSet();
toUpdateWithParents.addAll(Arrays.asList(toUpdate));
for (int i= 0; i < toUpdate.length; i++) {
TestElement parent= ((TestElement) toUpdate[i]).getParent();
while (parent != null) {
toUpdateWithParents.add(parent);
parent= parent.getParent();
}
}
fTreeViewer.update(toUpdateWithParents.toArray(), null);
}
}
if (! fTableNeedsRefresh && toUpdate.length > 0) {
if (fTableHasFilter)
for (int i= 0; i < toUpdate.length; i++)
updateElementInTable((TestElement) toUpdate[i]);
else
fTableViewer.update(toUpdate, null);
}
}
autoScrollInUI();
}
private void updateElementInTree(final TestElement testElement) {
if (isShown(testElement)) {
updateShownElementInTree(testElement);
} else {
TestElement current= testElement;
do {
if (fTreeViewer.testFindItem(current) != null)
fTreeViewer.remove(current);
current= current.getParent();
} while (! (current instanceof TestRoot) && ! isShown(current));
while (current != null && ! (current instanceof TestRoot)) {
fTreeViewer.update(current, null);
current= current.getParent();
}
}
}
private void updateShownElementInTree(TestElement testElement) {
if (testElement == null || testElement instanceof TestRoot) // paranoia null check
return;
TestSuiteElement parent= testElement.getParent();
updateShownElementInTree(parent); // make sure parent is shown and up-to-date
if (fTreeViewer.testFindItem(testElement) == null) {
fTreeViewer.add(parent, testElement); // if not yet in tree: add
} else {
fTreeViewer.update(testElement, null); // if in tree: update
}
}
private void updateElementInTable(TestElement element) {
if (isShown(element)) {
if (fTableViewer.testFindItem(element) == null) {
TestElement previous= getNextFailure(element, false);
int insertionIndex= -1;
if (previous != null) {
TableItem item= (TableItem) fTableViewer.testFindItem(previous);
if (item != null)
insertionIndex= fTableViewer.getTable().indexOf(item);
}
fTableViewer.insert(element, insertionIndex);
} else {
fTableViewer.update(element, null);
}
} else {
fTableViewer.remove(element);
}
}
private boolean isShown(TestElement current) {
return fFailuresOnlyFilter.select(current);
}
private void autoScrollInUI() {
if (! fTestRunnerPart.isAutoScroll()) {
clearAutoExpand();
fAutoClose.clear();
return;
}
if (fLayoutMode == TestRunnerViewPart.LAYOUT_FLAT) {
if (fAutoScrollTarget != null)
fTableViewer.reveal(fAutoScrollTarget);
return;
}
synchronized (this) {
for (Iterator iter= fAutoExpand.iterator(); iter.hasNext();) {
TestSuiteElement suite= (TestSuiteElement) iter.next();
fTreeViewer.setExpandedState(suite, true);
}
clearAutoExpand();
}
TestCaseElement current= fAutoScrollTarget;
fAutoScrollTarget= null;
TestSuiteElement parent= current == null ? null : (TestSuiteElement) fTreeContentProvider.getParent(current);
if (fAutoClose.isEmpty() || ! fAutoClose.getLast().equals(parent)) {
// we're in a new branch, so let's close old OK branches:
for (ListIterator iter= fAutoClose.listIterator(fAutoClose.size()); iter.hasPrevious();) {
TestSuiteElement previousAutoOpened= (TestSuiteElement) iter.previous();
if (previousAutoOpened.equals(parent))
break;
if (previousAutoOpened.getStatus() == TestElement.Status.OK) {
// auto-opened the element, and all children are OK -> auto close
iter.remove();
fTreeViewer.collapseToLevel(previousAutoOpened, AbstractTreeViewer.ALL_LEVELS);
}
}
while (parent != null && ! fTestRunSession.getTestRoot().equals(parent) && fTreeViewer.getExpandedState(parent) == false) {
fAutoClose.add(parent); // add to auto-opened elements -> close later if STATUS_OK
parent= (TestSuiteElement) fTreeContentProvider.getParent(parent);
}
}
if (current != null)
fTreeViewer.reveal(current);
}
public void selectFirstFailure() {
TestCaseElement firstFailure= getNextChildFailure(fTestRunSession.getTestRoot(), true);
if (firstFailure != null)
getActiveViewer().setSelection(new StructuredSelection(firstFailure), true);
}
public void selectFailure(boolean showNext) {
IStructuredSelection selection= (IStructuredSelection) getActiveViewer().getSelection();
TestElement selected= (TestElement) selection.getFirstElement();
TestElement next;
if (selected == null) {
next= getNextChildFailure(fTestRunSession.getTestRoot(), showNext);
} else {
next= getNextFailure(selected, showNext);
}
if (next != null)
getActiveViewer().setSelection(new StructuredSelection(next), true);
}
private TestElement getNextFailure(TestElement selected, boolean showNext) {
if (selected instanceof TestSuiteElement) {
TestElement nextChild= getNextChildFailure((TestSuiteElement) selected, showNext);
if (nextChild != null)
return nextChild;
}
return getNextFailureSibling(selected, showNext);
}
private TestCaseElement getNextFailureSibling(TestElement current, boolean showNext) {
TestSuiteElement parent= current.getParent();
if (parent == null)
return null;
List siblings= Arrays.asList(parent.getChildren());
if (! showNext)
siblings= new ReverseList(siblings);
int nextIndex= siblings.indexOf(current) + 1;
for (int i= nextIndex; i < siblings.size(); i++) {
TestElement sibling= (TestElement) siblings.get(i);
if (sibling.getStatus().isErrorOrFailure()) {
if (sibling instanceof TestCaseElement) {
return (TestCaseElement) sibling;
} else {
return getNextChildFailure((TestSuiteElement) sibling, showNext);
}
}
}
return getNextFailureSibling(parent, showNext);
}
private TestCaseElement getNextChildFailure(TestSuiteElement root, boolean showNext) {
List children= Arrays.asList(root.getChildren());
if (! showNext)
children= new ReverseList(children);
for (int i= 0; i < children.size(); i++) {
TestElement child= (TestElement) children.get(i);
if (child.getStatus().isErrorOrFailure()) {
if (child instanceof TestCaseElement) {
return (TestCaseElement) child;
} else {
return getNextChildFailure((TestSuiteElement) child, showNext);
}
}
}
return null;
}
public synchronized void registerViewersRefresh() {
fTreeNeedsRefresh= true;
fTableNeedsRefresh= true;
clearUpdateAndExpansion();
}
private void clearUpdateAndExpansion() {
fNeedUpdate= new LinkedHashSet();
fAutoClose= new LinkedList();
fAutoExpand= new HashSet();
}
public synchronized void registerTestAdded(TestElement testElement) {
//TODO: performance: would only need to refresh parent of added element
fTreeNeedsRefresh= true;
fTableNeedsRefresh= true;
}
public synchronized void registerViewerUpdate(final TestElement testElement) {
fNeedUpdate.add(testElement);
}
private synchronized void clearAutoExpand() {
fAutoExpand.clear();
}
public void registerAutoScrollTarget(TestCaseElement testCaseElement) {
fAutoScrollTarget= testCaseElement;
}
public synchronized void registerFailedForAutoScroll(TestElement testElement) {
Object parent= fTreeContentProvider.getParent(testElement);
if (parent != null)
fAutoExpand.add(parent);
}
}