package org.xtest.runner.statusbar; import java.util.Collection; import java.util.Collections; import java.util.List; import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.HandlerEvent; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.MenuManager; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.ide.IDE; import org.xtest.runner.Activator; import org.xtest.runner.RunAllJob; import org.xtest.runner.TestsProvider; import org.xtest.runner.events.TestFinished; import org.xtest.runner.events.TestsStarted; import org.xtest.runner.external.TestState; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.Ordering; import com.google.common.collect.TreeMultimap; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import com.google.inject.Inject; /** * Handler for stop-test events * * @author Michael Barry */ public class FailedTestsHandler extends AbstractHandler { /** * Returns a collection of error markers for a file * * @param failure * The test file * @return A collection of error markers for that file */ private static Collection<IMarker> getErrors(IFile failure) { Multimap<Integer, IMarker> startLineToMarker = TreeMultimap.create(Ordering.natural(), Ordering.arbitrary()); try { IMarker[] findMarkers = failure.findMarkers(null, true, 0); for (IMarker thisMarker : findMarkers) { Object attribute = thisMarker.getAttribute(IMarker.SEVERITY); if (attribute instanceof Integer && (Integer) attribute == IMarker.SEVERITY_ERROR) { Object attribute2 = thisMarker.getAttribute(IMarker.LINE_NUMBER); Integer lineNum = attribute2 instanceof Integer ? (Integer) attribute2 : 0; startLineToMarker.put(lineNum, thisMarker); } } } catch (CoreException e1) { } return startLineToMarker.values(); } private final EventBus bus; private final Collection<IFile> failures; /** * SHOULD ONLY BE CALLED BY GUICE * * @param job * The test runner job provided by Guice * @param bus * The event bus provided by Guice * @param tests * The test provider instance provided by Guice */ @Inject public FailedTestsHandler(RunAllJob job, EventBus bus, TestsProvider tests) { this.bus = bus; bus.register(this); Collection<IFile> failingTests = tests.getTestFilesWithState(TestState.FAIL); List<IFile> unsynchronizedList = Lists.newArrayList(failingTests); failures = Collections.synchronizedCollection(unsynchronizedList); } /** * SHOULD ONLY BE CALLED BY {@link EventBus} * * @param finished * Test finished event */ @Subscribe public void disabledIfFailed(TestFinished finished) { if (!finished.passed()) { failures.add(finished.getFile()); } fireHandlerChanged(new HandlerEvent(this, true, false)); } @Override public void dispose() { super.dispose(); bus.unregister(this); } /** * SHOULD ONLY BE CALLED BY {@link EventBus} * * @param started * Tests started event */ @Subscribe public void enable(TestsStarted started) { failures.clear(); fireHandlerChanged(new HandlerEvent(this, true, false)); } @Override public Object execute(ExecutionEvent event) throws ExecutionException { if (!failures.isEmpty()) { MenuManager menuManager = new MenuManager("Failed Test Menu", "org.xtest.statusbar.menu.failures"); int i = 0; // construct menu for (final IFile failure : failures) { Collection<IMarker> errors = getErrors(failure); for (IMarker error : errors) { try { String attribute = (String) error.getAttribute(IMarker.MESSAGE); attribute = attribute.replaceAll("[\\r\\n].*", ""); // first line only String name = failure.getName() + ": " + attribute; NavigateToFailureAction create = NavigateToFailureAction .create(name, error); menuManager.add(create); if (i++ > 100) { break; } } catch (CoreException e) { } } } // Display menu Event trigger = (Event) event.getTrigger(); ToolItem widget = (ToolItem) trigger.widget; ToolBar parent = widget.getParent(); Menu menu = menuManager.createContextMenu(parent); parent.setMenu(menu); menu.setVisible(true); } return null; } @Override public boolean isEnabled() { return !failures.isEmpty(); } /** * Menu action to navigate to a test failure * * @author Michael Barry */ private static class NavigateToFailureAction extends Action { /** * Static factory to construct from an {@link IFile} * * @param name * Label to display on marker * @param first * the marker to navigate to * @return The new {@link NavigateToFailureAction} */ public static NavigateToFailureAction create(String name, IMarker first) { return new NavigateToFailureAction(name, first); } private final IMarker first; private NavigateToFailureAction(String name, IMarker first) { super(name, Activator.getDefault().getErrorFileImage()); this.first = first; } @Override public void run() { // Open editor page at the first failure marker found, or just open the file if the // marker is not found IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); IWorkbenchPage activePage = window.getActivePage(); try { if (first != null) { IDE.openEditor(activePage, first); } } catch (PartInitException e) { } } } }