Write correct process output in case it terminates with an error.
[idea/community.git] / python / testSrc / com / jetbrains / env / python / debug / PyDebuggerTask.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.jetbrains.env.python.debug;
17
18 import com.intellij.execution.*;
19 import com.intellij.execution.configurations.ConfigurationFactory;
20 import com.intellij.execution.configurations.RunProfile;
21 import com.intellij.execution.executors.DefaultDebugExecutor;
22 import com.intellij.execution.process.KillableColoredProcessHandler;
23 import com.intellij.execution.process.ProcessAdapter;
24 import com.intellij.execution.process.ProcessEvent;
25 import com.intellij.execution.process.ProcessHandler;
26 import com.intellij.execution.runners.ExecutionEnvironment;
27 import com.intellij.openapi.application.Result;
28 import com.intellij.openapi.application.WriteAction;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.util.Key;
31 import com.intellij.xdebugger.*;
32 import com.jetbrains.env.python.PythonDebuggerTest;
33 import com.jetbrains.python.debugger.PyDebugProcess;
34 import com.jetbrains.python.debugger.PyDebugRunner;
35 import com.jetbrains.python.run.PythonCommandLineState;
36 import com.jetbrains.python.run.PythonConfigurationType;
37 import com.jetbrains.python.run.PythonRunConfiguration;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
40 import org.junit.Assert;
41
42 import java.io.IOException;
43 import java.lang.reflect.InvocationTargetException;
44 import java.net.ServerSocket;
45 import java.util.concurrent.Semaphore;
46
47 /**
48  * @author traff
49  */
50 public class PyDebuggerTask extends PyBaseDebuggerTask {
51
52   private boolean myMultiprocessDebug = false;
53   protected PythonRunConfiguration myRunConfiguration;
54
55
56   public PyDebuggerTask(@Nullable final String relativeTestDataPath, String scriptName, String scriptParameters) {
57     super(relativeTestDataPath);
58     setScriptName(scriptName);
59     setScriptParameters(scriptParameters);
60     init();
61   }
62
63   public PyDebuggerTask(@Nullable final String relativeTestDataPath, String scriptName) {
64     this(relativeTestDataPath, scriptName, null);
65   }
66
67   protected void init() {
68
69   }
70
71   public void runTestOn(String sdkHome) throws Exception {
72     final Project project = getProject();
73
74     final ConfigurationFactory factory = PythonConfigurationType.getInstance().getConfigurationFactories()[0];
75
76
77     final RunnerAndConfigurationSettings settings =
78       RunManager.getInstance(project).createRunConfiguration("test", factory);
79
80     myRunConfiguration = (PythonRunConfiguration)settings.getConfiguration();
81
82     myRunConfiguration.setSdkHome(sdkHome);
83     myRunConfiguration.setScriptName(getScriptName());
84     myRunConfiguration.setWorkingDirectory(myFixture.getTempDirPath());
85     myRunConfiguration.setScriptParameters(getScriptParameters());
86
87     new WriteAction() {
88       @Override
89       protected void run(@NotNull Result result) throws Throwable {
90         RunManagerEx.getInstanceEx(project).addConfiguration(settings, false);
91         RunManagerEx.getInstanceEx(project).setSelectedConfiguration(settings);
92         Assert.assertSame(settings, RunManagerEx.getInstanceEx(project).getSelectedConfiguration());
93       }
94     }.execute();
95
96     final PyDebugRunner runner = (PyDebugRunner)ProgramRunnerUtil.getRunner(getExecutorId(), settings);
97     Assert.assertTrue(runner.canRun(getExecutorId(), myRunConfiguration));
98
99     final Executor executor = DefaultDebugExecutor.getDebugExecutorInstance();
100     final ExecutionEnvironment env = new ExecutionEnvironment(executor, runner, settings, project);
101
102     final PythonCommandLineState pyState = (PythonCommandLineState)myRunConfiguration.getState(executor, env);
103
104     assert pyState != null;
105     pyState.setMultiprocessDebug(isMultiprocessDebug());
106
107     final ServerSocket serverSocket;
108     try {
109       //noinspection SocketOpenedButNotSafelyClosed
110       serverSocket = new ServerSocket(0);
111     }
112     catch (IOException e) {
113       throw new ExecutionException("Failed to find free socket port", e);
114     }
115
116
117     final int serverLocalPort = serverSocket.getLocalPort();
118     final RunProfile profile = env.getRunProfile();
119
120     PythonDebuggerTest.createExceptionBreak(myFixture, false, false, false); //turn off exception breakpoints by default
121
122     before();
123
124     setProcessCanTerminate(false);
125
126     myTerminateSemaphore = new Semaphore(0);
127     
128     new WriteAction<ExecutionResult>() {
129       @Override
130       protected void run(@NotNull Result<ExecutionResult> result) throws Throwable {
131         myExecutionResult =
132           pyState.execute(executor, runner.createCommandLinePatchers(myFixture.getProject(), pyState, profile, serverLocalPort));
133
134         mySession = XDebuggerManager.getInstance(getProject()).
135           startSession(env, new XDebugProcessStarter() {
136             @NotNull
137             public XDebugProcess start(@NotNull final XDebugSession session) {
138               myDebugProcess =
139                 new PyDebugProcess(session, serverSocket, myExecutionResult.getExecutionConsole(), myExecutionResult.getProcessHandler(), isMultiprocessDebug());
140
141
142               StringBuilder output = new StringBuilder();
143
144               myDebugProcess.getProcessHandler().addProcessListener(new ProcessAdapter() {
145
146                 @Override
147                 public void onTextAvailable(ProcessEvent event, Key outputType) {
148                   output.append(event.getText());
149                 }
150
151                 @Override
152                 public void processTerminated(ProcessEvent event) {
153                   myTerminateSemaphore.release();
154                   if (event.getExitCode() != 0 && !myProcessCanTerminate) {
155                     Assert.fail("Process terminated unexpectedly\n" + output.toString());
156                   }
157                 }
158               });
159
160
161               myDebugProcess.getProcessHandler().startNotify();
162
163               return myDebugProcess;
164             }
165           });
166         result.setResult(myExecutionResult);
167       }
168     }.execute().getResultObject();
169
170     OutputPrinter myOutputPrinter = null;
171     if (shouldPrintOutput) {
172       myOutputPrinter = new OutputPrinter();
173       myOutputPrinter.start();
174     }
175
176
177     myPausedSemaphore = new Semaphore(0);
178     
179
180     mySession.addSessionListener(new XDebugSessionListener() {
181       @Override
182       public void sessionPaused() {
183         if (myPausedSemaphore != null) {
184           myPausedSemaphore.release();
185         }
186       }
187     });
188
189     doTest(myOutputPrinter);
190   }
191
192   protected String getExecutorId() {
193     return DefaultDebugExecutor.EXECUTOR_ID;
194   }
195
196   public PythonRunConfiguration getRunConfiguration() {
197     return myRunConfiguration;
198   }
199
200   private boolean isMultiprocessDebug() {
201     return myMultiprocessDebug;
202   }
203
204   public void setMultiprocessDebug(boolean multiprocessDebug) {
205     myMultiprocessDebug = multiprocessDebug;
206   }
207
208   protected void waitForAllThreadsPause() throws InterruptedException, InvocationTargetException {
209     waitForPause();
210     Assert.assertTrue(String.format("All threads didn't stop within timeout\n" +
211                                     "Output: %s", output()), waitForAllThreads());
212     XDebuggerTestUtil.waitForSwing();
213   }
214
215   protected boolean waitForAllThreads() throws InterruptedException {
216     long until = System.currentTimeMillis() + NORMAL_TIMEOUT;
217     while (System.currentTimeMillis() < until && getRunningThread() != null) {
218       Thread.sleep(1000);
219     }
220     return getRunningThread() == null;
221   }
222
223   @Override
224   protected void disposeDebugProcess() throws InterruptedException {
225     if (myDebugProcess != null) {
226       ProcessHandler processHandler = myDebugProcess.getProcessHandler();
227
228       myDebugProcess.stop();
229
230       waitFor(processHandler);
231
232       if (!processHandler.isProcessTerminated()) {
233         killDebugProcess();
234         if (!waitFor(processHandler)) {
235           new Throwable("Cannot stop debugger process").printStackTrace();
236         }
237       }
238     }
239   }
240
241   private void killDebugProcess() {
242     if (myDebugProcess.getProcessHandler() instanceof KillableColoredProcessHandler) {
243       KillableColoredProcessHandler h = (KillableColoredProcessHandler)myDebugProcess.getProcessHandler();
244
245       h.killProcess();
246     }
247     else {
248       myDebugProcess.getProcessHandler().destroyProcess();
249     }
250   }
251 }