blob: b4b0267a5c4fcac7df473dc60df00677f8e6bbb5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Andreas Schmid, service@aaschmid.de - Locate test method even if it contains parameters - http://bugs.eclipse.org/343935
*******************************************************************************/
package org.eclipse.jdt.internal.junit.ui;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaConventions;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.corext.util.JavaConventionsUtil;
import org.eclipse.jdt.internal.junit.BasicElementLabels;
import org.eclipse.jdt.internal.junit.Messages;
import org.eclipse.jdt.internal.junit.model.TestCaseElement;
import org.eclipse.jdt.internal.junit.model.TestElement;
import org.eclipse.jdt.internal.ui.actions.SelectionConverter;
/**
* Open a class on a Test method.
*/
public class OpenTestAction extends OpenEditorAction {
private String fMethodName;
private String[] fMethodParamTypes;
private IMethod fMethod;
private int fLineNumber= -1;
private IType fType;
public OpenTestAction(TestRunnerViewPart testRunnerPart, TestCaseElement testCase, String[] methodParamTypes) {
this(testRunnerPart, testCase.getClassName(), extractRealMethodName(testCase), methodParamTypes, true);
String trace= testCase.getTrace();
if (trace != null) {
String rawClassName= TestElement.extractRawClassName(testCase.getTestName());
rawClassName= rawClassName.replaceAll("\\.", "\\\\."); //$NON-NLS-1$//$NON-NLS-2$
rawClassName= rawClassName.replaceAll("\\$", "\\\\\\$"); //$NON-NLS-1$//$NON-NLS-2$
Pattern pattern= Pattern.compile(FailureTrace.FRAME_PREFIX
+ rawClassName + '.' + fMethodName
+ "\\(.*:(\\d+)\\)" //$NON-NLS-1$
);
Matcher matcher= pattern.matcher(trace);
if (matcher.find()) {
try {
fLineNumber= Integer.parseInt(matcher.group(1));
} catch (NumberFormatException e) {
// continue
}
}
}
}
public OpenTestAction(TestRunnerViewPart testRunner, String className) {
this(testRunner, className, null, null, true);
}
public OpenTestAction(TestRunnerViewPart testRunner, String className, String method, String[] methodParamTypes, boolean activate) {
super(testRunner, className, activate);
PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IJUnitHelpContextIds.OPENTEST_ACTION);
fMethodName= method;
fMethodParamTypes= methodParamTypes;
}
private static String extractRealMethodName(TestCaseElement testCase) {
//workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=334864 :
if (testCase.isIgnored() && JavaConventions.validateJavaTypeName(testCase.getTestName(), JavaCore.VERSION_1_5, JavaCore.VERSION_1_5, null).getSeverity() != IStatus.ERROR) {
return null;
}
//workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=275308 :
String testMethodName= testCase.getTestMethodName();
for (int i= 0; i < testMethodName.length(); i++) {
if (!Character.isJavaIdentifierPart(testMethodName.charAt(i))) {
return testMethodName.substring(0, i);
}
}
return testMethodName;
}
@Override
protected IJavaElement findElement(IJavaProject project, String className) throws JavaModelException {
IType type= findType(project, className);
if (type == null)
return null;
if (fMethodName == null) {
fType= type;
return type;
}
IMethod method= null;
try {
method= findMethod(type);
if (method == null) {
ITypeHierarchy typeHierarchy= type.newSupertypeHierarchy(null);
IType[] supertypes= typeHierarchy.getAllSupertypes(type);
for (IType supertype : supertypes) {
method= findMethod(supertype);
if (method != null)
break;
}
}
} catch (OperationCanceledException e) {
// user cancelled the selection dialog - ignore and proceed
}
if (method == null) {
if (fLineNumber < 0) {
String title= JUnitMessages.OpenTestAction_dialog_title;
String message= Messages.format(JUnitMessages.OpenTestAction_error_methodNoFound, BasicElementLabels.getJavaElementName(fMethodName));
MessageDialog.openInformation(getShell(), title, message);
}
return type;
}
fMethod= method;
return method;
}
private IMethod findMethod(IType type) {
IStatus status= JavaConventionsUtil.validateMethodName(fMethodName, type);
if (! status.isOK())
return null;
List<IMethod> foundMethods= new ArrayList<>();
try {
PlatformUI.getWorkbench().getProgressService().busyCursorWhile(monitor -> {
String methodPattern= type.getFullyQualifiedName('.') + '.' + fMethodName;
if (fMethodParamTypes != null && fMethodParamTypes.length > 0) {
String paramTypes= Arrays.stream(fMethodParamTypes).map(paramType -> {
try {
return paramType= Signature.toString(paramType);
} catch (IllegalArgumentException e1) {
// return the paramType as it is
}
return paramType.replace('$', '.'); // for nested classes... See OpenEditorAction#findType also.
}).collect(Collectors.joining(", ", "(", ")")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
methodPattern+= paramTypes;
} else {
methodPattern+= "()"; //$NON-NLS-1$
}
int matchRule= SearchPattern.R_ERASURE_MATCH | SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE;
SearchPattern searchPattern= SearchPattern.createPattern(methodPattern, IJavaSearchConstants.METHOD, IJavaSearchConstants.DECLARATIONS, matchRule);
if (searchPattern == null) {
return;
}
SearchRequestor requestor= new SearchRequestor() {
@Override
public void acceptSearchMatch(SearchMatch match) throws CoreException {
Object element= match.getElement();
if (element instanceof IMethod) {
foundMethods.add((IMethod) element);
}
}
};
SearchParticipant[] participants= new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() };
IJavaSearchScope scope= SearchEngine.createJavaSearchScope(new IJavaElement[] { type });
try {
new SearchEngine().search(searchPattern, participants, scope, requestor, monitor);
} catch (CoreException e2) {
JUnitPlugin.log(e2);
}
});
} catch (InvocationTargetException e) {
JUnitPlugin.log(e);
} catch (InterruptedException e) {
// user cancelled
}
if (foundMethods.size() == 1) {
return foundMethods.get(0);
} else if (foundMethods.size() > 1) {
IMethod method= openSelectionDialog(foundMethods);
if (method == null) {
throw new OperationCanceledException();
}
return method;
}
// search just by name and number of parameters, if method not found yet
try {
for (IMethod method : type.getMethods()) {
String methodName= method.getElementName();
if (fMethodName.equals(methodName)) {
int numOfParams= method.getNumberOfParameters();
int requiredNumOfParams= 0;
if (fMethodParamTypes != null) {
requiredNumOfParams= fMethodParamTypes.length;
}
if (numOfParams == requiredNumOfParams) {
foundMethods.add(method);
}
}
}
if (foundMethods.isEmpty()) {
return null;
} else if (foundMethods.size() > 1) {
IMethod method= openSelectionDialog(foundMethods);
if (method == null) {
throw new OperationCanceledException();
}
return method;
} else {
return foundMethods.get(0);
}
} catch (JavaModelException e) {
// if type does not exist or if an exception occurs while accessing its resource => ignore (no method found)
}
return null;
}
private IMethod openSelectionDialog(List<IMethod> foundMethods) {
IMethod[] elements= foundMethods.toArray(new IMethod[foundMethods.size()]);
String title= JUnitMessages.OpenTestAction_dialog_title;
String message= JUnitMessages.OpenTestAction_select_element;
return (IMethod) SelectionConverter.selectJavaElement(elements, getShell(), title, message);
}
@Override
protected void reveal(ITextEditor textEditor) {
if (fLineNumber >= 0) {
try {
IDocument document= textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput());
int lineOffset= document.getLineOffset(fLineNumber-1);
int lineLength= document.getLineLength(fLineNumber-1);
if (fMethod != null) {
try {
ISourceRange sr= fMethod.getSourceRange();
if (sr == null || sr.getOffset() == -1
|| lineOffset < sr.getOffset()
|| sr.getOffset() + sr.getLength() < lineOffset + lineLength) {
throw new BadLocationException();
}
} catch (JavaModelException e) {
// not a problem
}
}
textEditor.selectAndReveal(lineOffset, lineLength);
return;
} catch (BadLocationException x) {
// marker refers to invalid text position -> do nothing
}
}
if (fMethod != null) {
try {
ISourceRange range= fMethod.getNameRange();
if (range != null && range.getOffset() >= 0)
textEditor.selectAndReveal(range.getOffset(), range.getLength());
return;
} catch (JavaModelException e) {
// not a problem
}
}
if (fType != null) {
try {
ISourceRange range= fType.getNameRange();
if (range != null && range.getOffset() >= 0)
textEditor.selectAndReveal(range.getOffset(), range.getLength());
} catch (JavaModelException e) {
// not a problem
}
}
}
}