junit: skip inner classes for known runners in test detection (IDEA-185621)
[idea/community.git] / java / execution / impl / src / com / intellij / execution / junit / JUnitUtil.java
1 // Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.execution.junit;
3
4 import com.intellij.codeInsight.AnnotationUtil;
5 import com.intellij.codeInsight.MetaAnnotationUtil;
6 import com.intellij.codeInsight.TestFrameworks;
7 import com.intellij.execution.*;
8 import com.intellij.execution.junit2.info.MethodLocation;
9 import com.intellij.execution.testframework.SourceScope;
10 import com.intellij.openapi.application.ReadAction;
11 import com.intellij.openapi.module.Module;
12 import com.intellij.openapi.module.ModuleUtilCore;
13 import com.intellij.openapi.progress.ProgressManager;
14 import com.intellij.openapi.project.Project;
15 import com.intellij.openapi.util.Condition;
16 import com.intellij.psi.*;
17 import com.intellij.psi.search.GlobalSearchScope;
18 import com.intellij.psi.search.searches.ClassInheritorsSearch;
19 import com.intellij.psi.util.*;
20 import com.intellij.testIntegration.JavaTestFramework;
21 import com.intellij.testIntegration.TestFramework;
22 import com.siyeh.ig.psiutils.TestUtils;
23 import org.jetbrains.annotations.NotNull;
24 import org.jetbrains.annotations.Nullable;
25
26 import java.util.*;
27
28 import static com.intellij.codeInsight.AnnotationUtil.CHECK_HIERARCHY;
29
30 @SuppressWarnings({"UtilityClassWithoutPrivateConstructor"})
31 public class JUnitUtil {
32   public static final String TEST_CASE_CLASS = "junit.framework.TestCase";
33   private static final String TEST_INTERFACE = "junit.framework.Test";
34   private static final String TEST_SUITE_CLASS = "junit.framework.TestSuite";
35   public static final String TEST_ANNOTATION = "org.junit.Test";
36   public static final String TEST5_PACKAGE_FQN = "org.junit.jupiter.api";
37   public static final String TEST5_ANNOTATION = "org.junit.jupiter.api.Test";
38   public static final String CUSTOM_TESTABLE_ANNOTATION = "org.junit.platform.commons.annotation.Testable";
39   public static final String TEST5_FACTORY_ANNOTATION = "org.junit.jupiter.api.TestFactory";
40   public static final String IGNORE_ANNOTATION = "org.junit.Ignore";
41   public static final String RUN_WITH = "org.junit.runner.RunWith";
42   public static final String DATA_POINT = "org.junit.experimental.theories.DataPoint";
43   public static final String SUITE_METHOD_NAME = "suite";
44
45   public static final String BEFORE_ANNOTATION_NAME = "org.junit.Before";
46   public static final String AFTER_ANNOTATION_NAME = "org.junit.After";
47
48   public static final String BEFORE_EACH_ANNOTATION_NAME = "org.junit.jupiter.api.BeforeEach";
49   public static final String AFTER_EACH_ANNOTATION_NAME = "org.junit.jupiter.api.AfterEach";
50
51   public static final String PARAMETRIZED_PARAMETERS_ANNOTATION_NAME = "org.junit.runners.Parameterized.Parameters";
52   public static final String PARAMETRIZED_PARAMETER_ANNOTATION_NAME = "org.junit.runners.Parameterized.Parameter";
53
54   public static final String AFTER_CLASS_ANNOTATION_NAME = "org.junit.AfterClass";
55   public static final String BEFORE_CLASS_ANNOTATION_NAME = "org.junit.BeforeClass";
56   public static final Collection<String> TEST5_CONFIG_METHODS = Collections.unmodifiableList(Arrays.asList(
57     BEFORE_EACH_ANNOTATION_NAME, AFTER_EACH_ANNOTATION_NAME));
58
59   public static final String BEFORE_ALL_ANNOTATION_NAME = "org.junit.jupiter.api.BeforeAll";
60   public static final String AFTER_ALL_ANNOTATION_NAME = "org.junit.jupiter.api.AfterAll";
61   public static final Collection<String> TEST5_STATIC_CONFIG_METHODS = Collections.unmodifiableList(Arrays.asList(
62     BEFORE_ALL_ANNOTATION_NAME, AFTER_ALL_ANNOTATION_NAME));
63
64   public static final Collection<String> TEST5_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(
65     TEST5_ANNOTATION, TEST5_FACTORY_ANNOTATION, CUSTOM_TESTABLE_ANNOTATION));
66   public static final Collection<String> TEST5_JUPITER_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(
67     TEST5_ANNOTATION, TEST5_FACTORY_ANNOTATION));
68
69   private static final List<String> INSTANCE_CONFIGS = Arrays.asList(BEFORE_ANNOTATION_NAME, AFTER_ANNOTATION_NAME);
70   private static final List<String> INSTANCE_5_CONFIGS = Arrays.asList(BEFORE_EACH_ANNOTATION_NAME, AFTER_EACH_ANNOTATION_NAME);
71
72   private static final List<String> STATIC_CONFIGS = Arrays.asList(
73     BEFORE_CLASS_ANNOTATION_NAME, AFTER_CLASS_ANNOTATION_NAME, PARAMETRIZED_PARAMETERS_ANNOTATION_NAME);
74   private static final List<String> STATIC_5_CONFIGS = Arrays.asList(BEFORE_ALL_ANNOTATION_NAME, AFTER_ALL_ANNOTATION_NAME);
75
76   private static final Collection<String> CONFIGURATIONS_ANNOTATION_NAME = Collections.unmodifiableList(Arrays.asList(
77     DATA_POINT, AFTER_ANNOTATION_NAME, BEFORE_ANNOTATION_NAME, AFTER_CLASS_ANNOTATION_NAME, BEFORE_CLASS_ANNOTATION_NAME,
78     BEFORE_ALL_ANNOTATION_NAME, AFTER_ALL_ANNOTATION_NAME));
79
80   public static final String PARAMETERIZED_CLASS_NAME = "org.junit.runners.Parameterized";
81   public static final String SUITE_CLASS_NAME = "org.junit.runners.Suite";
82   public static final String JUNIT5_NESTED = "org.junit.jupiter.api.Nested";
83
84   private static final String[] RUNNERS_UNAWARE_OF_INNER_CLASSES = {
85     "org.junit.runners.Parameterized",
86     "org.junit.runners.BlockJUnit4ClassRunner",
87     "org.junit.runners.JUnit4",
88     "org.junit.internal.runners.JUnit38ClassRunner",
89     "org.junit.internal.runners.JUnit4ClassRunner",
90     "org.junit.runners.Suite"
91   };
92
93   private static final String[] RUNNERS_REQUIRE_ANNOTATION_ON_TEST_METHOD = {
94     "org.junit.runners.Parameterized",
95     "org.junit.runners.BlockJUnit4ClassRunner",
96     "org.junit.runners.JUnit4",
97     "org.junit.internal.runners.JUnit4ClassRunner"
98   };
99
100   public static boolean isSuiteMethod(@NotNull PsiMethod psiMethod) {
101     if (!psiMethod.hasModifierProperty(PsiModifier.PUBLIC)) return false;
102     if (!psiMethod.hasModifierProperty(PsiModifier.STATIC)) return false;
103     if (psiMethod.isConstructor()) return false;
104     if (!psiMethod.getParameterList().isEmpty()) return false;
105     final PsiType returnType = psiMethod.getReturnType();
106     if (returnType == null || returnType instanceof PsiPrimitiveType) return false;
107     return returnType.equalsToText(TEST_INTERFACE) ||
108            returnType.equalsToText(TEST_SUITE_CLASS) ||
109            InheritanceUtil.isInheritor(returnType, TEST_INTERFACE);
110   }
111
112   public static boolean isTestMethod(final Location<? extends PsiMethod> location) {
113     return isTestMethod(location, true);
114   }
115
116   public static boolean isTestMethod(final Location<? extends PsiMethod> location, boolean checkAbstract) {
117     return isTestMethod(location, checkAbstract, true);
118   }
119
120   public static boolean isTestMethod(final Location<? extends PsiMethod> location, boolean checkAbstract, boolean checkRunWith) {
121     return isTestMethod(location, checkAbstract, checkRunWith, true);
122   }
123
124   public static boolean isTestMethod(final Location<? extends PsiMethod> location, boolean checkAbstract, boolean checkRunWith, boolean checkClass) {
125     final PsiMethod psiMethod = location.getPsiElement();
126     final PsiClass aClass = location instanceof MethodLocation ? ((MethodLocation)location).getContainingClass() : psiMethod.getContainingClass();
127     if (checkClass && (aClass == null || !isTestClass(aClass, checkAbstract, true))) return false;
128     if (isTestAnnotated(psiMethod, false)) return !psiMethod.hasModifierProperty(PsiModifier.STATIC);
129     if (MetaAnnotationUtil.isMetaAnnotated(psiMethod, Collections.singletonList(CUSTOM_TESTABLE_ANNOTATION))) return true;
130     if (psiMethod.isConstructor()) return false;
131     if (!psiMethod.hasModifierProperty(PsiModifier.PUBLIC)) return false;
132     if (psiMethod.hasModifierProperty(PsiModifier.ABSTRACT)) return false;
133     if (AnnotationUtil.isAnnotated(psiMethod, CONFIGURATIONS_ANNOTATION_NAME, 0)) return false;
134     if (checkClass && checkRunWith) {
135       PsiAnnotation annotation = getRunWithAnnotation(aClass);
136       if (annotation != null) {
137         return !isInheritorOrSelfRunner(annotation, RUNNERS_REQUIRE_ANNOTATION_ON_TEST_METHOD);
138       }
139     }
140     if (!psiMethod.getParameterList().isEmpty()) return false;
141     if (psiMethod.hasModifierProperty(PsiModifier.STATIC)) return false;
142     if (!psiMethod.getName().startsWith("test")) return false;
143     if (checkClass) {
144       PsiClass testCaseClass = getTestCaseClassOrNull(location);
145       if (testCaseClass == null || !psiMethod.getContainingClass().isInheritor(testCaseClass, true)) return false;
146     }
147     return PsiType.VOID.equals(psiMethod.getReturnType());
148   }
149
150   public static boolean isTestCaseInheritor(final PsiClass aClass) {
151     if (!aClass.isValid()) return false;
152     Location<PsiClass> location = PsiLocation.fromPsiElement(aClass);
153     PsiClass testCaseClass = getTestCaseClassOrNull(location);
154     return testCaseClass != null && aClass.isInheritor(testCaseClass, true);
155   }
156
157   public static boolean isTestClass(final PsiClass psiClass) {
158     return isTestClass(psiClass, true, true);
159   }
160
161   public static boolean isTestClass(@NotNull PsiClass psiClass, boolean checkAbstract, boolean checkForTestCaseInheritance) {
162     if (psiClass.getQualifiedName() == null) return false;
163     if (isJUnit5(psiClass) && isJUnit5TestClass(psiClass, checkAbstract)) {
164       return true;
165     }
166     final PsiClass topLevelClass = PsiTreeUtil.getTopmostParentOfType(psiClass, PsiClass.class);
167     if (topLevelClass != null) {
168       final PsiAnnotation annotation = AnnotationUtil.findAnnotationInHierarchy(topLevelClass, Collections.singleton(RUN_WITH));
169       if (annotation != null && !isInheritorOrSelfRunner(annotation, RUNNERS_REQUIRE_ANNOTATION_ON_TEST_METHOD)) {
170         return true;
171       }
172     }
173
174     if (!PsiClassUtil.isRunnableClass(psiClass, true, checkAbstract)) return false;
175
176     if (AnnotationUtil.isAnnotated(psiClass, RUN_WITH, CHECK_HIERARCHY)) return true;
177
178     if (checkForTestCaseInheritance && isTestCaseInheritor(psiClass)) return true;
179
180     return CachedValuesManager.getCachedValue(psiClass, () ->
181       CachedValueProvider.Result.create(hasTestOrSuiteMethods(psiClass), PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT));
182   }
183
184   private static boolean hasTestOrSuiteMethods(@NotNull PsiClass psiClass) {
185     for (final PsiMethod method : psiClass.getAllMethods()) {
186       if (isSuiteMethod(method)) return true;
187       if (isTestAnnotated(method)) return true;
188     }
189
190     if (isJUnit5(psiClass)) {
191       for (PsiClass innerClass : psiClass.getInnerClasses()) {
192         for (PsiMethod method : innerClass.getAllMethods()) {
193           if (isTestAnnotated(method)) return true;
194         }
195       }
196     }
197
198     return false;
199   }
200
201   public static boolean isJUnit3TestClass(final PsiClass clazz) {
202     return isTestCaseInheritor(clazz);
203   }
204
205   public static boolean isJUnit4TestClass(final PsiClass psiClass) {
206     return isJUnit4TestClass(psiClass, true);
207   }
208
209   public static boolean isJUnit4TestClass(final PsiClass psiClass, boolean checkAbstract) {
210     final PsiModifierList modifierList = psiClass.getModifierList();
211     if (modifierList == null) return false;
212     final PsiClass topLevelClass = PsiTreeUtil.getTopmostParentOfType(modifierList, PsiClass.class);
213     if (topLevelClass != null) {
214       if (AnnotationUtil.isAnnotated(topLevelClass, RUN_WITH, CHECK_HIERARCHY)) {
215         PsiAnnotation annotation = getRunWithAnnotation(topLevelClass);
216         if (topLevelClass == psiClass) {
217           return true;
218         }
219
220         //default runners do not implicitly run inner classes
221         if (annotation != null && !isInheritorOrSelfRunner(annotation, RUNNERS_UNAWARE_OF_INNER_CLASSES)) {
222           return true;
223         }
224       }
225     }
226
227     if (!PsiClassUtil.isRunnableClass(psiClass, true, checkAbstract)) return false;
228
229     for (final PsiMethod method : psiClass.getAllMethods()) {
230       ProgressManager.checkCanceled();
231       if (isTestAnnotated(method)) return true;
232     }
233
234     return false;
235   }
236
237   public static boolean isJUnit5TestClass(@NotNull final PsiClass psiClass, boolean checkAbstract) {
238     final PsiModifierList modifierList = psiClass.getModifierList();
239     if (modifierList == null) return false;
240
241     if (psiClass.getContainingClass() != null && AnnotationUtil.isAnnotated(psiClass, JUNIT5_NESTED, 0)) {
242       return true;
243     }
244
245     if (MetaAnnotationUtil.isMetaAnnotated(psiClass, Collections.singleton(CUSTOM_TESTABLE_ANNOTATION))) {
246       return true;
247     }
248
249     if (!PsiClassUtil.isRunnableClass(psiClass, false, checkAbstract)) return false;
250
251     Module module = ModuleUtilCore.findModuleForPsiElement(psiClass);
252     if (module != null) {
253       return CachedValuesManager.getCachedValue(psiClass, () -> {
254         boolean hasAnnotation = false;
255         for (final PsiMethod method : psiClass.getAllMethods()) {
256           ProgressManager.checkCanceled();
257           if (MetaAnnotationUtil.isMetaAnnotated(method, TEST5_ANNOTATIONS)) {
258             hasAnnotation = true;
259             break;
260           }
261         }
262
263         if (!hasAnnotation) {
264           for (PsiClass aClass : psiClass.getAllInnerClasses()) {
265             if (MetaAnnotationUtil.isMetaAnnotated(aClass, Collections.singleton(JUNIT5_NESTED))) {
266               hasAnnotation = true;
267               break;
268             }
269           }
270         }
271         return CachedValueProvider.Result.create(hasAnnotation, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
272       });
273     }
274
275     return false;
276   }
277
278   public static boolean isJUnit5(@NotNull PsiElement element) {
279     return isJUnit5(element.getResolveScope(), element.getProject());
280   }
281
282   public static boolean isJUnit5(GlobalSearchScope scope, Project project) {
283     JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
284     Condition<String> foundCondition = aPackageName -> {
285       PsiPackage aPackage = facade.findPackage(aPackageName);
286       return aPackage != null && aPackage.getDirectories(scope).length > 0;
287     };
288
289     return ReadAction.compute(() -> foundCondition.value(TEST5_PACKAGE_FQN));
290   }
291
292   public static boolean isTestAnnotated(final PsiMethod method) {
293     return isTestAnnotated(method, true);
294   }
295
296   public static boolean isTestAnnotated(final PsiMethod method, boolean includeCustom) {
297     if (AnnotationUtil.isAnnotated(method, TEST_ANNOTATION, 0) || JUnitRecognizer.willBeAnnotatedAfterCompilation(method)) {
298       return true;
299     }
300
301     return MetaAnnotationUtil.isMetaAnnotated(method, includeCustom ? TEST5_ANNOTATIONS : TEST5_JUPITER_ANNOTATIONS);
302   }
303
304
305   @Nullable
306   private static PsiClass getTestCaseClassOrNull(final Location<?> location) {
307     final Location<PsiClass> ancestorOrSelf = location.getAncestorOrSelf(PsiClass.class);
308     if (ancestorOrSelf == null) return null;
309     final PsiClass aClass = ancestorOrSelf.getPsiElement();
310     Module module = JavaExecutionUtil.findModule(aClass);
311     if (module == null) return null;
312     GlobalSearchScope scope = GlobalSearchScope.moduleRuntimeScope(module, true);
313     return getTestCaseClassOrNull(scope, module.getProject());
314   }
315
316   public static PsiClass getTestCaseClass(final Module module) throws NoJUnitException {
317     if (module == null) throw new NoJUnitException();
318     final GlobalSearchScope scope = GlobalSearchScope.moduleRuntimeScope(module, true);
319     return getTestCaseClass(scope, module.getProject());
320   }
321
322   public static PsiClass getTestCaseClass(final SourceScope scope) throws NoJUnitException {
323     if (scope == null) throw new NoJUnitException();
324     return getTestCaseClass(scope.getLibrariesScope(), scope.getProject());
325   }
326
327   public static void checkTestCase(SourceScope scope, Project project) throws NoJUnitException {
328     if (scope == null) throw new NoJUnitException();
329     PsiPackage aPackage = JavaPsiFacade.getInstance(project).findPackage("junit.framework");
330     if (aPackage == null || aPackage.getDirectories(scope.getLibrariesScope()).length == 0) {
331       throw new NoJUnitException();
332     }
333   }
334
335   private static PsiClass getTestCaseClass(final GlobalSearchScope scope, final Project project) throws NoJUnitException {
336     PsiClass testCaseClass = getTestCaseClassOrNull(scope, project);
337     if (testCaseClass == null) throw new NoJUnitException(scope.getDisplayName());
338     return testCaseClass;
339   }
340
341   @Nullable
342   private static PsiClass getTestCaseClassOrNull(final GlobalSearchScope scope, final Project project) {
343     return JavaPsiFacade.getInstance(project).findClass(TEST_CASE_CLASS, scope);
344   }
345
346   public static boolean isTestMethodOrConfig(@NotNull PsiMethod psiMethod) {
347     final PsiClass containingClass = psiMethod.getContainingClass();
348     if (containingClass == null) {
349       return false;
350     }
351     if (isTestMethod(PsiLocation.fromPsiElement(psiMethod), false)) {
352       if (containingClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
353         final boolean[] foundNonAbstractInheritor = new boolean[1];
354         ClassInheritorsSearch.search(containingClass).forEach(psiClass -> {
355           if (!psiClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
356             foundNonAbstractInheritor[0] = true;
357             return false;
358           }
359           return true;
360         });
361         if (foundNonAbstractInheritor[0]) {
362           return true;
363         }
364       } else {
365         return true;
366       }
367     }
368     final String name = psiMethod.getName();
369     final boolean isPublic = psiMethod.hasModifierProperty(PsiModifier.PUBLIC);
370     if (!psiMethod.hasModifierProperty(PsiModifier.ABSTRACT)) {
371       if (isPublic && (SUITE_METHOD_NAME.equals(name) || "setUp".equals(name) || "tearDown".equals(name))) {
372         return true;
373       }
374
375       if (psiMethod.hasModifierProperty(PsiModifier.STATIC)) {
376         if (AnnotationUtil.isAnnotated(psiMethod, STATIC_CONFIGS, 0)) {
377           return isPublic;
378         }
379         if (AnnotationUtil.isAnnotated(psiMethod, STATIC_5_CONFIGS, 0)) {
380           return true;
381         }
382       }
383       else {
384         if (AnnotationUtil.isAnnotated(psiMethod, INSTANCE_CONFIGS, 0)) {
385           return isPublic;
386         }
387         if (AnnotationUtil.isAnnotated(psiMethod, INSTANCE_5_CONFIGS, 0)) {
388           return true;
389         }
390         if (TestUtils.testInstancePerClass(containingClass) && AnnotationUtil.isAnnotated(psiMethod, STATIC_5_CONFIGS, 0)) {
391           return true;
392         }
393       }
394     }
395     return false;
396   }
397
398   @Nullable
399   public static PsiMethod findFirstTestMethod(PsiClass clazz) {
400     PsiMethod testMethod = null;
401     for (PsiMethod method : clazz.getMethods()) {
402       if (isTestMethod(MethodLocation.elementInClass(method, clazz)) || isSuiteMethod(method)) {
403         testMethod = method;
404         break;
405       }
406     }
407     return testMethod;
408   }
409
410   @Nullable
411   public static PsiMethod findSuiteMethod(PsiClass clazz) {
412     final PsiMethod[] suiteMethods = clazz.findMethodsByName(SUITE_METHOD_NAME, false);
413     for (PsiMethod method : suiteMethods) {
414       if (isSuiteMethod(method)) return method;
415     }
416     return null;
417   }
418
419   public static PsiAnnotation getRunWithAnnotation(PsiClass aClass) {
420     return AnnotationUtil.findAnnotationInHierarchy(aClass, Collections.singleton(RUN_WITH));
421   }
422
423   public static boolean isParameterized(PsiAnnotation annotation) {
424     return isInheritorOrSelfRunner(annotation, "org.junit.runners.Parameterized");
425   }
426
427   public static boolean isInheritorOrSelfRunner(PsiAnnotation annotation, String... runners) {
428     final PsiAnnotationMemberValue value = annotation.findAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME);
429     if (value instanceof PsiClassObjectAccessExpression) {
430       final PsiTypeElement operand = ((PsiClassObjectAccessExpression)value).getOperand();
431       final PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(operand.getType());
432       return psiClass != null && Arrays.stream(runners).anyMatch(runner -> InheritanceUtil.isInheritor(psiClass, runner));
433     }
434     return false;
435   }
436
437   public static class  TestMethodFilter implements Condition<PsiMethod> {
438     private final PsiClass myClass;
439     private final JavaTestFramework framework;
440
441     public TestMethodFilter(final PsiClass aClass) {
442       myClass = aClass;
443       TestFramework framework = TestFrameworks.detectFramework(aClass);
444       this.framework = (framework instanceof JavaTestFramework) ? (JavaTestFramework)framework : null;
445     }
446
447     public boolean value(final PsiMethod method) {
448       return framework != null
449              ? framework.isTestMethod(method, myClass)
450              : isTestMethod(MethodLocation.elementInClass(method, myClass));
451     }
452   }
453
454   public static PsiClass findPsiClass(final String qualifiedName, final Module module, final Project project) {
455     final GlobalSearchScope scope = module == null ? GlobalSearchScope.projectScope(project) : GlobalSearchScope.moduleWithDependenciesScope(module);
456     return JavaPsiFacade.getInstance(project).findClass(qualifiedName, scope);
457   }
458
459   public static PsiPackage getContainingPackage(@NotNull PsiClass psiClass) {
460     PsiDirectory directory = psiClass.getContainingFile().getContainingDirectory();
461     return directory == null ? null : JavaDirectoryService.getInstance().getPackage(directory);
462   }
463
464   public static PsiClass getTestClass(final PsiElement element) {
465     return getTestClass(PsiLocation.fromPsiElement(element));
466   }
467
468   public static PsiClass getTestClass(final Location<?> location) {
469     for (Iterator<Location<PsiClass>> iterator = location.getAncestors(PsiClass.class, false); iterator.hasNext();) {
470       final Location<PsiClass> classLocation = iterator.next();
471       if (isTestClass(classLocation.getPsiElement(), false, true)) return classLocation.getPsiElement();
472     }
473     PsiElement element = location.getPsiElement();
474     if (element instanceof PsiClassOwner) {
475       PsiClass[] classes = ((PsiClassOwner)element).getClasses();
476       if (classes.length == 1 && isTestClass(classes[0], false, true)) return classes[0];
477     }
478     return null;
479   }
480
481   public static PsiMethod getTestMethod(final PsiElement element) {
482     return getTestMethod(element, true);
483   }
484
485
486   public static PsiMethod getTestMethod(final PsiElement element, boolean checkAbstract) {
487     return getTestMethod(element, checkAbstract, true);
488   }
489
490   public static PsiMethod getTestMethod(final PsiElement element, boolean checkAbstract, boolean checkRunWith) {
491     final PsiManager manager = element.getManager();
492     final Location<PsiElement> location = PsiLocation.fromPsiElement(manager.getProject(), element);
493     for (Iterator<Location<PsiMethod>> iterator = location.getAncestors(PsiMethod.class, false); iterator.hasNext();) {
494       final Location<? extends PsiMethod> methodLocation = iterator.next();
495       if (isTestMethod(methodLocation, checkAbstract, checkRunWith)) return methodLocation.getPsiElement();
496     }
497     return null;
498   }
499
500   public static class NoJUnitException extends CantRunException {
501     public NoJUnitException() {
502       super(ExecutionBundle.message("no.junit.error.message"));
503     }
504
505     public NoJUnitException(final String message) {
506       super(ExecutionBundle.message("no.junit.in.scope.error.message", message));
507     }
508   }
509 }