search for tests in directory restored for down-up approach
[idea/community.git] / plugins / junit / src / com / intellij / execution / junit / TestPackage.java
1 /*
2  * Copyright 2000-2009 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
17 package com.intellij.execution.junit;
18
19 import com.intellij.execution.*;
20 import com.intellij.execution.configurations.JavaParameters;
21 import com.intellij.execution.configurations.RuntimeConfigurationException;
22 import com.intellij.execution.configurations.RuntimeConfigurationWarning;
23 import com.intellij.execution.runners.ExecutionEnvironment;
24 import com.intellij.execution.testframework.SearchForTestsTask;
25 import com.intellij.execution.testframework.SourceScope;
26 import com.intellij.execution.testframework.TestSearchScope;
27 import com.intellij.openapi.application.ReadAction;
28 import com.intellij.openapi.module.Module;
29 import com.intellij.openapi.project.DumbService;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.util.Comparing;
32 import com.intellij.openapi.util.Condition;
33 import com.intellij.openapi.util.Ref;
34 import com.intellij.openapi.util.registry.Registry;
35 import com.intellij.psi.*;
36 import com.intellij.psi.search.GlobalSearchScope;
37 import com.intellij.psi.search.PackageScope;
38 import com.intellij.psi.util.ClassUtil;
39 import com.intellij.refactoring.listeners.RefactoringElementListener;
40 import com.intellij.rt.execution.junit.JUnitStarter;
41 import com.intellij.util.containers.JBTreeTraverser;
42 import gnu.trove.THashSet;
43 import org.jetbrains.annotations.Nullable;
44 import org.jetbrains.annotations.TestOnly;
45
46 import java.io.File;
47 import java.lang.annotation.Annotation;
48 import java.lang.reflect.Constructor;
49 import java.lang.reflect.Method;
50 import java.lang.reflect.Modifier;
51 import java.nio.file.Path;
52 import java.util.Arrays;
53 import java.util.function.Predicate;
54
55 public class TestPackage extends TestObject {
56
57   public TestPackage(JUnitConfiguration configuration, ExecutionEnvironment environment) {
58     super(configuration, environment);
59   }
60
61   @Nullable
62   @Override
63   public SourceScope getSourceScope() {
64     final JUnitConfiguration.Data data = getConfiguration().getPersistentData();
65     return data.getScope().getSourceScope(getConfiguration());
66   }
67
68   @Override
69   public SearchForTestsTask createSearchingForTestsTask() {
70     final JUnitConfiguration.Data data = getConfiguration().getPersistentData();
71
72     return new SearchForTestsTask(getConfiguration().getProject(), myServerSocket) {
73       private final THashSet<PsiClass> myClasses = new THashSet<>();
74       @Override
75       protected void search() {
76         myClasses.clear();
77         final SourceScope sourceScope = getSourceScope();
78         final Module module = getConfiguration().getConfigurationModule().getModule();
79         if (sourceScope != null && !JUnitStarter.JUNIT5_PARAMETER.equals(getRunner())) {
80           DumbService instance = DumbService.getInstance(myProject);
81           try {
82             instance.setAlternativeResolveEnabled(true);
83             final TestClassFilter classFilter = getClassFilter(data);
84             LOG.assertTrue(classFilter.getBase() != null);
85             long start = System.currentTimeMillis();
86             if (Registry.is("junit4.search.4.tests.all.in.scope", true)) {
87               PsiPackage aPackage = getPackage(data);
88               if (aPackage != null) {
89                 collectClassesRecursively(aPackage, GlobalSearchScope.projectScope(myProject).intersectWith(classFilter.getScope()),
90                                           aClass -> ReadAction.compute(() -> classFilter.isAccepted(aClass)));
91               }
92             }
93             else if (Registry.is("junit4.search.4.tests.in.classpath", false)) {
94               String packageName = getPackageName(data);
95               String[] classNames = TestClassCollector.collectClassFQNames(packageName, getRootPath(), getConfiguration(), TestPackage::createPredicate);
96               PsiManager manager = PsiManager.getInstance(myProject);
97               Arrays.stream(classNames)
98                 .filter(className -> acceptClassName(className)) //check patterns
99                 .map(name -> ReadAction.compute(() -> ClassUtil.findPsiClass(manager, name, null, true, classFilter.getScope())))
100                 .filter(aClass -> aClass != null)
101                 .forEach(myClasses::add);
102               LOG.info("Found tests in " + (System.currentTimeMillis() - start));
103             }
104             else {
105               ConfigurationUtil.findAllTestClasses(classFilter, module, myClasses);
106             }
107           }
108           catch (CantRunException ignored) {}
109           finally {
110             instance.setAlternativeResolveEnabled(false);
111           }
112         }
113       }
114
115       private void collectClassesRecursively(PsiPackage aPackage,
116                                              GlobalSearchScope scope,
117                                              Condition<PsiClass> acceptAsTest) {
118         PsiPackage[] psiPackages = ReadAction.compute(() -> aPackage.getSubPackages(scope));
119         for (PsiPackage psiPackage : psiPackages) {
120           collectClassesRecursively(psiPackage, scope, acceptAsTest);
121         }
122         PsiClass[] psiClasses = ReadAction.compute(() -> aPackage.getClasses(scope));
123         for (PsiClass aClass : psiClasses) {
124           if (Registry.is("junit4.accept.inner.classes", true)) {
125             myClasses.addAll(ReadAction.compute(() -> JBTreeTraverser.of(PsiClass::getInnerClasses).withRoot(aClass).filter(acceptAsTest).toList()));
126           }
127           else if (acceptAsTest.value(aClass)) {
128             myClasses.add(aClass);  
129           }
130         }
131       }
132
133       @Override
134       protected void onFound() {
135
136         try {
137           addClassesListToJavaParameters(myClasses,
138                                          psiClass -> psiClass != null ? JavaExecutionUtil.getRuntimeQualifiedName(psiClass) : null, getPackageName(data), createTempFiles(), getJavaParameters());
139         }
140         catch (ExecutionException ignored) {}
141       }
142     };
143   }
144
145   @Nullable
146   protected Path getRootPath() {
147     Module module = getConfiguration().getConfigurationModule().getModule();
148     boolean chooseSingleModule = getConfiguration().getTestSearchScope() == TestSearchScope.SINGLE_MODULE;
149     return TestClassCollector.getRootPath(module, chooseSingleModule);
150   }
151
152   protected boolean acceptClassName(String className) {
153     return true;
154   }
155
156   protected boolean createTempFiles() {
157     return false;
158   }
159
160   protected String getPackageName(JUnitConfiguration.Data data) throws CantRunException {
161     return getPackage(data).getQualifiedName();
162   }
163
164   @Override
165   protected JavaParameters createJavaParameters() throws ExecutionException {
166     final JavaParameters javaParameters = super.createJavaParameters();
167     final JUnitConfiguration.Data data = getConfiguration().getPersistentData();
168     final Project project = getConfiguration().getProject();
169     final SourceScope sourceScope = data.getScope().getSourceScope(getConfiguration());
170     if (sourceScope == null || !JUnitStarter.JUNIT5_PARAMETER.equals(getRunner())) { //check for junit 5
171       JUnitUtil.checkTestCase(sourceScope, project);
172     }
173     createTempFiles(javaParameters);
174
175     createServerSocket(javaParameters);
176     return javaParameters;
177   }
178
179   @Override
180   protected boolean configureByModule(Module module) {
181     return super.configureByModule(module) && getConfiguration().getPersistentData().getScope() != TestSearchScope.WHOLE_PROJECT;
182   }
183
184   protected TestClassFilter getClassFilter(final JUnitConfiguration.Data data) throws CantRunException {
185     Module module = getConfiguration().getConfigurationModule().getModule();
186     if (getConfiguration().getPersistentData().getScope() == TestSearchScope.WHOLE_PROJECT){
187       module = null;
188     }
189     final TestClassFilter classFilter = TestClassFilter.create(getSourceScope(), module);
190     return classFilter.intersectionWith(filterScope(data));
191   }
192
193   protected GlobalSearchScope filterScope(final JUnitConfiguration.Data data) throws CantRunException {
194     final Ref<CantRunException> ref = new Ref<>();
195     final GlobalSearchScope aPackage = ReadAction.compute(() -> {
196       try {
197         return PackageScope.packageScope(getPackage(data), true);
198       }
199       catch (CantRunException e) {
200         ref.set(e);
201         return null;
202       }
203     });
204     final CantRunException exception = ref.get();
205     if (exception != null) throw exception;
206     return aPackage;
207   }
208
209   protected PsiPackage getPackage(JUnitConfiguration.Data data) throws CantRunException {
210     final Project project = getConfiguration().getProject();
211     final String packageName = data.getPackageName();
212     final PsiManager psiManager = PsiManager.getInstance(project);
213     final PsiPackage aPackage = JavaPsiFacade.getInstance(psiManager.getProject()).findPackage(packageName);
214     if (aPackage == null) throw CantRunException.packageNotFound(packageName);
215     return aPackage;
216   }
217
218   @Override
219   public String suggestActionName() {
220     final JUnitConfiguration.Data data = getConfiguration().getPersistentData();
221     if (data.getPackageName().trim().length() > 0) {
222       return ExecutionBundle.message("test.in.scope.presentable.text", data.getPackageName());
223     }
224     return ExecutionBundle.message("all.tests.scope.presentable.text");
225   }
226
227   @Override
228   public RefactoringElementListener getListener(final PsiElement element, final JUnitConfiguration configuration) {
229     if (!(element instanceof PsiPackage)) return null;
230     return RefactoringListeners.getListener((PsiPackage)element, configuration.myPackage);
231   }
232
233   @Override
234   public boolean isConfiguredByElement(final JUnitConfiguration configuration,
235                                        PsiClass testClass,
236                                        PsiMethod testMethod,
237                                        PsiPackage testPackage,
238                                        PsiDirectory testDir) {
239     return testPackage != null
240            && Comparing.equal(testPackage.getQualifiedName(), configuration.getPersistentData().getPackageName());
241   }
242
243   @Override
244   public void checkConfiguration() throws RuntimeConfigurationException {
245     super.checkConfiguration();
246     final String packageName = getConfiguration().getPersistentData().getPackageName();
247     final PsiPackage aPackage =
248       JavaPsiFacade.getInstance(getConfiguration().getProject()).findPackage(packageName);
249     if (aPackage == null) {
250       throw new RuntimeConfigurationWarning(ExecutionBundle.message("package.does.not.exist.error.message", packageName));
251     }
252     if (getSourceScope() == null) {
253       getConfiguration().getConfigurationModule().checkForWarning();
254     }
255   }
256
257   @TestOnly
258   public File getWorkingDirsFile() {
259     return myWorkingDirsFile;
260   }
261
262   private static Predicate<Class<?>> createPredicate(ClassLoader classLoader) {
263
264     Class<?> testCaseClass = loadClass(classLoader,"junit.framework.TestCase");
265
266     @SuppressWarnings("unchecked")
267     Class<? extends Annotation> runWithAnnotationClass = (Class<? extends Annotation>)loadClass(classLoader, "org.junit.runner.RunWith");
268
269     @SuppressWarnings("unchecked")
270     Class<? extends Annotation> testAnnotationClass = (Class<? extends Annotation>)loadClass(classLoader, "org.junit.Test");
271
272     return aClass -> {
273       //annotation
274       if (runWithAnnotationClass != null && aClass.isAnnotationPresent(runWithAnnotationClass)) {
275         return true;
276       }
277       //junit 3
278       if (testCaseClass != null && testCaseClass.isAssignableFrom(aClass)) {
279         return Arrays.stream(aClass.getConstructors()).anyMatch(constructor -> {
280           Class<?>[] parameterTypes = constructor.getParameterTypes();
281           return parameterTypes.length == 0 ||
282                  parameterTypes.length == 1 && CommonClassNames.JAVA_LANG_STRING.equals(parameterTypes[0].getName());
283         });
284       }
285       else {
286         //junit 4 & suite
287         for (Method method : aClass.getMethods()) {
288           if (Modifier.isStatic(method.getModifiers()) && "suite".equals(method.getName())) {
289             return true;
290           }
291           if (testAnnotationClass != null && method.isAnnotationPresent(testAnnotationClass)) {
292             return hasSingleConstructor(aClass);
293           }
294         }
295       }
296       return false;
297     };
298   }
299
300   private static Class<?> loadClass(ClassLoader classLoader, String className) {
301     try {
302       return Class.forName(className, true, classLoader);
303     }
304     catch (ClassNotFoundException e) {
305       return null;
306     }
307   }
308
309   private static boolean hasSingleConstructor(Class<?> aClass) {
310     Constructor<?>[] constructors = aClass.getConstructors();
311     return constructors.length == 1 && constructors[0].getParameterTypes().length == 0;
312   }
313 }