Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,27 @@

import static org.moreunit.elements.CorrespondingMemberRequest.newCorrespondingMemberRequest;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorPart;
import org.moreunit.actions.RunTestAction;
import org.moreunit.actions.RunTestFromCompilationUnitAction;
Expand All @@ -42,7 +50,12 @@
import org.moreunit.launch.TestLauncher;
import org.moreunit.preferences.Preferences;
import org.moreunit.preferences.Preferences.MethodSearchMode;
import org.moreunit.ui.ChooseDialog;
import org.moreunit.ui.MemberContentProvider;
import org.moreunit.ui.TreeActionElement;
import org.moreunit.util.FeatureDetector;
import org.moreunit.util.MemberJumpHistory;
import org.moreunit.util.SearchTools;

/**
* Executes the actions "Run test(s)" launched from the handlers:<br>
Expand Down Expand Up @@ -134,10 +147,56 @@ private void executeRunAllTestsAction(ICompilationUnit compilationUnit, String l
{
testCases.add(selectedJavaType);
}
return testCases;
return resolveAbstractTestCases(testCases);
}, testCases -> runTests(testCases, launchMode));
}

private Collection<IType> resolveAbstractTestCases(Collection<IType> testCases)
{
Collection<IType> resolvedTestCases = new LinkedHashSet<>();
for (IType testCase : testCases)
{
resolvedTestCases.addAll(resolveAbstractTestCase(testCase));
}
return resolvedTestCases;
}

private Collection<IType> resolveAbstractTestCase(IType testCase)
{
try
{
if(! Flags.isAbstract(testCase.getFlags()))
{
return Collections.singleton(testCase);
}

Collection<IType> concreteSubclasses = SearchTools.findConcreteSubclasses(testCase);
if(concreteSubclasses.isEmpty())
{
return Collections.singleton(testCase);
}
if(concreteSubclasses.size() == 1)
{
return Collections.singleton(concreteSubclasses.iterator().next());
}

Collection<IType> choice = chooseSubclasses(testCase, concreteSubclasses);
if(choice != null && ! choice.isEmpty())
{
for (IType type : choice)
{
MemberJumpHistory.getInstance().registerJump(testCase, type);
}
return choice;
}
}
catch (JavaModelException e)
{
// ignore and return original
}
return Collections.singleton(testCase);
}

private void saveIfNeeded(ICompilationUnit compilationUnit)
{
IEditorPart editorPart = EditorUtility.isOpenInEditor(compilationUnit);
Expand Down Expand Up @@ -194,10 +253,139 @@ private void executeRunTestsOfSelectedMemberAction(IMethod methodFromEditor, ICo
{
testElements.add(getTestElementFromTestCase(methodFromEditor, selectedJavaType));
}
return testElements;
return resolveAbstractTestElements(testElements);
}, testElements -> runTests(testElements, launchMode));
}

private Collection<IMember> resolveAbstractTestElements(Collection<IMember> testElements)
{
Collection<IMember> resolvedTestElements = new LinkedHashSet<>();
for (IMember testElement : testElements)
{
resolvedTestElements.addAll(resolveAbstractTestElement(testElement));
}
return resolvedTestElements;
}

private Collection<IMember> resolveAbstractTestElement(IMember testElement)
{
IType type = testElement instanceof IType ? (IType) testElement : testElement.getDeclaringType();
try
{
if(! Flags.isAbstract(type.getFlags()))
{
return Collections.singleton(testElement);
}

Collection<IType> concreteSubclasses = SearchTools.findConcreteSubclasses(type);
if(concreteSubclasses.isEmpty())
{
return Collections.singleton(testElement);
}
if(concreteSubclasses.size() == 1)
{
return Collections.singleton(substituteType(testElement, concreteSubclasses.iterator().next()));
}

Collection<IType> chosenSubclasses = chooseSubclasses(type, concreteSubclasses);
if(chosenSubclasses != null && ! chosenSubclasses.isEmpty())
{
Collection<IMember> resolvedElements = new ArrayList<>();
for (IType chosenSubclass : chosenSubclasses)
{
MemberJumpHistory.getInstance().registerJump(testElement, chosenSubclass);
resolvedElements.add(substituteType(testElement, chosenSubclass));
}
return resolvedElements;
}
}
catch (JavaModelException e)
{
// ignore and return original
}
return Collections.singleton(testElement);
}

private IMember substituteType(IMember testElement, IType newType)
{
if(testElement instanceof IType)
{
return newType;
}
// It's a method. We return the method with the same name from the new type if it exists.
// If not, we still return the method from the abstract class, but the launcher should handle it.
// Actually, JDT's JUnit launcher handles methods from superclasses correctly if the target type is the subclass.
// But for clarity, let's see if we can find the method in the subclass.
IMethod method = (IMethod) testElement;
IMethod subclassMethod = newType.getMethod(method.getElementName(), method.getParameterTypes());
if(subclassMethod.exists())
{
return subclassMethod;
}
return method;
}

private Collection<IType> chooseSubclasses(IType abstractType, Collection<IType> concreteSubclasses)
{
IMember defaultSelection = MemberJumpHistory.getInstance().getLastCorrespondingJumpMember(abstractType);
if(! concreteSubclasses.contains(defaultSelection))
{
defaultSelection = null;
}

MemberContentProvider contentProvider = new MemberContentProvider(concreteSubclasses, (IType) defaultSelection);
contentProvider.withAction(new AllSubclassesAction(concreteSubclasses));

String promptText = "Choose concrete subclass for " + abstractType.getElementName();
return Display.getDefault().syncCall(() -> {
ChooseDialog<Object> dialog = new ChooseDialog<>(promptText, contentProvider);
Object choice = dialog.getChoice();
if(choice instanceof Collection)
{
return (Collection<IType>) choice;
}
if(choice instanceof IType)
{
return Collections.singleton((IType) choice);
}
return null;
});
}

private static class AllSubclassesAction implements TreeActionElement<Collection<IType>>
{
private final Collection<IType> concreteSubclasses;

public AllSubclassesAction(Collection<IType> concreteSubclasses)
{
this.concreteSubclasses = concreteSubclasses;
}

@Override
public boolean provideElement()
{
return true;
}

@Override
public Collection<IType> execute()
{
return concreteSubclasses;
}

@Override
public String getText()
{
return "All concrete subclasses";
}

@Override
public Image getImage()
{
return null;
}
}

/**
* Returns the test method that is selected in editor if any, otherwise
* returns the test case.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,16 @@
*/
class JUnitTestSelectionLaunchConfigurationDelegate extends JUnitLaunchConfigurationDelegate
{
private final Collection<IType> testCasesToRun;
private final Collection<IMember> testMembersToRun;

JUnitTestSelectionLaunchConfigurationDelegate(Collection< ? extends IMember> testsToRun)
{
testCasesToRun = getTestCases(testsToRun);
}

private Collection<IType> getTestCases(Collection< ? extends IMember> testsToRun)
{
Collection<IType> testCases = new LinkedHashSet<IType>();
for (IMember testMember : testsToRun)
{
testCases.add(testMember instanceof IType ? (IType) testMember : testMember.getDeclaringType());
}
return testCases;
testMembersToRun = new LinkedHashSet<>(testsToRun);
}

@Override
protected IMember[] evaluateTests(ILaunchConfiguration configuration, IProgressMonitor monitor) throws CoreException
{
return testCasesToRun.toArray(new IMember[0]);
return testMembersToRun.toArray(new IMember[0]);
}
}
19 changes: 19 additions & 0 deletions org.moreunit.plugin/src/org/moreunit/util/SearchTools.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
import java.util.TreeSet;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
Expand All @@ -31,6 +35,21 @@ public static Set<IType> searchFor(Collection<String> typeNamePatterns, IJavaSea
return search(createSearchPattern(typeNamePatterns, TYPE, DECLARATIONS, R_PATTERN_MATCH), scope);
}

