/******************************************************************************* * Copyright (c) 2017 Rogue Wave Software Inc. 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: * Rogue Wave Software Inc. - initial implementation *******************************************************************************/ package org.eclipse.php.phpunit.ui.view; import java.util.*; import org.eclipse.core.resources.IProject; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.dltk.core.DLTKCore; import org.eclipse.dltk.core.IType; import org.eclipse.dltk.core.index2.search.ISearchEngine.MatchRule; import org.eclipse.dltk.core.search.SearchEngine; import org.eclipse.dltk.ui.viewsupport.SelectionProviderMediator; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.viewers.*; import org.eclipse.php.internal.core.model.PHPModelAccess; import org.eclipse.php.phpunit.PHPUnitPlugin; import org.eclipse.php.phpunit.model.elements.*; import org.eclipse.php.phpunit.model.providers.PHPUnitElementTreeContentProvider; import org.eclipse.php.phpunit.ui.view.actions.OpenTestAction; import org.eclipse.php.phpunit.ui.view.actions.RerunAction; import org.eclipse.swt.SWT; 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.ui.IWorkbenchActionConstants; import org.eclipse.ui.part.PageBook; public class TestViewer { private final class FailuresOnlyFilter extends ViewerFilter { public boolean select(final PHPUnitElement testInfo) { if (testInfo instanceof PHPUnitTestGroup) { final PHPUnitTestGroup testGroup = (PHPUnitTestGroup) testInfo; if (testGroup.getRunCount() > testGroup.getTotalCount()) return true; if (testInfo == PHPUnitElementManager.getInstance().getRoot()) return true; } if (testInfo instanceof PHPUnitTest && ((PHPUnitTest) testInfo).getStatus() >= PHPUnitTest.STATUS_FAIL) { return true; } return false; } @Override public boolean select(final Viewer viewer, final Object parentElement, final Object element) { return select(((PHPUnitElement) element)); } } private final class TestSelectionListener implements ISelectionChangedListener { @Override public void selectionChanged(final SelectionChangedEvent event) { handleSelected(); } private void handleSelected() { final IStructuredSelection selection = (IStructuredSelection) fSelectionProvider.getSelection(); PHPUnitElement test = null; if (selection.size() == 1) { test = (PHPUnitElement) selection.getFirstElement(); } view.handleTestSelected(test); } } private LinkedList<Object>/* <TestSuiteElement> */ fAutoClose; private Set<Object> fAutoExpand; private PHPUnitTestCase fAutoScrollTarget; private final FailuresOnlyFilter fFailuresOnlyFilter = new FailuresOnlyFilter(); private final Image fHierarchyIcon; private Set<PHPUnitElement> fNeedUpdate; private IPostSelectionProvider fSelectionProvider; private PHPUnitElementTreeContentProvider fTreeContentProvider; private boolean fTreeHasFilter; private boolean fTreeNeedsRefresh; private TreeViewer fTreeViewer; private PageBook fViewerbook; private PHPUnitTestGroup testRoot; private final PHPUnitView view; public TestViewer(final Composite parent, final PHPUnitView runner) { view = runner; fHierarchyIcon = PHPUnitPlugin.createImage("obj16/testhier.png"); //$NON-NLS-1$ parent.addDisposeListener(e -> disposeIcons()); createTestViewers(parent); registerViewersRefresh(); initContextMenu(); } private void autoScrollInUI() { if (!view.isAutoScroll()) { clearAutoExpand(); fAutoClose.clear(); return; } synchronized (this) { for (final Iterator iter = fAutoExpand.iterator(); iter.hasNext();) { final PHPUnitTestGroup suite = (PHPUnitTestGroup) iter.next(); fTreeViewer.setExpandedState(suite, true); } clearAutoExpand(); } final PHPUnitTestCase current = fAutoScrollTarget; fAutoScrollTarget = null; PHPUnitTestGroup parent = current == null ? null : (PHPUnitTestGroup) fTreeContentProvider.getParent(current); if (fAutoClose.isEmpty() || !fAutoClose.getLast().equals(parent)) { // we're in a new branch, so let's close old OK branches: for (final ListIterator<Object> iter = fAutoClose.listIterator(fAutoClose.size()); iter.hasPrevious();) { final PHPUnitTestGroup previousAutoOpened = (PHPUnitTestGroup) iter.previous(); if (previousAutoOpened.equals(parent)) break; if (previousAutoOpened.getStatus() == PHPUnitTest.STATUS_PASS) { // auto-opened the element, and all children are OK -> auto // close iter.remove(); fTreeViewer.collapseToLevel(previousAutoOpened, AbstractTreeViewer.ALL_LEVELS); } } while (parent != null && !testRoot.equals(parent) && fTreeViewer.getExpandedState(parent) == false) { fAutoClose.add(parent); // add to auto-opened elements -> close // later if STATUS_OK parent = (PHPUnitTestGroup) fTreeContentProvider.getParent(parent); } } if (current != null) { fTreeViewer.reveal(current); } } private synchronized void clearAutoExpand() { fAutoExpand.clear(); } private void clearUpdateAndExpansion() { fNeedUpdate = new LinkedHashSet<>(); fAutoClose = new LinkedList<>(); fAutoExpand = new HashSet<>(); } public void collapseAll() { fTreeViewer.collapseAll(); fTreeViewer.refresh(); } private void createTestViewers(final Composite parent) { fViewerbook = new PageBook(parent, SWT.NULL); fTreeViewer = new TreeViewer(fViewerbook, SWT.V_SCROLL | SWT.SINGLE); fTreeViewer.setUseHashlookup(true); fTreeContentProvider = new PHPUnitElementTreeContentProvider(); fTreeViewer.setContentProvider(fTreeContentProvider); fTreeViewer.setLabelProvider(new TestLabelProvider(view)); fSelectionProvider = new SelectionProviderMediator(new StructuredViewer[] { fTreeViewer }, fTreeViewer); fSelectionProvider.addSelectionChangedListener(new TestSelectionListener()); fTreeViewer.addDoubleClickListener(event -> handleDefaultSelected((IStructuredSelection) event.getSelection())); fViewerbook.showPage(fTreeViewer.getTree()); } void disposeIcons() { fHierarchyIcon.dispose(); } public void expandAll() { fTreeViewer.expandAll(); fTreeViewer.refresh(); } private StructuredViewer getActiveViewer() { return fTreeViewer; } private boolean getActiveViewerHasFilter() { return fTreeHasFilter; } private boolean getActiveViewerNeedsRefresh() { return fTreeNeedsRefresh; } private IType getClass(final String className, final String fileName) { IProject project = view.getProject(); IType[] classes = PHPModelAccess.getDefault().findTypes(className, MatchRule.EXACT, 0, 0, SearchEngine.createSearchScope(DLTKCore.create(project)), null); if (classes != null && classes.length > 0) { return classes[0]; } return null; } private PHPUnitTestCase getNextChildFailure(final PHPUnitTestGroup root, boolean showNext) { List<PHPUnitTest> children = new ArrayList<>(root.getChildren()); if (!showNext) { Collections.reverse(children); } for (PHPUnitTest child : children) { if (child.getStatus() > PHPUnitTest.STATUS_PASS) { if (child instanceof PHPUnitTestCase) { return (PHPUnitTestCase) child; } return getNextChildFailure((PHPUnitTestGroup) child, showNext); } } return null; } private PHPUnitElement getNextFailure(final PHPUnitElement selected, final boolean showNext) { if (selected instanceof PHPUnitTestGroup) { final PHPUnitElement nextChild = getNextChildFailure((PHPUnitTestGroup) selected, showNext); if (nextChild != null) { return nextChild; } } return getNextFailureSibling(selected, showNext); } private PHPUnitTestCase getNextFailureSibling(final PHPUnitElement current, boolean showNext) { final PHPUnitTestGroup parent = (PHPUnitTestGroup) current.getParent(); if (parent == null) { return null; } Set<PHPUnitTest> children = parent.getChildren(); if (children == null) { return null; } List<PHPUnitTest> siblings = new ArrayList<>(children); if (!showNext) { Collections.reverse(siblings); } final int nextIndex = siblings.indexOf(current) + 1; for (int i = nextIndex; i < siblings.size(); i++) { final PHPUnitTest sibling = siblings.get(i); if (sibling.getStatus() > PHPUnitTest.STATUS_PASS) { if (sibling instanceof PHPUnitTestCase) return (PHPUnitTestCase) sibling; return getNextChildFailure((PHPUnitTestGroup) sibling, showNext); } } return getNextFailureSibling(parent, showNext); } public Control getTestViewerControl() { return fViewerbook; } void handleDefaultSelected(IStructuredSelection selection) { if (selection.isEmpty()) { return; } final PHPUnitTest test = (PHPUnitTest) selection.getFirstElement(); OpenTestAction action; if (test instanceof PHPUnitTestGroup) action = new OpenTestAction(null, view, test.getName(), test.getLocalFile(), test.getLine()); else if (test instanceof PHPUnitTestCase) { final PHPUnitTestCase testCase = (PHPUnitTestCase) test; action = new OpenTestAction(null, view, ((PHPUnitTestGroup) testCase.getParent()).getName(), testCase.getLocalFile(), testCase.getLine(), testCase.getName()); } else throw new IllegalStateException(String.valueOf(test)); if (action.isEnabled()) { action.run(); } } void handleMenuAboutToShow(final IMenuManager manager) { final IStructuredSelection selection = (IStructuredSelection) fSelectionProvider.getSelection(); if (!selection.isEmpty()) { final PHPUnitTest test = (PHPUnitTest) selection.getFirstElement(); final String testLabel = test.getName(); String className = testLabel; String fileName = test.getLocalFile(); int lineNumber = test.getLine(); if (test instanceof PHPUnitTestGroup) { manager.add(new OpenTestAction(null, view, testLabel, fileName, lineNumber)); manager.add(new Separator()); final IType phpClass = getClass(className, fileName); if (phpClass != null) { manager.add(new RerunAction(view, test.getTestId(), phpClass, null, ILaunchManager.RUN_MODE)); manager.add(new RerunAction(view, test.getTestId(), phpClass, null, ILaunchManager.DEBUG_MODE)); } } else { final String testMethodName = test.getName(); final PHPUnitTestGroup parent = (PHPUnitTestGroup) test.getParent(); className = parent.getName(); fileName = parent.getFile(); lineNumber = parent.getLine(); final IType phpClass = getClass(className, fileName); if (phpClass != null) { manager.add(new OpenTestAction(null, view, className, phpClass.getResource().getFullPath().toPortableString(), lineNumber, testMethodName)); } } } manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS)); manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS + "-end")); //$NON-NLS-1$ } private void initContextMenu() { final MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$ menuMgr.setRemoveAllWhenShown(true); menuMgr.addMenuListener(manager -> handleMenuAboutToShow(manager)); view.getSite().registerContextMenu(menuMgr, fSelectionProvider); final Menu menu = menuMgr.createContextMenu(fViewerbook); fTreeViewer.getTree().setMenu(menu); } private boolean isShown(final PHPUnitElement current) { return fFailuresOnlyFilter.select(current); } /** * To be called periodically by the PHPUnitView (in the UI thread). */ public void processChangesInUI() { if (view.isDisposed()) { return; } if (testRoot == null) { registerViewersRefresh(); fTreeNeedsRefresh = false; fTreeViewer.setInput(null); return; } final StructuredViewer viewer = getActiveViewer(); if (viewer.getInput() != testRoot) { viewer.setInput(testRoot); } viewer.refresh(); if (getActiveViewerNeedsRefresh()) { clearUpdateAndExpansion(); setActiveViewerRefreshed(); } else { PHPUnitElement[] toUpdate; synchronized (this) { toUpdate = fNeedUpdate.toArray(new PHPUnitElement[0]); fNeedUpdate.clear(); } if (!fTreeNeedsRefresh && toUpdate.length > 0) if (fTreeHasFilter) for (PHPUnitElement element : toUpdate) updateElementInTree(element); else { final Set<PHPUnitElement> toUpdateWithParents = new HashSet<>(); toUpdateWithParents.addAll(Arrays.asList(toUpdate)); for (PHPUnitElement element : toUpdate) { if (element != null) { PHPUnitElement parent = element.getParent(); while (parent != null) { toUpdateWithParents.add(parent); parent = parent.getParent(); } } } fTreeViewer.update(toUpdateWithParents.toArray(), null); } } autoScrollInUI(); } public synchronized void registerActiveSession(final PHPUnitTestGroup testRoot) { if (this.testRoot != testRoot) { this.testRoot = testRoot; registerAutoScrollTarget(null); registerViewersRefresh(); } } public void registerAutoScrollTarget(final PHPUnitTestCase TestCase) { fAutoScrollTarget = TestCase; } public synchronized void registerFailedForAutoScroll(final PHPUnitElement testRunInfo) { final Object parent = fTreeContentProvider.getParent(testRunInfo); if (parent != null) { fAutoExpand.add(parent); } } public synchronized void registerTestAdded() { fTreeNeedsRefresh = true; } public synchronized void registerViewersRefresh() { fTreeNeedsRefresh = true; clearUpdateAndExpansion(); } public synchronized void registerViewerUpdate(final PHPUnitElement test) { if (test == null) { return; } fNeedUpdate.add(test); } public void selectFailure(final boolean showNext) { final IStructuredSelection selection = (IStructuredSelection) getActiveViewer().getSelection(); final PHPUnitElement selected = (PHPUnitElement) selection.getFirstElement(); PHPUnitElement next; if (selected == null) { next = getNextChildFailure(testRoot, showNext); } else { next = getNextFailure(selected, showNext); } if (next != null) { getActiveViewer().setSelection(new StructuredSelection(next), true); } } public void selectFirstFailure() { final PHPUnitTestCase firstFailure = getNextChildFailure(testRoot, true); if (firstFailure != null) getActiveViewer().setSelection(new StructuredSelection(firstFailure), true); } private void setActiveViewerHasFilter(final boolean filter) { fTreeHasFilter = filter; } private void setActiveViewerRefreshed() { fTreeNeedsRefresh = false; } public synchronized void setShowFailuresOnly(final boolean failuresOnly) { try { fViewerbook.setRedraw(false); // avoid realizing all TableItems, especially in flat mode! final 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(); } finally { fViewerbook.setRedraw(true); } } private void updateElementInTree(final PHPUnitElement test) { if (isShown(test)) updateShownElementInTree(test); else { PHPUnitElement current = test; do { if (fTreeViewer.testFindItem(current) != null) fTreeViewer.remove(current); current = current.getParent(); } while (!(current instanceof PHPUnitTestGroup) && !isShown(current)); while (current != null && !(current == testRoot)) { fTreeViewer.update(current, null); current = current.getParent(); } } } private void updateShownElementInTree(final PHPUnitElement test) { if (test == null || test == testRoot) { // paranoia null check return; } final PHPUnitTestGroup parent = (PHPUnitTestGroup) test.getParent(); if (parent == null) { return; } updateShownElementInTree(parent); // make sure parent is shown and // up-to-date if (fTreeViewer.testFindItem(test) == null) fTreeViewer.add(parent, test); // if not yet in tree: add else fTreeViewer.update(test, null); // if in tree: update } }