[junit] add flag to check test class
[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     return isTestMethod(location, checkAbstract, checkRunWith, true);
111   }
112
113   public static boolean isTestMethod(final Location<? extends PsiMethod> location, boolean checkAbstract, boolean checkRunWith, boolean checkClass) {
114     final PsiMethod psiMethod = location.getPsiElement();
115     final PsiClass aClass = location instanceof MethodLocation ? ((MethodLocation)location).getContainingClass() : psiMethod.getContainingClass();
116     if (checkClass && (aClass == null || !isTestClass(aClass, checkAbstract, true))) return false;
117     if (isTestAnnotated(psiMethod)) return true;
118     if (psiMethod.isConstructor()) return false;
119     if (!psiMethod.hasModifierProperty(PsiModifier.PUBLIC)) return false;
120     if (psiMethod.hasModifierProperty(PsiModifier.ABSTRACT)) return false;
121     if (AnnotationUtil.isAnnotated(psiMethod, CONFIGURATIONS_ANNOTATION_NAME, false)) return false;
122     if (checkClass && checkRunWith) {
123       PsiAnnotation annotation = AnnotationUtil.findAnnotation(aClass, RUN_WITH);
124       if (annotation != null) {
125         return !isParameterized(annotation);
126       }
127     }
128     if (psiMethod.getParameterList().getParametersCount() > 0) return false;
129     if (psiMethod.hasModifierProperty(PsiModifier.STATIC)) return false;
130     if (!psiMethod.getName().startsWith("test")) return false;
131     if (checkClass) {
132       PsiClass testCaseClass = getTestCaseClassOrNull(location);
133       if (testCaseClass == null || !psiMethod.getContainingClass().isInheritor(testCaseClass, true)) return false;
134     }
135     return PsiType.VOID.equals(psiMethod.getReturnType());
136   }
137
138   public static boolean isTestCaseInheritor(final PsiClass aClass) {
139     if (!aClass.isValid()) return false;
140     Location<PsiClass> location = PsiLocation.fromPsiElement(aClass);
141     PsiClass testCaseClass = getTestCaseClassOrNull(location);
142     return testCaseClass != null && aClass.isInheritor(testCaseClass, true);
143   }
144
145   public static boolean isTestClass(final PsiClass psiClass) {
146     return isTestClass(psiClass, true, true);
147   }
148
149   public static boolean isTestClass(@NotNull PsiClass psiClass, boolean checkAbstract, boolean checkForTestCaseInheritance) {
150     if (psiClass.getQualifiedName() == null) return false;
151     if (isJUnit5(psiClass) && isJUnit5TestClass(psiClass, checkAbstract)) {
152       return true;
153     }
154     final PsiClass topLevelClass = PsiTreeUtil.getTopmostParentOfType(psiClass, PsiClass.class);
155     if (topLevelClass != null) {
156       final PsiAnnotation annotation = AnnotationUtil.findAnnotationInHierarchy(topLevelClass, Collections.singleton(RUN_WITH));
157       if (annotation != null) {
158         final PsiAnnotationMemberValue attributeValue = annotation.findAttributeValue("value");
159         if (attributeValue instanceof PsiClassObjectAccessExpression) {
160           final String runnerName = ((PsiClassObjectAccessExpression)attributeValue).getOperand().getType().getCanonicalText();
161           if (!(PARAMETERIZED_CLASS_NAME.equals(runnerName) || SUITE_CLASS_NAME.equals(runnerName))) {
162             return true;
163           }
164         }
165       }
166     }
167     if (!PsiClassUtil.isRunnableClass(psiClass, true, checkAbstract)) return false;
168
169     if (AnnotationUtil.isAnnotated(psiClass, RUN_WITH, true)) return true;
170
171     if (checkForTestCaseInheritance && isTestCaseInheritor(psiClass)) return true;
172
173     return CachedValuesManager.getCachedValue(psiClass, () ->
174       CachedValueProvider.Result.create(hasTestOrSuiteMethods(psiClass), PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT));
175   }
176
177   private static boolean hasTestOrSuiteMethods(@NotNull PsiClass psiClass) {
178     for (final PsiMethod method : psiClass.getAllMethods()) {
179       if (isSuiteMethod(method)) return true;
180       if (isTestAnnotated(method)) return true;
181     }
182
183     if (isJUnit5(psiClass)) {
184       for (PsiClass innerClass : psiClass.getInnerClasses()) {
185         for (PsiMethod method : innerClass.getAllMethods()) {
186           if (isTestAnnotated(method)) return true;
187         }
188       }
189     }
190
191     return false;
192   }
193
194   public static boolean isJUnit3TestClass(final PsiClass clazz) {
195     return isTestCaseInheritor(clazz);
196   }
197
198   public static boolean isJUnit4TestClass(final PsiClass psiClass) {
199     return isJUnit4TestClass(psiClass, true);
200   }
201
202   private static boolean isJUnit4TestClass(final PsiClass psiClass, boolean checkAbstract) {
203     final PsiModifierList modifierList = psiClass.getModifierList();
204     if (modifierList == null) return false;
205     if (AnnotationUtil.isAnnotated(psiClass, RUN_WITH, true)) return true;
206
207     if (!PsiClassUtil.isRunnableClass(psiClass, true, checkAbstract)) return false;
208
209     for (final PsiMethod method : psiClass.getAllMethods()) {
210       ProgressManager.checkCanceled();
211       if (isTestAnnotated(method)) return true;
212     }
213
214     return false;
215   }
216
217   public static boolean isJUnit5TestClass(final PsiClass psiClass, boolean checkAbstract) {
218     final PsiModifierList modifierList = psiClass.getModifierList();
219     if (modifierList == null) return false;
220
221     if (psiClass.getContainingClass() != null && AnnotationUtil.isAnnotated(psiClass, JUNIT5_NESTED, false)) {
222       return true;
223     }
224
225     if (!PsiClassUtil.isRunnableClass(psiClass, false, checkAbstract)) return false;
226
227     Module module = ModuleUtilCore.findModuleForPsiElement(psiClass);
228     if (module != null) {
229       for (final PsiMethod method : psiClass.getAllMethods()) {
230         ProgressManager.checkCanceled();
231         if (MetaAnnotationUtil.isMetaAnnotated(method, TEST5_ANNOTATIONS)) return true;
232       }
233
234       for (PsiClass aClass : psiClass.getInnerClasses()) {
235         if (MetaAnnotationUtil.isMetaAnnotated(aClass, Collections.singleton(JUNIT5_NESTED))) return true;
236       }
237     }
238
239     return false;
240   }
241
242   public static boolean isJUnit5(@NotNull PsiElement element) {
243     return isJUnit5(element.getResolveScope(), element.getProject());
244   }
245
246   public static boolean isJUnit5(GlobalSearchScope scope, Project project) {
247     return JavaPsiFacade.getInstance(project).findClass(TEST5_ANNOTATION, scope) != null;
248   }
249   
250   public static boolean isTestAnnotated(final PsiMethod method) {
251     if (AnnotationUtil.isAnnotated(method, TEST_ANNOTATION, false) || JUnitRecognizer.willBeAnnotatedAfterCompilation(method)) {
252       return true;
253     }
254
255     return MetaAnnotationUtil.isMetaAnnotated(method, TEST5_ANNOTATIONS);
256   }
257
258
259   @Nullable
260   private static PsiClass getTestCaseClassOrNull(final Location<?> location) {
261     final Location<PsiClass> ancestorOrSelf = location.getAncestorOrSelf(PsiClass.class);
262     if (ancestorOrSelf == null) return null;
263     final PsiClass aClass = ancestorOrSelf.getPsiElement();
264     Module module = JavaExecutionUtil.findModule(aClass);
265     if (module == null) return null;
266     GlobalSearchScope scope = GlobalSearchScope.moduleRuntimeScope(module, true);
267     return getTestCaseClassOrNull(scope, module.getProject());
268   }
269
270   public static PsiClass getTestCaseClass(final Module module) throws NoJUnitException {
271     if (module == null) throw new NoJUnitException();
272     final GlobalSearchScope scope = GlobalSearchScope.moduleRuntimeScope(module, true);
273     return getTestCaseClass(scope, module.getProject());
274   }
275
276   public static PsiClass getTestCaseClass(final SourceScope scope) throws NoJUnitException {
277     if (scope == null) throw new NoJUnitException();
278     return getTestCaseClass(scope.getLibrariesScope(), scope.getProject());
279   }
280
281   private static PsiClass getTestCaseClass(final GlobalSearchScope scope, final Project project) throws NoJUnitException {
282     PsiClass testCaseClass = getTestCaseClassOrNull(scope, project);
283     if (testCaseClass == null) throw new NoJUnitException(scope.getDisplayName());
284     return testCaseClass;
285   }
286
287   @Nullable
288   private static PsiClass getTestCaseClassOrNull(final GlobalSearchScope scope, final Project project) {
289     return JavaPsiFacade.getInstance(project).findClass(TESTCASE_CLASS, scope);
290   }
291
292   public static boolean isTestMethodOrConfig(@NotNull PsiMethod psiMethod) {
293     if (isTestMethod(PsiLocation.fromPsiElement(psiMethod), false)) {
294       final PsiClass containingClass = psiMethod.getContainingClass();
295       assert containingClass != null : psiMethod + "; " + psiMethod.getClass() + "; " + psiMethod.getParent();
296       if (containingClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
297         final boolean[] foundNonAbstractInheritor = new boolean[1];
298         ClassInheritorsSearch.search(containingClass).forEach(psiClass -> {
299           if (!psiClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
300             foundNonAbstractInheritor[0] = true;
301             return false;
302           }
303           return true;
304         });
305         if (foundNonAbstractInheritor[0]) {
306           return true;
307         }
308       } else {
309         return true;
310       }
311     }
312     final String name = psiMethod.getName();
313     final boolean isPublic = psiMethod.hasModifierProperty(PsiModifier.PUBLIC);
314     if (!psiMethod.hasModifierProperty(PsiModifier.ABSTRACT)) {
315       if (isPublic && (SUITE_METHOD_NAME.equals(name) || "setUp".equals(name) || "tearDown".equals(name))) {
316         return true;
317       }
318
319       if (psiMethod.hasModifierProperty(PsiModifier.STATIC)) {
320         if (AnnotationUtil.isAnnotated(psiMethod, STATIC_CONFIGS)) {
321           return isPublic;
322         }
323         if (AnnotationUtil.isAnnotated(psiMethod, STATIC_5_CONFIGS)) {
324           return true;
325         }
326       }
327       else {
328         if (AnnotationUtil.isAnnotated(psiMethod, INSTANCE_CONFIGS)) {
329           return isPublic;
330         }
331
332         if (AnnotationUtil.isAnnotated(psiMethod, INSTANCE_5_CONFIGS)) {
333           return true;
334         }
335       }
336     }
337     return false;
338   }
339
340   @Nullable
341   public static PsiMethod findFirstTestMethod(PsiClass clazz) {
342     PsiMethod testMethod = null;
343     for (PsiMethod method : clazz.getMethods()) {
344       if (isTestMethod(MethodLocation.elementInClass(method, clazz)) || isSuiteMethod(method)) {
345         testMethod = method;
346         break;
347       }
348     }
349     return testMethod;
350   }
351
352   @Nullable
353   public static PsiMethod findSuiteMethod(PsiClass clazz) {
354     final PsiMethod[] suiteMethods = clazz.findMethodsByName(SUITE_METHOD_NAME, false);
355     for (PsiMethod method : suiteMethods) {
356       if (isSuiteMethod(method)) return method;
357     }
358     return null;
359   }
360
361   public static boolean isParameterized(PsiAnnotation annotation) {
362     final PsiAnnotationMemberValue value = annotation.findAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME);
363     if (value instanceof PsiClassObjectAccessExpression) {
364       final PsiTypeElement operand = ((PsiClassObjectAccessExpression)value).getOperand();
365       final PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(operand.getType());
366       return psiClass != null && "org.junit.runners.Parameterized".equals(psiClass.getQualifiedName());
367     }
368     return false;
369   }
370
371   public static class  TestMethodFilter implements Condition<PsiMethod> {
372     private final PsiClass myClass;
373     private final JavaTestFramework framework;
374
375     public TestMethodFilter(final PsiClass aClass) {
376       myClass = aClass;
377       TestFramework framework = TestFrameworks.detectFramework(aClass);
378       this.framework = (framework instanceof JavaTestFramework) ? (JavaTestFramework)framework : null;
379     }
380
381     public boolean value(final PsiMethod method) {
382       return framework != null
383              ? framework.isTestMethod(method, myClass)
384              : isTestMethod(MethodLocation.elementInClass(method, myClass));
385     }
386   }
387
388   public static PsiClass findPsiClass(final String qualifiedName, final Module module, final Project project) {
389     final GlobalSearchScope scope = module == null ? GlobalSearchScope.projectScope(project) : GlobalSearchScope.moduleWithDependenciesScope(module);
390     return JavaPsiFacade.getInstance(project).findClass(qualifiedName, scope);
391   }
392
393   public static PsiPackage getContainingPackage(@NotNull PsiClass psiClass) {
394     PsiDirectory directory = psiClass.getContainingFile().getContainingDirectory();
395     return directory == null ? null : JavaDirectoryService.getInstance().getPackage(directory);
396   }
397
398   public static PsiClass getTestClass(final PsiElement element) {
399     return getTestClass(PsiLocation.fromPsiElement(element));
400   }
401
402   public static PsiClass getTestClass(final Location<?> location) {
403     for (Iterator<Location<PsiClass>> iterator = location.getAncestors(PsiClass.class, false); iterator.hasNext();) {
404       final Location<PsiClass> classLocation = iterator.next();
405       if (isTestClass(classLocation.getPsiElement(), false, true)) return classLocation.getPsiElement();
406     }
407     PsiElement element = location.getPsiElement();
408     if (element instanceof PsiClassOwner) {
409       PsiClass[] classes = ((PsiClassOwner)element).getClasses();
410       if (classes.length == 1) return classes[0];
411     }
412     return null;
413   }
414
415   public static PsiMethod getTestMethod(final PsiElement element) {
416     return getTestMethod(element, true);
417   }
418
419
420   public static PsiMethod getTestMethod(final PsiElement element, boolean checkAbstract) {
421     return getTestMethod(element, checkAbstract, true);
422   }
423
424   public static PsiMethod getTestMethod(final PsiElement element, boolean checkAbstract, boolean checkRunWith) {
425     final PsiManager manager = element.getManager();
426     final Location<PsiElement> location = PsiLocation.fromPsiElement(manager.getProject(), element);
427     for (Iterator<Location<PsiMethod>> iterator = location.getAncestors(PsiMethod.class, false); iterator.hasNext();) {
428       final Location<? extends PsiMethod> methodLocation = iterator.next();
429       if (isTestMethod(methodLocation, checkAbstract, checkRunWith)) return methodLocation.getPsiElement();
430     }
431     return null;
432   }
433
434   public static class NoJUnitException extends CantRunException {
435     public NoJUnitException() {
436       super(ExecutionBundle.message("no.junit.error.message"));
437     }
438
439     public NoJUnitException(final String message) {
440       super(ExecutionBundle.message("no.junit.in.scope.error.message", message));
441     }
442   }
443 }