/******************************************************************************* * Copyright (c) 2006, 2015 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 * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * David Saff (saff@mit.edu) - initial API and implementation * (bug 102632: [JUnit] Support for JUnit 4.) *******************************************************************************/ package org.eclipse.jdt.internal.junit.launcher; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IRegion; import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeHierarchy; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.IAnnotationBinding; import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.TypeDeclaration; 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.junit.JUnitCorePlugin; import org.eclipse.jdt.internal.junit.JUnitMessages; import org.eclipse.jdt.internal.junit.util.CoreTestSearchEngine; public class JUnit4TestFinder implements ITestFinder { private static class Annotation { private static final Annotation RUN_WITH= new Annotation("org.junit.runner.RunWith"); //$NON-NLS-1$ private static final Annotation TEST= new Annotation("org.junit.Test"); //$NON-NLS-1$ private final String fName; private Annotation(String name) { fName= name; } public String getName() { return fName; } private boolean annotates(IAnnotationBinding[] annotations) { for (int i= 0; i < annotations.length; i++) { ITypeBinding annotationType= annotations[i].getAnnotationType(); if (annotationType != null && (annotationType.getQualifiedName().equals(fName))) { return true; } } return false; } public boolean annotatesTypeOrSuperTypes(ITypeBinding type) { while (type != null) { if (annotates(type.getAnnotations())) { return true; } type= type.getSuperclass(); } return false; } public boolean annotatesAtLeastOneMethod(ITypeBinding type) { while (type != null) { IMethodBinding[] declaredMethods= type.getDeclaredMethods(); for (int i= 0; i < declaredMethods.length; i++) { IMethodBinding curr= declaredMethods[i]; if (annotates(curr.getAnnotations())) { return true; } } type= type.getSuperclass(); } return false; } } @Override public void findTestsInContainer(IJavaElement element, Set<IType> result, IProgressMonitor pm) throws CoreException { if (element == null || result == null) { throw new IllegalArgumentException(); } if (element instanceof IType) { if (internalIsTest((IType) element, pm)) { result.add((IType) element); return; } } if (pm == null) pm= new NullProgressMonitor(); try { pm.beginTask(JUnitMessages.JUnit4TestFinder_searching_description, 4); IRegion region= CoreTestSearchEngine.getRegion(element); ITypeHierarchy hierarchy= JavaCore.newTypeHierarchy(region, null, new SubProgressMonitor(pm, 1)); IType[] allClasses= hierarchy.getAllClasses(); // search for all types with references to RunWith and Test and all subclasses HashSet<IType> candidates= new HashSet<>(allClasses.length); SearchRequestor requestor= new AnnotationSearchRequestor(hierarchy, candidates); IJavaSearchScope scope= SearchEngine.createJavaSearchScope(allClasses, IJavaSearchScope.SOURCES); int matchRule= SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE; SearchPattern runWithPattern= SearchPattern.createPattern(Annotation.RUN_WITH.getName(), IJavaSearchConstants.ANNOTATION_TYPE, IJavaSearchConstants.ANNOTATION_TYPE_REFERENCE, matchRule); SearchPattern testPattern= SearchPattern.createPattern(Annotation.TEST.getName(), IJavaSearchConstants.ANNOTATION_TYPE, IJavaSearchConstants.ANNOTATION_TYPE_REFERENCE, matchRule); SearchPattern annotationsPattern= SearchPattern.createOrPattern(runWithPattern, testPattern); SearchParticipant[] searchParticipants= new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }; new SearchEngine().search(annotationsPattern, searchParticipants, scope, requestor, new SubProgressMonitor(pm, 2)); // find all classes in the region for (Iterator<IType> iterator= candidates.iterator(); iterator.hasNext();) { IType curr= iterator.next(); if (CoreTestSearchEngine.isAccessibleClass(curr) && !Flags.isAbstract(curr.getFlags()) && region.contains(curr)) { result.add(curr); } } // add all classes implementing JUnit 3.8's Test interface in the region IType testInterface= element.getJavaProject().findType(JUnitCorePlugin.TEST_INTERFACE_NAME); if (testInterface != null) { CoreTestSearchEngine.findTestImplementorClasses(hierarchy, testInterface, region, result); } //JUnit 4.3 can also run JUnit-3.8-style public static Test suite() methods: CoreTestSearchEngine.findSuiteMethods(element, result, new SubProgressMonitor(pm, 1)); } finally { pm.done(); } } private static class AnnotationSearchRequestor extends SearchRequestor { private final Collection<IType> fResult; private final ITypeHierarchy fHierarchy; public AnnotationSearchRequestor(ITypeHierarchy hierarchy, Collection<IType> result) { fHierarchy= hierarchy; fResult= result; } @Override public void acceptSearchMatch(SearchMatch match) throws CoreException { if (match.getAccuracy() == SearchMatch.A_ACCURATE && !match.isInsideDocComment()) { Object element= match.getElement(); if (element instanceof IType || element instanceof IMethod) { IMember member= (IMember) element; IType type= member.getElementType() == IJavaElement.TYPE ? (IType) member : member.getDeclaringType(); addTypeAndSubtypes(type); } } } private void addTypeAndSubtypes(IType type) { if (fResult.add(type)) { IType[] subclasses= fHierarchy.getSubclasses(type); for (int i= 0; i < subclasses.length; i++) { addTypeAndSubtypes(subclasses[i]); } } } } @Override public boolean isTest(IType type) throws JavaModelException { return internalIsTest(type, null); } private boolean internalIsTest(IType type, IProgressMonitor monitor) throws JavaModelException { if (CoreTestSearchEngine.isAccessibleClass(type)) { if (CoreTestSearchEngine.hasSuiteMethod(type)) { // since JUnit 4.3.1 return true; } ASTParser parser= ASTParser.newParser(AST.JLS8); /* TODO: When bug 156352 is fixed: parser.setProject(type.getJavaProject()); IBinding[] bindings= parser.createBindings(new IJavaElement[] { type }, monitor); if (bindings.length == 1 && bindings[0] instanceof ITypeBinding) { ITypeBinding binding= (ITypeBinding) bindings[0]; return isTest(binding); }*/ if (type.getCompilationUnit() != null) { parser.setSource(type.getCompilationUnit()); } else if (!isAvailable(type.getSourceRange())) { // class file with no source parser.setProject(type.getJavaProject()); IBinding[] bindings= parser.createBindings(new IJavaElement[] { type }, monitor); if (bindings.length == 1 && bindings[0] instanceof ITypeBinding) { ITypeBinding binding= (ITypeBinding) bindings[0]; return isTest(binding); } return false; } else { parser.setSource(type.getClassFile()); } parser.setFocalPosition(0); parser.setResolveBindings(true); CompilationUnit root= (CompilationUnit) parser.createAST(monitor); ASTNode node= root.findDeclaringNode(type.getKey()); if (node instanceof TypeDeclaration) { ITypeBinding binding= ((TypeDeclaration) node).resolveBinding(); if (binding != null) { return isTest(binding); } } } return false; } private static boolean isAvailable(ISourceRange range) { return range != null && range.getOffset() != -1; } private boolean isTest(ITypeBinding binding) { if (Modifier.isAbstract(binding.getModifiers())) return false; if (Annotation.RUN_WITH.annotatesTypeOrSuperTypes(binding) || Annotation.TEST.annotatesAtLeastOneMethod(binding)) { return true; } return CoreTestSearchEngine.isTestImplementor(binding); } }