dd5d7148e811fb56989ff8f15db16fd47c1d4d1b
[idea/community.git] / java / execution / impl / src / com / intellij / execution / JavaTestFrameworkRunnableState.java
1 /*
2  * Copyright 2000-2015 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;
17
18 import com.intellij.ExtensionPoints;
19 import com.intellij.debugger.impl.GenericDebuggerRunnerSettings;
20 import com.intellij.diagnostic.logging.OutputFileUtil;
21 import com.intellij.execution.configurations.*;
22 import com.intellij.execution.process.OSProcessHandler;
23 import com.intellij.execution.process.ProcessAdapter;
24 import com.intellij.execution.process.ProcessEvent;
25 import com.intellij.execution.runners.ExecutionEnvironment;
26 import com.intellij.execution.testframework.*;
27 import com.intellij.execution.testframework.actions.AbstractRerunFailedTestsAction;
28 import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil;
29 import com.intellij.execution.testframework.sm.runner.SMRunnerConsolePropertiesProvider;
30 import com.intellij.execution.testframework.sm.runner.SMTRunnerConsoleProperties;
31 import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView;
32 import com.intellij.execution.testframework.sm.runner.ui.SMTestRunnerResultsForm;
33 import com.intellij.execution.testframework.ui.BaseTestsOutputConsoleView;
34 import com.intellij.execution.util.JavaParametersUtil;
35 import com.intellij.openapi.diagnostic.Logger;
36 import com.intellij.openapi.extensions.Extensions;
37 import com.intellij.openapi.module.Module;
38 import com.intellij.openapi.project.Project;
39 import com.intellij.openapi.projectRoots.JavaSdkType;
40 import com.intellij.openapi.projectRoots.JdkUtil;
41 import com.intellij.openapi.projectRoots.Sdk;
42 import com.intellij.openapi.projectRoots.ex.JavaSdkUtil;
43 import com.intellij.openapi.roots.ModuleRootManager;
44 import com.intellij.openapi.roots.ProjectRootManager;
45 import com.intellij.openapi.util.Comparing;
46 import com.intellij.openapi.util.Disposer;
47 import com.intellij.openapi.util.Getter;
48 import com.intellij.openapi.util.io.FileUtil;
49 import com.intellij.openapi.util.registry.Registry;
50 import com.intellij.openapi.util.text.StringUtil;
51 import com.intellij.openapi.vfs.CharsetToolkit;
52 import com.intellij.psi.JavaPsiFacade;
53 import com.intellij.psi.PsiDirectory;
54 import com.intellij.psi.PsiPackage;
55 import com.intellij.psi.search.GlobalSearchScope;
56 import com.intellij.psi.search.GlobalSearchScopesCore;
57 import com.intellij.util.PathUtil;
58 import com.intellij.util.ui.UIUtil;
59 import org.jetbrains.annotations.NotNull;
60 import org.jetbrains.annotations.Nullable;
61 import org.jetbrains.jps.model.serialization.PathMacroUtil;
62
63 import java.io.*;
64 import java.net.InetAddress;
65 import java.net.ServerSocket;
66 import java.util.Collections;
67 import java.util.List;
68 import java.util.Locale;
69 import java.util.Map;
70
71 public abstract class JavaTestFrameworkRunnableState<T extends ModuleBasedConfiguration<JavaRunConfigurationModule> & CommonJavaRunConfigurationParameters & SMRunnerConsolePropertiesProvider> extends JavaCommandLineState {
72   private static final Logger LOG = Logger.getInstance("#" + JavaTestFrameworkRunnableState.class.getName());
73   protected ServerSocket myServerSocket;
74   protected File myTempFile;
75   protected File myWorkingDirsFile = null;
76
77   public JavaTestFrameworkRunnableState(ExecutionEnvironment environment) {
78     super(environment);
79   }
80
81   @NotNull protected abstract String getFrameworkName();
82
83   @NotNull protected abstract String getFrameworkId();
84
85   protected abstract void passTempFile(ParametersList parametersList, String tempFilePath);
86
87   @NotNull protected abstract T getConfiguration();
88
89   @Nullable protected abstract TestSearchScope getScope();
90
91   @NotNull protected abstract String getForkMode();
92
93   @NotNull protected abstract OSProcessHandler createHandler(Executor executor) throws ExecutionException;
94
95   public SearchForTestsTask createSearchingForTestsTask() {
96     return null;
97   }
98
99   protected boolean configureByModule(Module module) {
100     return module != null;
101   }
102
103   protected ExecutionResult startSMRunner(Executor executor) throws ExecutionException {
104     if (!isSmRunnerUsed()) {
105       return null;
106     }
107     getJavaParameters().getVMParametersList().addProperty("idea." + getFrameworkId() + ".sm_runner");
108
109     final RunnerSettings runnerSettings = getRunnerSettings();
110
111     final SMTRunnerConsoleProperties testConsoleProperties = getConfiguration().createTestConsoleProperties(executor);
112     testConsoleProperties.setIfUndefined(TestConsoleProperties.HIDE_PASSED_TESTS, false);
113
114     final BaseTestsOutputConsoleView consoleView = SMTestRunnerConnectionUtil.createConsole(getFrameworkName(), testConsoleProperties);
115     final SMTestRunnerResultsForm viewer = ((SMTRunnerConsoleView)consoleView).getResultsViewer();
116     Disposer.register(getConfiguration().getProject(), consoleView);
117
118     final OSProcessHandler handler = createHandler(executor);
119     consoleView.attachToProcess(handler);
120     final AbstractTestProxy root = viewer.getRoot();
121     if (root instanceof TestProxyRoot) {
122       ((TestProxyRoot)root).setHandler(handler);
123     }
124     handler.addProcessListener(new ProcessAdapter() {
125       @Override
126       public void startNotified(ProcessEvent event) {
127         if (getConfiguration().isSaveOutputToFile()) {
128           final File file = OutputFileUtil.getOutputFile(getConfiguration());
129           root.setOutputFilePath(file != null ? file.getAbsolutePath() : null);
130         }
131       }
132
133       @Override
134       public void processTerminated(ProcessEvent event) {
135         Runnable runnable = new Runnable() {
136           public void run() {
137             root.flush();
138             deleteTempFiles();
139             clear();
140           }
141         };
142         UIUtil.invokeLaterIfNeeded(runnable);
143         handler.removeProcessListener(this);
144       }
145     });
146
147     AbstractRerunFailedTestsAction rerunFailedTestsAction = testConsoleProperties.createRerunFailedTestsAction(consoleView);
148     LOG.assertTrue(rerunFailedTestsAction != null);
149     rerunFailedTestsAction.setModelProvider(new Getter<TestFrameworkRunningModel>() {
150       @Override
151       public TestFrameworkRunningModel get() {
152         return viewer;
153       }
154     });
155
156     final DefaultExecutionResult result = new DefaultExecutionResult(consoleView, handler);
157     result.setRestartActions(rerunFailedTestsAction);
158
159     JavaRunConfigurationExtensionManager.getInstance().attachExtensionsToProcess(getConfiguration(), handler, runnerSettings);
160     return result;
161   }
162
163   protected boolean isSmRunnerUsed() {
164     return Registry.is(getFrameworkId() + "_sm");
165   }
166
167   protected abstract void configureRTClasspath(JavaParameters javaParameters);
168
169   @Override
170   protected JavaParameters createJavaParameters() throws ExecutionException {
171     final JavaParameters javaParameters = new JavaParameters();
172     javaParameters.setUseClasspathJar(true);
173     final Module module = getConfiguration().getConfigurationModule().getModule();
174
175     Project project = getConfiguration().getProject();
176     Sdk jdk = module == null ? ProjectRootManager.getInstance(project).getProjectSdk() : ModuleRootManager.getInstance(module).getSdk();
177     javaParameters.setJdk(jdk);
178     
179     final String parameters = getConfiguration().getProgramParameters();
180     getConfiguration().setProgramParameters(null);
181     try {
182       JavaParametersUtil.configureConfiguration(javaParameters, getConfiguration());
183     }
184     finally {
185       getConfiguration().setProgramParameters(parameters);
186     }
187     javaParameters.getClassPath().addFirst(JavaSdkUtil.getIdeaRtJarPath());
188     configureClasspath(javaParameters);
189
190     final Object[] patchers = Extensions.getExtensions(ExtensionPoints.JUNIT_PATCHER);
191     for (Object patcher : patchers) {
192       ((JUnitPatcher)patcher).patchJavaParameters(module, javaParameters);
193     }
194
195     // Append coverage parameters if appropriate
196     for (RunConfigurationExtension ext : Extensions.getExtensions(RunConfigurationExtension.EP_NAME)) {
197       ext.updateJavaParameters(getConfiguration(), javaParameters, getRunnerSettings());
198     }
199
200     if (!StringUtil.isEmptyOrSpaces(parameters)) {
201       javaParameters.getProgramParametersList().addAll(getNamedParams(parameters));
202     }
203
204     return javaParameters;
205   }
206
207   protected List<String> getNamedParams(String parameters) {
208     return Collections.singletonList("@name" + parameters);
209   }
210
211   private ServerSocket myForkSocket = null;
212
213   @Nullable
214   public ServerSocket getForkSocket() {
215     if (myForkSocket == null && (!Comparing.strEqual(getForkMode(), "none") || forkPerModule()) && getRunnerSettings() != null) {
216       try {
217         myForkSocket = new ServerSocket(0, 0, InetAddress.getByName("127.0.0.1"));
218       }
219       catch (IOException e) {
220         LOG.error(e);
221       }
222     }
223     return myForkSocket;
224   }
225
226   private boolean isExecutorDisabledInForkedMode() {
227     final RunnerSettings settings = getRunnerSettings();
228     return settings != null && !(settings instanceof GenericDebuggerRunnerSettings);
229   }
230
231   protected void appendForkInfo(Executor executor) throws ExecutionException {
232     final String forkMode = getForkMode();
233     if (Comparing.strEqual(forkMode, "none")) {
234       if (forkPerModule()) {
235         if (isExecutorDisabledInForkedMode()) {
236           final String actionName = UIUtil.removeMnemonic(executor.getStartActionText());
237           throw new CantRunException("'" + actionName + "' is disabled when per-module working directory is configured.<br/>" +
238                                      "Please specify single working directory, or change test scope to single module.");
239         }
240       } else {
241         return;
242       }
243     } else if (isExecutorDisabledInForkedMode()) {
244       final String actionName = executor.getActionName();
245       throw new CantRunException(actionName + " is disabled in fork mode.<br/>Please change fork mode to &lt;none&gt; to " + actionName.toLowerCase(
246         Locale.ENGLISH) + ".");
247     }
248
249     final JavaParameters javaParameters = getJavaParameters();
250     final Sdk jdk = javaParameters.getJdk();
251     if (jdk == null) {
252       throw new ExecutionException(ExecutionBundle.message("run.configuration.error.no.jdk.specified"));
253     }
254
255     try {
256       final File tempFile = FileUtil.createTempFile("command.line", "", true);
257       final PrintWriter writer = new PrintWriter(tempFile, CharsetToolkit.UTF8);
258       try {
259         if (JdkUtil.useDynamicClasspath(getConfiguration().getProject())) {
260           writer.println("use classpath jar");
261         }
262         else {
263           writer.println("");
264         }
265   
266         writer.println(((JavaSdkType)jdk.getSdkType()).getVMExecutablePath(jdk));
267         for (String vmParameter : javaParameters.getVMParametersList().getList()) {
268           writer.println(vmParameter);
269         }
270       }
271       finally {
272         writer.close();
273       }
274
275       passForkMode(getForkMode(), tempFile, javaParameters);
276     }
277     catch (IOException e) {
278       LOG.error(e);
279     }
280   }
281
282   protected abstract void passForkMode(String forkMode, File tempFile, JavaParameters parameters) throws ExecutionException;
283
284   protected void collectListeners(JavaParameters javaParameters, StringBuilder buf, String epName, String delimiter) {
285     final T configuration = getConfiguration();
286     final Object[] listeners = Extensions.getExtensions(epName);
287     for (final Object listener : listeners) {
288       boolean enabled = true;
289       for (RunConfigurationExtension ext : Extensions.getExtensions(RunConfigurationExtension.EP_NAME)) {
290         if (ext.isListenerDisabled(configuration, listener, getRunnerSettings())) {
291           enabled = false;
292           break;
293         }
294       }
295       if (enabled) {
296         if (buf.length() > 0) buf.append(delimiter);
297         final Class classListener = listener.getClass();
298         buf.append(classListener.getName());
299         javaParameters.getClassPath().add(PathUtil.getJarPathForClass(classListener));
300       }
301     }
302   }
303
304   protected void configureClasspath(final JavaParameters javaParameters) throws CantRunException {
305     configureRTClasspath(javaParameters);
306     RunConfigurationModule module = getConfiguration().getConfigurationModule();
307     final String jreHome = getConfiguration().isAlternativeJrePathEnabled() ? getConfiguration().getAlternativeJrePath() : null;
308     final int pathType = JavaParameters.JDK_AND_CLASSES_AND_TESTS;
309     if (configureByModule(module.getModule())) {
310       JavaParametersUtil.configureModule(module, javaParameters, pathType, jreHome);
311     }
312     else {
313       JavaParametersUtil.configureProject(getConfiguration().getProject(), javaParameters, pathType, jreHome);
314     }
315   }
316
317   protected void createServerSocket(JavaParameters javaParameters) {
318     try {
319       myServerSocket = new ServerSocket(0, 0, InetAddress.getByName("127.0.0.1"));
320       javaParameters.getProgramParametersList().add("-socket" + myServerSocket.getLocalPort());
321     }
322     catch (IOException e) {
323       LOG.error(e);
324     }
325   }
326
327   protected boolean spansMultipleModules(final String qualifiedName) {
328     if (qualifiedName != null) {
329       final Project project = getConfiguration().getProject();
330       final PsiPackage aPackage = JavaPsiFacade.getInstance(project).findPackage(qualifiedName);
331       if (aPackage != null) {
332         final TestSearchScope scope = getScope();
333         if (scope != null) {
334           final SourceScope sourceScope = scope.getSourceScope(getConfiguration());
335           if (sourceScope != null) {
336             final GlobalSearchScope configurationSearchScope = GlobalSearchScopesCore.projectTestScope(project).intersectWith(
337               sourceScope.getGlobalSearchScope());
338             final PsiDirectory[] directories = aPackage.getDirectories(configurationSearchScope);
339             return directories.length > 1;
340           }
341         }
342       }
343     }
344     return false;
345   }
346
347   /**
348    * Configuration based on package which spans multiple modules
349    */
350   protected boolean forkPerModule() {
351     final String workingDirectory = getConfiguration().getWorkingDirectory();
352     return getScope() != TestSearchScope.SINGLE_MODULE &&
353            ("$" + PathMacroUtil.MODULE_DIR_MACRO_NAME + "$").equals(workingDirectory) &&
354            spansMultipleModules(getConfiguration().getPackage());
355   }
356
357   protected void createTempFiles(JavaParameters javaParameters) {
358     try {
359       myWorkingDirsFile = FileUtil.createTempFile("idea_working_dirs_" + getFrameworkId(), ".tmp");
360       myWorkingDirsFile.deleteOnExit();
361       javaParameters.getProgramParametersList().add("@w@" + myWorkingDirsFile.getAbsolutePath());
362       
363       myTempFile = FileUtil.createTempFile("idea_" + getFrameworkId(), ".tmp");
364       myTempFile.deleteOnExit();
365       passTempFile(javaParameters.getProgramParametersList(), myTempFile.getAbsolutePath());
366     }
367     catch (Exception e) {
368       LOG.error(e);
369     }
370   }
371
372   protected void writeClassesPerModule(String packageName, JavaParameters javaParameters, Map<Module, List<String>> perModule)
373     throws FileNotFoundException, UnsupportedEncodingException, CantRunException {
374     if (perModule != null) {
375       final String classpath = getScope() == TestSearchScope.WHOLE_PROJECT
376                                ? null : javaParameters.getClassPath().getPathsString();
377
378       final PrintWriter wWriter = new PrintWriter(myWorkingDirsFile, CharsetToolkit.UTF8);
379       try {
380         wWriter.println(packageName);
381         for (Module module : perModule.keySet()) {
382           wWriter.println(PathMacroUtil.getModuleDir(module.getModuleFilePath()));
383           wWriter.println(module.getName());
384
385           if (classpath == null) {
386             final JavaParameters parameters = new JavaParameters();
387             parameters.getClassPath().add(JavaSdkUtil.getIdeaRtJarPath());
388             configureRTClasspath(parameters);
389             JavaParametersUtil.configureModule(module, parameters, JavaParameters.JDK_AND_CLASSES_AND_TESTS,
390                                                getConfiguration().isAlternativeJrePathEnabled() ? getConfiguration()
391                                                  .getAlternativeJrePath() : null);
392             wWriter.println(parameters.getClassPath().getPathsString());
393           }
394           else {
395             wWriter.println(classpath);
396           }
397
398           final List<String> classNames = perModule.get(module);
399           wWriter.println(classNames.size());
400           for (String className : classNames) {
401             wWriter.println(className);
402           }
403         }
404       }
405       finally {
406         wWriter.close();
407       }
408     }
409   }
410
411   protected void deleteTempFiles() {
412     if (myTempFile != null) {
413       FileUtil.delete(myTempFile);
414     }
415     
416     if (myWorkingDirsFile != null) {
417       FileUtil.delete(myWorkingDirsFile);
418     }
419   }
420
421 }