blob: 2accb114962fcc479b8d12e18a7f5cb8f7b672ae [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 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.dltk.internal.testing.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.debug.core.ILaunchManager;
import org.eclipse.dltk.internal.testing.model.TestCaseElement;
import org.eclipse.dltk.internal.testing.model.TestCategoryElement;
import org.eclipse.dltk.internal.testing.model.TestContainerElement;
import org.eclipse.dltk.internal.testing.model.TestElement;
import org.eclipse.dltk.internal.testing.model.TestElement.Status;
import org.eclipse.dltk.internal.testing.model.TestRoot;
import org.eclipse.dltk.internal.testing.model.TestRunSession;
import org.eclipse.dltk.internal.testing.model.TestSuiteElement;
import org.eclipse.dltk.testing.DLTKTestingMessages;
import org.eclipse.dltk.testing.ITestRunnerUI;
import org.eclipse.dltk.testing.ITestRunnerUIExtension;
import org.eclipse.dltk.testing.model.ITestElement;
import org.eclipse.dltk.ui.viewsupport.SelectionProviderMediator;
import org.eclipse.jface.action.Action;
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.DecoratingStyledCellLabelProvider;
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.ViewerComparator;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
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.ui.IWorkbenchActionConstants;
import org.eclipse.ui.part.PageBook;
public class TestViewer {
private final class TestSelectionListener
implements ISelectionChangedListener {
@Override
public void selectionChanged(SelectionChangedEvent event) {
handleSelected();
}
}
private final class TestOpenListener extends SelectionAdapter {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
handleDefaultSelected();
}
}
private final class FailuresOnlyFilter extends ViewerFilter {
@Override
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<T> extends AbstractList<T> {
private final List<T> fList;
public ReverseList(List<T> list) {
fList = list;
}
@Override
public T get(int index) {
return fList.get(fList.size() - index - 1);
}
@Override
public int size() {
return fList.size();
}
}
private class ExpandAllAction extends Action {
public ExpandAllAction() {
setText(DLTKTestingMessages.ExpandAllAction_text);
setToolTipText(DLTKTestingMessages.ExpandAllAction_tooltip);
}
@Override
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(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);
// ColoredViewersManager.install(fTreeViewer);
fTreeViewer.setUseHashlookup(true);
fTreeContentProvider = new TestSessionTreeContentProvider();
fTreeViewer.setContentProvider(fTreeContentProvider);
fTreeLabelProvider = new TestSessionLabelProvider(fTestRunnerPart,
TestRunnerViewPart.LAYOUT_HIERARCHICAL);
fTreeViewer.setLabelProvider(new DecoratingStyledCellLabelProvider(
fTreeLabelProvider, null, null));
fTreeViewer.setComparator(new TestTreeComparator());
fTableViewer = new TableViewer(fViewerbook,
SWT.V_SCROLL | SWT.H_SCROLL | SWT.SINGLE);
// ColoredViewersManager.install(fTableViewer);
fTableViewer.setUseHashlookup(true);
fTableContentProvider = new TestSessionTableContentProvider();
fTableViewer.setContentProvider(fTableContentProvider);
fTableLabelProvider = new TestSessionLabelProvider(fTestRunnerPart,
TestRunnerViewPart.LAYOUT_FLAT);
fTableViewer.setLabelProvider(new DecoratingStyledCellLabelProvider(
fTableLabelProvider, null, null));
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(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();
if (testElement instanceof TestSuiteElement) {
manager.add(new OpenTestAction(fTestRunnerPart, testElement));
manager.add(new Separator());
if (!fTestRunnerPart.lastLaunchIsKeptAlive()) {
addRerunActions(manager, testElement);
}
} else {
manager.add(new OpenTestAction(fTestRunnerPart, testElement));
manager.add(new Separator());
if (fTestRunnerPart.lastLaunchIsKeptAlive()) {
manager.add(new RerunAction(
DLTKTestingMessages.RerunAction_label_rerun,
fTestRunnerPart, testElement,
ILaunchManager.RUN_MODE));
} else {
addRerunActions(manager, testElement);
}
}
if (fLayoutMode == TestRunnerViewPart.LAYOUT_HIERARCHICAL) {
manager.add(new Separator());
manager.add(new ExpandAllAction());
}
}
if (fTestRunSession != null && fTestRunSession.getFailureCount()
+ fTestRunSession.getErrorCount() > 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 void addRerunActions(IMenuManager manager,
TestElement testElement) {
final boolean run, debug;
final ITestRunnerUI runnerUI = fTestRunSession.getTestRunnerUI();
if (runnerUI instanceof ITestRunnerUIExtension) {
final ITestRunnerUIExtension extension = (ITestRunnerUIExtension) runnerUI;
run = extension.canRerun(testElement, ILaunchManager.RUN_MODE);
debug = extension.canRerun(testElement, ILaunchManager.DEBUG_MODE);
} else {
run = runnerUI.canRerun(testElement);
debug = run;
}
if (run) {
manager.add(new RerunAction(
DLTKTestingMessages.RerunAction_label_run, fTestRunnerPart,
testElement, ILaunchManager.RUN_MODE));
}
if (debug) {
manager.add(new RerunAction(
DLTKTestingMessages.RerunAction_label_debug,
fTestRunnerPart, testElement, ILaunchManager.DEBUG_MODE));
}
}
public Control getTestViewerControl() {
return fViewerbook;
}
public synchronized void registerActiveSession(
TestRunSession testRunSession) {
fTestRunSession = testRunSession;
registerAutoScrollTarget(null);
registerViewersRefresh();
}
protected 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);
} else if (testElement instanceof TestCaseElement) {
action = new OpenTestAction(fTestRunnerPart, testElement);
} else if (testElement instanceof TestCategoryElement) {
return;
} 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 setShowTime(boolean showTime) {
try {
fViewerbook.setRedraw(false);
fTreeLabelProvider.setShowTime(showTime);
fTableLabelProvider.setShowTime(showTime);
} finally {
fViewerbook.setRedraw(true);
}
}
public synchronized void setShowFailuresOnly(boolean failuresOnly,
int layoutMode) {
/*
* Management of fTreeViewer and fTableViewer - invisible viewer is
* updated on registerViewerUpdate unless its fNeedsRefresh 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;
TestContainerElement 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<TestSuiteElement> iter = fAutoExpand.iterator(); iter
.hasNext();) {
TestSuiteElement suite = 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<TestSuiteElement> iter = fAutoClose
.listIterator(fAutoClose.size()); iter.hasPrevious();) {
TestSuiteElement previousAutoOpened = 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 TestContainerElement) {
TestElement nextChild = getNextChildFailure(
(TestContainerElement) selected, showNext);
if (nextChild != null)
return nextChild;
}
return getNextFailureSibling(selected, showNext);
}
private TestCaseElement getNextFailureSibling(TestElement current,
boolean showNext) {
TestContainerElement parent = current.getParent();
if (parent == null)
return null;
List<ITestElement> 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 if (sibling instanceof TestContainerElement) {
return getNextChildFailure((TestContainerElement) sibling,
showNext);
}
}
}
return getNextFailureSibling(parent, showNext);
}
private TestCaseElement getNextChildFailure(TestContainerElement root,
boolean showNext) {
List<ITestElement> 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 if (child instanceof TestContainerElement) {
return getNextChildFailure((TestContainerElement) 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);
}
public void expandFirstLevel() {
fTreeViewer.expandToLevel(2);
}
public void setSortDirection(int sortDirection) {
ViewerComparator comparator = fTreeViewer.getComparator();
if (comparator instanceof TestTreeComparator) {
((TestTreeComparator) comparator).setSortDirection(sortDirection);
}
}
}