From d36d61805cf0ca701da5b37de9e198ed8d744a35 Mon Sep 17 00:00:00 2001 From: Elizaveta Shashkova Date: Fri, 12 Apr 2019 18:44:08 +0300 Subject: [PATCH] PY-35341 Enable pytest output only when Debug console was enabled GitOrigin-RevId: f30a4921bc9c332860f6ba4daa4a29f5c6433fdd --- .../pydevd_console_integration.py | 6 +++ .../_pydevd_bundle/pydevd_console_pytest.py | 25 +++++++++++++ .../com/jetbrains/python/testing/PyTest.kt | 9 ++++- .../debug/test_debug_console_pytest.py | 10 +++++ .../env/python/PythonDebuggerTest.java | 29 +++++++++++++++ .../env/python/debug/PyDebuggerTask.java | 37 ++++++++++++++++++- 6 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 python/helpers/pydev/_pydevd_bundle/pydevd_console_pytest.py create mode 100644 python/testData/debug/test_debug_console_pytest.py diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_console_integration.py b/python/helpers/pydev/_pydevd_bundle/pydevd_console_integration.py index 5d164ed6237d..69c05a823f53 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_console_integration.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_console_integration.py @@ -13,6 +13,7 @@ from _pydev_bundle.pydev_code_executor import BaseCodeExecutor from _pydev_bundle.pydev_console_types import CodeFragment, Command from _pydev_bundle.pydev_imports import Exec from _pydevd_bundle import pydevd_vars, pydevd_save_locals +from _pydevd_bundle.pydevd_console_pytest import enable_pytest_output try: import __builtin__ @@ -41,6 +42,8 @@ if 'IPYTHONENABLE' in os.environ: else: IPYTHON = True +PYTEST_RUN_CONFIG = 'PYTEST_RUN_CONFIG' in os.environ + try: try: exitfunc = sys.exitfunc @@ -176,6 +179,9 @@ def console_exec(thread_id, frame_id, expression, dbg): updated_globals.update(frame.f_globals) updated_globals.update(frame.f_locals) # locals later because it has precedence over the actual globals + if PYTEST_RUN_CONFIG: + enable_pytest_output() + if IPYTHON: need_more = exec_code(CodeFragment(expression), updated_globals, frame.f_locals, dbg) if not need_more: diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_console_pytest.py b/python/helpers/pydev/_pydevd_bundle/pydevd_console_pytest.py new file mode 100644 index 000000000000..02999f8de93b --- /dev/null +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_console_pytest.py @@ -0,0 +1,25 @@ + +from _pydev_bundle import pydev_log + + +def enable_pytest_output(): + try: + import _pytest + + if hasattr(_pytest, "debugging"): # pytest >= 3 + from _pytest.debugging import pytestPDB as _pytestDebug + else: + raise ValueError("Failed to find debugger in _pytest") + + plugin_manager = _pytestDebug._pluginmanager + if plugin_manager is not None: + capman = plugin_manager.getplugin("capturemanager") + if hasattr(capman, "suspend"): # pytest 4 + capman.suspend(in_=True) + elif hasattr(capman, "suspend_global_capture"): # pytest 3 + capman.suspend_global_capture(in_=True) + else: + raise ValueError("Failed to find suspend method") + + except Exception: + pydev_log.debug("Failed to enable pytest output: %s" % traceback.format_exc()) diff --git a/python/src/com/jetbrains/python/testing/PyTest.kt b/python/src/com/jetbrains/python/testing/PyTest.kt index 83b748288796..2c6ed0be938b 100644 --- a/python/src/com/jetbrains/python/testing/PyTest.kt +++ b/python/src/com/jetbrains/python/testing/PyTest.kt @@ -40,6 +40,11 @@ class PyTestSettingsEditor(configuration: PyAbstractTestConfiguration) : class PyPyTestExecutionEnvironment(configuration: PyTestConfiguration, environment: ExecutionEnvironment) : PyTestExecutionEnvironment(configuration, environment) { override fun getRunner(): PythonHelper = PythonHelper.PYTEST + + override fun customizeEnvironmentVars(envs: MutableMap, passParentEnvs: Boolean) { + super.customizeEnvironmentVars(envs, passParentEnvs) + envs[PYTEST_RUN_CONFIG] = "True" + } } @@ -82,4 +87,6 @@ object PyTestFactory : PyAbstractTestFactory() { override fun getName(): String = PyTestFrameworkService.getSdkReadableNameByFramework(PyNames.PY_TEST) override fun getId() = "py.test" //Do not rename: used as ID for run configurations -} \ No newline at end of file +} + +private const val PYTEST_RUN_CONFIG: String = "PYTEST_RUN_CONFIG" diff --git a/python/testData/debug/test_debug_console_pytest.py b/python/testData/debug/test_debug_console_pytest.py new file mode 100644 index 000000000000..dc89d171c549 --- /dev/null +++ b/python/testData/debug/test_debug_console_pytest.py @@ -0,0 +1,10 @@ + + +def test_func1(): + a = 1 + assert a == 1 # breakpoint + + +def test_func2(): + b = 2 + assert b == 2 diff --git a/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java b/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java index d34276f1a3b9..34641f4eb99f 100644 --- a/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java +++ b/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java @@ -1977,4 +1977,33 @@ public class PythonDebuggerTest extends PyEnvTestCase { } }); } + + @Test + public void testDebugConsolePytest() { + runPythonTest(new PyDebuggerTask("/debug", "test_debug_console_pytest.py") { + @Override + public void before() { + toggleBreakpoint(getFilePath(getScriptName()), 4); + } + + @Override + protected boolean usePytestRunner() { + return true; + } + + @Override + public void testing() throws Exception { + waitForPause(); + eval("a").hasValue("1"); + consoleExec("print('a = %s' % a)"); + waitForOutput("a = 1"); + } + + @NotNull + @Override + public Set getTags() { + return Sets.newHashSet("pytest"); + } + }); + } } diff --git a/python/testSrc/com/jetbrains/env/python/debug/PyDebuggerTask.java b/python/testSrc/com/jetbrains/env/python/debug/PyDebuggerTask.java index b719fc7f0447..a764083f1950 100644 --- a/python/testSrc/com/jetbrains/env/python/debug/PyDebuggerTask.java +++ b/python/testSrc/com/jetbrains/env/python/debug/PyDebuggerTask.java @@ -4,6 +4,8 @@ package com.jetbrains.env.python.debug; import com.google.common.collect.Sets; import com.intellij.execution.*; import com.intellij.execution.configurations.ConfigurationFactory; +import com.intellij.execution.configurations.GeneralCommandLine; +import com.intellij.execution.configurations.ParamsGroup; import com.intellij.execution.configurations.RunProfile; import com.intellij.execution.executors.DefaultDebugExecutor; import com.intellij.execution.process.KillableColoredProcessHandler; @@ -17,9 +19,11 @@ import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.util.Key; import com.intellij.xdebugger.*; import com.jetbrains.env.python.PythonDebuggerTest; +import com.jetbrains.python.PythonHelper; import com.jetbrains.python.debugger.PyDebugProcess; import com.jetbrains.python.debugger.PyDebugRunner; import com.jetbrains.python.debugger.PyDebugValueExecutionService; +import com.jetbrains.python.run.CommandLinePatcher; import com.jetbrains.python.run.PythonCommandLineState; import com.jetbrains.python.run.PythonConfigurationType; import com.jetbrains.python.run.PythonRunConfiguration; @@ -29,6 +33,8 @@ import org.junit.Assert; import java.io.IOException; import java.net.ServerSocket; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Set; import java.util.concurrent.Semaphore; @@ -121,7 +127,7 @@ public class PyDebuggerTask extends PyBaseDebuggerTask { WriteAction.computeAndWait(() -> { myExecutionResult = - pyState.execute(executor, runner.createCommandLinePatchers(myFixture.getProject(), pyState, profile, serverLocalPort)); + pyState.execute(executor, createCommandLinePatchers(runner, pyState, profile, serverLocalPort)); mySession = XDebuggerManager.getInstance(getProject()). startSession(env, new XDebugProcessStarter() { @@ -182,6 +188,35 @@ public class PyDebuggerTask extends PyBaseDebuggerTask { doTest(myOutputPrinter); } + protected boolean usePytestRunner() { + return false; + } + + protected CommandLinePatcher[] createCommandLinePatchers(PyDebugRunner runner, PythonCommandLineState pyState, RunProfile profile, + int serverLocalPort) { + final CommandLinePatcher[] debugPatchers = runner.createCommandLinePatchers(myFixture.getProject(), pyState, profile, serverLocalPort); + if (!usePytestRunner()) { + return debugPatchers; + } + ArrayList result = new ArrayList<>(); + result.add(pytestPatcher()); + result.addAll(Arrays.asList(debugPatchers)); + return result.toArray(new CommandLinePatcher[0]); + } + + private static CommandLinePatcher pytestPatcher() { + return new CommandLinePatcher() { + @Override + public void patchCommandLine(GeneralCommandLine commandLine) { + final ParamsGroup scriptGroup = commandLine.getParametersList().getParamsGroup(PythonCommandLineState.GROUP_SCRIPT); + scriptGroup.addParameterAt(0, "--path"); + scriptGroup.addParameterAt(0, PythonHelper.PYTEST.asParamString()); + + commandLine.getEnvironment().put("PYTEST_RUN_CONFIG", "True"); + } + }; + } + protected String getExecutorId() { return DefaultDebugExecutor.EXECUTOR_ID; }