1 /******************************************************************************* 2 * Copyright (c) 2000, 2018 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.jdt.internal.junit.util; 15 16 import java.util.Collection; 17 import java.util.Set; 18 19 import org.eclipse.jdt.junit.JUnitCore; 20 21 import org.eclipse.core.runtime.CoreException; 22 import org.eclipse.core.runtime.IProgressMonitor; 23 24 import org.eclipse.jdt.core.Flags; 25 import org.eclipse.jdt.core.IClassFile; 26 import org.eclipse.jdt.core.IClasspathEntry; 27 import org.eclipse.jdt.core.ICompilationUnit; 28 import org.eclipse.jdt.core.IJavaElement; 29 import org.eclipse.jdt.core.IJavaProject; 30 import org.eclipse.jdt.core.IMethod; 31 import org.eclipse.jdt.core.IPackageFragmentRoot; 32 import org.eclipse.jdt.core.IRegion; 33 import org.eclipse.jdt.core.IType; 34 import org.eclipse.jdt.core.ITypeHierarchy; 35 import org.eclipse.jdt.core.JavaCore; 36 import org.eclipse.jdt.core.JavaModelException; 37 import org.eclipse.jdt.core.Signature; 38 import org.eclipse.jdt.core.dom.ITypeBinding; 39 import org.eclipse.jdt.core.dom.Modifier; 40 import org.eclipse.jdt.core.search.IJavaSearchConstants; 41 import org.eclipse.jdt.core.search.IJavaSearchScope; 42 import org.eclipse.jdt.core.search.SearchEngine; 43 import org.eclipse.jdt.core.search.SearchMatch; 44 import org.eclipse.jdt.core.search.SearchParticipant; 45 import org.eclipse.jdt.core.search.SearchPattern; 46 import org.eclipse.jdt.core.search.SearchRequestor; 47 48 import org.eclipse.jdt.internal.junit.JUnitCorePlugin; 49 import org.eclipse.jdt.internal.junit.launcher.ITestKind; 50 import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry; 51 52 53 /** 54 * Custom Search engine for suite() methods 55 */ 56 public class CoreTestSearchEngine { 57 isTestOrTestSuite(IType declaringType)58 public static boolean isTestOrTestSuite(IType declaringType) throws CoreException { 59 ITestKind testKind= TestKindRegistry.getContainerTestKind(declaringType); 60 return testKind.getFinder().isTest(declaringType); 61 } 62 isAccessibleClass(IType type, String testKindId)63 public static boolean isAccessibleClass(IType type, String testKindId) throws JavaModelException { 64 int flags= type.getFlags(); 65 if (Flags.isInterface(flags)) { 66 return false; 67 } 68 IJavaElement parent= type.getParent(); 69 while (true) { 70 if (parent instanceof ICompilationUnit || parent instanceof IClassFile) { 71 return true; 72 } 73 if (!(parent instanceof IType)) { 74 return false; 75 } 76 if (TestKindRegistry.JUNIT5_TEST_KIND_ID.equals(testKindId)) { 77 // A nested/inner class need not be public in JUnit 5. 78 if (Flags.isPrivate(flags)) { 79 return false; 80 } 81 // If the inner class is non-static, it must be annotated with @Nested. 82 if (!Flags.isStatic(flags) && !type.getAnnotation("Nested").exists()) { //$NON-NLS-1$ 83 return false; 84 } 85 } else if (!Flags.isStatic(flags) || !Flags.isPublic(flags)) { 86 return false; 87 } 88 flags= ((IType) parent).getFlags(); 89 parent= parent.getParent(); 90 } 91 } 92 isAccessibleClass(IType type)93 public static boolean isAccessibleClass(IType type) throws JavaModelException { 94 return isAccessibleClass(type, null); 95 } 96 isAccessibleClass(ITypeBinding type)97 public static boolean isAccessibleClass(ITypeBinding type) { // not used 98 if (type.isInterface()) { 99 return false; 100 } 101 int modifiers= type.getModifiers(); 102 while (true) { 103 if (type.getDeclaringMethod() != null) { 104 return false; 105 } 106 ITypeBinding declaringClass= type.getDeclaringClass(); 107 if (declaringClass == null) { 108 return true; 109 } 110 if (!Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) { 111 return false; 112 } 113 modifiers= declaringClass.getModifiers(); 114 type= declaringClass; 115 } 116 } 117 hasTestCaseType(IJavaProject javaProject)118 public static boolean hasTestCaseType(IJavaProject javaProject) { 119 try { 120 return javaProject != null && javaProject.findType(JUnitCorePlugin.TEST_SUPERCLASS_NAME) != null; 121 } catch (JavaModelException e) { 122 // not available 123 } 124 return false; 125 } 126 hasJUnit4TestAnnotation(IJavaProject project)127 public static boolean hasJUnit4TestAnnotation(IJavaProject project) { 128 try { 129 if (project != null) { 130 IType type= project.findType(JUnitCorePlugin.JUNIT4_ANNOTATION_NAME); 131 if (type != null) { 132 // @Test annotation is not accessible if the JUnit classpath container is set to JUnit 3 133 // (although it may resolve to a JUnit 4 JAR) 134 IPackageFragmentRoot root= (IPackageFragmentRoot) type.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); 135 IClasspathEntry cpEntry= root.getRawClasspathEntry(); 136 return ! JUnitCore.JUNIT3_CONTAINER_PATH.equals(cpEntry.getPath()); 137 } 138 } 139 } catch (JavaModelException e) { 140 // not available 141 } 142 return false; 143 } 144 hasJUnit5TestAnnotation(IJavaProject project)145 public static boolean hasJUnit5TestAnnotation(IJavaProject project) { 146 try { 147 if (project != null) { 148 IType type= project.findType(JUnitCorePlugin.JUNIT5_TESTABLE_ANNOTATION_NAME); 149 if (type != null) { 150 // @Testable annotation is not accessible if the JUnit classpath container is set to JUnit 3 or JUnit 4 151 // (although it may resolve to a JUnit 5 JAR) 152 IPackageFragmentRoot root= (IPackageFragmentRoot) type.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); 153 IClasspathEntry cpEntry= root.getRawClasspathEntry(); 154 return ! JUnitCore.JUNIT3_CONTAINER_PATH.equals(cpEntry.getPath()) && ! JUnitCore.JUNIT4_CONTAINER_PATH.equals(cpEntry.getPath()); 155 } 156 } 157 } catch (JavaModelException e) { 158 // not available 159 } 160 return false; 161 } 162 isTestImplementor(IType type)163 public static boolean isTestImplementor(IType type) throws JavaModelException { 164 ITypeHierarchy typeHier= type.newSupertypeHierarchy(null); 165 IType[] superInterfaces= typeHier.getAllInterfaces(); 166 for (IType superInterface : superInterfaces) { 167 if (JUnitCorePlugin.TEST_INTERFACE_NAME.equals(superInterface.getFullyQualifiedName('.'))) { 168 return true; 169 } 170 } 171 return false; 172 } 173 isTestImplementor(ITypeBinding type)174 public static boolean isTestImplementor(ITypeBinding type) { 175 ITypeBinding superType= type.getSuperclass(); 176 if (superType != null && isTestImplementor(superType)) { 177 return true; 178 } 179 ITypeBinding[] interfaces= type.getInterfaces(); 180 for (ITypeBinding curr : interfaces) { 181 if (JUnitCorePlugin.TEST_INTERFACE_NAME.equals(curr.getQualifiedName()) || isTestImplementor(curr)) { 182 return true; 183 } 184 } 185 return false; 186 } 187 hasSuiteMethod(IType type)188 public static boolean hasSuiteMethod(IType type) throws JavaModelException { 189 IMethod method= type.getMethod("suite", new String[0]); //$NON-NLS-1$ 190 if (!method.exists()) 191 return false; 192 193 if (!Flags.isStatic(method.getFlags()) || !Flags.isPublic(method.getFlags())) { 194 return false; 195 } 196 if (!Signature.getSimpleName(Signature.toString(method.getReturnType())).equals(JUnitCorePlugin.SIMPLE_TEST_INTERFACE_NAME)) { 197 return false; 198 } 199 return true; 200 } 201 getRegion(IJavaElement element)202 public static IRegion getRegion(IJavaElement element) throws JavaModelException { 203 IRegion result= JavaCore.newRegion(); 204 if (element.getElementType() == IJavaElement.JAVA_PROJECT) { 205 // for projects only add the contained source folders 206 IPackageFragmentRoot[] roots= ((IJavaProject) element).getPackageFragmentRoots(); 207 for (IPackageFragmentRoot root : roots) { 208 if (!root.isArchive()) { 209 result.add(root); 210 } 211 } 212 } else { 213 result.add(element); 214 } 215 return result; 216 } 217 findTestImplementorClasses(ITypeHierarchy typeHierarchy, IType testInterface, IRegion region, Set<IType> result)218 public static void findTestImplementorClasses(ITypeHierarchy typeHierarchy, IType testInterface, IRegion region, Set<IType> result) 219 throws JavaModelException { 220 IType[] subtypes= typeHierarchy.getAllSubtypes(testInterface); 221 for (IType type : subtypes) { 222 int cachedFlags= typeHierarchy.getCachedFlags(type); 223 if (!Flags.isInterface(cachedFlags) && !Flags.isAbstract(cachedFlags) // do the cheaper tests first 224 && region.contains(type) && CoreTestSearchEngine.isAccessibleClass(type)) { 225 result.add(type); 226 } 227 } 228 } 229 230 private static class SuiteMethodTypesCollector extends SearchRequestor { 231 232 private Collection<IType> fResult; 233 SuiteMethodTypesCollector(Collection<IType> result)234 public SuiteMethodTypesCollector(Collection<IType> result) { 235 fResult= result; 236 } 237 238 @Override acceptSearchMatch(SearchMatch match)239 public void acceptSearchMatch(SearchMatch match) throws CoreException { 240 Object enclosingElement= match.getElement(); 241 if (!(enclosingElement instanceof IMethod)) 242 return; 243 244 IMethod method= (IMethod) enclosingElement; 245 if (!Flags.isStatic(method.getFlags()) || !Flags.isPublic(method.getFlags())) { 246 return; 247 } 248 249 IType declaringType= ((IMethod) enclosingElement).getDeclaringType(); 250 if (!CoreTestSearchEngine.isAccessibleClass(declaringType)) { 251 return; 252 } 253 fResult.add(declaringType); 254 } 255 } 256 findSuiteMethods(IJavaElement element, Set<IType> result, IProgressMonitor pm)257 public static void findSuiteMethods(IJavaElement element, Set<IType> result, IProgressMonitor pm) throws CoreException { 258 // fix for bug 36449 JUnit should constrain tests to selected project 259 // [JUnit] 260 IJavaSearchScope scope= SearchEngine.createJavaSearchScope(new IJavaElement[] { element }, IJavaSearchScope.SOURCES); 261 262 SearchRequestor requestor= new SuiteMethodTypesCollector(result); 263 int matchRule= SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_ERASURE_MATCH; 264 SearchPattern suitePattern= SearchPattern.createPattern("suite() Test", IJavaSearchConstants.METHOD, IJavaSearchConstants.DECLARATIONS, matchRule); //$NON-NLS-1$ 265 SearchParticipant[] participants= new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }; 266 new SearchEngine().search(suitePattern, participants, scope, requestor, pm); 267 } 268 269 270 // --- copied from org.eclipse.jdt.internal.corext.util.JavaModelUtil: --- 271 /** 272 * @param version1 the first version 273 * @param version2 the second version 274 * @return <code>true</code> iff version1 is less than version2 275 */ isVersionLessThan(String version1, String version2)276 public static boolean isVersionLessThan(String version1, String version2) { 277 return JavaCore.compareJavaVersions(version1, version2) < 0; 278 } 279 280 is50OrHigher(String compliance)281 public static boolean is50OrHigher(String compliance) { 282 return !isVersionLessThan(compliance, JavaCore.VERSION_1_5); 283 } 284 285 /** 286 * Checks if the given project or workspace has source compliance 5.0 or greater. 287 * 288 * @param project the project to test or <code>null</code> to test the workspace settings 289 * @return <code>true</code> if the given project or workspace has source compliance 5.0 or greater. 290 */ is50OrHigher(IJavaProject project)291 public static boolean is50OrHigher(IJavaProject project) { 292 String source= project != null ? project.getOption(JavaCore.COMPILER_SOURCE, true) : JavaCore.getOption(JavaCore.COMPILER_SOURCE); 293 return is50OrHigher(source); 294 } 295 is18OrHigher(String compliance)296 public static boolean is18OrHigher(String compliance) { 297 return !isVersionLessThan(compliance, JavaCore.VERSION_1_8); 298 } 299 300 /** 301 * Checks if the given project or workspace has source compliance 1.8 or greater. 302 * 303 * @param project the project to test or <code>null</code> to test the workspace settings 304 * @return <code>true</code> if the given project or workspace has source compliance 1.8 or 305 * greater. 306 */ is18OrHigher(IJavaProject project)307 public static boolean is18OrHigher(IJavaProject project) { 308 String source= project != null ? project.getOption(JavaCore.COMPILER_SOURCE, true) : JavaCore.getOption(JavaCore.COMPILER_SOURCE); 309 return is18OrHigher(source); 310 } 311 // --- 312 313 } 314