de0c115aa717a330d10545f81afdde8139d89abf
[idea/community.git] / java / execution / impl / src / com / intellij / execution / junit / JUnitUtil.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.execution.junit;
17
18 import com.intellij.codeInsight.AnnotationUtil;
19 import com.intellij.codeInsight.MetaAnnotationUtil;
20 import com.intellij.codeInsight.TestFrameworks;
21 import com.intellij.execution.*;
22 import com.intellij.execution.junit2.info.MethodLocation;
23 import com.intellij.execution.testframework.SourceScope;
24 import com.intellij.openapi.module.Module;
25 import com.intellij.openapi.module.ModuleUtilCore;
26 import com.intellij.openapi.progress.ProgressManager;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.util.Condition;
29 import com.intellij.psi.*;
30 import com.intellij.psi.search.GlobalSearchScope;
31 import com.intellij.psi.search.searches.ClassInheritorsSearch;
32 import com.intellij.psi.util.*;
33 import com.intellij.testIntegration.JavaTestFramework;
34 import com.intellij.testIntegration.TestFramework;
35 import org.jetbrains.annotations.NonNls;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
38
39 import java.util.*;
40
41 @SuppressWarnings({"UtilityClassWithoutPrivateConstructor"})
42 public class JUnitUtil {
43   @NonNls public static final String TESTCASE_CLASS = "junit.framework.TestCase";
44   @NonNls private static final String TEST_INTERFACE = "junit.framework.Test";
45   @NonNls private static final String TESTSUITE_CLASS = "junit.framework.TestSuite";
46   @NonNls public static final String TEST_ANNOTATION = "org.junit.Test";
47   @NonNls public static final String TEST5_ANNOTATION = "org.junit.jupiter.api.Test";
48   @NonNls public static final String TEST5_FACTORY_ANNOTATION = "org.junit.jupiter.api.TestFactory";
49   @NonNls public static final String IGNORE_ANNOTATION = "org.junit.Ignore";
50   @NonNls public static final String RUN_WITH = "org.junit.runner.RunWith";
51   @NonNls public static final String DATA_POINT = "org.junit.experimental.theories.DataPoint";
52   @NonNls public static final String SUITE_METHOD_NAME = "suite";
53
54   public static final String BEFORE_ANNOTATION_NAME = "org.junit.Before";
55   public static final String AFTER_ANNOTATION_NAME = "org.junit.After";
56
57   public static final String BEFORE_EACH_ANNOTATION_NAME = "org.junit.jupiter.api.BeforeEach";
58   public static final String AFTER_EACH_ANNOTATION_NAME = "org.junit.jupiter.api.AfterEach";
59
60   public static final String PARAMETRIZED_PARAMETERS_ANNOTATION_NAME = "org.junit.runners.Parameterized.Parameters";
61   public static final String PARAMETRIZED_PARAMETER_ANNOTATION_NAME = "org.junit.runners.Parameterized.Parameter";
62
63   public static final String AFTER_CLASS_ANNOTATION_NAME = "org.junit.AfterClass";
64   public static final String BEFORE_CLASS_ANNOTATION_NAME = "org.junit.BeforeClass";
65
66   public static final String BEFORE_ALL_ANNOTATION_NAME = "org.junit.jupiter.api.BeforeAll";
67   public static final String AFTER_ALL_ANNOTATION_NAME = "org.junit.jupiter.api.AfterAll";
68
69   private static final Collection<String> TEST_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(TEST_ANNOTATION,
70                                                                                                         TEST5_ANNOTATION,
71                                                                                                         TEST5_FACTORY_ANNOTATION));
72   public static final Collection<String> TEST5_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(TEST5_ANNOTATION, TEST5_FACTORY_ANNOTATION));
73
74   private static final List<String> INSTANCE_CONFIGS = Arrays.asList(BEFORE_ANNOTATION_NAME, AFTER_ANNOTATION_NAME);
75   private static final List<String> INSTANCE_5_CONFIGS = Arrays.asList(BEFORE_EACH_ANNOTATION_NAME, AFTER_EACH_ANNOTATION_NAME);
76
77   private static final List<String> STATIC_5_CONFIGS = Arrays.asList(BEFORE_ALL_ANNOTATION_NAME, AFTER_ALL_ANNOTATION_NAME);
78
79   private static final List<String> STATIC_CONFIGS = Arrays.asList(BEFORE_CLASS_ANNOTATION_NAME, AFTER_CLASS_ANNOTATION_NAME,
80                                                                    PARAMETRIZED_PARAMETERS_ANNOTATION_NAME);
81   private static final Collection<String> CONFIGURATIONS_ANNOTATION_NAME = Collections.unmodifiableList(
82     Arrays.asList(DATA_POINT, AFTER_ANNOTATION_NAME, BEFORE_ANNOTATION_NAME, AFTER_CLASS_ANNOTATION_NAME, BEFORE_CLASS_ANNOTATION_NAME,
83                   BEFORE_ALL_ANNOTATION_NAME, AFTER_ALL_ANNOTATION_NAME));
84   
85   @NonNls public static final String PARAMETERIZED_CLASS_NAME = "org.junit.runners.Parameterized";
86   @NonNls public static final String SUITE_CLASS_NAME = "org.junit.runners.Suite";
87   public static final String JUNIT5_NESTED = "org.junit.jupiter.api.Nested";
88
89   public static boolean isSuiteMethod(@NotNull PsiMethod psiMethod) {
90     if (!psiMethod.hasModifierProperty(PsiModifier.PUBLIC)) return false;
91     if (!psiMethod.hasModifierProperty(PsiModifier.STATIC)) return false;
92     if (psiMethod.isConstructor()) return false;
93     if (psiMethod.getParameterList().getParametersCount() > 0) return false;
94     final PsiType returnType = psiMethod.getReturnType();
95     if (returnType == null || returnType instanceof PsiPrimitiveType) return false;
96     return returnType.equalsToText(TEST_INTERFACE)||
97            returnType.equalsToText(TESTSUITE_CLASS) ||
98            InheritanceUtil.isInheritor(returnType, TEST_INTERFACE);
99   }
100
101   public static boolean isTestMethod(final Location<? extends PsiMethod> location) {
102     return isTestMethod(location, true);
103   }
104
105   public static boolean isTestMethod(final Location<? extends PsiMethod> location, boolean checkAbstract) {
106     return isTestMethod(location, checkAbstract, true);
107   }
108
109   public static boolean isTestMethod(final Location<? extends PsiMethod> location, boolean checkAbstract, boolean checkRunWith) {
110     final PsiMethod psiMethod = location.getPsiElement();
111     final PsiClass aClass = location instanceof MethodLocation ? ((MethodLocation)location).getContainingClass() : psiMethod.getContainingClass();
112     if (aClass == null || !isTestClass(aClass, checkAbstract, true)) return false;
113     if (isTestAnnotated(psiMethod)) return true;
114     if (psiMethod.isConstructor()) return false;
115     if (!psiMethod.hasModifierProperty(PsiModifier.PUBLIC)) return false;
116     if (psiMethod.hasModifierProperty(PsiModifier.ABSTRACT)) return false;
117     if (AnnotationUtil.isAnnotated(psiMethod, CONFIGURATIONS_ANNOTATION_NAME, false)) return false;
118     if (checkRunWith) {
119       PsiAnnotation annotation = AnnotationUtil.findAnnotation(aClass, RUN_WITH);
120       if (annotation != null) {
121         return !isParameterized(annotation);
122       }
123     }
124     if (psiMethod.getParameterList().getParametersCount() > 0) return false;
125     if (psiMethod.hasModifierProperty(PsiModifier.STATIC)) return false;
126     if (!psiMethod.getName().startsWith("test")) return false;
127     PsiClass testCaseClass = getTestCaseClassOrNull(location);
128     return testCaseClass != null && psiMethod.getContainingClass().isInheritor(testCaseClass, true) && PsiType.VOID.equals(psiMethod.getReturnType());
129   }
130
131   public static boolean isTestCaseInheritor(final PsiClass aClass) {
132     if (!aClass.isValid()) return false;
133     Location<PsiClass> location = PsiLocation.fromPsiElement(aClass);
134     PsiClass testCaseClass = getTestCaseClassOrNull(location);
135     return testCaseClass != null && aClass.isInheritor(testCaseClass, true);
136   }
137
138   public static boolean isTestClass(final PsiClass psiClass) {
139     return isTestClass(psiClass, true, true);
140   }
141
142   public static boolean isTestClass(@NotNull PsiClass psiClass, boolean checkAbstract, boolean checkForTestCaseInheritance) {
143     if (psiClass.getQualifiedName() == null) return false;
144     if (isJUnit5(psiClass) && isJUnit5TestClass(psiClass, checkAbstract)) {
145       return true;
146     }
147     final PsiClass topLevelClass = PsiTreeUtil.getTopmostParentOfType(psiClass, PsiClass.class);
148     if (topLevelClass != null) {
149       final PsiAnnotation annotation = AnnotationUtil.findAnnotationInHierarchy(topLevelClass, Collections.singleton(RUN_WITH));
150       if (annotation != null) {
151         final PsiAnnotationMemberValue attributeValue = annotation.findAttributeValue("value");
152         if (attributeValue instanceof PsiClassObjectAccessExpression) {
153           final String runnerName = ((PsiClassObjectAccessExpression)attributeValue).getOperand().getType().getCanonicalText();
154           if (!(PARAMETERIZED_CLASS_NAME.equals(runnerName) || SUITE_CLASS_NAME.equals(runnerName))) {
155             return true;
156           }
157         }
158       }
159     }
160     if (!PsiClassUtil.isRunnableClass(psiClass, true, checkAbstract)) return false;
161
162     if (AnnotationUtil.isAnnotated(psiClass, RUN_WITH, true)) return true;
163
164     if (checkForTestCaseInheritance && isTestCaseInheritor(psiClass)) return true;
165
166     return CachedValuesManager.getCachedValue(psiClass, () ->
167       CachedValueProvider.Result.create(hasTestOrSuiteMethods(psiClass), PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT));
168   }
169
170   private static boolean hasTestOrSuiteMethods(@NotNull PsiClass psiClass) {
171     for (final PsiMethod method : psiClass.getAllMethods()) {
172       if (isSuiteMethod(method)) return true;
173       if (isTestAnnotated(method)) return true;
174     }
175
176     if (isJUnit5(psiClass)) {
177       for (PsiClass innerClass : psiClass.getInnerClasses()) {
178         for (PsiMethod method : innerClass.getAllMethods()) {
179           if (isTestAnnotated(method)) return true;
180         }
181       }
182     }
183
184     return false;
185   }
186
187   public static boolean isJUnit3TestClass(final PsiClass clazz) {
188     return isTestCaseInheritor(clazz);
189   }
190
191   public static boolean isJUnit4TestClass(final PsiClass psiClass) {
192     return isJUnit4TestClass(psiClass, true);
193   }
194
195   private static boolean isJUnit4TestClass(final PsiClass psiClass, boolean checkAbstract) {
196     final PsiModifierList modifierList = psiClass.getModifierList();
197     if (modifierList == null) return false;
198     if (AnnotationUtil.isAnnotated(psiClass, RUN_WITH, true)) return true;
199
200     if (!PsiClassUtil.isRunnableClass(psiClass, true, checkAbstract)) return false;
201
202     for (final PsiMethod method : psiClass.getAllMethods()) {
203       ProgressManager.checkCanceled();
204       if (isTestAnnotated(method)) return true;
205     }
206
207     return false;
208   }
209
210   public static boolean isJUnit5TestClass(final PsiClass psiClass, boolean checkAbstract) {
211     final PsiModifierList modifierList = psiClass.getModifierList();
212     if (modifierList == null) return false;
213
214     if (psiClass.getContainingClass() != null && AnnotationUtil.isAnnotated(psiClass, JUNIT5_NESTED, false)) {
215       return true;
216     }
217
218     if (!PsiClassUtil.isRunnableClass(psiClass, false, checkAbstract)) return false;
219
220     Module module = ModuleUtilCore.findModuleForPsiElement(psiClass);
221     if (module != null) {
222       for (final PsiMethod method : psiClass.getAllMethods()) {
223         ProgressManager.checkCanceled();
224         if (MetaAnnotationUtil.isMetaAnnotated(method, TEST5_ANNOTATIONS)) return true;
225       }
226
227       for (PsiClass aClass : psiClass.getInnerClasses()) {
228         if (MetaAnnotationUtil.isMetaAnnotated(aClass, Collections.singleton(JUNIT5_NESTED))) return true;
229       }
230     }
231
232     return false;
233   }
234
235   public static boolean isJUnit5(@NotNull PsiElement element) {
236     return isJUnit5(element.getResolveScope(), element.getProject());
237   }
238
239   public static boolean isJUnit5(GlobalSearchScope scope, Project project) {
240     return JavaPsiFacade.getInstance(project).findClass(TEST5_ANNOTATION, scope) != null;
241   }
242   
243   public static boolean isTestAnnotated(final PsiMethod method) {
244     if (AnnotationUtil.isAnnotated(method, TEST_ANNOTATION, false) || JUnitRecognizer.willBeAnnotatedAfterCompilation(method)) {
245       return true;
246     }
247
248     return MetaAnnotationUtil.isMetaAnnotated(method, TEST5_ANNOTATIONS);
249   }
250
251
252   @Nullable
253   private static PsiClass getTestCaseClassOrNull(final Location<?> location) {
254     final Location<PsiClass> ancestorOrSelf = location.getAncestorOrSelf(PsiClass.class);
255     if (ancestorOrSelf == null) return null;
256     final PsiClass aClass = ancestorOrSelf.getPsiElement();
257     Module module = JavaExecutionUtil.findModule(aClass);
258     if (module == null) return null;
259     GlobalSearchScope scope = GlobalSearchScope.moduleRuntimeScope(module, true);
260     return getTestCaseClassOrNull(scope, module.getProject());
261   }
262
263   public static PsiClass getTestCaseClass(final Module module) throws NoJUnitException {
264     if (module == null) throw new NoJUnitException();
265     final GlobalSearchScope scope = GlobalSearchScope.moduleRuntimeScope(module, true);
266     return getTestCaseClass(scope, module.getProject());
267   }
268
269   public static PsiClass getTestCaseClass(final SourceScope scope) throws NoJUnitException {
270     if (scope == null) throw new NoJUnitException();
271     return getTestCaseClass(scope.getLibrariesScope(), scope.getProject());
272   }
273
274   private static PsiClass getTestCaseClass(final GlobalSearchScope scope, final Project project) throws NoJUnitException {
275     PsiClass testCaseClass = getTestCaseClassOrNull(scope, project);
276     if (testCaseClass == null) throw new NoJUnitException(scope.getDisplayName());
277     return testCaseClass;
278   }
279
280   @Nullable
281   private static PsiClass getTestCaseClassOrNull(final GlobalSearchScope scope, final Project project) {
282     return JavaPsiFacade.getInstance(project).findClass(TESTCASE_CLASS, scope);
283   }
284
285   public static boolean isTestMethodOrConfig(@NotNull PsiMethod psiMethod) {
286     if (isTestMethod(PsiLocation.fromPsiElement(psiMethod), false)) {
287       final PsiClass containingClass = psiMethod.getContainingClass();
288       assert containingClass != null : psiMethod + "; " + psiMethod.getClass() + "; " + psiMethod.getParent();
289       if (containingClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
290         final boolean[] foundNonAbstractInheritor = new boolean[1];
291         ClassInheritorsSearch.search(containingClass).forEach(psiClass -> {
292           if (!psiClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
293             foundNonAbstractInheritor[0] = true;
294             return false;
295           }
296           return true;
297         });
298         if (foundNonAbstractInheritor[0]) {
299           return true;
300         }
301       } else {
302         return true;
303       }
304     }
305     final String name = psiMethod.getName();
306     final boolean isPublic = psiMethod.hasModifierProperty(PsiModifier.PUBLIC);
307     if (!psiMethod.hasModifierProperty(PsiModifier.ABSTRACT)) {
308       if (isPublic && (SUITE_METHOD_NAME.equals(name) || "setUp".equals(name) || "tearDown".equals(name))) {
309         return true;
310       }
311
312       if (psiMethod.hasModifierProperty(PsiModifier.STATIC)) {
313         if (AnnotationUtil.isAnnotated(psiMethod, STATIC_CONFIGS)) {
314           return isPublic;
315         }
316         if (AnnotationUtil.isAnnotated(psiMethod, STATIC_5_CONFIGS)) {
317           return true;
318         }
319       }
320       else {
321         if (AnnotationUtil.isAnnotated(psiMethod, INSTANCE_CONFIGS)) {
322           return isPublic;
323         }
324
325         if (AnnotationUtil.isAnnotated(psiMethod, INSTANCE_5_CONFIGS)) {
326           return true;
327         }
328       }
329     }
330     return false;
331   }
332
333   @Nullable
334   public static PsiMethod findFirstTestMethod(PsiClass clazz) {
335     PsiMethod testMethod = null;
336     for (PsiMethod method : clazz.getMethods()) {
337       if (isTestMethod(MethodLocation.elementInClass(method, clazz)) || isSuiteMethod(method)) {
338         testMethod = method;
339         break;
340       }
341     }
342     return testMethod;
343   }
344
345   @Nullable
346   public static PsiMethod findSuiteMethod(PsiClass clazz) {
347     final PsiMethod[] suiteMethods = clazz.findMethodsByName(SUITE_METHOD_NAME, false);
348     for (PsiMethod method : suiteMethods) {
349       if (isSuiteMethod(method)) return method;
350     }
351     return null;
352   }
353
354   public static boolean isParameterized(PsiAnnotation annotation) {
355     final PsiAnnotationMemberValue value = annotation.findAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME);
356     if (value instanceof PsiClassObjectAccessExpression) {
357       final PsiTypeElement operand = ((PsiClassObjectAccessExpression)value).getOperand();
358       final PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(operand.getType());
359       return psiClass != null && "org.junit.runners.Parameterized".equals(psiClass.getQualifiedName());
360     }
361     return false;
362   }
363
364   public static class  TestMethodFilter implements Condition<PsiMethod> {
365     private final PsiClass myClass;
366     private final JavaTestFramework framework;
367
368     public TestMethodFilter(final PsiClass aClass) {
369       myClass = aClass;
370       TestFramework framework = TestFrameworks.detectFramework(aClass);
371       this.framework = (framework instanceof JavaTestFramework) ? (JavaTestFramework)framework : null;
372     }
373
374     public boolean value(final PsiMethod method) {
375       return framework != null
376              ? framework.isTestMethod(method, myClass)
377              : isTestMethod(MethodLocation.elementInClass(method, myClass));
378     }
379   }
380
381   public static PsiClass findPsiClass(final String qualifiedName, final Module module, final Project project) {
382     final GlobalSearchScope scope = module == null ? GlobalSearchScope.projectScope(project) : GlobalSearchScope.moduleWithDependenciesScope(module);
383     return JavaPsiFacade.getInstance(project).findClass(qualifiedName, scope);
384   }
385
386   public static PsiPackage getContainingPackage(@NotNull PsiClass psiClass) {
387     PsiDirectory directory = psiClass.getContainingFile().getContainingDirectory();
388     return directory == null ? null : JavaDirectoryService.getInstance().getPackage(directory);
389   }
390
391   public static PsiClass getTestClass(final PsiElement element) {
392     return getTestClass(PsiLocation.fromPsiElement(element));
393   }
394
395   public static PsiClass getTestClass(final Location<?> location) {
396     for (Iterator<Location<PsiClass>> iterator = location.getAncestors(PsiClass.class, false); iterator.hasNext();) {
397       final Location<PsiClass> classLocation = iterator.next();
398       if (isTestClass(classLocation.getPsiElement(), false, true)) return classLocation.getPsiElement();
399     }
400     PsiElement element = location.getPsiElement();
401     if (element instanceof PsiClassOwner) {
402       PsiClass[] classes = ((PsiClassOwner)element).getClasses();
403       if (classes.length == 1) return classes[0];
404     }
405     return null;
406   }
407
408   public static PsiMethod getTestMethod(final PsiElement element) {
409     return getTestMethod(element, true);
410   }
411
412
413   public static PsiMethod getTestMethod(final PsiElement element, boolean checkAbstract) {
414     return getTestMethod(element, checkAbstract, true);
415   }
416
417   public static PsiMethod getTestMethod(final PsiElement element, boolean checkAbstract, boolean checkRunWith) {
418     final PsiManager manager = element.getManager();
419     final Location<PsiElement> location = PsiLocation.fromPsiElement(manager.getProject(), element);
420     for (Iterator<Location<PsiMethod>> iterator = location.getAncestors(PsiMethod.class, false); iterator.hasNext();) {
421       final Location<? extends PsiMethod> methodLocation = iterator.next();
422       if (isTestMethod(methodLocation, checkAbstract, checkRunWith)) return methodLocation.getPsiElement();
423     }
424     return null;
425   }
426
427   public static class NoJUnitException extends CantRunException {
428     public NoJUnitException() {
429       super(ExecutionBundle.message("no.junit.error.message"));
430     }
431
432     public NoJUnitException(final String message) {
433       super(ExecutionBundle.message("no.junit.in.scope.error.message", message));
434     }
435   }
436 }