From c0230a7f3b0fdefa5fa03f230c5593c0ec72282e Mon Sep 17 00:00:00 2001 From: Andrey Lisin Date: Wed, 13 Feb 2019 16:59:32 +0300 Subject: [PATCH] PY-23611 Support debugging for PySide2 --- .../pydev/_pydev_bundle/pydev_monkey_qt.py | 30 ++-- .../pydevd_command_line_handling.py | 4 +- python/setup-test-environment/build.gradle | 2 +- .../debugger/PyDebuggerConfigurable.java | 2 +- python/testData/debug/test_pyside2_1.py | 16 ++ python/testData/debug/test_pyside2_2.py | 23 +++ python/testData/debug/test_pyside2_3.py | 19 +++ .../env/python/PythonDebuggerTest.java | 160 +++++++++++++++++- 8 files changed, 239 insertions(+), 17 deletions(-) create mode 100644 python/testData/debug/test_pyside2_1.py create mode 100644 python/testData/debug/test_pyside2_2.py create mode 100644 python/testData/debug/test_pyside2_3.py diff --git a/python/helpers/pydev/_pydev_bundle/pydev_monkey_qt.py b/python/helpers/pydev/_pydev_bundle/pydev_monkey_qt.py index 62172c43d465..0c8b1a270bc6 100644 --- a/python/helpers/pydev/_pydev_bundle/pydev_monkey_qt.py +++ b/python/helpers/pydev/_pydev_bundle/pydev_monkey_qt.py @@ -35,21 +35,31 @@ def patch_qt(qt_support_mode): patch_qt_on_import = None try: - import PySide # @UnresolvedImport @UnusedImport - qt_support_mode = 'pyside' + import PySide2 # @UnresolvedImport @UnusedImport + qt_support_mode = 'pyside2' except: try: - import PyQt5 # @UnresolvedImport @UnusedImport - qt_support_mode = 'pyqt5' + import Pyside # @UnresolvedImport @UnusedImport + qt_support_mode = 'pyside' except: try: - import PyQt4 # @UnresolvedImport @UnusedImport - qt_support_mode = 'pyqt4' + import PyQt5 # @UnresolvedImport @UnusedImport + qt_support_mode = 'pyqt5' except: - return + try: + import PyQt4 # @UnresolvedImport @UnusedImport + qt_support_mode = 'pyqt4' + except: + return + if qt_support_mode == 'pyside2': + try: + import PySide2.QtCore # @UnresolvedImport + _internal_patch_qt(PySide2.QtCore, qt_support_mode) + except: + return - if qt_support_mode == 'pyside': + elif qt_support_mode == 'pyside': try: import PySide.QtCore # @UnresolvedImport _internal_patch_qt(PySide.QtCore, qt_support_mode) @@ -134,14 +144,14 @@ def _internal_patch_qt(QtCore, qt_support_mode='auto'): QtCore.QObject.__init__(self) self.thread = thread self.original_started = original_started - if qt_support_mode == 'pyside': + if qt_support_mode in ('pyside', 'pyside2'): self._signal = original_started else: self._signal.connect(self._on_call) self.original_started.connect(self._signal) def connect(self, func, *args, **kwargs): - if qt_support_mode == 'pyside': + if qt_support_mode in ('pyside', 'pyside2'): return self._signal.connect(FuncWrapper(func), *args, **kwargs) else: return self._signal.connect(func, *args, **kwargs) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_command_line_handling.py b/python/helpers/pydev/_pydevd_bundle/pydevd_command_line_handling.py index f5c09c4e4e54..361a093c315a 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_command_line_handling.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_command_line_handling.py @@ -111,13 +111,13 @@ def process_command_line(argv): # The --qt-support is special because we want to keep backward compatibility: # Previously, just passing '--qt-support' meant that we should use the auto-discovery mode # whereas now, if --qt-support is passed, it should be passed as --qt-support=, where - # mode can be one of 'auto', 'none', 'pyqt5', 'pyqt4', 'pyside'. + # mode can be one of 'auto', 'none', 'pyqt5', 'pyqt4', 'pyside', 'pyside2'. if argv[i] == '--qt-support': setup['qt-support'] = 'auto' elif argv[i].startswith('--qt-support='): qt_support = argv[i][len('--qt-support='):] - valid_modes = ('none', 'auto', 'pyqt5', 'pyqt4', 'pyside') + valid_modes = ('none', 'auto', 'pyqt5', 'pyqt4', 'pyside', 'pyside2') if qt_support not in valid_modes: raise ValueError("qt-support mode invalid: " + qt_support) if qt_support == 'none': diff --git a/python/setup-test-environment/build.gradle b/python/setup-test-environment/build.gradle index 2ddec111c0c4..2bb401f9e37e 100644 --- a/python/setup-test-environment/build.gradle +++ b/python/setup-test-environment/build.gradle @@ -122,7 +122,7 @@ envs { true) if (Os.isFamily(Os.FAMILY_UNIX)) { - createPython("pyqt_env", "3.5.4", ["pyqt5==5.10.1"], "pyqt5", true) + createPython("qt_env", "3.7.2", ["pyqt5==5.12", "PySide2==5.12.1"], "qt", true) } diff --git a/python/src/com/jetbrains/python/debugger/PyDebuggerConfigurable.java b/python/src/com/jetbrains/python/debugger/PyDebuggerConfigurable.java index afbb03d3aaf9..6a370501d0d4 100644 --- a/python/src/com/jetbrains/python/debugger/PyDebuggerConfigurable.java +++ b/python/src/com/jetbrains/python/debugger/PyDebuggerConfigurable.java @@ -40,7 +40,7 @@ public class PyDebuggerConfigurable implements SearchableConfigurable, Configura private ActionLink myActionLink; private JBTextField myAttachProcessFilter; private JBLabel myAttachFilterLabel; - private final List myPyQtBackendsList = Lists.newArrayList("Auto", "PyQt4", "PyQt5", "PySide"); + private final List myPyQtBackendsList = Lists.newArrayList("Auto", "PyQt4", "PyQt5", "PySide", "PySide2"); private final Project myProject; diff --git a/python/testData/debug/test_pyside2_1.py b/python/testData/debug/test_pyside2_1.py new file mode 100644 index 000000000000..8366109ba149 --- /dev/null +++ b/python/testData/debug/test_pyside2_1.py @@ -0,0 +1,16 @@ +from PySide2 import QtCore +import sys + + +class AThread(QtCore.QThread): + + def run(self): + for i in range(3): + print("ping %d" % i) + + +app = QtCore.QCoreApplication([]) +thread = AThread() +thread.finished.connect(app.exit) +thread.start() +sys.exit(app.exec_()) diff --git a/python/testData/debug/test_pyside2_2.py b/python/testData/debug/test_pyside2_2.py new file mode 100644 index 000000000000..35475212c58b --- /dev/null +++ b/python/testData/debug/test_pyside2_2.py @@ -0,0 +1,23 @@ +from PySide2 import QtCore +import sys + + +class SomeObject(QtCore.QObject): + + finished = QtCore.Signal() + + def longRunning(self): + for i in range(3): + print("ping %d" % i) + self.finished.emit() + + +app = QtCore.QCoreApplication([]) +objThread = QtCore.QThread() +obj = SomeObject() +obj.moveToThread(objThread) +obj.finished.connect(objThread.quit) +objThread.started.connect(obj.longRunning) +objThread.finished.connect(app.exit) +objThread.start() +sys.exit(app.exec_()) \ No newline at end of file diff --git a/python/testData/debug/test_pyside2_3.py b/python/testData/debug/test_pyside2_3.py new file mode 100644 index 000000000000..fcba42cece72 --- /dev/null +++ b/python/testData/debug/test_pyside2_3.py @@ -0,0 +1,19 @@ +from PySide2 import QtCore +import sys + + +class Runnable(QtCore.QRunnable): + + def run(self): + app = QtCore.QCoreApplication.instance() + for i in range(3): + print("ping %d" % i) + app.quit() + + +app = QtCore.QCoreApplication([]) +runnable = Runnable() +QtCore.QThreadPool.globalInstance().start(runnable) +sys.exit(app.exec_()) + + diff --git a/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java b/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java index 8e29cdcf71c6..9fbaf748412d 100644 --- a/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java +++ b/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java @@ -21,6 +21,7 @@ import com.jetbrains.env.ut.PyUnitTestProcessRunner; import com.jetbrains.python.PythonHelpersLocator; import com.jetbrains.python.console.pydev.PydevCompletionVariant; import com.jetbrains.python.debugger.PyDebugValue; +import com.jetbrains.python.debugger.PyDebuggerOptionsProvider; import com.jetbrains.python.debugger.PyExceptionBreakpointProperties; import com.jetbrains.python.debugger.PyExceptionBreakpointType; import com.jetbrains.python.debugger.settings.PyDebuggerSettings; @@ -833,6 +834,12 @@ public class PythonDebuggerTest extends PyEnvTestCase { @Override public void before() { toggleBreakpoint(getScriptName(), 8); + PyDebuggerOptionsProvider.getInstance(myFixture.getProject()).setPyQtBackend("pyqt5"); + } + + @Override + public void doFinally() { + PyDebuggerOptionsProvider.getInstance(myFixture.getProject()).setPyQtBackend("auto"); } @Override @@ -854,7 +861,7 @@ public class PythonDebuggerTest extends PyEnvTestCase { @NotNull @Override public Set getTags() { - return Sets.newHashSet("pyqt5"); + return Sets.newHashSet("qt"); } }); } @@ -872,6 +879,12 @@ public class PythonDebuggerTest extends PyEnvTestCase { @Override public void before() { toggleBreakpoint(getScriptName(), 10); + PyDebuggerOptionsProvider.getInstance(myFixture.getProject()).setPyQtBackend("pyqt5"); + } + + @Override + public void doFinally() { + PyDebuggerOptionsProvider.getInstance(myFixture.getProject()).setPyQtBackend("auto"); } @Override @@ -893,7 +906,7 @@ public class PythonDebuggerTest extends PyEnvTestCase { @NotNull @Override public Set getTags() { - return Sets.newHashSet("pyqt5"); + return Sets.newHashSet("qt"); } }); } @@ -912,6 +925,12 @@ public class PythonDebuggerTest extends PyEnvTestCase { @Override public void before() { toggleBreakpoint(getScriptName(), 9); + PyDebuggerOptionsProvider.getInstance(myFixture.getProject()).setPyQtBackend("pyqt5"); + } + + @Override + public void doFinally() { + PyDebuggerOptionsProvider.getInstance(myFixture.getProject()).setPyQtBackend("auto"); } @Override @@ -933,11 +952,146 @@ public class PythonDebuggerTest extends PyEnvTestCase { @NotNull @Override public Set getTags() { - return Sets.newHashSet("pyqt5"); + return Sets.newHashSet("qt"); } }); } + @Test + public void testPySide2QThreadInheritor() { + Assume.assumeFalse("Don't run under Windows", UsefulTestCase.IS_UNDER_TEAMCITY && SystemInfo.isWindows); + + runPythonTest(new PyDebuggerTask("/debug", "test_pyside2_1.py") { + @Override + protected void init() { + setMultiprocessDebug(true); + } + + @Override + public void before() { + toggleBreakpoint(getScriptName(), 8); + PyDebuggerOptionsProvider.getInstance(myFixture.getProject()).setPyQtBackend("pyside2"); + } + + @Override + public void doFinally() { + PyDebuggerOptionsProvider.getInstance(myFixture.getProject()).setPyQtBackend("auto"); + } + + @Override + public void testing() throws Exception { + + waitForPause(); + + eval("i").hasValue("0"); + + resume(); + + waitForPause(); + + eval("i").hasValue("1"); + + resume(); + } + + @NotNull + @Override + public Set getTags() { + return Sets.newHashSet("qt"); + } + }); + + } + + @Test + public void testPySide2MoveToThread() { + Assume.assumeFalse("Don't run under Windows", UsefulTestCase.IS_UNDER_TEAMCITY && SystemInfo.isWindows); + + runPythonTest(new PyDebuggerTask("/debug", "test_pyside2_2.py") { + @Override + protected void init() { + setMultiprocessDebug(true); + } + + @Override + public void before() { + toggleBreakpoint(getScriptName(), 10); + PyDebuggerOptionsProvider.getInstance(myFixture.getProject()).setPyQtBackend("pyside2"); + } + + @Override + public void doFinally() { + PyDebuggerOptionsProvider.getInstance(myFixture.getProject()).setPyQtBackend("auto"); + } + + @Override + public void testing() throws Exception { + + waitForPause(); + + eval("i").hasValue("0"); + + resume(); + + waitForPause(); + + eval("i").hasValue("1"); + + resume(); + } + + @NotNull + @Override + public Set getTags() { + return Sets.newHashSet("qt"); + } + }); + } + + @Test + public void testPySide2QRunnableInheritor() { + Assume.assumeFalse("Don't run under Windows", UsefulTestCase.IS_UNDER_TEAMCITY && SystemInfo.isWindows); + + runPythonTest(new PyDebuggerTask("/debug", "test_pyside2_3.py") { + @Override + protected void init() { + setMultiprocessDebug(true); + } + + @Override + public void before() { + toggleBreakpoint(getScriptName(), 9); + PyDebuggerOptionsProvider.getInstance(myFixture.getProject()).setPyQtBackend("pyside2"); + } + + @Override + public void doFinally() { + PyDebuggerOptionsProvider.getInstance(myFixture.getProject()).setPyQtBackend("auto"); + } + + @Override + public void testing() throws Exception { + + waitForPause(); + + eval("i").hasValue("0"); + + resume(); + + waitForPause(); + + eval("i").hasValue("1"); + + resume(); + } + + @NotNull + @Override + public Set getTags() { + return Sets.newHashSet("qt"); + } + }); + } @Test public void testStepOverYieldFrom() {