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