/******************************************************************************
* Copyright (C) 2012-2013 Jonah Graham and others
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Jonah Graham <jonah@kichwacoders.com> - initial API and implementation
* Andrew Ferrazzutti <aferrazz@redhat.com> - ongoing maintenance
* Fabio Zadrozny <fabiofz@gmail.com> - ongoing maintenance
******************************************************************************/
package org.python.pydev.debug.newconsole;
import java.io.File;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import junit.framework.TestCase;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.model.IProcess;
import org.junit.Assert;
import org.python.pydev.core.TestDependent;
import org.python.pydev.debug.model.AbstractDebugTarget;
import org.python.pydev.debug.model.AbstractDebugTargetWithTransmission;
import org.python.pydev.debug.model.IVariableLocator;
import org.python.pydev.debug.model.PyStackFrameConsole;
import org.python.pydev.debug.model.PyThreadConsole;
import org.python.pydev.debug.model.PyVariable;
import org.python.pydev.debug.model.PyVariableCollection;
import org.python.pydev.debug.model.remote.AbstractDebuggerCommand;
import org.python.pydev.debug.model.remote.GetFrameCommand;
import org.python.pydev.debug.model.remote.VersionCommand;
import org.python.pydev.runners.SimpleRunner;
import org.python.pydev.shared_core.callbacks.ICallback;
import org.python.pydev.shared_core.io.FileUtils;
import org.python.pydev.shared_core.net.SocketUtil;
import org.python.pydev.shared_interactive_console.console.InterpreterResponse;
/**
* The purpose of this test is to verify the pydevconsole + pydevd works. This
* test does not try to test the console or debugger itself, just the
* combination and new code paths that the feature introduces.
*
* TODO: Iterate over Jython/Python with and without IPython available.
*
*/
public class PydevConsoleDebugCommsTest extends TestCase {
private PydevConsoleCommunication pydevConsoleCommunication;
private Process process;
private DummyDebugTarget debugTarget;
/** Fake $HOME for IPython */
private File homeDir;
@Override
protected void setUp() throws Exception {
String consoleFile = FileUtils.createFileFromParts(TestDependent.TEST_PYDEV_PLUGIN_LOC, "pysrc",
"pydevconsole.py").getAbsolutePath();
String pydevdDir = new File(TestDependent.TEST_PYDEV_DEBUG_PLUGIN_LOC, "pysrc").getAbsolutePath();
Integer[] ports = SocketUtil.findUnusedLocalPorts(2);
int port = ports[0];
int clientPort = ports[1];
homeDir = FileUtils.getTempFileAt(new File("."), "fake_homedir");
if (homeDir.exists()) {
FileUtils.deleteDirectoryTree(homeDir);
}
homeDir = homeDir.getAbsoluteFile();
homeDir.mkdir();
String[] cmdarray = new String[] { TestDependent.PYTHON_EXE, consoleFile, String.valueOf(port),
String.valueOf(clientPort) };
Map<String, String> env = new TreeMap<String, String>();
env.put("HOME", homeDir.toString());
env.put("PYTHONPATH", pydevdDir);
env.put("PYTHONIOENCODING", "utf-8");
String sysRoot = System.getenv("SystemRoot");
if (sysRoot != null) {
env.put("SystemRoot", sysRoot); //Needed on windows boxes (random/socket. module needs it to work).
}
String[] envp = new String[env.size()];
int i = 0;
for (Object entry : env.entrySet()) {
Map.Entry e = (Entry) entry;
Object key = e.getKey();
envp[i] = key + "=" + e.getValue();
i += 1;
}
process = SimpleRunner.createProcess(cmdarray, envp, null);
pydevConsoleCommunication = new PydevConsoleCommunication(port, process, clientPort, cmdarray, envp, "utf-8");
pydevConsoleCommunication.hello(new NullProgressMonitor());
ServerSocket socket = SocketUtil.createLocalServerSocket();
pydevConsoleCommunication.connectToDebugger(socket.getLocalPort());
socket.setSoTimeout(5000);
Socket accept = socket.accept();
debugTarget = new DummyDebugTarget();
debugTarget.startTransmission(accept);
}
@Override
protected void tearDown() throws Exception {
process.destroy();
pydevConsoleCommunication.close();
debugTarget.terminate();
if (homeDir.exists()) {
//This must be the last thing: the process will lock this directory.
FileUtils.deleteDirectoryTree(homeDir);
}
}
private final class CustomGetFrameCommand extends GetFrameCommand {
private final Boolean[] passed;
private CustomGetFrameCommand(Boolean[] passed, AbstractDebugTarget debugger, String locator) {
super(debugger, locator);
this.passed = passed;
}
@Override
public void processErrorResponse(int cmdCode, String payload) {
passed[0] = false;
}
@Override
public void processOKResponse(int cmdCode, String payload) {
super.processOKResponse(cmdCode, payload);
passed[0] = true;
}
}
private class DummyDebugTarget extends AbstractDebugTarget {
@Override
public void processCommand(String sCmdCode, String sSeqCode, String payload) {
System.out.println(sCmdCode + ":" + sSeqCode + ":" + payload);
}
@Override
public IProcess getProcess() {
return null;
}
@Override
public void launchRemoved(ILaunch launch) {
}
@Override
public boolean canTerminate() {
return false;
}
@Override
public boolean isTerminated() {
return false;
}
}
private void waitUntilNonNull(Object[] object) {
for (int i = 0; i < 50; i++) {
if (object[0] != null) {
return;
}
try {
Thread.sleep(250);
} catch (InterruptedException e) {
// Retry now
}
}
Assert.fail("Timed out waiting for non-null");
}
/**
* This test is the basic comms working, send the command down via XML-RPC
* based new method postCommand and receive response back via
* {@link AbstractDebugTargetWithTransmission}
*/
public void testVersion() throws Exception {
final Boolean passed[] = new Boolean[1];
debugTarget.postCommand(new VersionCommand(debugTarget) {
@Override
public void processOKResponse(int cmdCode, String payload) {
if (cmdCode == AbstractDebuggerCommand.CMD_VERSION && "@@BUILD_NUMBER@@".equals(payload)) {
passed[0] = true;
} else {
passed[0] = false;
}
}
@Override
public void processErrorResponse(int cmdCode, String payload) {
passed[0] = false;
}
});
waitUntilNonNull(passed);
Assert.assertTrue(passed[0]);
}
private InterpreterResponse execInterpreter(String command) {
final InterpreterResponse response[] = new InterpreterResponse[1];
ICallback<Object, InterpreterResponse> onResponseReceived = new ICallback<Object, InterpreterResponse>() {
@Override
public Object call(InterpreterResponse arg) {
response[0] = arg;
return null;
}
};
pydevConsoleCommunication.execInterpreter(command, onResponseReceived);
waitUntilNonNull(response);
return response[0];
}
/**
* #PyDev-502: PyDev 3.9 F2 doesn't support backslash continuations
*/
public void testContinuation() throws Exception {
InterpreterResponse response = execInterpreter("from os import \\\n");
assertTrue(response.more);
response = execInterpreter(" path,\\\n");
assertTrue(response.more);
response = execInterpreter(" remove\n");
assertTrue(response.more);
response = execInterpreter("\n");
}
/**
* Test that variables can be seen
*/
public void testVariable() throws Exception {
execInterpreter("my_var=1");
IVariableLocator frameLocator = new IVariableLocator() {
@Override
public String getPyDBLocation() {
// Make a reference to the virtual frame representing the interactive console
return PyThreadConsole.VIRTUAL_CONSOLE_ID + "\t" + PyStackFrameConsole.VIRTUAL_FRAME_ID + "\tFRAME";
}
@Override
public String getThreadId() {
return PyThreadConsole.VIRTUAL_CONSOLE_ID;
}
};
final Boolean passed[] = new Boolean[1];
CustomGetFrameCommand cmd = new CustomGetFrameCommand(passed, debugTarget, frameLocator.getPyDBLocation());
debugTarget.postCommand(cmd);
waitUntilNonNull(passed);
Assert.assertTrue(passed[0]);
PyVariable[] variables = PyVariableCollection.getCommandVariables(cmd, debugTarget, frameLocator);
Map<String, PyVariable> variableMap = new HashMap<String, PyVariable>();
for (PyVariable variable : variables) {
variableMap.put(variable.getName(), variable);
}
Assert.assertTrue(variableMap.containsKey("my_var"));
Assert.assertEquals("int: 1", variableMap.get("my_var").getValueString());
}
}