Revert to Tool Window API
[idea/community.git] / python / testSrc / com / jetbrains / env / python / console / PyConsoleTask.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.console;
17
18 import com.google.common.collect.Lists;
19 import com.google.common.collect.Maps;
20 import com.intellij.execution.ExecutionManager;
21 import com.intellij.execution.console.LanguageConsoleView;
22 import com.intellij.execution.process.ProcessAdapter;
23 import com.intellij.execution.process.ProcessEvent;
24 import com.intellij.execution.ui.RunContentDescriptor;
25 import com.intellij.openapi.application.Result;
26 import com.intellij.openapi.application.WriteAction;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.projectRoots.Sdk;
29 import com.intellij.openapi.util.Disposer;
30 import com.intellij.openapi.util.Ref;
31 import com.intellij.openapi.util.text.StringUtil;
32 import com.intellij.psi.PsiDocumentManager;
33 import com.intellij.util.ui.UIUtil;
34 import com.intellij.xdebugger.frame.XValueChildrenList;
35 import com.jetbrains.env.PyExecutionFixtureTestTask;
36 import com.jetbrains.python.console.*;
37 import com.jetbrains.python.console.pydev.ConsoleCommunicationListener;
38 import com.jetbrains.python.debugger.PyDebugValue;
39 import com.jetbrains.python.debugger.PyDebuggerException;
40 import com.jetbrains.python.sdkTools.SdkCreationType;
41 import org.jetbrains.annotations.NotNull;
42 import org.junit.Assert;
43
44 import java.util.List;
45 import java.util.concurrent.Semaphore;
46
47 /**
48  * @author traff
49  */
50 public class PyConsoleTask extends PyExecutionFixtureTestTask {
51   private boolean myProcessCanTerminate;
52
53   protected PyConsoleProcessHandler myProcessHandler;
54   protected PydevConsoleCommunication myCommunication;
55
56   private boolean shouldPrintOutput = false;
57   private PythonConsoleView myConsoleView;
58   private Semaphore myCommandSemaphore;
59   private Semaphore myConsoleInitSemaphore;
60   private PydevConsoleExecuteActionHandler myExecuteHandler;
61
62   private Ref<RunContentDescriptor> myContentDescriptorRef = Ref.create();
63
64   public PyConsoleTask() {
65     super(null);
66   }
67
68   public PythonConsoleView getConsoleView() {
69     return myConsoleView;
70   }
71
72   @Override
73   public void setUp(final String testName) throws Exception {
74     if (myFixture == null) {
75       super.setUp(testName);
76     }
77   }
78
79   @NotNull
80   protected String output() {
81     return myConsoleView.getHistoryViewer().getDocument().getText();
82   }
83
84   public void setProcessCanTerminate(boolean processCanTerminate) {
85     myProcessCanTerminate = processCanTerminate;
86   }
87
88   @Override
89   public void tearDown() throws Exception {
90     UIUtil.invokeAndWaitIfNeeded(new Runnable() {
91       @Override
92       public void run() {
93         try {
94           if (myConsoleView != null) {
95             disposeConsole();
96             myCommunication.waitForTerminate();
97           }
98           PyConsoleTask.super.tearDown();
99         }
100         catch (Exception e) {
101           throw new RuntimeException(e);
102         }
103       }
104     });
105   }
106
107   private void disposeConsole() throws InterruptedException {
108     if (myCommunication != null) {
109       UIUtil.invokeAndWaitIfNeeded(new Runnable() {
110         @Override
111         public void run() {
112           try {
113             myCommunication.close();
114           }
115           catch (Exception e) {
116             e.printStackTrace();
117           }
118           myCommunication = null;
119         }
120       });
121     }
122
123     disposeConsoleProcess();
124
125     ExecutionManager.getInstance(getProject()).getContentManager().getAllDescriptors().forEach((Disposer::dispose));
126
127     if (myConsoleView != null) {
128       new WriteAction() {
129         @Override
130         protected void run(@NotNull Result result) throws Throwable {
131           Disposer.dispose(myConsoleView);
132           myConsoleView = null;
133         }
134       }.execute();
135     }
136   }
137
138   @Override
139   public void runTestOn(final String sdkHome) throws Exception {
140     final Project project = getProject();
141
142     final Sdk sdk = createTempSdk(sdkHome, SdkCreationType.EMPTY_SDK);
143
144     setProcessCanTerminate(false);
145
146     PydevConsoleRunner consoleRunner =
147       new PydevConsoleRunnerImpl(project, sdk, PyConsoleType.PYTHON, myFixture.getTempDirPath(), Maps.newHashMap(),
148                                  PyConsoleOptions.getInstance(project).getPythonConsoleSettings(),
149                                  (s) -> {});
150     before();
151
152     myConsoleInitSemaphore = new Semaphore(0);
153
154     consoleRunner.addConsoleListener(new PydevConsoleRunnerImpl.ConsoleListener() {
155       @Override
156       public void handleConsoleInitialized(LanguageConsoleView consoleView) {
157         myConsoleInitSemaphore.release();
158       }
159     });
160
161     consoleRunner.run();
162
163     waitFor(myConsoleInitSemaphore);
164
165     myCommandSemaphore = new Semaphore(1);
166
167     myConsoleView = consoleRunner.getConsoleView();
168     myProcessHandler = consoleRunner.getProcessHandler();
169
170     myExecuteHandler = consoleRunner.getConsoleExecuteActionHandler();
171
172     myCommunication = consoleRunner.getPydevConsoleCommunication();
173
174     myCommunication.addCommunicationListener(new ConsoleCommunicationListener() {
175       @Override
176       public void commandExecuted(boolean more) {
177         myCommandSemaphore.release();
178       }
179
180       @Override
181       public void inputRequested() {
182       }
183     });
184
185     myProcessHandler.addProcessListener(new ProcessAdapter() {
186       @Override
187       public void processTerminated(ProcessEvent event) {
188         if (event.getExitCode() != 0 && !myProcessCanTerminate) {
189           Assert.fail("Process terminated unexpectedly\n" + output());
190         }
191       }
192     });
193
194     OutputPrinter myOutputPrinter = null;
195     if (shouldPrintOutput) {
196       myOutputPrinter = new OutputPrinter();
197       myOutputPrinter.start();
198     }
199
200     waitForOutput("PyDev console");
201
202     try {
203       testing();
204       after();
205     }
206     finally {
207       setProcessCanTerminate(true);
208
209       if (myOutputPrinter != null) {
210         myOutputPrinter.stop();
211       }
212
213       disposeConsole();
214     }
215   }
216
217   private void disposeConsoleProcess() throws InterruptedException {
218     myProcessHandler.destroyProcess();
219
220     waitFor(myProcessHandler);
221
222     if (!myProcessHandler.isProcessTerminated()) {
223       if (!waitFor(myProcessHandler)) {
224         if (!myProcessHandler.isProcessTerminated()) {
225           throw new RuntimeException("Cannot stop console process");
226         }
227       }
228     }
229     myProcessHandler = null;
230   }
231
232   /**
233    * Waits until all passed strings appear in output.
234    * If they don't appear in time limit, then exception is raised.
235    *
236    * @param string
237    * @throws InterruptedException
238    */
239   public void waitForOutput(String... string) throws InterruptedException {
240     int count = 0;
241     while (true) {
242       List<String> missing = Lists.newArrayList();
243       String out = output();
244       boolean flag = true;
245       for (String s : string) {
246         if (!out.contains(s)) {
247           flag = false;
248           missing.add(s);
249         }
250       }
251       if (flag) {
252         break;
253       }
254       if (count > 10) {
255         Assert.fail("Strings: <--\n" + StringUtil.join(missing, "\n---\n") + "-->" + "are not present in output.\n" + output());
256       }
257       Thread.sleep(2000);
258       count++;
259     }
260   }
261
262   protected void waitForReady() throws InterruptedException {
263     int count = 0;
264     while (!myExecuteHandler.isEnabled() || !canExecuteNow()) {
265       if (count > 10) {
266         Assert.fail("Console is not ready");
267       }
268       Thread.sleep(300);
269       count++;
270     }
271   }
272
273   protected boolean canExecuteNow() {
274     return myExecuteHandler.canExecuteNow();
275   }
276
277   public void setShouldPrintOutput(boolean shouldPrintOutput) {
278     this.shouldPrintOutput = shouldPrintOutput;
279   }
280
281   private class OutputPrinter {
282     private Thread myThread;
283     private int myLen = 0;
284
285     public void start() {
286       myThread = new Thread(() -> doJob(), "py console printer");
287       myThread.setDaemon(true);
288       myThread.start();
289     }
290
291     private void doJob() {
292       try {
293         while (true) {
294           printToConsole();
295
296           Thread.sleep(500);
297         }
298       }
299       catch (Exception ignored) {
300       }
301     }
302
303     private synchronized void printToConsole() {
304       String s = output();
305       if (s.length() > myLen) {
306         System.out.print(s.substring(myLen));
307       }
308       myLen = s.length();
309     }
310
311     public void stop() throws InterruptedException {
312       printToConsole();
313       myThread.interrupt();
314       myThread.join();
315     }
316   }
317
318   protected void exec(final String command) throws InterruptedException {
319     waitForReady();
320     myCommandSemaphore.acquire(1);
321     UIUtil.invokeAndWaitIfNeeded(new Runnable() {
322       @Override
323       public void run() {
324         myConsoleView.executeInConsole(command);
325       }
326     });
327   }
328
329   protected boolean hasValue(String varName, String value) throws PyDebuggerException {
330     PyDebugValue val = getValue(varName);
331     return val != null && value.equals(val.getValue());
332   }
333
334   protected void setValue(String varName, String value) throws PyDebuggerException {
335     PyDebugValue val = getValue(varName);
336     myCommunication.changeVariable(val, value);
337   }
338
339   protected PyDebugValue getValue(String varName) throws PyDebuggerException {
340     XValueChildrenList l = myCommunication.loadFrame();
341
342     if (l == null) {
343       return null;
344     }
345     for (int i = 0; i < l.size(); i++) {
346       String name = l.getName(i);
347       if (varName.equals(name)) {
348         return (PyDebugValue)l.getValue(i);
349       }
350     }
351
352     return null;
353   }
354
355   protected List<String> getCompoundValueChildren(PyDebugValue value) throws PyDebuggerException {
356     XValueChildrenList list = myCommunication.loadVariable(value);
357     List<String> result = Lists.newArrayList();
358     for (int i = 0; i < list.size(); i++) {
359       result.add(((PyDebugValue)list.getValue(i)).getValue());
360     }
361     return result;
362   }
363
364   protected void input(String text) {
365     myConsoleView.executeInConsole(text);
366   }
367
368   protected void waitForFinish() throws InterruptedException {
369     waitFor(myCommandSemaphore);
370   }
371
372   protected void execNoWait(final String command) {
373     UIUtil.invokeLaterIfNeeded(() -> myConsoleView.executeCode(command, null));
374   }
375
376   protected void interrupt() {
377     myCommunication.interrupt();
378   }
379
380
381   public void addTextToEditor(final String text) {
382     UIUtil.invokeAndWaitIfNeeded(new Runnable() {
383                                    @Override
384                                    public void run() {
385                                      getConsoleView().setInputText(text);
386                                      PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
387                                    }
388                                  }
389     );
390   }
391 }