diff --git a/org.eclipse.jdt.junit.core/.gitignore b/org.eclipse.jdt.junit.core/.gitignore new file mode 100644 index 0000000..ae3c172 --- /dev/null +++ b/org.eclipse.jdt.junit.core/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/model/TestCaseElement.java b/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/model/TestCaseElement.java index c4e1dee..b25c06a 100644 --- a/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/model/TestCaseElement.java +++ b/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/model/TestCaseElement.java @@ -7,7 +7,9 @@ * * Contributors: * IBM Corporation - initial API and implementation - * Xavier Coulon - https://bugs.eclipse.org/bugs/show_bug.cgi?id=102512 - [JUnit] test method name cut off before ( + * Xavier Coulon + * - [JUnit] test method name cut off before '(' - https://bugs.eclipse.org/bugs/show_bug.cgi?id=102512 + * - [JUnit] Add "Link with Editor" to JUnit view - https://bugs.eclipse.org/bugs/show_bug.cgi?id=372588 *******************************************************************************/ package org.eclipse.jdt.internal.junit.model; @@ -15,15 +17,47 @@ import org.eclipse.jdt.junit.model.ITestCaseElement; import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; -public class TestCaseElement extends TestElement implements ITestCaseElement { +import org.eclipse.jdt.internal.junit.JUnitCorePlugin; + +/** + * + * @since 3.7 implements {@link IAdaptable} + */ +public class TestCaseElement extends TestElement implements ITestCaseElement, IAdaptable { + + private IMethod fJavaMethod= null; + + private boolean fJavaMethodResolved= false; private boolean fIgnored; public TestCaseElement(TestSuiteElement parent, String id, String testName) { super(parent, id, testName); Assert.isNotNull(parent); + } + + /** + * @return the name of the Java Method associated with this {@link TestCaseElement}, ie, it + * returns the valid java identifier part of the name (in particular, it removes the + * brackets suffix for Parameterized JUnit tests). + * + * + */ + private String getJavaTestMethodName() { + String testMethodName= getTestMethodName(); + for (int i= 0; i < testMethodName.length(); i++) { + if (!Character.isJavaIdentifierPart(testMethodName.charAt(i))) { + return testMethodName.substring(0, i); + } + } + return testMethodName; } /** @@ -41,6 +75,36 @@ if (index > 0) return testName.substring(0, index); return testName; + } + + /** + * Finds and returns the {@link IMethod} associated with this {@link TestCaseElement}. + * + * @return the corresponding Java method element or null if not found. + * @since 3.7 + */ + public IMethod getJavaMethod() { + if (!fJavaMethodResolved) { + try { + final IType type= getJavaType(); + if (type != null) { + final IMethod[] methods= type.getMethods(); + String testMethodName= getJavaTestMethodName(); + for (int i= 0; i < methods.length; i++) { + if (methods[i].getElementName().equals(testMethodName)) { + fJavaMethod= methods[i]; + return methods[i]; + } + } + } + return null; + } catch (JavaModelException e) { + JUnitCorePlugin.log(e); + } finally { + fJavaMethodResolved= true; + } + } + return fJavaMethod; } /** @@ -73,4 +137,19 @@ public String toString() { return "TestCase: " + getTestClassName() + "." + getTestMethodName() + " : " + super.toString(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } + + /** + * Provides support for converting this {@link TestCaseElement} into an {@link IMethod} when the + * given adapter class is {@link IJavaElement}. + * + * @param adapter the class in which this {@link TestCaseElement} should be adapted. + * @return an object in the request type, or null if it could not be adapted. + * @since 3.7 + */ + public Object getAdapter(Class adapter) { + if (adapter != null && adapter.equals(IJavaElement.class)) { + return getJavaMethod(); + } + return null; + } } diff --git a/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/model/TestElement.java b/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/model/TestElement.java index 0987059..5bb10f6 100644 --- a/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/model/TestElement.java +++ b/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/model/TestElement.java @@ -9,7 +9,9 @@ * IBM Corporation - initial API and implementation * Brock Janiczak (brockj@tpg.com.au) * - https://bugs.eclipse.org/bugs/show_bug.cgi?id=102236: [JUnit] display execution time next to each test - * Xavier Coulon - https://bugs.eclipse.org/bugs/show_bug.cgi?id=102512 - [JUnit] test method name cut off before ( + * Xavier Coulon + * - [JUnit] test method name cut off before '(' - https://bugs.eclipse.org/bugs/show_bug.cgi?id=102512 + * - [JUnit] Add "Link with Editor" to JUnit view - https://bugs.eclipse.org/bugs/show_bug.cgi?id=372588 *******************************************************************************/ package org.eclipse.jdt.internal.junit.model; @@ -19,6 +21,11 @@ import org.eclipse.jdt.junit.model.ITestRunSession; import org.eclipse.core.runtime.Assert; + +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; + +import org.eclipse.jdt.internal.junit.JUnitCorePlugin; public abstract class TestElement implements ITestElement { @@ -175,6 +182,10 @@ private boolean fAssumptionFailed; + private IType fJavaType= null; + + private boolean fJavaTypeResolved= false; + /** * Running time in seconds. Contents depend on the current {@link #getProgressState()}: *
    @@ -324,6 +335,25 @@ return extractClassName(getTestName()); } + /** + * @return the Java {@link IType} associated with this {@link TestElement}. + * @since 3.7 + */ + public IType getJavaType() { + if (!fJavaTypeResolved) { + try { + fJavaType= getTestRunSession().getLaunchedProject().findType(getClassName()); + } catch (JavaModelException e) { + JUnitCorePlugin.log(e); + } finally { + fJavaTypeResolved= true; + } + } + return fJavaType; + } + + + private static String extractClassName(String testNameString) { testNameString= extractRawClassName(testNameString); testNameString= testNameString.replace('$', '.'); // see bug 178503 diff --git a/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/model/TestSuiteElement.java b/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/model/TestSuiteElement.java index df8c4a5..504c18d 100644 --- a/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/model/TestSuiteElement.java +++ b/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/model/TestSuiteElement.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2008 IBM Corporation and others. + * Copyright (c) 2000, 2014 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 @@ -7,6 +7,7 @@ * * Contributors: * IBM Corporation - initial API and implementation + * Xavier Coulon - [JUnit] Add "Link with Editor" to JUnit view - https://bugs.eclipse.org/bugs/show_bug.cgi?id=372588 *******************************************************************************/ package org.eclipse.jdt.internal.junit.model; @@ -17,8 +18,18 @@ import org.eclipse.jdt.junit.model.ITestElement; import org.eclipse.jdt.junit.model.ITestSuiteElement; +import org.eclipse.core.runtime.IAdaptable; -public class TestSuiteElement extends TestElement implements ITestSuiteElement { +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IType; + + +public class TestSuiteElement extends TestElement implements ITestSuiteElement, IAdaptable { + + private IJavaElement fJavaElement= null; + + private boolean fJavaElementResolved= false; private List/**/ fChildren; private Status fChildrenStatus; @@ -151,4 +162,62 @@ return "TestSuite: " + getSuiteTypeName() + " : " + super.toString() + " (" + fChildren.size() + ")"; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ } + /** + * Provides support for converting this {@link TestSuiteElement} into an {@link IType} (or an + * {@link IMethod} when this {@link TestSuiteElement} matches a JUnit Parameterized Test) when + * the given adapter class is {@link IJavaElement}. + * + * @param adapter the class in which this {@link TestSuiteElement} should be adapted. + * @return an object in the request type, or null if it could not be adapted. + * @since 3.7 + */ + public Object getAdapter(Class adapter) { + if (adapter != null && adapter.equals(IJavaElement.class)) { + return getJavaElement(); + } + return null; + } + + /** + * Returns the closest {@link IJavaElement} for the given {@link TestSuiteElement}, including + * with a work-around for Parameterized tests by looking at the child elements: if there's only + * one, return its Java {@link IMethod}. Otherwise, return the {@link IType} + * + * @return the {@link IJavaElement} found for this {@link TestSuiteElement}. + * + * @since 3.7 + * @see TestElement#getJavaType() + */ + public IJavaElement getJavaElement() { + if (!fJavaElementResolved) { + // whatever happens, let's consider that the Java Type has been resolved, to make sure we don't come back here again for this TestSuitElement. + fJavaElementResolved= true; + fJavaElement= super.getJavaType(); + if (fJavaElement == null) { + if (getChildren().length == 1 && getChildren()[0] instanceof TestCaseElement) { + fJavaElement= ((TestCaseElement)getChildren()[0]).getJavaMethod(); + } + } + } + return fJavaElement; + } + + /** + * Returns the {@link IType} associated with the given {@link TestSuiteElement}, or uses the + * work-around in {@link TestSuiteElement#getJavaElement()} to retrieve the {@link IType} + * associated with the single child {@link TestCaseElement} if this {@link TestSuiteElement} + * matches a Parameterized JUnit Test. + * + * @since 3.7 + * @see TestElement#getJavaType() + */ + public IType getJavaType() { + final IType javaType= super.getJavaType(); + if (javaType != null) { + return javaType; + } else if (getJavaElement() != null) { + return (IType)fJavaElement.getAncestor(IJavaElement.TYPE); + } + return null; + } } diff --git a/org.eclipse.jdt.junit/.gitignore b/org.eclipse.jdt.junit/.gitignore new file mode 100644 index 0000000..ae3c172 --- /dev/null +++ b/org.eclipse.jdt.junit/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/TestRunnerViewPart.java b/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/TestRunnerViewPart.java index 41fe5cc..5ec5730 100644 --- a/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/TestRunnerViewPart.java +++ b/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/TestRunnerViewPart.java @@ -16,6 +16,7 @@ * Andrew Eisenberg - [JUnit] Rerun failed first does not work with JUnit4 - https://bugs.eclipse.org/bugs/show_bug.cgi?id=140392 * Thirumala Reddy Mutchukota - [JUnit] Avoid rerun test launch on UI thread - https://bugs.eclipse.org/bugs/show_bug.cgi?id=411841 * Andrew Eisenberg - [JUnit] Add a monospace font option for the junit results view - https://bugs.eclipse.org/bugs/show_bug.cgi?id=411794 + * Xavier Coulon - [JUnit] Add "Link with Editor" to JUnit view - https://bugs.eclipse.org/bugs/show_bug.cgi?id=372588 *******************************************************************************/ package org.eclipse.jdt.internal.junit.ui; @@ -36,6 +37,7 @@ import java.util.Iterator; import java.util.List; +import org.eclipse.jdt.junit.model.ITestElement.ProgressState; import org.eclipse.jdt.junit.model.ITestElement.Result; import org.eclipse.swt.SWT; @@ -96,6 +98,7 @@ import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.ISelection; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IEditorActionBarContributor; @@ -149,6 +152,8 @@ import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; +import org.eclipse.jdt.internal.ui.JavaPluginImages; +import org.eclipse.jdt.internal.ui.actions.AbstractToggleLinkingAction; import org.eclipse.jdt.internal.ui.viewsupport.ViewHistory; /** @@ -210,6 +215,7 @@ private Action fPreviousAction; private StopAction fStopAction; + private LinkWithEditorAction fLinkWithEditorAction; private JUnitCopyAction fCopyAction; private Action fPasteAction; @@ -899,6 +905,77 @@ } } + /** + * This action toggles whether this {@link TestRunnerViewPart} links its selection to the active + * editor. + * + */ + private class LinkWithEditorAction extends AbstractToggleLinkingAction { + + /** Image to use when the sync is on.*/ + private static final String SYNCED_GIF= "synced.gif"; //$NON-NLS-1$ + + /** Image to use when the sync is broken.*/ + private static final String SYNC_BROKEN_GIF= "sync_broken.gif"; //$NON-NLS-1$ + + /** + * Default image when the action is created (as defined in the constructor of the + * superclass). + */ + private String fIconName= SYNCED_GIF; + + LinkWithEditorAction() { + super(); + // enable by default + setChecked(true); + } + + @Override + public void run() { + setLinkingWithEditorActive(isChecked()); + } + + /** + * Updates the Link image with a "normal link" image if parameter is true, or a + * "broken link" image otherwise + * + * @param isInSync the state of synchronization + */ + public void updateLinkImage(boolean isInSync) { + String iconName= isInSync ? SYNCED_GIF : SYNC_BROKEN_GIF; + if (!iconName.equals(fIconName)) { + fIconName= iconName; + refreshIcon(); + } + } + + private void refreshIcon() { + JavaPluginImages.setLocalImageDescriptors(this, fIconName); + } + + } + + /** + * @return {@code true} if the {@link LinkWithEditorAction} is checked, {@code false} otherwise. + * @since 3.8 + */ + public boolean isLinkWithEditorActive() { + return fLinkWithEditorAction.isChecked(); + } + + /** + * @return the {@link ProgressState} of the current {@link TestRunSession}, or + * {@link ProgressState#NOT_STARTED} if there's no {@link TestRunSession} yet. + * @since 3.8 + */ + public ProgressState getCurrentProgressState() { + if (fTestRunSession == null) { + return ProgressState.NOT_STARTED; + } + return fTestRunSession.getProgressState(); + } + + private class RerunLastAction extends Action { public RerunLastAction() { setText(JUnitMessages.TestRunnerViewPart_rerunaction_label); @@ -1200,6 +1277,40 @@ } } + /** + * Activate the 'Link with Editor' in the current {@link IWorkbenchPage}. (The + * {@link TestViewer} will respond to {@link ISelection} changes.) + * + * @param active boolean to indicate if the link with editor is active ({@code true}) or not ( + * {@code false}) + * + * @since 3.8 + */ + public void setLinkingWithEditorActive(final boolean active) { + if (active) { + getSite().getPage().addPostSelectionListener(fTestViewer); + fTestViewer.setSelection(getSite().getPage().getActiveEditor()); + } else { + getSite().getPage().removePostSelectionListener(fTestViewer); + // force the icon to 'synced' mode + setLinkingWithEditorInSync(true); + } + } + + + + /** + * Updates the Link image with a "normal link" image if parameter is true, or a "broken link" + * image otherwise + * + * @param isInSync the state of synchronization + * + * @since 3.8 + */ + public void setLinkingWithEditorInSync(final boolean isInSync) { + fLinkWithEditorAction.updateLinkImage(isInSync); + } + private void startUpdateJobs() { postSyncProcessChanges(); @@ -1245,7 +1356,7 @@ boolean hasErrorsOrFailures= hasErrorsOrFailures(); fNextAction.setEnabled(hasErrorsOrFailures); fPreviousAction.setEnabled(hasErrorsOrFailures); - + fLinkWithEditorAction.setEnabled(fTestRunSession != null); fTestViewer.processChangesInUI(); } @@ -1512,6 +1623,12 @@ stopUpdateJobs(); fStopAction.setEnabled(fTestRunSession.isKeptAlive()); + // if "Link with Editor" is active, register the listener + if (isLinkWithEditorActive()) { + getSite().getPage().addPostSelectionListener(fTestViewer); + fTestViewer.setSelection(getSite().getPage().getActiveEditor()); + } + fTestViewer.expandFirstLevel(); } } @@ -1579,6 +1696,7 @@ if (fFailureTrace != null) { fFailureTrace.dispose(); } + getSite().getPage().removePostSelectionListener(fTestViewer); } private void disposeImages() { @@ -1783,6 +1901,9 @@ if (!testRunSessions.isEmpty()) { fTestRunSessionListener.sessionAdded(testRunSessions.get(0)); } + fLinkWithEditorAction.setChecked(true); + setLinkingWithEditorActive(true); + setLinkingWithEditorInSync(true); } private void addDropAdapter(Composite parent) { @@ -1887,6 +2008,10 @@ fStopAction= new StopAction(); fStopAction.setEnabled(false); + fLinkWithEditorAction= new LinkWithEditorAction(); + fLinkWithEditorAction.setChecked(false); + fLinkWithEditorAction.setEnabled(false); + fRerunLastTestAction= new RerunLastAction(); IHandlerService handlerService= getSite().getWorkbenchWindow().getService(IHandlerService.class); IHandler handler = new AbstractHandler() { @@ -1937,6 +2062,8 @@ toolBar.add(fRerunFailedFirstAction); toolBar.add(fStopAction); toolBar.add(fViewHistory.createHistoryDropDownAction()); + toolBar.add(new Separator()); + toolBar.add(fLinkWithEditorAction); viewMenu.add(fShowTestHierarchyAction); diff --git a/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/TestViewer.java b/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/TestViewer.java index 8ff5d29..a6459c8 100644 --- a/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/TestViewer.java +++ b/org.eclipse.jdt.junit/src/org/eclipse/jdt/internal/junit/ui/TestViewer.java @@ -9,8 +9,9 @@ * IBM Corporation - initial API and implementation * Brock Janiczak (brockj@tpg.com.au) * - https://bugs.eclipse.org/bugs/show_bug.cgi?id=102236: [JUnit] display execution time next to each test - * Xavier Coulon - https://bugs.eclipse.org/bugs/show_bug.cgi?id=102512 - [JUnit] test method name cut off before ( - + * Xavier Coulon + * - https://bugs.eclipse.org/bugs/show_bug.cgi?id=102512 - [JUnit] test method name cut off before ( + * - https://bugs.eclipse.org/bugs/show_bug.cgi?id=372588 - [JUnit] Add "Link with Editor" to JUnit view *******************************************************************************/ package org.eclipse.jdt.internal.junit.ui; @@ -23,7 +24,10 @@ import java.util.List; import java.util.ListIterator; +import org.eclipse.jdt.junit.model.ITestCaseElement; import org.eclipse.jdt.junit.model.ITestElement; +import org.eclipse.jdt.junit.model.ITestElement.ProgressState; +import org.eclipse.jdt.junit.model.ITestSuiteElement; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.Clipboard; @@ -42,8 +46,10 @@ import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.viewers.AbstractTreeViewer; +import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITreeSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.StructuredViewer; @@ -52,13 +58,24 @@ import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.jface.text.ITextSelection; + +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.IWorkbenchActionConstants; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.PartInitException; import org.eclipse.ui.part.PageBook; +import org.eclipse.ui.views.contentoutline.ContentOutline; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchManager; +import org.eclipse.jdt.core.IClassFile; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; @@ -72,11 +89,16 @@ import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; +import org.eclipse.jdt.ui.JavaUI; + +import org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor; +import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility; +import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor; import org.eclipse.jdt.internal.ui.viewsupport.ColoringLabelProvider; import org.eclipse.jdt.internal.ui.viewsupport.SelectionProviderMediator; -public class TestViewer { +public class TestViewer implements ISelectionListener { private final class TestSelectionListener implements ISelectionChangedListener { public void selectionChanged(SelectionChangedEvent event) { handleSelected(); @@ -343,6 +365,11 @@ testElement= (TestElement) selection.getFirstElement(); } fTestRunnerPart.handleTestSelected(testElement); + // if LinkWithEditor is active, reveal the JavaEditor and select the java type or method + // matching the selected test element, even if the JavaEditor is opened by not active. + if (fTestRunnerPart.isLinkWithEditorActive()) { + handleTestElementSelected(testElement); + } } public synchronized void setShowTime(boolean showTime) { @@ -609,6 +636,144 @@ fTreeViewer.reveal(current); } + /** + * Sets the current selection from the given {@link IEditorPart} (if it is a + * {@link CompilationUnitEditor}) and its selection. + * + * @param editor the selected Java Element in the active Compilation Unit Editor + * + * @since 3.8 + */ + public void setSelection(final IEditorPart editor) { + if (editor != null) { + final IJavaElement selectedJavaElement= getSelectedJavaElementInEditor(editor); + setSelection(selectedJavaElement); + } + } + + /** + * Sets the current selection from the given {@link IJavaElement} if it matches an + * {@link ITestCaseElement} and updates the LinkWithEditorAction image in the associated + * {@link TestRunnerViewPart}. + * + * @param activeJavaElement the selected Java Element (or null) in the active + * {@link IWorkbenchPart} + * + */ + private void setSelection(final IJavaElement activeJavaElement) { + final ITestElement activeTestCaseElement= findTestElement(activeJavaElement); + if (activeTestCaseElement != null) { + // update the current selection in the viewer if it does not match with the java element selected in the given part (if it's not the parent TestViewerPart) + final Object currentSelection= getCurrentViewerSelection(); + if ((currentSelection instanceof TestCaseElement && ((TestCaseElement)currentSelection).getJavaMethod() != null && !((TestCaseElement)currentSelection).getJavaMethod().equals( + activeJavaElement)) + || (currentSelection instanceof TestSuiteElement && ((TestSuiteElement)currentSelection).getJavaType() != null && !((TestSuiteElement)currentSelection).getJavaType().equals( + activeJavaElement))) { + final IStructuredSelection selection= new StructuredSelection(activeTestCaseElement); + fSelectionProvider.setSelection(selection, true); + } + // ensure link with editor is in 'synced' mode + fTestRunnerPart.setLinkingWithEditorInSync(true); + } + else { + // selection is out-of-sync: show a different icon on the button. + fTestRunnerPart.setLinkingWithEditorInSync(false); + } + } + + /** + * @return the current selection in the JUnit Viewer (provided by the underlying selection + * provider), or {@code null} if the kind of selection is not an {@link ITreeSelection} + * nor an {@link IStructuredSelection}. + */ + private Object getCurrentViewerSelection() { + final ISelection currentSelection= fSelectionProvider.getSelection(); + if (currentSelection instanceof ITreeSelection) { + return ((ITreeSelection)currentSelection).getFirstElement(); + } else if (currentSelection instanceof IStructuredSelection) { + return ((IStructuredSelection)currentSelection).getFirstElement(); + } + return null; + } + + /** + * Finds the {@link ITestElement} from the given {@link IJavaElement} + * + * @param javaElement the java element associated with the {@link ITestElement} to find. + * @return the {@link ITestElement} or null if it could not be found. + */ + private ITestElement findTestElement(final IJavaElement javaElement) { + // skip if JUnit is still running or if no Java element was selected + if (fTestRunnerPart.getCurrentProgressState() != ProgressState.COMPLETED || javaElement == null) { + return null; + } + switch (javaElement.getElementType()) { + case IJavaElement.METHOD: + final IMethod javaMethod= (IMethod)javaElement; + final IType javaType= (IType)javaMethod.getAncestor(IJavaElement.TYPE); + final String testClassName= javaType.getFullyQualifiedName(); + final String testMethodName= javaMethod.getElementName(); + return findTestCaseElement(fTestRunSession.getTestRoot(), testClassName, testMethodName); + case IJavaElement.TYPE: + return findTestSuiteElement(fTestRunSession.getTestRoot(), ((IType)javaElement).getFullyQualifiedName()); + default: + return null; + } + } + + /** + * Finds the {@link ITestCaseElement} with the given test class name and test method name in the + * given {@link ITestSuiteElement} + * + * @param parentElement the parent Test Suite + * @param testClassName the name of the test class + * @param testMethodName the name of the test method + * + * @return the {@link ITestCaseElement} or null if it could not be found. + */ + private ITestCaseElement findTestCaseElement(final ITestSuiteElement parentElement, final String testClassName, final String testMethodName) { + for (ITestElement childElement : parentElement.getChildren()) { + if (childElement instanceof ITestCaseElement) { + final TestCaseElement testCaseElement= (TestCaseElement)childElement; + if (testCaseElement.getJavaType() != null && testCaseElement.getJavaType().getFullyQualifiedName().equals(testClassName) && testCaseElement.getJavaMethod() != null + && testCaseElement.getJavaMethod() != null && testCaseElement.getJavaMethod().getElementName().equals(testMethodName)) { + return testCaseElement; + } + } else if (childElement instanceof ITestSuiteElement) { + final ITestCaseElement localResult= findTestCaseElement((ITestSuiteElement)childElement, testClassName, testMethodName); + if (localResult != null) { + return localResult; + } + } + } + return null; + } + + /** + * Finds the {@link ITestSuiteElement} with the given test class name in the given + * {@link ITestSuiteElement} + * + * @param parentElement the parent Test Suite + * @param testClassName the name of the test class + * + * @return the {@link ITestSuiteElement} or null if it could not be found. + */ + private ITestSuiteElement findTestSuiteElement(final ITestSuiteElement parentElement, final String testClassName) { + for (ITestElement childElement : parentElement.getChildren()) { + if (childElement instanceof ITestSuiteElement) { + final ITestSuiteElement childTestSuite= (ITestSuiteElement)childElement; + if (childTestSuite.getSuiteTypeName().equals(testClassName)) { + return childTestSuite; + } + final ITestSuiteElement matchingNestedTestSuiteElement= findTestSuiteElement(childTestSuite, testClassName); + if (matchingNestedTestSuiteElement != null) { + return matchingNestedTestSuiteElement; + } + } + } + return null; + } + public void selectFirstFailure() { TestCaseElement firstFailure= getNextChildFailure(fTestRunSession.getTestRoot(), true); if (firstFailure != null) @@ -722,5 +887,129 @@ fTreeViewer.expandToLevel(2); } -} + /** + * Reacts to a selection change in the active {@link IWorkbenchPart} (or when another part + * received the focus). + * + * @param part the {@link IWorkbenchPart} in which the selection change occurred + * @param selection the selection in the given part + * @since 3.8 + */ + public void selectionChanged(IWorkbenchPart part, ISelection selection) { + if (part instanceof IEditorPart) { + setSelection((IEditorPart)part); + } else if (part instanceof ContentOutline) { + final ITreeSelection outlineSelection= (ITreeSelection) ((ContentOutline) part).getSelection(); + if (outlineSelection.size() == 1) { + setSelection((IJavaElement) outlineSelection.getFirstElement()); + } + } + } + /** + * @return the selected {@link IJavaElement} in the current editor if it is a {@link CompilationUnitEditor}, null otherwise. + * @param editor the editor + */ + private IJavaElement getSelectedJavaElementInEditor(final IEditorPart editor) { + if (editor instanceof JavaEditor) { + final JavaEditor javaEditor= (JavaEditor) editor; + try { + final IJavaElement inputJavaElement= JavaUI.getEditorInputJavaElement(editor.getEditorInput()); + // when the editor is opened on a .class file, not a .java source file + if (inputJavaElement.getElementType() == IJavaElement.CLASS_FILE) { + final IClassFile classFile= (IClassFile) inputJavaElement; + return classFile.getType(); + } + final ITextSelection selection= (ITextSelection) javaEditor.getSelectionProvider().getSelection(); + final ICompilationUnit compilationUnit= (ICompilationUnit)inputJavaElement.getAncestor(IJavaElement.COMPILATION_UNIT); + final IJavaElement selectedElement= compilationUnit.getElementAt(selection.getOffset()); + // valid results + if (selectedElement == null || selectedElement.getElementType() == IJavaElement.METHOD || selectedElement.getElementType() == IJavaElement.TYPE) { + return selectedElement; + } + // if the selected element is not a Method or a Type, let's return something meaningful + if (selectedElement.getElementType() == IJavaElement.FIELD) { + return selectedElement.getAncestor(IJavaElement.TYPE); + } + if (selectedElement.getElementType() == IJavaElement.PACKAGE_DECLARATION) { + return ((ICompilationUnit) selectedElement.getAncestor(IJavaElement.COMPILATION_UNIT)).findPrimaryType(); + } + if (selectedElement.getElementType() == IJavaElement.IMPORT_CONTAINER || selectedElement.getElementType() == IJavaElement.IMPORT_DECLARATION) { + return ((ICompilationUnit) selectedElement.getAncestor(IJavaElement.COMPILATION_UNIT)).findPrimaryType(); + } + return selectedElement; + } catch (JavaModelException e) { + JUnitPlugin.log(e); + } + } + return null; + } + + /** + * Handles the case when a {@link TestCaseElement} has been selected, unless there's a + * {@link TestRunSession} in progress. + * + * @param testElement the new selected {@link TestCaseElement} + */ + private void handleTestElementSelected(final ITestElement testElement) { + if (fTestRunnerPart.getCurrentProgressState().equals(ProgressState.RUNNING)) { + return; + } + if (testElement instanceof TestCaseElement) { + final IMethod selectedMethod= ((TestCaseElement)testElement).getJavaMethod(); + handleJavaElementSelected(selectedMethod); + } else if (testElement instanceof TestSuiteElement) { + final IJavaElement selectedElement= ((TestSuiteElement)testElement).getJavaElement(); + handleJavaElementSelected(selectedElement); + } + } + + /** + * Reveals the given {@link IJavaElement} in its associated Editor if this later is already + * open, and sets the "Link with Editor" button state accordingly. + * + * @param selectedJavaElement the selected {@link IJavaElement} in the {@link TestViewer} that + * should be revealed in its Java Editor. + */ + private void handleJavaElementSelected(final IJavaElement selectedJavaElement) { + // skip if there's no editor open (yet) + if (fTestRunnerPart.getSite().getPage().getActiveEditor() == null) { + return; + } + try { + final IEditorPart editor= EditorUtility.isOpenInEditor(selectedJavaElement); + if (selectedJavaElement != null && editor != null && editor instanceof JavaEditor) { + final JavaEditor javaEditor= (JavaEditor)editor; + final ITextSelection javaEditorSelection= (ITextSelection)javaEditor.getSelectionProvider().getSelection(); + final IEditorPart selectedMethodEditor= EditorUtility.isOpenInEditor(selectedJavaElement); + // checks if the editor is already open or not + if (selectedMethodEditor != null) { + // Retrieve the current active editor + final IEditorPart activeEditor= fTestRunnerPart.getSite().getPage().getActiveEditor(); + // open the required editor if it is not the active one + if (!selectedMethodEditor.equals(activeEditor)) { + EditorUtility.openInEditor(selectedJavaElement, false); + } + // retrieve the current java element (unless the associated compilation unit cannot be retrieved) + final ICompilationUnit compilationUnit= (ICompilationUnit)selectedJavaElement.getAncestor(IJavaElement.COMPILATION_UNIT); + fTestRunnerPart.setLinkingWithEditorInSync(true); + if (compilationUnit != null) { + final IJavaElement javaEditorSelectedElement= compilationUnit.getElementAt(javaEditorSelection.getOffset()); + // force to reveal the selected element in case where the editor was not active + if (!selectedMethodEditor.equals(activeEditor) || !selectedJavaElement.equals(javaEditorSelectedElement)) { + EditorUtility.revealInEditor(selectedMethodEditor, selectedJavaElement); + } + } + return; + } + } + } catch (JavaModelException e) { + JUnitPlugin.log(e); + } catch (PartInitException e) { + // occurs if the editor could not be opened or the input element is not valid Status code + JUnitPlugin.log(e); + } + fTestRunnerPart.setLinkingWithEditorInSync(false); + } + +} diff --git a/org.eclipse.jdt.ui/.gitignore b/org.eclipse.jdt.ui/.gitignore index c614df2..77a61af 100644 --- a/org.eclipse.jdt.ui/.gitignore +++ b/org.eclipse.jdt.ui/.gitignore @@ -1 +1,2 @@ /bin-jar-in-jar-loader/ +/bin/