Continued with the multiprocess debugger. Implemented QProcess.startDetached() wrapper. multi_processing

Wed, 12 Feb 2020 20:04:31 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 12 Feb 2020 20:04:31 +0100
branch
multi_processing
changeset 7410
401791e6f50f
parent 7409
1413bfe73d41
child 7411
6d8dcb3551b3

Continued with the multiprocess debugger. Implemented QProcess.startDetached() wrapper.

eric6/DebugClients/Python/QProcessExtension.py file | annotate | diff | comparison | revisions
eric6/Debugger/DebugUI.py file | annotate | diff | comparison | revisions
eric6/Debugger/DebugViewer.py file | annotate | diff | comparison | revisions
eric6/Debugger/DebuggerInterfacePython.py file | annotate | diff | comparison | revisions
--- a/eric6/DebugClients/Python/QProcessExtension.py	Tue Feb 11 18:58:38 2020 +0100
+++ b/eric6/DebugClients/Python/QProcessExtension.py	Wed Feb 12 20:04:31 2020 +0100
@@ -13,6 +13,25 @@
 _debugClient = None
 
 
+# TODO: extend this with first line logic
+def _isPythonProgram(program, arguments):
+    """
+    Protected function to check, if program is a Python interpreter and
+    arguments don't include '-m'.
+    
+    @param program program to be executed
+    @type str
+    @param arguments list of command line arguments
+    @type list of str
+    @return flag indicating a python program and a tuple containing the
+        interpreter to be used and the arguments
+    @rtype tuple of (bool, tuple of (str, list of str))
+    """
+    prog = program.lower()
+    ok = "python" in prog and arguments[0] != '-m'
+    return ok, (program, arguments[:])
+
+
 def patchQProcess(module, debugClient):
     """
     Function to patch the QtCore module's QProcess.
@@ -28,13 +47,16 @@
         """
         Wrapper class for *.QProcess.
         """
+        _origQProcessStartDetached = module.QProcess.startDetached
+        
         def __init__(self, parent=None):
             """
             Constructor
             """
             super(QProcessWrapper, self).__init__(parent)
         
-        def __modifyArgs(self, arguments, multiprocessSupport):
+        @classmethod
+        def modifyArgs(cls, arguments, multiprocessSupport):
             """
             Private method to modify the arguments given to the start method.
             
@@ -71,6 +93,10 @@
             
             return modifiedArguments
         
