2cba3d2e4069cafe6f0e2f760f6fdea3d44b0ec1
[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                                  () -> {
150                                  }, new String[]{});
151     before();
152
153     myConsoleInitSemaphore = new Semaphore(0);
154
155     consoleRunner.addConsoleListener(new PydevConsoleRunnerImpl.ConsoleListener() {
156       @Override
157       public void handleConsoleInitialized(LanguageConsoleView consoleView) {
158         myConsoleInitSemaphore.release();
159       }
160     });
161
162     consoleRunner.run();
163
164     waitFor(myConsoleInitSemaphore);
165
166     myCommandSemaphore = new Semaphore(1);
167
168     myConsoleView = consoleRunner.getConsoleView();
169     myProcessHandler = consoleRunner.getProcessHandler();
170
171     myExecuteHandler = consoleRunner.getConsoleExecuteActionHandler();
172
173     myCommunication = consoleRunner.getPydevConsoleCommunication();
174
175     myCommunication.addCommunicationListener(new ConsoleCommunicationListener() {
176       @Override
177       public void commandExecuted(boolean more) {
178         myCommandSemaphore.release();
179       }
180
181       @Override
182       public void inputRequested() {
183       }
184     });
185
186     myProcessHandler.addProcessListener(new ProcessAdapter() {
187       @Override
188       public void processTerminated(ProcessEvent event) {
189         if (event.getExitCode() != 0 && !myProcessCanTerminate) {
190           Assert.fail("Process terminated unexpectedly\n" + output());
191         }
192       }
193     });
194
195     OutputPrinter myOutputPrinter = null;
196     if (shouldPrintOutput) {
197       myOutputPrinter = new OutputPrinter();
198       myOutputPrinter.start();
199     }
200
201     waitForOutput("PyDev console");
202
203     try {
204       testing();
205       after();
206     }
207     finally {
208       setProcessCanTerminate(true);
209
210       if (myOutputPrinter != null) {
211         myOutputPrinter.stop();
212       }
213
214       disposeConsole();
215     }
216   }
217
218   private void disposeConsoleProcess() throws InterruptedException {
219     myProcessHandler.destroyProcess();
220
221     waitFor(myProcessHandler);
222
223     if (!myProcessHandler.isProcessTerminated()) {
224       if (!waitFor(myProcessHandler)) {
225         if (!myProcessHandler.isProcessTerminated()) {
226           throw new RuntimeException("Cannot stop console process");
227         }
228       }
229     }
230     myProcessHandler = null;
231   }
232
233   /**
234    * Waits until all passed strings appear in output.
235    * If they don't appear in time limit, then exception is raised.
236    *
237    * @param string
238    * @throws InterruptedException
239    */
240   public void waitForOutput(String... string) throws InterruptedException {
241     int count = 0;
242     while (true) {
243       List<String> missing = Lists.newArrayList();
244       String out = output();
245       boolean flag = true;
246       for (String s : string) {
247         if (!out.contains(s)) {
248           flag = false;
249           missing.add(s);
250         }
251       }
252       if (flag) {
253         break;
254       }
255       if (count > 10) {
256         Assert.fail("Strings: <--\n" + StringUtil.join(missing, "\n---\n") + "-->" + "are not present in output.\n" + output());
257       }
258       Thread.sleep(2000);
259       count++;
260     }
261   }
262
263   protected void waitForReady() throws InterruptedException {
264     int count = 0;
265     while (!myExecuteHandler.isEnabled() || !canExecuteNow()) {
266       if (count > 10) {
267         Assert.fail("Console is not ready");
268       }
269       Thread.sleep(300);
270       count++;
271     }
272   }
273
274   protected boolean canExecuteNow() {
275     return myExecuteHandler.canExecuteNow();
276   }
277
278   public void setShouldPrintOutput(boolean shouldPrintOutput) {
279     this.shouldPrintOutput = shouldPrintOutput;
280   }
281
282   private class OutputPrinter {
283     private Thread myThread;
284     private int myLen = 0;
285
286     public void start() {
287       myThread = new Thread(() -> doJob(), "py console printer");
288       myThread.setDaemon(true);
289       myThread.start();
290     }
291
292     private void doJob() {
293       try {
294         while (true) {
295           printToConsole();
296
297           Thread.sleep(500);
298         }
299       }
300       catch (Exception ignored) {
301       }
302     }
303
304     private synchronized void printToConsole() {
305       String s = output();
306       if (s.length() > myLen) {
307         System.out.print(s.substring(myLen));
308       }
309       myLen = s.length();
310     }
311
312     public void stop() throws InterruptedException {
313       printToConsole();
314       myThread.interrupt();
315       myThread.join();
316     }
317   }
318
319   protected void exec(final String command) throws InterruptedException {
320     waitForReady();
321     myCommandSemaphore.acquire(1);
322     UIUtil.invokeAndWaitIfNeeded(new Runnable() {
323       @Override
324       public void run() {
325         myConsoleView.executeInConsole(command);
326       }
327     });
328   }
329
330   protected boolean hasValue(String varName, String value) throws PyDebuggerException {
331     PyDebugValue val = getValue(varName);
332     return val != null && value.equals(val.getValue());
333   }
334
335   protected void setValue(String varName, String value) throws PyDebuggerException {
336     PyDebugValue val = getValue(varName);
337     myCommunication.changeVariable(val, value);
338   }
339
340   protected PyDebugValue getValue(String varName) throws PyDebuggerException {
341     XValueChildrenList l = myCommunication.loadFrame();
342
343     if (l == null) {
344       return null;
345     }
346     for (int i = 0; i < l.size(); i++) {
347       String name = l.getName(i);
348       if (varName.equals(name)) {
349         return (PyDebugValue)l.getValue(i);
350       }
351     }
352
353     return null;
354   }
355
356   protected List<String> getCompoundValueChildren(PyDebugValue value) throws PyDebuggerException {
357     XValueChildrenList list = myCommunication.loadVariable(value);
358     List<String> result = Lists.newArrayList();
359     for (int i = 0; i < list.size(); i++) {
360       result.add(((PyDebugValue)list.getValue(i)).getValue());
361     }
362     return result;
363   }
364
365   protected void input(String text) {
366     myConsoleView.executeInConsole(text);
367   }
368
369   protected void waitForFinish() throws InterruptedException {
370     waitFor(myCommandSemaphore);
371   }
372
373   protected void execNoWait(final String command) {
374     UIUtil.invokeLaterIfNeeded(() -> myConsoleView.executeCode(command, null));
375   }
376
377   protected void interrupt() {
378     myCommunication.interrupt();
379   }
380
381
382   public void addTextToEditor(final String text) {
383     UIUtil.invokeAndWaitIfNeeded(new Runnable() {
384                                    @Override
385                                    public void run() {
386                                      getConsoleView().setInputText(text);
387                                      PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
388                                    }
389                                  }
390     );
391   }
392 }