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..5e35495 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,9 @@ 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.javaeditor.JavaEditor; import org.eclipse.jdt.internal.ui.viewsupport.ViewHistory; /** @@ -210,6 +216,7 @@ private Action fPreviousAction; private StopAction fStopAction; + private LinkWithEditorAction fLinkWithEditorAction; private JUnitCopyAction fCopyAction; private Action fPasteAction; @@ -320,12 +327,21 @@ public static final Object FAMILY_JUNIT_RUN = new Object(); private IPartListener2 fPartListener= new IPartListener2() { - public void partActivated(IWorkbenchPartReference ref) { } - public void partBroughtToTop(IWorkbenchPartReference ref) { } + + public void partActivated(IWorkbenchPartReference ref) { + } + + public void partBroughtToTop(IWorkbenchPartReference ref) { + updateTestViewerSelection(ref); + } + public void partInputChanged(IWorkbenchPartReference ref) { } public void partClosed(IWorkbenchPartReference ref) { } public void partDeactivated(IWorkbenchPartReference ref) { } - public void partOpened(IWorkbenchPartReference ref) { } + + public void partOpened(IWorkbenchPartReference ref) { + updateTestViewerSelection(ref); + } public void partVisible(IWorkbenchPartReference ref) { if (getSite().getId().equals(ref.getId())) { @@ -336,6 +352,14 @@ public void partHidden(IWorkbenchPartReference ref) { if (getSite().getId().equals(ref.getId())) { fPartIsVisible= false; + } + } + + private void updateTestViewerSelection(IWorkbenchPartReference ref) { + final IWorkbenchPart part= ref.getPart(false); + if (part instanceof JavaEditor && fLinkWithEditorAction.isChecked()) { + final ISelection selection= ((JavaEditor) part).getSelectionProvider().getSelection(); + fTestViewer.selectionChanged(part, selection); } } }; @@ -899,6 +923,70 @@ } } + /** + * 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) { + fIconName= isInSync ? SYNCED_GIF : SYNC_BROKEN_GIF; + 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 +1288,38 @@ } } + /** + * 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 +1365,7 @@ boolean hasErrorsOrFailures= hasErrorsOrFailures(); fNextAction.setEnabled(hasErrorsOrFailures); fPreviousAction.setEnabled(hasErrorsOrFailures); - + fLinkWithEditorAction.setEnabled(fTestRunSession != null); fTestViewer.processChangesInUI(); } @@ -1512,6 +1632,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 +1705,7 @@ if (fFailureTrace != null) { fFailureTrace.dispose(); } + getSite().getPage().removePostSelectionListener(fTestViewer); } private void disposeImages() { @@ -1783,6 +1910,9 @@ if (!testRunSessions.isEmpty()) { fTestRunSessionListener.sessionAdded(testRunSessions.get(0)); } + fLinkWithEditorAction.setChecked(true); + setLinkingWithEditorActive(true); + setLinkingWithEditorInSync(true); } private void addDropAdapter(Composite parent) { @@ -1887,6 +2017,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 +2071,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..eed3dfe 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,11 @@ 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.ITestElementContainer; +import org.eclipse.jdt.junit.model.ITestSuiteElement; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.Clipboard; @@ -42,8 +47,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 +59,23 @@ 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.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,173 @@ 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= findClosestTestElement(activeJavaElement, getCurrentViewerSelection()); + 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 (!activeTestCaseElement.equals(currentSelection)) { + 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 closest {@link ITestElement} from the given {@link IJavaElement} + * + * @param javaElement the java element associated with the {@link ITestElement} to find. + * @param currentSelection the current selection in the TestViewer + * @return the {@link ITestElement} or null if it could not be found. + */ + private ITestElement findClosestTestElement(final IJavaElement javaElement, final Object currentSelection) { + // skip if JUnit is still running or if no Java element was selected + if (fTestRunnerPart.getCurrentProgressState() != ProgressState.COMPLETED || javaElement == null) { + return null; + } + final ITestElement currentTestElement= (ITestElement) currentSelection; + // if the current selection already matches the given java element + if ((currentSelection instanceof TestCaseElement && ((TestCaseElement) currentSelection).getJavaMethod() != null && ((TestCaseElement) currentSelection).getJavaMethod().equals( + javaElement)) + || (currentSelection instanceof TestSuiteElement && ((TestSuiteElement) currentSelection).getJavaType() != null && ((TestSuiteElement) currentSelection).getJavaType().equals( + javaElement))) { + return currentTestElement; + } + // if current selection is a TestCaseElement / Java method, move to the parent + ITestElementContainer currentElementContainer= fTestRunSession; + if (currentTestElement instanceof ITestElementContainer) { + currentElementContainer= (ITestElementContainer) currentTestElement; + } else if (currentTestElement != null) { + currentElementContainer= currentTestElement.getParentContainer(); + } + // now, look in the current test container, or move to parent container until root + while (currentElementContainer != null) { + switch (javaElement.getElementType()) { + case IJavaElement.METHOD: + final ITestCaseElement resultTestCaseElement= findTestCaseElement(currentElementContainer, (IMethod) javaElement); + if (resultTestCaseElement != null) { + return resultTestCaseElement; + } + break; + case IJavaElement.TYPE: + final ITestSuiteElement resultTestSuiteElement= findTestSuiteElement(currentElementContainer, (IType) javaElement); + if (resultTestSuiteElement != null) { + return resultTestSuiteElement; + } + break; + default: + // no result will be provided if the user selects anything else, including package declaration, imports and fields. + break; + } + currentElementContainer= currentElementContainer.getParentContainer(); + } + 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 javaMethod the java method corresponding to the {@link ITestCaseElement} to find + * + * @return the {@link ITestCaseElement} or null if it could not be found. + */ + private ITestCaseElement findTestCaseElement(final ITestElementContainer parentElement, final IMethod javaMethod) { + final IType javaType= (IType) javaMethod.getAncestor(IJavaElement.TYPE); + final String testClassName= javaType.getFullyQualifiedName(); + final String testMethodName= javaMethod.getElementName(); + 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, javaMethod); + 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 javaType the Java type corresponding to the {@link ITestSuiteElement} to find + * + * @return the {@link ITestSuiteElement} or null if it could not be found. + */ + private ITestSuiteElement findTestSuiteElement(final ITestElementContainer parentElement, final IType javaType) { + final String testClassName= javaType.getFullyQualifiedName(); + if (parentElement instanceof ITestSuiteElement && ((ITestSuiteElement) parentElement).getSuiteTypeName().equals(testClassName)) { + return (ITestSuiteElement) parentElement; + } + 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, javaType); + if (matchingNestedTestSuiteElement != null) { + return matchingNestedTestSuiteElement; + } + } + } + return null; + } + public void selectFirstFailure() { TestCaseElement firstFailure= getNextChildFailure(fTestRunSession.getTestRoot(), true); if (firstFailure != null) @@ -722,5 +916,117 @@ 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); + } + // any ITreeSelection (eg: Project Explorer, Package Explorer, Content Outline) should be considered, too. + else if (selection instanceof ITreeSelection) { + final ITreeSelection treeSelection= (ITreeSelection) selection; + if (treeSelection.size() == 1) { + setSelection((IJavaElement) treeSelection.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()); + 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.junit/src/org/eclipse/jdt/junit/launcher/JUnitLaunchConfigurationTab.java b/org.eclipse.jdt.junit/src/org/eclipse/jdt/junit/launcher/JUnitLaunchConfigurationTab.java index 78dacea..10648bb 100644 --- a/org.eclipse.jdt.junit/src/org/eclipse/jdt/junit/launcher/JUnitLaunchConfigurationTab.java +++ b/org.eclipse.jdt.junit/src/org/eclipse/jdt/junit/launcher/JUnitLaunchConfigurationTab.java @@ -697,7 +697,7 @@ String superclassName= type.getSuperclassName(); if (superclassName != null) { int pos= superclassName.indexOf('<'); - if (pos != -1) + if (pos != -1) superclassName= superclassName.substring(0, pos); String[][] resolvedSupertype= type.resolveType(superclassName); if (resolvedSupertype != null && resolvedSupertype.length > 0) { @@ -1123,7 +1123,7 @@ IEditorPart part = page.getActiveEditor(); if (part != null) { IEditorInput input = part.getEditorInput(); - return input.getAdapter(IJavaElement.class); + return (IJavaElement) input.getAdapter(IJavaElement.class); } } return null; 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/