public static Set<IType> findConcreteSubclasses(IType type) throws JavaModelException
{
Set<IType> concreteSubclasses = new LinkedHashSet<>();
ITypeHierarchy hierarchy = type.newTypeHierarchy(new NullProgressMonitor());
IType[] subtypes = hierarchy.getAllSubtypes(type);
for (IType subtype : subtypes)
{
if(! Flags.isAbstract(subtype.getFlags()) && ! Flags.isInterface(subtype.getFlags()))
{
concreteSubclasses.add(subtype);
}
}
return concreteSubclasses;
}

private static Set<IType> search(SearchPattern pattern, IJavaSearchScope scope) throws CoreException
{
SearchParticipant[] participants = new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,10 @@ private Collection<JavaType> splitTypes(String classes)
{
types.add(JavaType.newEnum(packageName, typeDef.substring(5)));
}
else if(typeDef.startsWith("interface "))
{
types.add(JavaType.newInterface(packageName, typeDef.substring(10)));
}
else if(typeDef.startsWith("class "))
{
types.add(JavaType.newClass(packageName, typeDef.substring(6)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public static JavaType newClass(String packageName, String typeName)
return new JavaType(JavaTypeKind.CLASS, packageName, typeName);
}

public static JavaType newInterface(String packageName, String typeName)
{
return new JavaType(JavaTypeKind.INTERFACE, packageName, typeName);
}

@Override
public int hashCode()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

public enum JavaTypeKind
{
CLASS, ENUM;
CLASS, ENUM, INTERFACE;

String toJavaCode()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ public TypeHandler createType(JavaType javaType)
{
type = WorkspaceHelper.createJavaClass(packageFragment, javaType.typeName);
}
else if(javaType.typeKind == JavaTypeKind.INTERFACE)
{
type = WorkspaceHelper.createJavaInterface(packageFragment, javaType.typeName);
}
else
{
type = WorkspaceHelper.createJavaEnum(packageFragment, javaType.typeName);
Expand All @@ -90,6 +94,11 @@ public TypeHandler createEnum(String fullyQualifiedTypeName)
return createType(JavaTypeKind.ENUM, fullyQualifiedTypeName);
}

public TypeHandler createInterface(String fullyQualifiedTypeName)
{
return createType(JavaTypeKind.INTERFACE, fullyQualifiedTypeName);
}

public CompilationUnitHandler createCompilationUnit(String fullyQualifiedTypeName, String contents)
{
try
Expand All @@ -113,6 +122,10 @@ private TypeHandler createType(JavaTypeKind typeKind, String fullyQualifiedTypeN
{
return createType(JavaType.newEnum(name.packageName, name.typeName));
}
if(typeKind == JavaTypeKind.INTERFACE)
{
return createType(JavaType.newInterface(name.packageName, name.typeName));
}
return createType(JavaType.newClass(name.packageName, name.typeName));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ public static IType createJavaEnum(IPackageFragment packageFragment, String java
return createJavaType(packageFragment, javaClassName, JavaTypeKind.ENUM);
}

public static IType createJavaInterface(IPackageFragment packageFragment, String javaClassName) throws JavaModelException
{
return createJavaType(packageFragment, javaClassName, JavaTypeKind.INTERFACE);
}

private static IType createJavaType(IPackageFragment packageFragment, String javaClassName, JavaTypeKind type) throws JavaModelException
{
String sourceCode = String.format("%s%s%s", getPackageDeclarationString(packageFragment), NEW_LINE, getTypeDeclarationString(type, javaClassName));
Expand Down
Loading
Loading