+        ###################################################################
+        ## Handling of 'start(...)' below
+        ###################################################################
+        
         def start(self, *args, **kwargs):
             """
             Public method to start the process.
@@ -91,7 +117,6 @@
                  (len(args) == 1 and not isinstance(args[0], str)) or
                  len(args) == 0)
             ):
-                # TODO: implement this
                 if len(args) >= 2:
                     program = args[0]
                     arguments = args[1]
@@ -106,16 +131,115 @@
                         mode = args[0]
                     else:
                         mode = module.QIODevice.ReadWrite
-                if "python" in program.lower() and arguments[0] != "-m":
-                    # assume a Python script is to be started
-                    # '-m' option to python is not (yet) supported
-                    newArgs = self.__modifyArgs(
+                ok, (program, arguments) = _isPythonProgram(program, arguments)
+                if ok:
+                    newArgs = self.modifyArgs(
                         arguments, _debugClient.multiprocessSupport)
                     super(QProcessWrapper, self).start(program, newArgs, mode)
                 else:
                     super(QProcessWrapper, self).start(*args, **kwargs)
             else:
                 super(QProcessWrapper, self).start(*args, **kwargs)
+        
+        ###################################################################
+        ## Handling of 'startDetached(...)' below
+        ###################################################################
+        
+        def startDetached(self, *args, **kwargs):
+            """
+            Public method to start the detached process.
+            
+            This method patches the arguments such, that a debug client is
+            started for the Python script. A Python script is assumed, if the
+            program to be started contains the string 'python'.
+            
+            @param args arguments of the start call
+            @type list
+            @param kwargs keyword arguments of the start call
+            @type dict
+            @return flag indicating a successful start
+            @rtype bool
+            """
+            if isinstance(self, str):
+                return QProcessWrapper.startDetachedStatic(
+                    self, *args)
+            else:
+                return self.__startDetached(*args, **kwargs)
+        
+        def __startDetached(self, *args, **kwargs):
+            """
+            Private method to start the detached process.
+            
+            This method patches the arguments such, that a debug client is
+            started for the Python script. A Python script is assumed, if the
+            program to be started contains the string 'python'.
+            
+            @param args arguments of the start call
+            @type list
+            @param kwargs keyword arguments of the start call
+            @type dict
+            @return flag indicating a successful start
+            @rtype bool
+            """
+            if (
+                _debugClient.debugging and
+                _debugClient.multiprocessSupport and
+                len(args) == 0
+            ):
+                program = self.program()
+                arguments = self.arguments()
+                wd = self.workingDirectory()
+                
+                ok, (program, arguments) = _isPythonProgram(program, arguments)
+                if ok:
+                    return QProcessWrapper.startDetachedStatic(
+                        program, arguments, wd)
+                else:
+                    return super(QProcessWrapper, self).startDetached(
+                        *args, **kwargs)
+            else:
+                return super(QProcessWrapper, self).startDetached(
+                    *args, **kwargs)
+        
+        @staticmethod
+        def startDetachedStatic(*args, **kwargs):
+            """
+            Static method to start the detached process.
+            
+            This method patches the arguments such, that a debug client is
+            started for the Python script. A Python script is assumed, if the
+            program to be started contains the string 'python'.
+            
+            @param args arguments of the start call
+            @type list
+            @param kwargs keyword arguments of the start call
+            @type dict
+            @return flag indicating a successful start
+            @rtype bool
+            """
+            if (
+                _debugClient.debugging and
+                _debugClient.multiprocessSupport and
+                (len(args) >= 2 and isinstance(args[1], list))
+            ):
+                program = args[0]
+                arguments = args[1]
+                if len(args) >= 3:
+                    wd = args[2]
+                else:
+                    wd = ""
+                ok, (program, arguments) = _isPythonProgram(program, arguments)
+                if ok:
+                    newArgs = QProcessWrapper.modifyArgs(
+                        arguments, _debugClient.multiprocessSupport)
+                    return QProcessWrapper._origQProcessStartDetached(
+                        program, newArgs, wd)
+                else:
+                    return QProcessWrapper._origQProcessStartDetached(
+                        *args, **kwargs)
+            else:
+                return QProcessWrapper._origQProcessStartDetached(
+                    *args, **kwargs)
     
     _debugClient = debugClient
     module.QProcess = QProcessWrapper
--- a/eric6/Debugger/DebugUI.py	Tue Feb 11 18:58:38 2020 +0100
+++ b/eric6/Debugger/DebugUI.py	Wed Feb 12 20:04:31 2020 +0100
@@ -2028,7 +2028,6 @@
             cap = self.tr("Debug Project")
         else:
             cap = self.tr("Debug Script")
-        # TODO: add "Multiprocess" checkbox and pass to debugger
         dlg = StartDialog(
             cap, self.lastUsedVenvName, self.argvHistory, self.wdHistory,
             self.envHistory, self.exceptions, self.ui, 0,
--- a/eric6/Debugger/DebugViewer.py	Tue Feb 11 18:58:38 2020 +0100
+++ b/eric6/Debugger/DebugViewer.py	Wed Feb 12 20:04:31 2020 +0100
@@ -23,7 +23,7 @@
 
 import os
 
-from PyQt5.QtCore import pyqtSignal, Qt
+from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt
 from PyQt5.QtWidgets import (
     QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QSizePolicy, QPushButton,
     QComboBox, QLabel, QTreeWidget, QTreeWidgetItem, QHeaderView, QFrame
@@ -282,6 +282,8 @@
             self.__clientSyntaxError)
         self.debugServer.clientException.connect(
             self.__clientException)
+        self.debugServer.clientExit.connect(
+            self.__clientExit)
         
         self.debugServer.clientException.connect(
             self.exceptionLogger.addException)
@@ -432,8 +434,35 @@
         @param debuggerId ID of the debugger backend
         @type str
         """
