Skip to content

Commit

Permalink
Show progress when searching test methods in run configuration #653
Browse files Browse the repository at this point in the history
Move the logic to search for test methods from
`JUnitLaunchConfigurationTab` to `TestSearchEngine::findTestMethods`.
Extract the whole logic for caching the results into `TestMethodsCache`
(new class) and do the whole searching and caching more efficiently i.e.
only when necessary and showing the progress with a monitor by using
`ModalContext::run`

Fixes #683
Contributes to #653
  • Loading branch information
fedejeanne authored and jjohnstn committed Aug 11, 2023
1 parent f39fa82 commit 169cc10
Show file tree
Hide file tree
Showing 5 changed files with 351 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ public final class JUnitMessages extends NLS {

public static String JUnitLaunchConfigurationTab_error_notJavaProject;

public static String JUnitLaunchConfigurationTab_error_operation_canceled;

public static String JUnitLaunchConfigurationTab_error_projectnotdefined;

public static String JUnitLaunchConfigurationTab_error_projectnotexists;
Expand Down Expand Up @@ -372,4 +374,6 @@ private JUnitMessages() {
public static String TestRunnerViewPart_JUnitPasteAction_label;

public static String TestRunnerViewPart_layout_menu;

public static String TestSearchEngine_search_message_progress_monitor;
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ JUnitLaunchConfigurationTab_folderdialog_message=Choose a Project, Source Folder
JUnitLaunchConfigurationTab_error_projectnotdefined=Project not specified
JUnitLaunchConfigurationTab_error_projectnotexists=Project does not exist
JUnitLaunchConfigurationTab_error_notJavaProject=Specified project is not a Java project
JUnitLaunchConfigurationTab_error_operation_canceled=(Operation canceled by the user)
JUnitLaunchConfigurationTab_error_testnotdefined=Test not specified
JUnitLaunchConfigurationTab_error_testcasenotonpath=Cannot find class 'junit.framework.TestCase' on project build path.
JUnitLaunchConfigurationTab_addtag_label=Con&figure...
Expand Down Expand Up @@ -276,3 +277,5 @@ JUnitViewEditorLauncher_dialog_title=Import Test Run
JUnitViewEditorLauncher_error_occurred=An error occurred while opening a test run file.
ClasspathVariableMarkerResolutionGenerator_use_JUnit3=Use the JUnit 3 library
ClasspathVariableMarkerResolutionGenerator_use_JUnit3_desc=Changes the classpath variable entry to use the JUnit 3 library

TestSearchEngine_search_message_progress_monitor=Searching for test methods in ''{0}''
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,27 @@
import java.util.Set;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;

import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;

import org.eclipse.jdt.core.IAnnotation;
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;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.dom.Modifier;

import org.eclipse.jdt.internal.junit.JUnitCorePlugin;
import org.eclipse.jdt.internal.junit.Messages;
import org.eclipse.jdt.internal.junit.launcher.ITestKind;
import org.eclipse.jdt.internal.junit.launcher.TestKind;
import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry;
import org.eclipse.jdt.internal.junit.ui.JUnitMessages;


/**
Expand All @@ -48,4 +61,124 @@ public static Set<IType> findTests(IRunnableContext context, final IJavaElement
return result;
}

public static Set<String> findTestMethods(IRunnableContext context, final IJavaProject javaProject, IType type, TestKind testKind) throws InvocationTargetException, InterruptedException {
final Set<String> result= new HashSet<>();

IRunnableWithProgress runnable= progressMonitor -> {
try {
String message= Messages.format(JUnitMessages.TestSearchEngine_search_message_progress_monitor, type.getElementName());
SubMonitor subMonitor= SubMonitor.convert(progressMonitor, message, 1);

collectMethodNames(type, javaProject, testKind.getId(), result, subMonitor.split(1));
} catch (CoreException e) {
throw new InvocationTargetException(e);
}
};
context.run(true, true, runnable);
return result;
}

private static void collectMethodNames(IType type, IJavaProject javaProject, String testKindId, Set<String> methodNames, IProgressMonitor monitor) throws JavaModelException {
if (type == null) {
return;
}

SubMonitor subMonitor= SubMonitor.convert(monitor, 3);

collectDeclaredMethodNames(type, javaProject, testKindId, methodNames);
subMonitor.split(1);

String superclassName= type.getSuperclassName();
IType superType= getResolvedType(superclassName, type, javaProject);
collectMethodNames(superType, javaProject, testKindId, methodNames, subMonitor.split(1));

String[] superInterfaceNames= type.getSuperInterfaceNames();
subMonitor.setWorkRemaining(superInterfaceNames.length);
for (String interfaceName : superInterfaceNames) {
superType= getResolvedType(interfaceName, type, javaProject);
collectMethodNames(superType, javaProject, testKindId, methodNames, subMonitor.split(1));
}
}

private static IType getResolvedType(String typeName, IType type, IJavaProject javaProject) throws JavaModelException {
IType resolvedType= null;
if (typeName != null) {
int pos= typeName.indexOf('<');
if (pos != -1) {
typeName= typeName.substring(0, pos);
}
String[][] resolvedTypeNames= type.resolveType(typeName);
if (resolvedTypeNames != null && resolvedTypeNames.length > 0) {
String[] resolvedTypeName= resolvedTypeNames[0];
resolvedType= javaProject.findType(resolvedTypeName[0], resolvedTypeName[1]); // secondary types not found by this API
}
}
return resolvedType;
}

private static void collectDeclaredMethodNames(IType type, IJavaProject javaProject, String testKindId, Set<String> methodNames) throws JavaModelException {
IMethod[] methods= type.getMethods();
for (IMethod method : methods) {
String methodName= method.getElementName();
int flags= method.getFlags();
// Only include public, non-static, no-arg methods that return void and start with "test":
if (Modifier.isPublic(flags) && !Modifier.isStatic(flags) &&
method.getNumberOfParameters() == 0 && Signature.SIG_VOID.equals(method.getReturnType()) &&
methodName.startsWith("test")) { //$NON-NLS-1$
methodNames.add(methodName);
}
boolean isJUnit3= TestKindRegistry.JUNIT3_TEST_KIND_ID.equals(testKindId);
boolean isJUnit5= TestKindRegistry.JUNIT5_TEST_KIND_ID.equals(testKindId);
if (!isJUnit3 && !Modifier.isPrivate(flags) && !Modifier.isStatic(flags)) {
IAnnotation annotation= method.getAnnotation("Test"); //$NON-NLS-1$
if (annotation.exists()) {
methodNames.add(methodName + JUnitStubUtility.getParameterTypes(method, false));
} else if (isJUnit5) {
boolean hasAnyTestAnnotation= method.getAnnotation("TestFactory").exists() //$NON-NLS-1$
|| method.getAnnotation("Testable").exists() //$NON-NLS-1$
|| method.getAnnotation("TestTemplate").exists() //$NON-NLS-1$
|| method.getAnnotation("ParameterizedTest").exists() //$NON-NLS-1$
|| method.getAnnotation("RepeatedTest").exists(); //$NON-NLS-1$
if (hasAnyTestAnnotation || isAnnotatedWithTestable(method, type, javaProject)) {
methodNames.add(methodName + JUnitStubUtility.getParameterTypes(method, false));
}
}
}
}
}

// See JUnit5TestFinder.Annotation#annotates also.
private static boolean isAnnotatedWithTestable(IMethod method, IType declaringType, IJavaProject javaProject) throws JavaModelException {
for (IAnnotation annotation : method.getAnnotations()) {
IType annotationType= getResolvedType(annotation.getElementName(), declaringType, javaProject);
if (annotationType != null) {
if (matchesTestable(annotationType)) {
return true;
}
Set<IType> hierarchy= new HashSet<>();
if (matchesTestableInAnnotationHierarchy(annotationType, javaProject, hierarchy)) {
return true;
}
}
}
return false;
}

private static boolean matchesTestable(IType annotationType) {
return annotationType != null && JUnitCorePlugin.JUNIT5_TESTABLE_ANNOTATION_NAME.equals(annotationType.getFullyQualifiedName());
}

private static boolean matchesTestableInAnnotationHierarchy(IType annotationType, IJavaProject javaProject, Set<IType> hierarchy) throws JavaModelException {
if (annotationType != null) {
for (IAnnotation annotation : annotationType.getAnnotations()) {
IType annType= getResolvedType(annotation.getElementName(), annotationType, javaProject);
if (annType != null && hierarchy.add(annType)) {
if (matchesTestable(annType) || matchesTestableInAnnotationHierarchy(annType, javaProject, hierarchy)) {
return true;
}
}
}
}
return false;
}
}
Loading

0 comments on commit 169cc10

Please sign in to comment.