--- a/eric6/Debugger/DebugServer.py Sun Jan 17 13:53:08 2021 +0100 +++ b/eric6/Debugger/DebugServer.py Mon Feb 01 10:38:16 2021 +0100 @@ -9,7 +9,7 @@ import os -from PyQt5.QtCore import pyqtSignal, QModelIndex +from PyQt5.QtCore import pyqtSignal, pyqtSlot, QModelIndex from PyQt5.QtNetwork import ( QTcpServer, QHostAddress, QHostInfo, QNetworkInterface ) @@ -40,49 +40,57 @@ @signal clientProcessStderr(str) emitted after the client has sent some output via stderr @signal clientOutput(str) emitted after the client has sent some output - @signal clientRawInputSent() emitted after the data was sent to the - debug client - @signal clientLine(filename, lineno, forStack) emitted after the + @signal clientRawInputSent(debuggerId) emitted after the data was sent + to the indicated debug client + @signal clientLine(filename, lineno, debuggerId, threadName, forStack) + emitted after the debug client has executed a line of code + @signal clientStack(stack, debuggerId, threadName) emitted after the debug client has executed a line of code - @signal clientStack(stack) emitted after the debug client has executed a - line of code - @signal clientThreadList(currentId, threadList) emitted after a thread list - has been received - @signal clientThreadSet() emitted after the client has acknowledged the - change of the current thread - @signal clientVariables(scope, variables) emitted after a variables dump - has been received - @signal clientVariable(scope, variables) emitted after a dump for one class - variable has been received - @signal clientStatement(bool) emitted after an interactive command has - been executed. The parameter is 0 to indicate that the command is - complete and 1 if it needs more input. - @signal clientDisassembly(disassembly) emitted after the client has sent - a disassembly of the code raising an exception - @signal clientException(exception) emitted after an exception occured on - the client side - @signal clientSyntaxError(exception) emitted after a syntax error has been - detected on the client side - @signal clientSignal(signal) emitted after a signal has been generated on - the client side - @signal clientExit(int, str, bool) emitted after the client has exited - giving the exit status, an exit message and an indication to be quiet - @signal clientClearBreak(filename, lineno) emitted after the debug client - has decided to clear a temporary breakpoint - @signal clientBreakConditionError(fn, lineno) emitted after the client has - signaled a syntax error in a breakpoint condition - @signal clientClearWatch(condition) emitted after the debug client - has decided to clear a temporary watch expression - @signal clientWatchConditionError(condition) emitted after the client has - signaled a syntax error in a watch expression - @signal clientRawInput(prompt, echo) emitted after a raw input request was - received - @signal clientBanner(version, platform, dbgclient, venvname) emitted after + @signal clientThreadList(currentId, threadList, debuggerId) emitted after + a thread list has been received + @signal clientThreadSet(debuggerId) emitted after the client has + acknowledged the change of the current thread + @signal clientVariables(scope, variables, debuggerId) emitted after a + variables dump has been received + @signal clientVariable(scope, variables, debuggerId) emitted after a dump + for one class variable has been received + @signal clientStatement(continue, debuggerId) emitted after an interactive + command has been executed. The parameter is False to indicate that the + command is complete and True if it needs more input. + @signal clientDisassembly(disassembly, debuggerId) emitted after the client + has sent a disassembly of the code raising an exception + @signal clientException(exceptionType, exceptionMessage, stackTrace, + debuggerId, threadName) emitted after an exception occured on the + client side + @signal clientSyntaxError(message, filename, linenumber, characternumber, + debuggerId, threadName) emitted after a syntax error has been detected + on the client side + @signal clientSignal(message, filename, linenumber, function name, + function arguments, debuggerId) emitted after a signal has been + generated on the client side + @signal clientDisconnected(str) emitted after a debug client has + disconnected (i.e. closed the network socket) + @signal clientExit(str, int, str, bool, str) emitted after the client has + exited giving the program name, the exit status, an exit message, an + indication to be quiet and the ID of the exited client + @signal lastClientExited() emitted to indicate that the last connected + debug client has terminated + @signal clientClearBreak(filename, lineno, debuggerId) emitted after the + debug client has decided to clear a temporary breakpoint + @signal clientBreakConditionError(fn, lineno, debuggerId) emitted after the + client has signaled a syntax error in a breakpoint condition + @signal clientClearWatch(condition, debuggerId) emitted after the debug + client has decided to clear a temporary watch expression + @signal clientWatchConditionError(condition, debuggerId) emitted after the + client has signaled a syntax error in a watch expression + @signal clientRawInput(prompt, echo, debuggerId) emitted after a raw input + request was received + @signal clientBanner(version, platform, venvname) emitted after the client banner data was received @signal clientCapabilities(capabilities, cltype, venvname) emitted after the clients capabilities were received - @signal clientCompletionList(completionList, text) emitted after the client - the commandline completion list and the reworked searchstring was + @signal clientCompletionList(completionList, text) emitted after the + commandline completion list and the reworked search string was received from the client @signal passiveDebugStarted(str, bool) emitted after the debug client has connected in passive debug mode @@ -111,36 +119,41 @@ reported an unexpected test success @signal callTraceInfo emitted after the client reported the call trace data (isCall, fromFile, fromLine, fromFunction, toFile, toLine, - toFunction) + toFunction, debuggerId) @signal appendStdout(msg) emitted when a passive debug connection is established or lost + @signal clientDebuggerId(debuggerId) emitted to indicate a newly connected + debugger backend """ - clientClearBreak = pyqtSignal(str, int) - clientClearWatch = pyqtSignal(str) + clientClearBreak = pyqtSignal(str, int, str) + clientClearWatch = pyqtSignal(str, str) clientGone = pyqtSignal(bool) clientProcessStdout = pyqtSignal(str) clientProcessStderr = pyqtSignal(str) - clientRawInputSent = pyqtSignal() + clientRawInputSent = pyqtSignal(str) clientOutput = pyqtSignal(str) - clientLine = pyqtSignal(str, int, bool) - clientStack = pyqtSignal(list) - clientThreadList = pyqtSignal('PyQt_PyObject', list) - clientThreadSet = pyqtSignal() - clientVariables = pyqtSignal(int, list) - clientVariable = pyqtSignal(int, list) - clientStatement = pyqtSignal(bool) - clientDisassembly = pyqtSignal(dict) - clientException = pyqtSignal(str, str, list) - clientSyntaxError = pyqtSignal(str, str, int, int) - clientSignal = pyqtSignal(str, str, int, str, str) - clientExit = pyqtSignal(int, str, bool) - clientBreakConditionError = pyqtSignal(str, int) - clientWatchConditionError = pyqtSignal(str) - clientRawInput = pyqtSignal(str, bool) - clientBanner = pyqtSignal(str, str, str, str) + clientLine = pyqtSignal(str, int, str, str, bool) + clientStack = pyqtSignal(list, str, str) + clientThreadList = pyqtSignal('PyQt_PyObject', list, str) + clientThreadSet = pyqtSignal(str) + clientVariables = pyqtSignal(int, list, str) + clientVariable = pyqtSignal(int, list, str) + clientStatement = pyqtSignal(bool, str) + clientDisassembly = pyqtSignal(dict, str) + clientException = pyqtSignal(str, str, list, str, str) + clientSyntaxError = pyqtSignal(str, str, int, int, str, str) + clientSignal = pyqtSignal(str, str, int, str, str, str) + clientDisconnected = pyqtSignal(str) + clientExit = pyqtSignal(str, int, str, bool, str) + lastClientExited = pyqtSignal() + clientBreakConditionError = pyqtSignal(str, int, str) + clientWatchConditionError = pyqtSignal(str, str) + clientRawInput = pyqtSignal(str, bool, str) + clientBanner = pyqtSignal(str, str, str) clientCapabilities = pyqtSignal(int, str, str) clientCompletionList = pyqtSignal(list, str) clientInterpreterChanged = pyqtSignal(str) + clientDebuggerId = pyqtSignal(str) utDiscovered = pyqtSignal(list, str, str) utPrepared = pyqtSignal(int, str, str) utStartTest = pyqtSignal(str, str) @@ -152,7 +165,7 @@ utTestSucceededUnexpected = pyqtSignal(str, str) utFinished = pyqtSignal() passiveDebugStarted = pyqtSignal(str, bool) - callTraceInfo = pyqtSignal(bool, str, str, str, str, str, str) + callTraceInfo = pyqtSignal(bool, str, str, str, str, str, str, str) appendStdout = pyqtSignal(str) def __init__(self, originalPathString, preventPassiveDebugging=False): @@ -187,6 +200,10 @@ self.watchSpecialChanged = self.tr( "changed", "must be same as in EditWatchpointDialog") + # arrays to track already reported issues + self.__reportedBreakpointIssues = [] + self.__reportedWatchpointIssues = [] + self.networkInterface = Preferences.getDebugger("NetworkInterface") if self.networkInterface == "all": hostAddress = QHostAddress("0.0.0.0") # QHostAddress.Any) # secok @@ -242,16 +259,19 @@ self.__maxVariableSize = Preferences.getDebugger("MaxVariableSize") + self.__multiprocessNoDebugList = [] + self.__registerDebuggerInterfaces() - + def getHostAddress(self, localhost): """ Public method to get the IP address or hostname the debug server is listening. @param localhost flag indicating to return the address for localhost - (boolean) - @return IP address or hostname (string) + @type bool + @return IP address or hostname + @rtype str """ if self.networkInterface == "all": if localhost: @@ -266,14 +286,16 @@ else: return "{0}@@i{1}".format(self.networkInterface, self.networkInterfaceIndex) - + def __getNetworkInterfaceAndIndex(self, address): """ Private method to determine the network interface and the interface index. - @param address address to determine the info for (string) - @return tuple of network interface name (string) and index (integer) + @param address address to determine the info for + @type str + @return tuple of network interface name and index + @rtype tuple of (str, int) """ if address not in ["all", "allv6"]: for networkInterface in QNetworkInterface.allInterfaces(): @@ -286,7 +308,7 @@ networkInterface.index()) return "", 0 - + def preferencesChanged(self): """ Public slot to handle the preferencesChanged signal. @@ -302,7 +324,7 @@ reregister=True) self.__maxVariableSize = Preferences.getDebugger("MaxVariableSize") - + def registerDebuggerInterface(self, interfaceName, getRegistryData, reregister=False): """ @@ -336,7 +358,7 @@ self.__debuggerInterfaceRegistry[clientLanguage] = [ clientCapabilities, clientExtensions, interfaceCreator, interfaceName] - + def unregisterDebuggerInterface(self, interfaceName): """ Public method to unregister a debugger interface. @@ -353,7 +375,7 @@ for clientLanguage in clientLanguages: del self.__debuggerInterfaceRegistry[clientLanguage] del self.__debuggerInterfaces[interfaceName] - + def __findLanguageForExtension(self, ext): """ Private method to get the language associated with a file extension. @@ -368,7 +390,7 @@ return language return "" - + def __registerDebuggerInterfaces(self): """ Private method to register the available internal debugger interfaces. @@ -381,14 +403,16 @@ mod = getattr(mod, comp) self.registerDebuggerInterface(name, mod.getRegistryData) - + def getSupportedLanguages(self, shellOnly=False): """ Public slot to return the supported programming languages. @param shellOnly flag indicating only languages supporting an interactive shell should be returned - @return list of supported languages (list of strings) + @type bool + @return list of supported languages + @rtype list of str """ languages = list(self.__debuggerInterfaceRegistry.keys()) try: @@ -402,25 +426,27 @@ DebugClientCapabilities.HasShell] return languages[:] - + def getExtensions(self, language): """ Public slot to get the extensions associated with the given language. - @param language language to get extensions for (string) + @param language language to get extensions for + @type str @return tuple of extensions associated with the language - (tuple of strings) + @rtype tuple of str """ if language in self.__debuggerInterfaceRegistry: return tuple(self.__debuggerInterfaceRegistry[language][1]) else: return () - + def __createDebuggerInterface(self, clientType=None): """ Private slot to create the debugger interface object. - @param clientType type of the client interface to be created (string) + @param clientType type of the client interface to be created + @type str """ if self.lastClientType != self.clientType or clientType is not None: if clientType is None: @@ -434,35 +460,36 @@ self.__debuggerInterfaceRegistry["None"][2]( self, self.passive)) self.clientType = "None" - + def __setClientType(self, clType): """ Private method to set the client type. - @param clType type of client to be started (string) + @param clType type of client to be started + @type str """ if clType is not None and clType in self.getSupportedLanguages(): self.clientType = clType Preferences.Prefs.settings.setValue( 'DebugClient/Type', self.clientType) - + def startClient(self, unplanned=True, clType=None, forProject=False, runInConsole=False, venvName="", workingDir=None): """ Public method to start a debug client. - @keyparam unplanned flag indicating that the client has died + @param unplanned flag indicating that the client has died @type bool - @keyparam clType type of client to be started + @param clType type of client to be started @type str - @keyparam forProject flag indicating a project related action + @param forProject flag indicating a project related action @type bool - @keyparam runInConsole flag indicating to start the debugger in a + @param runInConsole flag indicating to start the debugger in a console window @type bool - @keyparam venvName name of the virtual environment to be used + @param venvName name of the virtual environment to be used @type str - @keyparam workingDir directory to start the debugger client in + @param workingDir directory to start the debugger client in @type str """ self.running = False @@ -521,8 +548,6 @@ elif self.__autoClearShell: self.__autoClearShell = False self.remoteBanner() -## self.remoteClientVariables(0, [], 0) -## self.remoteClientVariables(1, [], 0) else: if clType and self.lastClientType: self.__setClientType(self.lastClientType) @@ -533,7 +558,7 @@ if clientInterpreter != self.clientInterpreter: self.clientInterpreter = clientInterpreter self.clientInterpreterChanged.emit(clientInterpreter) - + def __clientProcessOutput(self): """ Private slot to process client output received via stdout. @@ -542,7 +567,7 @@ Preferences.getSystem("IOEncoding"), 'replace') self.clientProcessStdout.emit(output) - + def __clientProcessError(self): """ Private slot to process client output received via stderr. @@ -551,82 +576,112 @@ Preferences.getSystem("IOEncoding"), 'replace') self.clientProcessStderr.emit(error) - + + @pyqtSlot(str, int) def __clientClearBreakPoint(self, fn, lineno): """ Private slot to handle the clientClearBreak signal. - @param fn filename of breakpoint to clear (string) - @param lineno line number of breakpoint to clear (integer) + @param fn filename of breakpoint to clear + @type str + @param lineno line number of breakpoint to clear + @type int """ if self.debugging: index = self.breakpointModel.getBreakPointIndex(fn, lineno) self.breakpointModel.deleteBreakPointByIndex(index) - + if (fn, lineno) in self.__reportedBreakpointIssues: + self.__reportedBreakpointIssues.remove((fn, lineno)) + def __deleteBreakPoints(self, parentIndex, start, end): """ Private slot to delete breakpoints. - @param parentIndex index of parent item (QModelIndex) - @param start start row (integer) - @param end end row (integer) + @param parentIndex index of parent item + @type QModelIndex + @param start start row + @type int + @param end end row + @type int """ if self.debugging: for row in range(start, end + 1): index = self.breakpointModel.index(row, 0, parentIndex) fn, lineno = ( self.breakpointModel.getBreakPointByIndex(index)[0:2]) - self.remoteBreakpoint(fn, lineno, False) - + # delete the breakpoints of all connected backends + self.remoteBreakpoint("", fn, lineno, False) + if (fn, lineno) in self.__reportedBreakpointIssues: + self.__reportedBreakpointIssues.remove((fn, lineno)) + def __changeBreakPoints(self, startIndex, endIndex): """ Private slot to set changed breakpoints. - @param startIndex starting index of the change breakpoins (QModelIndex) - @param endIndex ending index of the change breakpoins (QModelIndex) + @param startIndex starting index of the change breakpoins + @type QModelIndex + @param endIndex ending index of the change breakpoins + @type QModelIndex """ if self.debugging: self.__addBreakPoints( QModelIndex(), startIndex.row(), endIndex.row()) - + def __breakPointDataAboutToBeChanged(self, startIndex, endIndex): """ Private slot to handle the dataAboutToBeChanged signal of the breakpoint model. - @param startIndex start index of the rows to be changed (QModelIndex) - @param endIndex end index of the rows to be changed (QModelIndex) + @param startIndex start index of the rows to be changed + @type QModelIndex + @param endIndex end index of the rows to be changed + @type QModelIndex """ if self.debugging: self.__deleteBreakPoints( QModelIndex(), startIndex.row(), endIndex.row()) - - def __addBreakPoints(self, parentIndex, start, end): + + def __addBreakPoints(self, parentIndex, start, end, debuggerId=""): """ Private slot to add breakpoints. - @param parentIndex index of parent item (QModelIndex) - @param start start row (integer) - @param end end row (integer) + @param parentIndex index of parent item + @type QModelIndex + @param start start row + @type int + @param end end row + @type int + @param debuggerId ID of the debugger backend to send to. If this is + empty, they will be broadcast to all connected backends. + @type str """ if self.debugging: for row in range(start, end + 1): index = self.breakpointModel.index(row, 0, parentIndex) - fn, line, cond, temp, enabled, ignorecount = ( + fn, lineno, cond, temp, enabled, ignorecount = ( self.breakpointModel.getBreakPointByIndex(index)[:6]) - self.remoteBreakpoint(fn, line, True, cond, temp) + + if (fn, lineno) in self.__reportedBreakpointIssues: + self.__reportedBreakpointIssues.remove((fn, lineno)) + + self.remoteBreakpoint(debuggerId, fn, lineno, True, cond, temp) if not enabled: - self.__remoteBreakpointEnable(fn, line, False) + self.__remoteBreakpointEnable( + debuggerId, fn, lineno, False) if ignorecount: - self.__remoteBreakpointIgnore(fn, line, ignorecount) - + self.__remoteBreakpointIgnore( + debuggerId, fn, lineno, ignorecount) + def __makeWatchCondition(self, cond, special): """ Private method to construct the condition string. - @param cond condition (string) - @param special special condition (string) - @return condition string (string) + @param cond condition + @type str + @param special special condition + @type str + @return condition string + @rtype str """ if special == "": _cond = cond @@ -636,14 +691,15 @@ elif special == self.watchSpecialChanged: _cond = "{0} ??changed??".format(cond) return _cond - + def __splitWatchCondition(self, cond): """ Private method to split a remote watch expression. - @param cond remote expression (string) + @param cond remote expression + @type str @return tuple of local expression (string) and special condition - (string) + @rtype str """ if cond.endswith(" ??created??"): cond, special = cond.split() @@ -656,25 +712,32 @@ special = "" return cond, special - + + @pyqtSlot(str) def __clientClearWatchPoint(self, condition): """ Private slot to handle the clientClearWatch signal. - @param condition expression of watch expression to clear (string) + @param condition expression of watch expression to clear + @type str """ if self.debugging: cond, special = self.__splitWatchCondition(condition) index = self.watchpointModel.getWatchPointIndex(cond, special) self.watchpointModel.deleteWatchPointByIndex(index) - + if condition in self.__reportedWatchpointIssues: + self.__reportedWatchpointIssues.remove(condition) + def __deleteWatchPoints(self, parentIndex, start, end): """ Private slot to delete watch expressions. - @param parentIndex index of parent item (QModelIndex) - @param start start row (integer) - @param end end row (integer) + @param parentIndex index of parent item + @type QModelIndex + @param start start row + @type int + @param end end row + @type int """ if self.debugging: for row in range(start, end + 1): @@ -682,27 +745,37 @@ cond, special = ( self.watchpointModel.getWatchPointByIndex(index)[0:2]) cond = self.__makeWatchCondition(cond, special) - self.__remoteWatchpoint(cond, False) - + self.__remoteWatchpoint("", cond, False) + if cond in self.__reportedWatchpointIssues: + self.__reportedWatchpointIssues.remove(cond) + def __watchPointDataAboutToBeChanged(self, startIndex, endIndex): """ Private slot to handle the dataAboutToBeChanged signal of the watch expression model. - @param startIndex start index of the rows to be changed (QModelIndex) - @param endIndex end index of the rows to be changed (QModelIndex) + @param startIndex start index of the rows to be changed + @type QModelIndex + @param endIndex end index of the rows to be changed + @type QModelIndex """ if self.debugging: self.__deleteWatchPoints( QModelIndex(), startIndex.row(), endIndex.row()) - - def __addWatchPoints(self, parentIndex, start, end): + + def __addWatchPoints(self, parentIndex, start, end, debuggerId=""): """ Private slot to set a watch expression. - @param parentIndex index of parent item (QModelIndex) - @param start start row (integer) - @param end end row (integer) + @param parentIndex index of parent item + @type QModelIndex + @param start start row + @type int + @param end end row + @type int + @param debuggerId ID of the debugger backend to send to. If this is + empty, they will be broadcast to all connected backends. + @type str """ if self.debugging: for row in range(start, end + 1): @@ -710,40 +783,50 @@ cond, special, temp, enabled, ignorecount = ( self.watchpointModel.getWatchPointByIndex(index)[:5]) cond = self.__makeWatchCondition(cond, special) - self.__remoteWatchpoint(cond, True, temp) + + if cond in self.__reportedWatchpointIssues: + self.__reportedWatchpointIssues.remove(cond) + + self.__remoteWatchpoint(debuggerId, cond, True, temp) if not enabled: - self.__remoteWatchpointEnable(cond, False) + self.__remoteWatchpointEnable(debuggerId, cond, False) if ignorecount: - self.__remoteWatchpointIgnore(cond, ignorecount) - + self.__remoteWatchpointIgnore(debuggerId, cond, + ignorecount) + def __changeWatchPoints(self, startIndex, endIndex): """ Private slot to set changed watch expressions. - @param startIndex start index of the rows to be changed (QModelIndex) - @param endIndex end index of the rows to be changed (QModelIndex) + @param startIndex start index of the rows to be changed + @type QModelIndex + @param endIndex end index of the rows to be changed + @type QModelIndex """ if self.debugging: self.__addWatchPoints( QModelIndex(), startIndex.row(), endIndex.row()) - + def getClientCapabilities(self, clientType): """ Public method to retrieve the debug clients capabilities. - @param clientType debug client type (string) - @return debug client capabilities (integer) + @param clientType debug client type + @type str + @return debug client capabilities + @rtype int """ try: return self.__debuggerInterfaceRegistry[clientType][0] except KeyError: return 0 # no capabilities - + def getClientInterpreter(self): """ Public method to get the interpreter of the debug client. - @return interpreter of the debug client (string) + @return interpreter of the debug client + @rtype str """ return self.clientInterpreter @@ -793,20 +876,23 @@ self.__createDebuggerInterface( Preferences.getDebugger("PassiveDbgType")) - accepted = self.debuggerInterface.newConnection(sock) - if accepted: - # Perform actions necessary, if client type has changed - if self.lastClientType != self.clientType: - self.lastClientType = self.clientType - self.remoteBanner() - elif self.__autoClearShell: - self.__autoClearShell = False - self.remoteBanner() - elif self.passive: - self.remoteBanner() - - self.debuggerInterface.flush() - + self.debuggerInterface.newConnection(sock) + + def masterClientConnected(self): + """ + Public method to perform actions after the master client has finally + established the connection. + """ + # Perform actions necessary, if client type has changed + if self.lastClientType != self.clientType: + self.lastClientType = self.clientType + self.remoteBanner() + elif self.__autoClearShell: + self.__autoClearShell = False + self.remoteBanner() + elif self.passive: + self.remoteBanner() + def shutdownServer(self): """ Public method to cleanly shut down. @@ -816,12 +902,13 @@ """ if self.debuggerInterface is not None: self.debuggerInterface.shutdown() - + def remoteEnvironment(self, env): """ Public method to set the environment for a program to debug, run, ... - @param env environment settings (string) + @param env environment settings + @type str """ envlist = Utilities.parseEnvironmentString(env) envdict = {} @@ -834,11 +921,11 @@ except ValueError: pass self.debuggerInterface.remoteEnvironment(envdict) - + def remoteLoad(self, venvName, fn, argv, wd, env, autoClearShell=True, tracePython=False, autoContinue=True, forProject=False, - runInConsole=False, autoFork=False, forkChild=False, - clientType="", enableCallTrace=False): + runInConsole=False, clientType="", enableCallTrace=False, + enableMultiprocess=False, multiprocessNoDebug=""): """ Public method to load a new program to debug. @@ -852,32 +939,36 @@ @type str @param env environment parameter settings @type str - @keyparam autoClearShell flag indicating, that the interpreter window + @param autoClearShell flag indicating, that the interpreter window should be cleared @type bool - @keyparam tracePython flag indicating if the Python library should be + @param tracePython flag indicating if the Python library should be traced as well @type bool - @keyparam autoContinue flag indicating, that the debugger should not + @param autoContinue flag indicating, that the debugger should not stop at the first executable line @type bool - @keyparam forProject flag indicating a project related action + @param forProject flag indicating a project related action @type bool - @keyparam runInConsole flag indicating to start the debugger in a + @param runInConsole flag indicating to start the debugger in a console window @type bool - @keyparam autoFork flag indicating the automatic fork mode - @type bool - @keyparam forkChild flag indicating to debug the child after forking - @type bool - @keyparam clientType client type to be used + @param clientType client type to be used @type str - @keyparam enableCallTrace flag indicating to enable the call trace + @param enableCallTrace flag indicating to enable the call trace function @type bool + @param enableMultiprocess flag indicating to perform multiprocess + debugging + @type bool + @param multiprocessNoDebug space separated list of programs not to be + debugged + @type str """ self.__autoClearShell = autoClearShell - self.__autoContinue = autoContinue + self.__multiprocessNoDebugList = [ + s.strip() for s in multiprocessNoDebug.split(os.pathsep) + ] if clientType not in self.getSupportedLanguages(): # a not supported client language was requested @@ -902,19 +993,21 @@ self.startClient(False, forProject=forProject, runInConsole=runInConsole, venvName=venvName) - self.setCallTraceEnabled(enableCallTrace) + self.setCallTraceEnabled("", enableCallTrace) self.remoteEnvironment(env) - self.debuggerInterface.remoteLoad(fn, argv, wd, tracePython, - autoContinue, autoFork, forkChild) + self.debuggerInterface.remoteLoad( + fn, argv, wd, tracePython, autoContinue, + enableMultiprocess=enableMultiprocess + ) self.debugging = True self.running = True self.__restoreBreakpoints() self.__restoreWatchpoints() - + self.__restoreNoDebugList() + def remoteRun(self, venvName, fn, argv, wd, env, autoClearShell=True, - forProject=False, runInConsole=False, autoFork=False, - forkChild=False, clientType=""): + forProject=False, runInConsole=False, clientType=""): """ Public method to load a new program to run. @@ -928,19 +1021,15 @@ @type str @param env environment parameter settings @type str - @keyparam autoClearShell flag indicating, that the interpreter window + @param autoClearShell flag indicating, that the interpreter window should be cleared @type bool - @keyparam forProject flag indicating a project related action + @param forProject flag indicating a project related action @type bool - @keyparam runInConsole flag indicating to start the debugger in a + @param runInConsole flag indicating to start the debugger in a console window @type bool - @keyparam autoFork flag indicating the automatic fork mode - @type bool - @keyparam forkChild flag indicating to debug the child after forking - @type bool - @keyparam clientType client type to be used + @param clientType client type to be used @type str """ self.__autoClearShell = autoClearShell @@ -970,10 +1059,10 @@ self.remoteEnvironment(env) - self.debuggerInterface.remoteRun(fn, argv, wd, autoFork, forkChild) + self.debuggerInterface.remoteRun(fn, argv, wd) self.debugging = False self.running = True - + def remoteCoverage(self, venvName, fn, argv, wd, env, autoClearShell=True, erase=False, forProject=False, runInConsole=False, clientType=""): @@ -990,18 +1079,18 @@ @type str @param env environment parameter settings @type str - @keyparam autoClearShell flag indicating, that the interpreter window + @param autoClearShell flag indicating, that the interpreter window should be cleared @type bool - @keyparam erase flag indicating that coverage info should be + @param erase flag indicating that coverage info should be cleared first @type bool - @keyparam forProject flag indicating a project related action + @param forProject flag indicating a project related action @type bool - @keyparam runInConsole flag indicating to start the debugger in a + @param runInConsole flag indicating to start the debugger in a console window @type bool - @keyparam clientType client type to be used + @param clientType client type to be used @type str """ self.__autoClearShell = autoClearShell @@ -1034,7 +1123,7 @@ self.debuggerInterface.remoteCoverage(fn, argv, wd, erase) self.debugging = False self.running = True - + def remoteProfile(self, venvName, fn, argv, wd, env, autoClearShell=True, erase=False, forProject=False, runInConsole=False, clientType=""): @@ -1051,18 +1140,18 @@ @type str @param env environment parameter settings @type str - @keyparam autoClearShell flag indicating, that the interpreter window + @param autoClearShell flag indicating, that the interpreter window should be cleared @type bool - @keyparam erase flag indicating that coverage info should be + @param erase flag indicating that coverage info should be cleared first @type bool - @keyparam forProject flag indicating a project related action + @param forProject flag indicating a project related action @type bool - @keyparam runInConsole flag indicating to start the debugger in a + @param runInConsole flag indicating to start the debugger in a console window @type bool - @keyparam clientType client type to be used + @param clientType client type to be used @type str """ self.__autoClearShell = autoClearShell @@ -1095,219 +1184,328 @@ self.debuggerInterface.remoteProfile(fn, argv, wd, erase) self.debugging = False self.running = True - - def remoteStatement(self, stmt): + + def remoteStatement(self, debuggerId, stmt): """ Public method to execute a Python statement. - @param stmt the Python statement to execute (string). It - should not have a trailing newline. + @param debuggerId ID of the debugger backend + @type str + @param stmt the Python statement to execute. + @type str """ - self.debuggerInterface.remoteStatement(stmt) - - def remoteStep(self): + self.debuggerInterface.remoteStatement(debuggerId, stmt.rstrip()) + + def remoteStep(self, debuggerId): """ Public method to single step the debugged program. + + @param debuggerId ID of the debugger backend + @type str """ - self.debuggerInterface.remoteStep() - - def remoteStepOver(self): + self.debuggerInterface.remoteStep(debuggerId) + + def remoteStepOver(self, debuggerId): """ Public method to step over the debugged program. + + @param debuggerId ID of the debugger backend + @type str """ - self.debuggerInterface.remoteStepOver() - - def remoteStepOut(self): + self.debuggerInterface.remoteStepOver(debuggerId) + + def remoteStepOut(self, debuggerId): """ Public method to step out the debugged program. + + @param debuggerId ID of the debugger backend + @type str """ - self.debuggerInterface.remoteStepOut() - - def remoteStepQuit(self): + self.debuggerInterface.remoteStepOut(debuggerId) + + def remoteStepQuit(self, debuggerId): """ Public method to stop the debugged program. + + @param debuggerId ID of the debugger backend + @type str """ - self.debuggerInterface.remoteStepQuit() - - def remoteContinue(self, special=False): + self.debuggerInterface.remoteStepQuit(debuggerId) + + def remoteContinue(self, debuggerId, special=False): """ Public method to continue the debugged program. + @param debuggerId ID of the debugger backend + @type str @param special flag indicating a special continue operation """ - self.debuggerInterface.remoteContinue(special) - - def remoteMoveIP(self, line): + self.debuggerInterface.remoteContinue(debuggerId, special) + + def remoteContinueUntil(self, debuggerId, line): + """ + Public method to continue the debugged program to the given line + or until returning from the current frame. + + @param debuggerId ID of the debugger backend + @type str + @param line the new line, where execution should be continued to + @type int + """ + self.debuggerInterface.remoteContinueUntil(debuggerId, line) + + def remoteMoveIP(self, debuggerId, line): """ Public method to move the instruction pointer to a different line. + @param debuggerId ID of the debugger backend + @type str @param line the new line, where execution should be continued + @type int """ - self.debuggerInterface.remoteMoveIP(line) - - def remoteBreakpoint(self, fn, line, setBreakpoint, cond=None, temp=False): + self.debuggerInterface.remoteMoveIP(debuggerId, line) + + def remoteBreakpoint(self, debuggerId, fn, line, setBreakpoint, cond=None, + temp=False): """ Public method to set or clear a breakpoint. - @param fn filename the breakpoint belongs to (string) - @param line linenumber of the breakpoint (int) + @param debuggerId ID of the debugger backend + @type str + @param fn filename the breakpoint belongs to + @type str + @param line linenumber of the breakpoint + @type int @param setBreakpoint flag indicating setting or resetting a breakpoint - (boolean) - @param cond condition of the breakpoint (string) - @param temp flag indicating a temporary breakpoint (boolean) + @type bool + @param cond condition of the breakpoint + @type str + @param temp flag indicating a temporary breakpoint + @type bool """ - self.debuggerInterface.remoteBreakpoint(fn, line, setBreakpoint, cond, - temp) - - def __remoteBreakpointEnable(self, fn, line, enable): + self.debuggerInterface.remoteBreakpoint( + debuggerId, fn, line, setBreakpoint, cond, temp) + + def __remoteBreakpointEnable(self, debuggerId, fn, line, enable): """ Private method to enable or disable a breakpoint. - @param fn filename the breakpoint belongs to (string) - @param line linenumber of the breakpoint (int) + @param debuggerId ID of the debugger backend + @type str + @param fn filename the breakpoint belongs to + @type str + @param line linenumber of the breakpoint + @type int @param enable flag indicating enabling or disabling a breakpoint - (boolean) + @type bool """ - self.debuggerInterface.remoteBreakpointEnable(fn, line, enable) - - def __remoteBreakpointIgnore(self, fn, line, count): + self.debuggerInterface.remoteBreakpointEnable( + debuggerId, fn, line, enable) + + def __remoteBreakpointIgnore(self, debuggerId, fn, line, count): """ Private method to ignore a breakpoint the next couple of occurrences. - @param fn filename the breakpoint belongs to (string) - @param line linenumber of the breakpoint (int) - @param count number of occurrences to ignore (int) + @param debuggerId ID of the debugger backend + @type str + @param fn filename the breakpoint belongs to + @type str + @param line linenumber of the breakpoint + @type int + @param count number of occurrences to ignore + @type int """ - self.debuggerInterface.remoteBreakpointIgnore(fn, line, count) - - def __remoteWatchpoint(self, cond, setWatch, temp=False): + self.debuggerInterface.remoteBreakpointIgnore( + debuggerId, fn, line, count) + + def __remoteWatchpoint(self, debuggerId, cond, setWatch, temp=False): """ Private method to set or clear a watch expression. - @param cond expression of the watch expression (string) + @param debuggerId ID of the debugger backend + @type str + @param cond expression of the watch expression + @type str @param setWatch flag indicating setting or resetting a watch expression - (boolean) - @param temp flag indicating a temporary watch expression (boolean) + @type bool + @param temp flag indicating a temporary watch expression + @type bool """ # cond is combination of cond and special (s. watch expression viewer) - self.debuggerInterface.remoteWatchpoint(cond, setWatch, temp) + self.debuggerInterface.remoteWatchpoint(debuggerId, cond, setWatch, + temp) - def __remoteWatchpointEnable(self, cond, enable): + def __remoteWatchpointEnable(self, debuggerId, cond, enable): """ Private method to enable or disable a watch expression. - @param cond expression of the watch expression (string) + @param debuggerId ID of the debugger backend + @type str + @param cond expression of the watch expression + @type str @param enable flag indicating enabling or disabling a watch expression - (boolean) + @type bool """ # cond is combination of cond and special (s. watch expression viewer) - self.debuggerInterface.remoteWatchpointEnable(cond, enable) + self.debuggerInterface.remoteWatchpointEnable(debuggerId, cond, enable) - def __remoteWatchpointIgnore(self, cond, count): + def __remoteWatchpointIgnore(self, debuggerId, cond, count): """ Private method to ignore a watch expression the next couple of occurrences. - @param cond expression of the watch expression (string) - @param count number of occurrences to ignore (int) + @param debuggerId ID of the debugger backend + @type str + @param cond expression of the watch expression + @type str + @param count number of occurrences to ignore + @type int """ # cond is combination of cond and special (s. watch expression viewer) - self.debuggerInterface.remoteWatchpointIgnore(cond, count) + self.debuggerInterface.remoteWatchpointIgnore(debuggerId, cond, count) - def remoteRawInput(self, s): + def remoteRawInput(self, debuggerId, inputString): """ Public method to send the raw input to the debugged program. - @param s the raw input (string) + @param debuggerId ID of the debugger backend + @type str + @param inputString the raw input + @type str """ - self.debuggerInterface.remoteRawInput(s) - self.clientRawInputSent.emit() - - def remoteThreadList(self): + self.debuggerInterface.remoteRawInput(debuggerId, inputString) + self.clientRawInputSent.emit(debuggerId) + + def remoteThreadList(self, debuggerId): """ Public method to request the list of threads from the client. + + @param debuggerId ID of the debugger backend + @type str """ - self.debuggerInterface.remoteThreadList() - - def remoteSetThread(self, tid): + self.debuggerInterface.remoteThreadList(debuggerId) + + def remoteSetThread(self, debuggerId, tid): """ Public method to request to set the given thread as current thread. - @param tid id of the thread (integer) + @param debuggerId ID of the debugger backend + @type str + @param tid id of the thread + @type int + """ + self.debuggerInterface.remoteSetThread(debuggerId, tid) + + def remoteClientStack(self, debuggerId): """ - self.debuggerInterface.remoteSetThread(tid) + Public method to request the stack of the main thread. - def remoteClientVariables(self, scope, filterList, framenr=0): + @param debuggerId ID of the debugger backend + @type str + """ + self.debuggerInterface.remoteClientStack(debuggerId) + + def remoteClientVariables(self, debuggerId, scope, filterList, framenr=0): """ Public method to request the variables of the debugged program. + @param debuggerId ID of the debugger backend + @type str @param scope the scope of the variables (0 = local, 1 = global) - @param filterList list of variable types to filter out (list of int) - @param framenr framenumber of the variables to retrieve (int) + @type int + @param filterList list of variable types to filter out + @type list of str + @param framenr framenumber of the variables to retrieve + @type int """ self.debuggerInterface.remoteClientVariables( - scope, filterList, framenr, self.__maxVariableSize) - - def remoteClientVariable(self, scope, filterList, var, framenr=0): + debuggerId, scope, filterList, framenr, self.__maxVariableSize) + + def remoteClientVariable(self, debuggerId, scope, filterList, var, + framenr=0, maxSize=0): """ Public method to request the variables of the debugged program. + @param debuggerId ID of the debugger backend + @type str @param scope the scope of the variables (0 = local, 1 = global) - @param filterList list of variable types to filter out (list of int) - @param var list encoded name of variable to retrieve (string) - @param framenr framenumber of the variables to retrieve (int) + @type int + @param filterList list of variable types to filter out + @type list of str + @param var list encoded name of variable to retrieve + @type list of str + @param framenr framenumber of the variables to retrieve + @type int + @param maxSize maximum size the formatted value of a variable will + be shown. If it is bigger than that, a 'too big' indication will + be given (@@TOO_BIG_TO_SHOW@@). + @type int """ self.debuggerInterface.remoteClientVariable( - scope, filterList, var, framenr, self.__maxVariableSize) + debuggerId, scope, filterList, var, framenr, + self.__maxVariableSize) - def remoteClientDisassembly(self): + def remoteClientDisassembly(self, debuggerId): """ Public method to ask the client for the latest traceback disassembly. + + @param debuggerId ID of the debugger backend + @type str """ try: - self.debuggerInterface.remoteClientDisassembly() + self.debuggerInterface.remoteClientDisassembly(debuggerId) except AttributeError: # remote client doesn't support that pass - def remoteClientSetFilter(self, scope, filterStr): + def remoteClientSetFilter(self, debuggerId, scope, filterStr): """ Public method to set a variables filter list. + @param debuggerId ID of the debugger backend + @type str @param scope the scope of the variables (0 = local, 1 = global) + @type int @param filterStr regexp string for variable names to filter out - (string) + @type str """ - self.debuggerInterface.remoteClientSetFilter(scope, filterStr) - - def setCallTraceEnabled(self, on): + self.debuggerInterface.remoteClientSetFilter( + debuggerId, scope, filterStr) + + def setCallTraceEnabled(self, debuggerId, on): """ Public method to set the call trace state. - @param on flag indicating to enable the call trace function (boolean) + @param debuggerId ID of the debugger backend + @type str + @param on flag indicating to enable the call trace function + @type bool """ - self.debuggerInterface.setCallTraceEnabled(on) - + self.debuggerInterface.setCallTraceEnabled(debuggerId, on) + def remoteBanner(self): """ Public slot to get the banner info of the remote client. """ self.debuggerInterface.remoteBanner() - + def remoteCapabilities(self): """ Public slot to get the debug clients capabilities. """ self.debuggerInterface.remoteCapabilities() - - def remoteCompletion(self, text): + + def remoteCompletion(self, debuggerId, text): """ Public slot to get the a list of possible commandline completions from the remote client. - @param text the text to be completed (string) + @param debuggerId ID of the debugger backend + @type str + @param text the text to be completed + @type str """ - self.debuggerInterface.remoteCompletion(text) + self.debuggerInterface.remoteCompletion(debuggerId, text) def remoteUTDiscover(self, clientType, forProject, venvName, syspath, workdir, discoveryStart): @@ -1422,7 +1620,7 @@ if debug: self.__restoreBreakpoints() self.__restoreWatchpoints() - + def remoteUTRun(self, debug=False, failfast=False): """ Public method to start a unittest run. @@ -1433,125 +1631,179 @@ @type bool """ self.debuggerInterface.remoteUTRun(debug, failfast) - + def remoteUTStop(self): """ public method to stop a unittest run. """ self.debuggerInterface.remoteUTStop() - - def signalClientOutput(self, line): + + def signalClientOutput(self, line, debuggerId): """ Public method to process a line of client output. - @param line client output (string) + @param line client output + @type str + @param debuggerId ID of the debugger backend + @type str """ - self.clientOutput.emit(line) - - def signalClientLine(self, filename, lineno, forStack=False): + if debuggerId: + self.clientOutput.emit("{0}: {1}".format(debuggerId, line)) + else: + self.clientOutput.emit(line) + + def signalClientLine(self, filename, lineno, debuggerId, forStack=False, + threadName=""): """ Public method to process client position feedback. - @param filename name of the file currently being executed (string) - @param lineno line of code currently being executed (integer) - @param forStack flag indicating this is for a stack dump (boolean) + @param filename name of the file currently being executed + @type str + @param lineno line of code currently being executed + @type int + @param debuggerId ID of the debugger backend + @type str + @param forStack flag indicating this is for a stack dump + @type bool + @param threadName name of the thread signaling the event + @type str """ - self.clientLine.emit(filename, lineno, forStack) - - def signalClientStack(self, stack): + self.clientLine.emit(filename, lineno, debuggerId, threadName, + forStack) + + def signalClientStack(self, stack, debuggerId, threadName=""): """ Public method to process a client's stack information. @param stack list of stack entries. Each entry is a tuple of three values giving the filename, linenumber and method - (list of lists of (string, integer, string)) + @type list of lists of (string, integer, string) + @param debuggerId ID of the debugger backend + @type str + @param threadName name of the thread signaling the event + @type str """ - self.clientStack.emit(stack) - - def signalClientThreadList(self, currentId, threadList): + self.clientStack.emit(stack, debuggerId, threadName) + + def signalClientThreadList(self, currentId, threadList, debuggerId): """ Public method to process the client thread list info. - @param currentId id of the current thread (integer) + @param currentId id of the current thread + @type int @param threadList list of dictionaries containing the thread data + @type list of dict + @param debuggerId ID of the debugger backend + @type str """ - self.clientThreadList.emit(currentId, threadList) - - def signalClientThreadSet(self): + self.clientThreadList.emit(currentId, threadList, debuggerId) + + def signalClientThreadSet(self, debuggerId): """ Public method to handle the change of the client thread. + + @param debuggerId ID of the debugger backend + @type str """ - self.clientThreadSet.emit() - - def signalClientVariables(self, scope, variables): + self.clientThreadSet.emit(debuggerId) + + def signalClientVariables(self, scope, variables, debuggerId): """ Public method to process the client variables info. @param scope scope of the variables (-1 = empty global, 1 = global, 0 = local) + @type int @param variables the list of variables from the client + @type list + @param debuggerId ID of the debugger backend + @type str """ - self.clientVariables.emit(scope, variables) - - def signalClientVariable(self, scope, variables): + self.clientVariables.emit(scope, variables, debuggerId) + + def signalClientVariable(self, scope, variables, debuggerId): """ Public method to process the client variable info. @param scope scope of the variables (-1 = empty global, 1 = global, 0 = local) + @type int @param variables the list of members of a classvariable from the client + @type list + @param debuggerId ID of the debugger backend + @type str """ - self.clientVariable.emit(scope, variables) - - def signalClientStatement(self, more): + self.clientVariable.emit(scope, variables, debuggerId) + + def signalClientStatement(self, more, debuggerId): """ Public method to process the input response from the client. @param more flag indicating that more user input is required + @type bool + @param debuggerId ID of the debugger backend + @type str """ - self.clientStatement.emit(more) - - def signalClientDisassembly(self, disassembly): + self.clientStatement.emit(more, debuggerId) + + def signalClientDisassembly(self, disassembly, debuggerId): """ Public method to process the disassembly info from the client. @param disassembly dictionary containing the disassembly information @type dict + @param debuggerId ID of the debugger backend + @type str """ if self.running: - self.clientDisassembly.emit(disassembly) + self.clientDisassembly.emit(disassembly, debuggerId) def signalClientException(self, exceptionType, exceptionMessage, - stackTrace): + stackTrace, debuggerId, threadName=""): """ Public method to process the exception info from the client. - @param exceptionType type of exception raised (string) - @param exceptionMessage message given by the exception (string) + @param exceptionType type of exception raised + @type str + @param exceptionMessage message given by the exception + @type str @param stackTrace list of stack entries with the exception position first. Each stack entry is a list giving the filename and the linenumber. + @type list + @param debuggerId ID of the debugger backend + @type str + @param threadName name of the thread signaling the event + @type str """ if self.running: self.clientException.emit(exceptionType, exceptionMessage, - stackTrace) - - def signalClientSyntaxError(self, message, filename, lineNo, characterNo): + stackTrace, debuggerId, threadName) + + def signalClientSyntaxError(self, message, filename, lineNo, characterNo, + debuggerId, threadName=""): """ - Public method to process the syntax error info from the client. + Public method to process a syntax error info from the client. - @param message message of the syntax error (string) + @param message message of the syntax error + @type str @param filename translated filename of the syntax error position - (string) - @param lineNo line number of the syntax error position (integer) + @type str + @param lineNo line number of the syntax error position + @type int @param characterNo character number of the syntax error position - (integer) + @type int + @param debuggerId ID of the debugger backend + @type str + @param threadName name of the thread signaling the event + @type str """ if self.running: - self.clientSyntaxError.emit(message, filename, lineNo, characterNo) - + self.clientSyntaxError.emit(message, filename, lineNo, characterNo, + debuggerId, threadName) + def signalClientSignal(self, message, filename, lineNo, - funcName, funcArgs): + funcName, funcArgs, debuggerId): """ Public method to process a signal generated on the client side. @@ -1565,23 +1817,45 @@ @type str @param funcArgs function arguments @type str + @param debuggerId ID of the debugger backend + @type str """ if self.running: self.clientSignal.emit(message, filename, lineNo, - funcName, funcArgs) + funcName, funcArgs, debuggerId) + + def signalClientDisconnected(self, debuggerId): + """ + Public method to send a signal when a debug client has closed its + connection. - def signalClientExit(self, status, message=""): + @param debuggerId ID of the debugger backend + @type str + """ + self.clientDisconnected.emit(debuggerId) + + def signalClientExit(self, program, status, message, debuggerId): """ Public method to process the client exit status. + @param program name of the exited program + @type str @param status exit code @type int @param message message sent with the exit @type str + @param debuggerId ID of the debugger backend + @type str + """ + self.clientExit.emit(program, int(status), message, False, debuggerId) + + def signalLastClientExited(self): + """ + Public method to process the last client exit event. """ if self.passive: self.__passiveShutDown() - self.clientExit.emit(int(status), message, False) + self.lastClientExited.emit() if Preferences.getDebugger("AutomaticReset") or (self.running and not self.debugging): self.debugging = False @@ -1589,53 +1863,75 @@ if self.passive: self.__createDebuggerInterface("None") self.signalClientOutput(self.tr('\nNot connected\n')) - self.signalClientStatement(False) + self.signalClientStatement(False, "") self.running = False - - def signalClientClearBreak(self, filename, lineno): + + def signalClientClearBreak(self, filename, lineno, debuggerId): """ Public method to process the client clear breakpoint command. - @param filename filename of the breakpoint (string) - @param lineno line umber of the breakpoint (integer) + @param filename filename of the breakpoint + @type str + @param lineno line umber of the breakpoint + @type int + @param debuggerId ID of the debugger backend + @type str """ - self.clientClearBreak.emit(filename, lineno) - - def signalClientBreakConditionError(self, filename, lineno): + self.clientClearBreak.emit(filename, lineno, debuggerId) + + def signalClientBreakConditionError(self, filename, lineno, debuggerId): """ Public method to process the client breakpoint condition error info. - @param filename filename of the breakpoint (string) - @param lineno line umber of the breakpoint (integer) + @param filename filename of the breakpoint + @type str + @param lineno line umber of the breakpoint + @type int + @param debuggerId ID of the debugger backend + @type str """ - self.clientBreakConditionError.emit(filename, lineno) - - def signalClientClearWatch(self, condition): + if (filename, lineno) not in self.__reportedBreakpointIssues: + self.__reportedBreakpointIssues.append((filename, lineno)) + self.clientBreakConditionError.emit(filename, lineno, debuggerId) + + def signalClientClearWatch(self, condition, debuggerId): """ Public slot to handle the clientClearWatch signal. - @param condition expression of watch expression to clear (string) + @param condition expression of watch expression to clear + @type str + @param debuggerId ID of the debugger backend + @type str """ - self.clientClearWatch.emit(condition) - - def signalClientWatchConditionError(self, condition): + self.clientClearWatch.emit(condition, debuggerId) + + def signalClientWatchConditionError(self, condition, debuggerId): """ Public method to process the client watch expression error info. - @param condition expression of watch expression to clear (string) + @param condition expression of watch expression to clear + @type str + @param debuggerId ID of the debugger backend + @type str """ - self.clientWatchConditionError.emit(condition) - - def signalClientRawInput(self, prompt, echo): + if condition not in self.__reportedWatchpointIssues: + self.__reportedWatchpointIssues.append(condition) + self.clientWatchConditionError.emit(condition, debuggerId) + + def signalClientRawInput(self, prompt, echo, debuggerId): """ Public method to process the client raw input command. - @param prompt the input prompt (string) - @param echo flag indicating an echoing of the input (boolean) + @param prompt the input prompt + @type str + @param echo flag indicating an echoing of the input + @type bool + @param debuggerId ID of the debugger backend + @type str """ - self.clientRawInput.emit(prompt, echo) - - def signalClientBanner(self, version, platform, debugClient, venvName): + self.clientRawInput.emit(prompt, echo, debuggerId) + + def signalClientBanner(self, version, platform, venvName): """ Public method to process the client banner info. @@ -1643,12 +1939,10 @@ @type str @param platform hostname of the client @type str - @param debugClient additional debugger type info - @type str @param venvName name of the virtual environment @type str """ - self.clientBanner.emit(version, platform, debugClient, venvName) + self.clientBanner.emit(version, platform, venvName) def signalClientCapabilities(self, capabilities, clientType, venvName): """ @@ -1667,33 +1961,46 @@ except KeyError: # ignore silently pass - - def signalClientCompletionList(self, completionList, text): + + def signalClientCompletionList(self, completionList, text, debuggerId): """ Public method to process the client auto completion info. - @param completionList list of possible completions (list of strings) - @param text the text to be completed (string) + @param completionList list of possible completions + @type list of str + @param text the text to be completed + @type str + @param debuggerId ID of the debugger backend + @type str """ self.clientCompletionList.emit(completionList, text) - + def signalClientCallTrace(self, isCall, fromFile, fromLine, fromFunction, - toFile, toLine, toFunction): + toFile, toLine, toFunction, debuggerId): """ Public method to process the client call trace data. - @param isCall flag indicating a 'call' (boolean) - @param fromFile name of the originating file (string) - @param fromLine line number in the originating file (string) - @param fromFunction name of the originating function (string) - @param toFile name of the target file (string) - @param toLine line number in the target file (string) - @param toFunction name of the target function (string) + @param isCall flag indicating a 'call' + @type bool + @param fromFile name of the originating file + @type str + @param fromLine line number in the originating file + @type str + @param fromFunction name of the originating function + @type str + @param toFile name of the target file + @type str + @param toLine line number in the target file + @type str + @param toFunction name of the target function + @type str + @param debuggerId ID of the debugger backend + @type str """ self.callTraceInfo.emit( isCall, fromFile, fromLine, fromFunction, - toFile, toLine, toFunction) - + toFile, toLine, toFunction, debuggerId) + def clientUtDiscovered(self, testCases, exceptionType, exceptionValue): """ Public method to process the client unittest discover info. @@ -1706,81 +2013,100 @@ @type str """ self.utDiscovered.emit(testCases, exceptionType, exceptionValue) - + def clientUtPrepared(self, result, exceptionType, exceptionValue): """ Public method to process the client unittest prepared info. - @param result number of test cases (0 = error) (integer) - @param exceptionType exception type (string) - @param exceptionValue exception message (string) + @param result number of test cases (0 = error) + @type int + @param exceptionType exception type + @type str + @param exceptionValue exception message + @type str """ self.utPrepared.emit(result, exceptionType, exceptionValue) - + def clientUtStartTest(self, testname, doc): """ Public method to process the client start test info. - @param testname name of the test (string) - @param doc short description of the test (string) + @param testname name of the test + @type str + @param doc short description of the test + @type str """ self.utStartTest.emit(testname, doc) - + def clientUtStopTest(self): """ Public method to process the client stop test info. """ self.utStopTest.emit() - + def clientUtTestFailed(self, testname, traceback, testId): """ Public method to process the client test failed info. - @param testname name of the test (string) - @param traceback lines of traceback info (list of strings) - @param testId id of the test (string) + @param testname name of the test + @type str + @param traceback lines of traceback info + @type list of str + @param testId id of the test + @type str """ self.utTestFailed.emit(testname, traceback, testId) - + def clientUtTestErrored(self, testname, traceback, testId): """ Public method to process the client test errored info. - @param testname name of the test (string) - @param traceback lines of traceback info (list of strings) - @param testId id of the test (string) + @param testname name of the test + @type str + @param traceback lines of traceback info + @type list of str + @param testId id of the test + @type str """ self.utTestErrored.emit(testname, traceback, testId) - + def clientUtTestSkipped(self, testname, reason, testId): """ Public method to process the client test skipped info. - @param testname name of the test (string) - @param reason reason for skipping the test (string) - @param testId id of the test (string) + @param testname name of the test + @type str + @param reason reason for skipping the test + @type str + @param testId id of the test + @type str """ self.utTestSkipped.emit(testname, reason, testId) - + def clientUtTestFailedExpected(self, testname, traceback, testId): """ Public method to process the client test failed expected info. - @param testname name of the test (string) - @param traceback lines of traceback info (list of strings) - @param testId id of the test (string) + @param testname name of the test + @type str + @param traceback lines of traceback info + @type list of str + @param testId id of the test + @type str """ self.utTestFailedExpected.emit(testname, traceback, testId) - + def clientUtTestSucceededUnexpected(self, testname, testId): """ Public method to process the client test succeeded unexpected info. - @param testname name of the test (string) - @param testId id of the test (string) + @param testname name of the test + @type str + @param testId id of the test + @type str """ self.utTestSucceededUnexpected.emit(testname, testId) - + def clientUtFinished(self, status): """ Public method to process the client unit test finished info. @@ -1790,25 +2116,29 @@ """ self.utFinished.emit() - self.clientExit.emit(int(status), "", True) + self.clientExit.emit("", int(status), "", True, "") self.debugging = False self.running = False - - def passiveStartUp(self, fn, exc): + + def passiveStartUp(self, fn, exc, debuggerId): """ Public method to handle a passive debug connection. - @param fn filename of the debugged script (string) - @param exc flag to enable exception reporting of the IDE (boolean) + @param fn filename of the debugged script + @type str + @param exc flag to enable exception reporting of the IDE + @type bool + @param debuggerId ID of the debugger backend + @type str """ self.appendStdout.emit(self.tr("Passive debug connection received\n")) self.passiveClientExited = False self.debugging = True self.running = True - self.__restoreBreakpoints() - self.__restoreWatchpoints() + self.__restoreBreakpoints(debuggerId) + self.__restoreWatchpoints(debuggerId) self.passiveDebugStarted.emit(fn, exc) - + def __passiveShutDown(self): """ Private method to shut down a passive debug connection. @@ -1816,37 +2146,48 @@ self.passiveClientExited = True self.shutdownServer() self.appendStdout.emit(self.tr("Passive debug connection closed\n")) - - def __restoreBreakpoints(self): + + def __restoreBreakpoints(self, debuggerId=""): """ Private method to restore the breakpoints after a restart. + + @param debuggerId ID of the debugger backend to send to. If this is + empty, they will be broadcast to all connected backends. + @type str """ if self.debugging: self.__addBreakPoints( - QModelIndex(), 0, self.breakpointModel.rowCount() - 1) + QModelIndex(), 0, self.breakpointModel.rowCount() - 1, + debuggerId) - def __restoreWatchpoints(self): + def __restoreWatchpoints(self, debuggerId=""): """ Private method to restore the watch expressions after a restart. + + @param debuggerId ID of the debugger backend to send to. If this is + empty, they will be broadcast to all connected backends. + @type str """ if self.debugging: self.__addWatchPoints( - QModelIndex(), 0, self.watchpointModel.rowCount() - 1) + QModelIndex(), 0, self.watchpointModel.rowCount() - 1, + debuggerId) def getBreakPointModel(self): """ Public slot to get a reference to the breakpoint model object. - @return reference to the breakpoint model object (BreakPointModel) + @return reference to the breakpoint model object + @rtype BreakPointModel """ return self.breakpointModel - + def getWatchPointModel(self): """ Public slot to get a reference to the watch expression model object. @return reference to the watch expression model object - (WatchPointModel) + @rtype WatchPointModel """ return self.watchpointModel @@ -1854,7 +2195,8 @@ """ Public method to test, if the debug server is connected to a backend. - @return flag indicating a connection (boolean) + @return flag indicating a connection + @rtype bool """ return self.debuggerInterface and self.debuggerInterface.isConnected() @@ -1875,3 +2217,50 @@ @type bool """ self.debugging = on + + def signalClientDebuggerId(self, debuggerId): + """ + Public method to signal the receipt of a new debugger ID. + + This signal indicates, that a new debugger backend has connected. + + @param debuggerId ID of the newly connected debugger backend + @type str + """ + self.clientDebuggerId.emit(debuggerId) + + def getDebuggerIds(self): + """ + Public method to return the IDs of the connected debugger backends. + + @return list of connected debugger backend IDs + @rtype list of str + """ + if self.debuggerInterface: + return self.debuggerInterface.getDebuggerIds() + else: + return [] + + def initializeClient(self, debuggerId): + """ + Public method to initialize a freshly connected debug client. + + @param debuggerId ID of the connected debugger + @type str + """ + self.__restoreBreakpoints(debuggerId) + self.__restoreWatchpoints(debuggerId) + self.__restoreNoDebugList(debuggerId) + + def __restoreNoDebugList(self, debuggerId=""): + """ + Private method to restore the list of scripts not to be debugged after + a restart. + + @param debuggerId ID of the debugger backend to send to. If this is + empty, they will be broadcast to all connected backends. + @type str + """ + if self.debugging: + self.debuggerInterface.remoteNoDebugList( + debuggerId, self.__multiprocessNoDebugList)