PY-38941 Support debugging of processes created with posix_spawn

(cherry picked from commit e55c2168595e22e80a8f95c2e3812cf7f687c31c)

GitOrigin-RevId: 4db4aaaa9ce7198cfb5177bac262f9f72b92cdee
This commit is contained in:
Andrey Lisin
2019-11-19 17:05:35 +00:00
committed by intellij-monorepo-bot
parent 14b78ce15d
commit ba365df917
6 changed files with 227 additions and 163 deletions
@@ -3,7 +3,8 @@ import os
import sys
import traceback
from _pydev_imps._pydev_saved_modules import threading
from _pydevd_bundle.pydevd_constants import get_global_debugger, IS_WINDOWS, IS_MACOS, IS_JYTHON, IS_PY36_OR_LESSER, get_current_thread_id
from _pydevd_bundle.pydevd_constants import get_global_debugger, IS_WINDOWS, IS_MACOS, IS_JYTHON, IS_PY36_OR_LESSER, IS_PY38_OR_GREATER, \
get_current_thread_id
from _pydev_bundle import pydev_log
try:
@@ -497,6 +498,19 @@ def create_spawnve(original_name):
return new_spawnve
def create_posix_spawn(original_name):
"""
os.posix_spawn(path, argv, env, *, file_actions=None, ... (6 more))
os.posix_spawnp(path, argv, env, *, file_actions=None, ... (6 more))
"""
def new_posix_spawn(path, argv, env, **kwargs):
import os
argv = patch_args(argv)
send_process_created_message()
return getattr(os, original_name)(path, argv, env, **kwargs)
return new_posix_spawn
def create_fork_exec(original_name):
"""
_posixsubprocess.fork_exec(args, executable_list, close_fds, ... (13 more))
@@ -656,6 +670,10 @@ def patch_new_process_functions():
monkey_patch_os('spawnvp', create_spawnv)
monkey_patch_os('spawnvpe', create_spawnve)
if IS_PY38_OR_GREATER and not IS_WINDOWS:
monkey_patch_os('posix_spawn', create_posix_spawn)
monkey_patch_os('posix_spawnp', create_posix_spawn)
if not IS_JYTHON:
if not IS_WINDOWS:
monkey_patch_os('fork', create_fork)
@@ -691,6 +709,10 @@ def patch_new_process_functions_with_warning():
monkey_patch_os('spawnvp', create_warn_multiproc)
monkey_patch_os('spawnvpe', create_warn_multiproc)
if IS_PY38_OR_GREATER and not IS_WINDOWS:
monkey_patch_os('posix_spawn', create_warn_multiproc)
monkey_patch_os('posix_spawnp', create_warn_multiproc)
if not IS_JYTHON:
if not IS_WINDOWS:
monkey_patch_os('fork', create_warn_multiproc)
@@ -108,6 +108,7 @@ IS_PY34_OR_GREATER = False
IS_PY36_OR_GREATER = False
IS_PY37_OR_GREATER = False
IS_PY36_OR_LESSER = False
IS_PY38_OR_GREATER = False
IS_PY2 = True
IS_PY27 = False
IS_PY24 = False
@@ -119,6 +120,7 @@ try:
IS_PY36_OR_GREATER = sys.version_info >= (3, 6)
IS_PY37_OR_GREATER = sys.version_info >= (3, 7)
IS_PY36_OR_LESSER = sys.version_info[:2] <= (3, 6)
IS_PY38_OR_GREATER = sys.version_info >= (3, 8)
elif sys.version_info[0] == 2 and sys.version_info[1] == 7:
IS_PY27 = True
elif sys.version_info[0] == 2 and sys.version_info[1] == 4:
-1
View File
@@ -2,4 +2,3 @@ xval = 0
xvalue1 = 1
xvalue2 = 2
print(xvalue1 + xvalue2)
@@ -0,0 +1,7 @@
import os
import sys
pid = os.posix_spawn(sys.executable, [sys.executable, "test2.py"], os.environ)
pid, status = os.waitpid(pid, 0)
print(pid, status)
@@ -0,0 +1,195 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.env.python.debug;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.intellij.openapi.util.SystemInfo;
import com.jetbrains.env.PyEnvTestCase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.Assume;
import org.junit.Test;
import java.util.Set;
import static org.junit.Assert.assertFalse;
public class PythonDebuggerMultiprocessingTest extends PyEnvTestCase {
private static class PyDebuggerMultiprocessTask extends PyDebuggerTask {
public PyDebuggerMultiprocessTask(@Nullable String relativeTestDataPath, String scriptName) {
super(relativeTestDataPath, scriptName);
}
@Override
protected void init() {
setMultiprocessDebug(true);
}
}
@Test
public void testMultiprocess() {
runPythonTest(new PyDebuggerMultiprocessTask("/debug", "test_multiprocess.py") {
@Override
public void before() {
toggleBreakpoint(getScriptName(), 9);
}
@Override
public void testing() throws Exception {
waitForPause();
eval("i").hasValue("'Result:OK'");
resume();
waitForOutput("Result:OK");
}
@NotNull
@Override
public Set<String> getTags() {
return Sets.newHashSet("python3");
}
});
}
@Test
public void testMultiprocessingSubprocess() {
runPythonTest(new PyDebuggerMultiprocessTask("/debug", "test_multiprocess_args.py") {
@Override
public void before() {
toggleBreakpoint(getFilePath("test_remote.py"), 2);
setWaitForTermination(false);
}
@Override
public void testing() throws Exception {
waitForPause();
eval("sys.argv[1]").hasValue("'subprocess'");
eval("sys.argv[2]").hasValue("'etc etc'");
resume();
}
@NotNull
@Override
public Set<String> getTags() {
return ImmutableSet.of("-iron", "-jython"); //can't run on iron and jython
}
});
}
@Test
public void testMultiprocessPool() {
runPythonTest(new PyDebuggerMultiprocessTask("/debug", "test_multiprocess_pool.py") {
@Override
public void testing() throws Exception {
waitForOutput("Done");
assertFalse(output().contains("KeyboardInterrupt"));
}
@NotNull
@Override
public Set<String> getTags() {
return ImmutableSet.of("-iron");
}
});
}
@Test
public void testPythonSubprocessWithCParameter() {
runPythonTest(new PyDebuggerMultiprocessTask("/debug", "test_python_subprocess_with_c_parameter.py") {
@Override
public void before() {
toggleBreakpoint(getFilePath("test_python_subprocess_another_helper.py"), 2);
}
@Override
public void testing() throws Exception {
waitForPause();
eval("x").hasValue("42");
resume();
waitForOutput("Hello!");
}
@NotNull
@Override
public Set<String> getTags() {
return ImmutableSet.of("-iron", "-jython");
}
});
}
@Test
public void testMultiprocessProcess() {
runPythonTest(new PyDebuggerMultiprocessTask("/debug", "test_multiprocess_process.py") {
@Override
public void before() {
toggleBreakpoint(getFilePath("test_multiprocess_process.py"), 5);
setWaitForTermination(false);
}
@Override
public void testing() throws Exception {
waitForPause();
eval("name").hasValue("'subprocess'");
resume();
}
@NotNull
@Override
public Set<String> getTags() {
return ImmutableSet.of("-iron", "-jython"); //can't run on iron and jython
}
});
}
@Test
public void testSubprocess() {
runPythonTest(new PyDebuggerTask("/debug", "test_subprocess.py") {
@Override
public void before() throws Exception {
toggleBreakpoint(getFilePath(getScriptName()), 8);
}
@Override
public void testing() throws Exception {
waitForPause();
resume();
waitForTerminate();
outputContains("The subprocess finished with the return code 0.");
}
});
}
@Test
public void testPosixSpawn() {
runPythonTest(new PyDebuggerMultiprocessTask("/debug", "test_posix_spawn.py") {
@Override
public void before() {
toggleBreakpoint(getFilePath(getScriptName()), 6);
toggleBreakpoint(getFilePath("test2.py"), 7);
setWaitForTermination(false);
}
@Override
public void testing() throws Exception {
Assume.assumeFalse("Windows doesn't support `posix_spawn`", SystemInfo.isWindows);
waitForPause();
eval("z").hasValue("2");
resume();
waitForPause();
resume();
waitForOutput("Process finished with exit code 0");
}
@NotNull
@Override
public Set<String> getTags() {
return ImmutableSet.of("python3.8");
}
});
}
}
@@ -696,149 +696,6 @@ public class PythonDebuggerTest extends PyEnvTestCase {
});
}
@Test
public void testMultiprocess() {
runPythonTest(new PyDebuggerTask("/debug", "test_multiprocess.py") {
@Override
protected void init() {
setMultiprocessDebug(true);
}
@Override
public void before() {
toggleBreakpoint(getScriptName(), 9);
}
@Override
public void testing() throws Exception {
waitForPause();
eval("i").hasValue("'Result:OK'");
resume();
waitForOutput("Result:OK");
}
@NotNull
@Override
public Set<String> getTags() {
return Sets.newHashSet("python3");
}
});
}
@Test
public void testMultiprocessingSubprocess() {
runPythonTest(new PyDebuggerTask("/debug", "test_multiprocess_args.py") {
@Override
protected void init() {
setMultiprocessDebug(true);
}
@Override
public void before() {
toggleBreakpoint(getFilePath("test_remote.py"), 2);
setWaitForTermination(false);
}
@Override
public void testing() throws Exception {
waitForPause();
eval("sys.argv[1]").hasValue("'subprocess'");
eval("sys.argv[2]").hasValue("'etc etc'");
resume();
}
@NotNull
@Override
public Set<String> getTags() {
return ImmutableSet.of("-iron", "-jython"); //can't run on iron and jython
}
});
}
@Test
public void testMultiprocessPool() {
runPythonTest(new PyDebuggerTask("/debug", "test_multiprocess_pool.py") {
@Override
protected void init() {
setMultiprocessDebug(true);
}
@Override
public void testing() throws Exception {
waitForOutput("Done");
assertFalse(output().contains("KeyboardInterrupt"));
}
@NotNull
@Override
public Set<String> getTags() {
return ImmutableSet.of("-iron");
}
});
}
@Test
public void testPythonSubprocessWithCParameter() {
runPythonTest(new PyDebuggerTask("/debug", "test_python_subprocess_with_c_parameter.py") {
@Override
protected void init() {
setMultiprocessDebug(true);
}
@Override
public void before() {
toggleBreakpoint(getFilePath("test_python_subprocess_another_helper.py"), 2);
}
@Override
public void testing() throws Exception {
waitForPause();
eval("x").hasValue("42");
resume();
waitForOutput("Hello!");
}
@NotNull
@Override
public Set<String> getTags() {
return ImmutableSet.of("-iron", "-jython");
}
});
}
@Test
public void testMultiprocessProcess() {
runPythonTest(new PyDebuggerTask("/debug", "test_multiprocess_process.py") {
@Override
protected void init() {
setMultiprocessDebug(true);
}
@Override
public void before() {
toggleBreakpoint(getFilePath("test_multiprocess_process.py"), 5);
setWaitForTermination(false);
}
@Override
public void testing() throws Exception {
waitForPause();
eval("name").hasValue("'subprocess'");
resume();
}
@NotNull
@Override
public Set<String> getTags() {
return ImmutableSet.of("-iron", "-jython"); //can't run on iron and jython
}
});
}
@Test
public void testStepOverYieldFrom() {
runPythonTest(new PyDebuggerTask("/debug", "test_step_over_yield.py") {
@@ -1881,24 +1738,6 @@ public class PythonDebuggerTest extends PyEnvTestCase {
});
}
@Test
public void testSubprocess() {
runPythonTest(new PyDebuggerTask("/debug", "test_subprocess.py") {
@Override
public void before() throws Exception {
toggleBreakpoint(getFilePath(getScriptName()), 8);
}
@Override
public void testing() throws Exception {
waitForPause();
resume();
waitForTerminate();
outputContains("The subprocess finished with the return code 0.");
}
});
}
@Test
public void testCodeEvaluationWithGeneratorExpression() {
runPythonTest(new PyDebuggerTaskTagAware("/debug", "test_code_eval_with_generator_expr.py") {