-        self.__setDebuggerIconAndState(debuggerId, "mediaPlaybackPause",
-                                       "broken")
+        self.__setDebuggerIconAndState(debuggerId, "break", "broken")
+        if debuggerId != self.getSelectedDebuggerId():
+            self.__debuggersCombo.setCurrentText(debuggerId)
+    
+    @pyqtSlot(int, str, bool, str)
+    def __clientExit(self, status, message, quiet, debuggerId):
+        """
+        Private method to handle the debugged program terminating.
+        
+        @param status exit code of the debugged program
+        @type int
+        @param message exit message of the debugged program
+        @type str
+        @param quiet flag indicating to suppress exit info display
+        @type bool
+        @param debuggerId ID of the debugger backend
+        @type str
+        """
+        self.__setDebuggerIconAndState(debuggerId, "exit", "exited")
+        if debuggerId == self.getSelectedDebuggerId():
+            # the current client has exited
+            self.globalsViewer.handleResetUI()
+            self.localsViewer.handleResetUI()
+            self.setGlobalsFilter()
+            self.setLocalsFilter()
+            self.sourceButton.setEnabled(False)
+            self.currentStack = None
+            self.stackComboBox.clear()
+            self.__threadList.clear()
     
     def __clientSyntaxError(self, message, filename, lineNo, characterNo,
                             debuggerId):
@@ -580,7 +609,7 @@
                     debugStatus = 1
                 elif thread['broken']:
                     state = self.tr("waiting at breakpoint")
-                    icon = "mediaPlaybackPause"
+                    icon = "break"
                     if debugStatus < 1:
                         debugStatus = 0
                 else:
@@ -608,16 +637,22 @@
                 elif thread['broken']:
                     if debugStatus < 1:
                         debugStatus = 0
+        if not threadList:
+            # empty threadlist means 'exited'
+            debugStatus = 2
         
         if debugStatus == -1:
             icon = "mediaPlaybackStart"
             state = "running"
         elif debugStatus == 0:
-            icon = "mediaPlaybackPause"
+            icon = "break"
             state = "broken"
-        else:
+        elif debugStatus == 1:
             icon = "exceptions"
             state = "exception"
+        else:
+            icon = "exit"
+            state = "exited"
         self.__setDebuggerIconAndState("", icon, state)
     
     def __threadSelected(self, current, previous):
@@ -694,7 +729,7 @@
         """
         Public method to get the currently selected debugger's state.
         
-        @return selected debugger's state (running, broken, exception)
+        @return selected debugger's state (broken, exception, exited, running)
         @rtype str
         """
         return self.__debuggersCombo.currentData()
@@ -708,7 +743,7 @@
         @type str
         @param iconName name of the icon to be used
         @type str
-        @param state state of the debugger (running, broken, exception)
+        @param state state of the debugger (broken, exception, exited, running)
         @type str
         """
         if debuggerId:
--- a/eric6/Debugger/DebuggerInterfacePython.py	Tue Feb 11 18:58:38 2020 +0100
+++ b/eric6/Debugger/DebuggerInterfacePython.py	Wed Feb 12 20:04:31 2020 +0100
@@ -549,18 +549,9 @@
             self.debugServer.initializeClient(debuggerId)
             
             # perform auto-continue except for master
-            if (
-                self.__autoContinue and
-                debuggerId != self.__master and
-                debuggerId not in self.__autoContinued
-            ):
-                self.__autoContinued.append(debuggerId)
+            if debuggerId != self.__master:
                 QTimer.singleShot(
                     0, lambda: self.remoteContinue(debuggerId))
-                # TODO: maybe the debugger ID should not be registered here but
-                # only in user_line. Seems the debugger stops in a frame
-                # outside of the debugged script and this continue moves it to
-                # the first line of the script.
     
     def __socketDisconnected(self, sock):
         """

eric ide

mercurial