eric7/Debugger/DebuggerInterfacePython.py

branch
eric7
changeset 8312
800c432b34c8
parent 8257
28146736bbfc
child 8314
e3642a6a1e71
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the Python3 debugger interface for the debug server.
8 """
9
10 import sys
11 import os
12 import logging
13 import shlex
14 import contextlib
15
16 from PyQt5.QtCore import (
17 QObject, QProcess, QProcessEnvironment, QTimer
18 )
19
20 from E5Gui.E5Application import e5App
21 from E5Gui import E5MessageBox
22
23 from . import DebugClientCapabilities
24
25 import Preferences
26 import Utilities
27
28 from eric6config import getConfig
29
30
31 ClientDefaultCapabilities = DebugClientCapabilities.HasAll
32
33
34 class DebuggerInterfacePython(QObject):
35 """
36 Class implementing the debugger interface for the debug server for
37 Python 3.
38 """
39 def __init__(self, debugServer, passive):
40 """
41 Constructor
42
43 @param debugServer reference to the debug server
44 @type DebugServer
45 @param passive flag indicating passive connection mode
46 @type bool
47 """
48 super().__init__()
49
50 self.__isNetworked = True
51 self.__autoContinue = False
52 self.__autoContinued = []
53 self.__isStepCommand = False
54
55 self.debugServer = debugServer
56 self.passive = passive
57 self.process = None
58 self.__startedVenv = ""
59
60 self.queue = []
61 self.__master = None
62 self.__connections = {}
63 self.__pendingConnections = []
64 self.__inShutdown = False
65
66 # set default values for capabilities of clients
67 self.clientCapabilities = ClientDefaultCapabilities
68
69 # set translation function
70 self.translate = self.__identityTranslation
71
72 if passive:
73 # set translation function
74 if Preferences.getDebugger("PathTranslation"):
75 self.translateRemote = Preferences.getDebugger(
76 "PathTranslationRemote")
77 self.translateRemoteWindows = "\\" in self.translateRemote
78 self.translateLocal = Preferences.getDebugger(
79 "PathTranslationLocal")
80 self.translateLocalWindows = "\\" in self.translateLocal
81 self.translate = self.__remoteTranslation
82 else:
83 self.translate = self.__identityTranslation
84
85 # attribute to remember the name of the executed script
86 self.__scriptName = ""
87
88 def __identityTranslation(self, fn, remote2local=True):
89 """
90 Private method to perform the identity path translation.
91
92 @param fn filename to be translated
93 @type str
94 @param remote2local flag indicating the direction of translation
95 (False = local to remote, True = remote to local [default])
96 @type bool
97 @return translated filename
98 @rtype str
99 """
100 return fn
101
102 def __remoteTranslation(self, fn, remote2local=True):
103 """
104 Private method to perform the path translation.
105
106 @param fn filename to be translated
107 @type str
108 @param remote2local flag indicating the direction of translation
109 (False = local to remote, True = remote to local [default])
110 @type bool
111 @return translated filename
112 @rtype str
113 """
114 if remote2local:
115 path = fn.replace(self.translateRemote, self.translateLocal)
116 if self.translateLocalWindows:
117 path = path.replace("/", "\\")
118 else:
119 path = fn.replace(self.translateLocal, self.translateRemote)
120 if not self.translateRemoteWindows:
121 path = path.replace("\\", "/")
122
123 return path
124
125 def __startProcess(self, program, arguments, environment=None,
126 workingDir=None):
127 """
128 Private method to start the debugger client process.
129
130 @param program name of the executable to start
131 @type str
132 @param arguments arguments to be passed to the program
133 @type list of str
134 @param environment dictionary of environment settings to pass
135 @type dict of str
136 @param workingDir directory to start the debugger client in
137 @type str
138 @return the process object
139 @rtype QProcess or None
140 """
141 proc = QProcess()
142 if environment is not None:
143 env = QProcessEnvironment()
144 for key, value in list(environment.items()):
145 env.insert(key, value)
146 proc.setProcessEnvironment(env)
147 args = arguments[:]
148 if workingDir:
149 proc.setWorkingDirectory(workingDir)
150 proc.start(program, args)
151 if not proc.waitForStarted(10000):
152 proc = None
153
154 return proc
155
156 def startRemote(self, port, runInConsole, venvName, originalPathString,
157 workingDir=None, configOverride=None):
158 """
159 Public method to start a remote Python interpreter.
160
161 @param port port number the debug server is listening on
162 @type int
163 @param runInConsole flag indicating to start the debugger in a
164 console window
165 @type bool
166 @param venvName name of the virtual environment to be used
167 @type str
168 @param originalPathString original PATH environment variable
169 @type str
170 @param workingDir directory to start the debugger client in
171 @type str
172 @param configOverride dictionary containing the global config override
173 data
174 @type dict
175 @return client process object, a flag to indicate a network connection
176 and the name of the interpreter in case of a local execution
177 @rtype tuple of (QProcess, bool, str)
178 """
179 global origPathEnv
180
181 if not venvName:
182 venvName = Preferences.getDebugger("Python3VirtualEnv")
183 venvManager = e5App().getObject("VirtualEnvManager")
184 interpreter = venvManager.getVirtualenvInterpreter(venvName)
185 execPath = venvManager.getVirtualenvExecPath(venvName)
186 if interpreter == "":
187 # use the interpreter used to run eric for identical variants
188 interpreter = sys.executable.replace("w.exe", ".exe")
189 if interpreter == "":
190 E5MessageBox.critical(
191 None,
192 self.tr("Start Debugger"),
193 self.tr(
194 """<p>No suitable Python3 environment configured.</p>""")
195 )
196 return None, False, ""
197
198 self.__inShutdown = False
199
200 debugClientType = Preferences.getDebugger("DebugClientType3")
201 if debugClientType == "standard":
202 debugClient = os.path.join(getConfig('ericDir'),
203 "DebugClients", "Python",
204 "DebugClient.py")
205 else:
206 debugClient = Preferences.getDebugger("DebugClient3")
207 if debugClient == "":
208 debugClient = os.path.join(sys.path[0],
209 "DebugClients", "Python",
210 "DebugClient.py")
211
212 redirect = (
213 str(configOverride["redirect"])
214 if configOverride and configOverride["enable"] else
215 str(Preferences.getDebugger("Python3Redirect"))
216 )
217 noencoding = (Preferences.getDebugger("Python3NoEncoding") and
218 '--no-encoding' or '')
219 multiprocessEnabled = (
220 '--multiprocess' if Preferences.getDebugger("MultiProcessEnabled")
221 else ''
222 )
223
224 if Preferences.getDebugger("RemoteDbgEnabled"):
225 ipaddr = self.debugServer.getHostAddress(False)
226 rexec = Preferences.getDebugger("RemoteExecution")
227 rhost = Preferences.getDebugger("RemoteHost")
228 if rhost == "":
229 rhost = "localhost"
230 if rexec:
231 args = Utilities.parseOptionString(rexec) + [
232 rhost, interpreter, debugClient]
233 if noencoding:
234 args.append(noencoding)
235 if multiprocessEnabled:
236 args.append(multiprocessEnabled)
237 args.extend([str(port), redirect, ipaddr])
238 if Utilities.isWindowsPlatform():
239 if not os.path.splitext(args[0])[1]:
240 for ext in [".exe", ".com", ".cmd", ".bat"]:
241 prog = Utilities.getExecutablePath(args[0] + ext)
242 if prog:
243 args[0] = prog
244 break
245 else:
246 args[0] = Utilities.getExecutablePath(args[0])
247 process = self.__startProcess(args[0], args[1:],
248 workingDir=workingDir)
249 if process is None:
250 E5MessageBox.critical(
251 None,
252 self.tr("Start Debugger"),
253 self.tr(
254 """<p>The debugger backend could not be"""
255 """ started.</p>"""))
256
257 # set translation function
258 if Preferences.getDebugger("PathTranslation"):
259 self.translateRemote = Preferences.getDebugger(
260 "PathTranslationRemote")
261 self.translateRemoteWindows = "\\" in self.translateRemote
262 self.translateLocal = Preferences.getDebugger(
263 "PathTranslationLocal")
264 self.translate = self.__remoteTranslation
265 self.translateLocalWindows = "\\" in self.translateLocal
266 else:
267 self.translate = self.__identityTranslation
268 return process, self.__isNetworked, ""
269
270 # set translation function
271 self.translate = self.__identityTranslation
272
273 # setup the environment for the debugger
274 if Preferences.getDebugger("DebugEnvironmentReplace"):
275 clientEnv = {}
276 else:
277 clientEnv = os.environ.copy()
278 if originalPathString:
279 clientEnv["PATH"] = originalPathString
280 envlist = shlex.split(
281 Preferences.getDebugger("DebugEnvironment"))
282 for el in envlist:
283 with contextlib.suppress(ValueError):
284 key, value = el.split('=', 1)
285 clientEnv[str(key)] = str(value)
286 if execPath:
287 if "PATH" in clientEnv:
288 clientEnv["PATH"] = os.pathsep.join(
289 [execPath, clientEnv["PATH"]])
290 else:
291 clientEnv["PATH"] = execPath
292
293 ipaddr = self.debugServer.getHostAddress(True)
294 if runInConsole or Preferences.getDebugger("ConsoleDbgEnabled"):
295 ccmd = Preferences.getDebugger("ConsoleDbgCommand")
296 if ccmd:
297 args = Utilities.parseOptionString(ccmd) + [
298 interpreter, os.path.abspath(debugClient)]
299 if noencoding:
300 args.append(noencoding)
301 if multiprocessEnabled:
302 args.append(multiprocessEnabled)
303 args.extend([str(port), '0', ipaddr])
304 args[0] = Utilities.getExecutablePath(args[0])
305 process = self.__startProcess(args[0], args[1:], clientEnv,
306 workingDir=workingDir)
307 if process is None:
308 E5MessageBox.critical(
309 None,
310 self.tr("Start Debugger"),
311 self.tr(
312 """<p>The debugger backend could not be"""
313 """ started.</p>"""))
314 return process, self.__isNetworked, interpreter
315
316 args = [debugClient]
317 if noencoding:
318 args.append(noencoding)
319 if multiprocessEnabled:
320 args.append(multiprocessEnabled)
321 args.extend([str(port), redirect, ipaddr])
322 process = self.__startProcess(interpreter, args, clientEnv,
323 workingDir=workingDir)
324 if process is None:
325 self.__startedVenv = ""
326 E5MessageBox.critical(
327 None,
328 self.tr("Start Debugger"),
329 self.tr(
330 """<p>The debugger backend could not be started.</p>"""))
331 else:
332 self.__startedVenv = venvName
333
334 return process, self.__isNetworked, interpreter
335
336 def startRemoteForProject(self, port, runInConsole, venvName,
337 originalPathString, workingDir=None,
338 configOverride=None):
339 """
340 Public method to start a remote Python interpreter for a project.
341
342 @param port port number the debug server is listening on
343 @type int
344 @param runInConsole flag indicating to start the debugger in a
345 console window
346 @type bool
347 @param venvName name of the virtual environment to be used
348 @type str
349 @param originalPathString original PATH environment variable
350 @type str
351 @param workingDir directory to start the debugger client in
352 @type str
353 @param configOverride dictionary containing the global config override
354 data
355 @type dict
356 @return client process object, a flag to indicate a network connection
357 and the name of the interpreter in case of a local execution
358 @rtype tuple of (QProcess, bool, str)
359 """
360 global origPathEnv
361
362 project = e5App().getObject("Project")
363 if not project.isDebugPropertiesLoaded():
364 return None, self.__isNetworked, ""
365
366 # start debugger with project specific settings
367 debugClient = project.getDebugProperty("DEBUGCLIENT")
368 if not venvName:
369 venvName = project.getDebugProperty("VIRTUALENV")
370 if not venvName and project.getProjectLanguage() == "Python3":
371 venvName = Preferences.getDebugger("Python3VirtualEnv")
372
373 redirect = (
374 str(configOverride["redirect"])
375 if configOverride and configOverride["enable"] else
376 str(project.getDebugProperty("REDIRECT"))
377 )
378 noencoding = (
379 '--no-encoding' if project.getDebugProperty("NOENCODING") else ''
380 )
381 multiprocessEnabled = (
382 '--multiprocess' if Preferences.getDebugger("MultiProcessEnabled")
383 else ''
384 )
385
386 venvManager = e5App().getObject("VirtualEnvManager")
387 interpreter = venvManager.getVirtualenvInterpreter(venvName)
388 execPath = venvManager.getVirtualenvExecPath(venvName)
389 if (
390 interpreter == "" and
391 project.getProjectLanguage().startswith("Python")
392 ):
393 interpreter = sys.executable.replace("w.exe", ".exe")
394 if interpreter == "":
395 E5MessageBox.critical(
396 None,
397 self.tr("Start Debugger"),
398 self.tr(
399 """<p>No suitable Python3 environment configured.</p>""")
400 )
401 return None, self.__isNetworked, ""
402
403 self.__inShutdown = False
404
405 if project.getDebugProperty("REMOTEDEBUGGER"):
406 ipaddr = self.debugServer.getHostAddress(False)
407 rexec = project.getDebugProperty("REMOTECOMMAND")
408 rhost = project.getDebugProperty("REMOTEHOST")
409 if rhost == "":
410 rhost = "localhost"
411 if rexec:
412 args = Utilities.parseOptionString(rexec) + [
413 rhost, interpreter, debugClient]
414 if noencoding:
415 args.append(noencoding)
416 if multiprocessEnabled:
417 args.append(multiprocessEnabled)
418 args.extend([str(port), redirect, ipaddr])
419 if Utilities.isWindowsPlatform():
420 if not os.path.splitext(args[0])[1]:
421 for ext in [".exe", ".com", ".cmd", ".bat"]:
422 prog = Utilities.getExecutablePath(args[0] + ext)
423 if prog:
424 args[0] = prog
425 break
426 else:
427 args[0] = Utilities.getExecutablePath(args[0])
428 process = self.__startProcess(args[0], args[1:],
429 workingDir=workingDir)
430 if process is None:
431 E5MessageBox.critical(
432 None,
433 self.tr("Start Debugger"),
434 self.tr(
435 """<p>The debugger backend could not be"""
436 """ started.</p>"""))
437 # set translation function
438 if project.getDebugProperty("PATHTRANSLATION"):
439 self.translateRemote = project.getDebugProperty(
440 "REMOTEPATH")
441 self.translateRemoteWindows = "\\" in self.translateRemote
442 self.translateLocal = project.getDebugProperty("LOCALPATH")
443 self.translateLocalWindows = "\\" in self.translateLocal
444 self.translate = self.__remoteTranslation
445 else:
446 self.translate = self.__identityTranslation
447 return process, self.__isNetworked, ""
448 else:
449 # remote shell command is missing
450 return None, self.__isNetworked, ""
451
452 # set translation function
453 self.translate = self.__identityTranslation
454
455 # setup the environment for the debugger
456 if project.getDebugProperty("ENVIRONMENTOVERRIDE"):
457 clientEnv = {}
458 else:
459 clientEnv = os.environ.copy()
460 if originalPathString:
461 clientEnv["PATH"] = originalPathString
462 envlist = shlex.split(
463 project.getDebugProperty("ENVIRONMENTSTRING"))
464 for el in envlist:
465 with contextlib.suppress(ValueError):
466 key, value = el.split('=', 1)
467 clientEnv[str(key)] = str(value)
468 if execPath:
469 if "PATH" in clientEnv:
470 clientEnv["PATH"] = os.pathsep.join(
471 [execPath, clientEnv["PATH"]])
472 else:
473 clientEnv["PATH"] = execPath
474
475 ipaddr = self.debugServer.getHostAddress(True)
476 if runInConsole or project.getDebugProperty("CONSOLEDEBUGGER"):
477 ccmd = (project.getDebugProperty("CONSOLECOMMAND") or
478 Preferences.getDebugger("ConsoleDbgCommand"))
479 if ccmd:
480 args = Utilities.parseOptionString(ccmd) + [
481 interpreter, os.path.abspath(debugClient)]
482 if noencoding:
483 args.append(noencoding)
484 if multiprocessEnabled:
485 args.append(multiprocessEnabled)
486 args.extend([str(port), '0', ipaddr])
487 args[0] = Utilities.getExecutablePath(args[0])
488 process = self.__startProcess(args[0], args[1:], clientEnv,
489 workingDir=workingDir)
490 if process is None:
491 E5MessageBox.critical(
492 None,
493 self.tr("Start Debugger"),
494 self.tr(
495 """<p>The debugger backend could not be"""
496 """ started.</p>"""))
497 return process, self.__isNetworked, interpreter
498
499 args = [debugClient]
500 if noencoding:
501 args.append(noencoding)
502 if multiprocessEnabled:
503 args.append(multiprocessEnabled)
504 args.extend([str(port), redirect, ipaddr])
505 process = self.__startProcess(interpreter, args, clientEnv,
506 workingDir=workingDir)
507 if process is None:
508 self.__startedVenv = ""
509 E5MessageBox.critical(
510 None,
511 self.tr("Start Debugger"),
512 self.tr(
513 """<p>The debugger backend could not be started.</p>"""))
514 else:
515 self.__startedVenv = venvName
516
517 return process, self.__isNetworked, interpreter
518
519 def getClientCapabilities(self):
520 """
521 Public method to retrieve the debug clients capabilities.
522
523 @return debug client capabilities
524 @rtype int
525 """
526 return self.clientCapabilities
527
528 def newConnection(self, sock):
529 """
530 Public slot to handle a new connection.
531
532 @param sock reference to the socket object
533 @type QTcpSocket
534 @return flag indicating success
535 @rtype bool
536 """
537 self.__pendingConnections.append(sock)
538
539 sock.readyRead.connect(lambda: self.__parseClientLine(sock))
540 sock.disconnected.connect(lambda: self.__socketDisconnected(sock))
541
542 return True
543
544 def __assignDebuggerId(self, sock, debuggerId):
545 """
546 Private method to set the debugger id for a recent debugger connection
547 attempt.
548
549 @param sock reference to the socket object
550 @type QTcpSocket
551 @param debuggerId id of the connected debug client
552 @type str
553 """
554 if sock in self.__pendingConnections:
555 self.__connections[debuggerId] = sock
556 self.__pendingConnections.remove(sock)
557
558 if self.__master is None:
559 self.__master = debuggerId
560 # Get the remote clients capabilities
561 self.remoteCapabilities(debuggerId)
562
563 self.debugServer.signalClientDebuggerId(debuggerId)
564
565 if debuggerId == self.__master:
566 self.__flush()
567 self.debugServer.masterClientConnected()
568
569 self.debugServer.initializeClient(debuggerId)
570
571 # perform auto-continue except for master
572 if (
573 debuggerId != self.__master and
574 self.__autoContinue and
575 not self.__isStepCommand
576 ):
577 QTimer.singleShot(
578 0, lambda: self.remoteContinue(debuggerId))
579
580 def __socketDisconnected(self, sock):
581 """
582 Private slot handling a socket disconnecting.
583
584 @param sock reference to the disconnected socket
585 @type QTcpSocket
586 """
587 for debuggerId in self.__connections:
588 if self.__connections[debuggerId] is sock:
589 del self.__connections[debuggerId]
590 if debuggerId == self.__master:
591 self.__master = None
592 if debuggerId in self.__autoContinued:
593 self.__autoContinued.remove(debuggerId)
594 if not self.__inShutdown:
595 self.debugServer.signalClientDisconnected(debuggerId)
596 break
597 else:
598 if sock in self.__pendingConnections:
599 self.__pendingConnections.remove(sock)
600
601 if not self.__connections:
602 # no active connections anymore
603 with contextlib.suppress(RuntimeError):
604 self.debugServer.signalLastClientExited()
605 # debug server object might have been deleted already
606 # ignore this
607 self.__autoContinued.clear()
608 self.debugServer.startClient()
609
610 def getDebuggerIds(self):
611 """
612 Public method to return the IDs of the connected debugger backends.
613
614 @return list of connected debugger backend IDs
615 @rtype list of str
616 """
617 return sorted(self.__connections.keys())
618
619 def __flush(self):
620 """
621 Private slot to flush the queue.
622 """
623 if self.__master:
624 # Send commands that were waiting for the connection.
625 for cmd in self.queue:
626 self.__writeJsonCommandToSocket(
627 cmd, self.__connections[self.__master])
628
629 self.queue = []
630
631 def shutdown(self):
632 """
633 Public method to cleanly shut down.
634
635 It closes our sockets and shuts down the debug clients.
636 (Needed on Win OS)
637 """
638 if not self.__master:
639 return
640
641 self.__inShutdown = True
642
643 while self.__connections:
644 debuggerId, sock = self.__connections.popitem()
645 self.__shutdownSocket(sock)
646
647 while self.__pendingConnections:
648 sock = self.__pendingConnections.pop()
649 self.__shutdownSocket(sock)
650
651 # reinitialize
652 self.queue = []
653
654 self.__master = None
655
656 def __shutdownSocket(self, sock):
657 """
658 Private slot to shut down a socket.
659
660 @param sock reference to the socket
661 @type QTcpSocket
662 """
663 # do not want any slots called during shutdown
664 sock.readyRead.disconnect()
665 sock.disconnected.disconnect()
666
667 # close down socket, and shut down client as well.
668 self.__sendJsonCommand("RequestShutdown", {}, sock=sock)
669 sock.flush()
670 sock.close()
671
672 sock.setParent(None)
673 sock.deleteLater()
674 del sock
675
676 def isConnected(self):
677 """
678 Public method to test, if a debug client has connected.
679
680 @return flag indicating the connection status
681 @rtype bool
682 """
683 return bool(self.__connections)
684
685 def remoteEnvironment(self, env):
686 """
687 Public method to set the environment for a program to debug, run, ...
688
689 @param env environment settings
690 @type dict
691 """
692 self.__sendJsonCommand("RequestEnvironment", {"environment": env},
693 self.__master)
694
695 def remoteLoad(self, fn, argv, wd, traceInterpreter=False,
696 autoContinue=True, enableMultiprocess=False):
697 """
698 Public method to load a new program to debug.
699
700 @param fn the filename to debug
701 @type str
702 @param argv the commandline arguments to pass to the program
703 @type str
704 @param wd the working directory for the program
705 @type str
706 @param traceInterpreter flag indicating if the interpreter library
707 should be traced as well
708 @type bool
709 @param autoContinue flag indicating, that the debugger should not
710 stop at the first executable line
711 @type bool
712 @param enableMultiprocess flag indicating to perform multiprocess
713 debugging
714 @type bool
715 """
716 self.__autoContinue = autoContinue
717 self.__scriptName = os.path.abspath(fn)
718 self.__isStepCommand = False
719
720 wd = self.translate(wd, False)
721 fn = self.translate(os.path.abspath(fn), False)
722 self.__sendJsonCommand("RequestLoad", {
723 "workdir": wd,
724 "filename": fn,
725 "argv": Utilities.parseOptionString(argv),
726 "traceInterpreter": traceInterpreter,
727 "multiprocess": enableMultiprocess,
728 }, self.__master)
729
730 def remoteRun(self, fn, argv, wd):
731 """
732 Public method to load a new program to run.
733
734 @param fn the filename to run
735 @type str
736 @param argv the commandline arguments to pass to the program
737 @type str
738 @param wd the working directory for the program
739 @type str
740 """
741 self.__scriptName = os.path.abspath(fn)
742
743 wd = self.translate(wd, False)
744 fn = self.translate(os.path.abspath(fn), False)
745 self.__sendJsonCommand("RequestRun", {
746 "workdir": wd,
747 "filename": fn,
748 "argv": Utilities.parseOptionString(argv),
749 }, self.__master)
750
751 def remoteCoverage(self, fn, argv, wd, erase=False):
752 """
753 Public method to load a new program to collect coverage data.
754
755 @param fn the filename to run
756 @type str
757 @param argv the commandline arguments to pass to the program
758 @type str
759 @param wd the working directory for the program
760 @type str
761 @param erase flag indicating that coverage info should be
762 cleared first
763 @type bool
764 """
765 self.__scriptName = os.path.abspath(fn)
766
767 wd = self.translate(wd, False)
768 fn = self.translate(os.path.abspath(fn), False)
769 self.__sendJsonCommand("RequestCoverage", {
770 "workdir": wd,
771 "filename": fn,
772 "argv": Utilities.parseOptionString(argv),
773 "erase": erase,
774 }, self.__master)
775
776 def remoteProfile(self, fn, argv, wd, erase=False):
777 """
778 Public method to load a new program to collect profiling data.
779
780 @param fn the filename to run
781 @type str
782 @param argv the commandline arguments to pass to the program
783 @type str
784 @param wd the working directory for the program
785 @type str
786 @param erase flag indicating that timing info should be cleared
787 first
788 @type bool
789 """
790 self.__scriptName = os.path.abspath(fn)
791
792 wd = self.translate(wd, False)
793 fn = self.translate(os.path.abspath(fn), False)
794 self.__sendJsonCommand("RequestProfile", {
795 "workdir": wd,
796 "filename": fn,
797 "argv": Utilities.parseOptionString(argv),
798 "erase": erase,
799 }, self.__master)
800
801 def remoteStatement(self, debuggerId, stmt):
802 """
803 Public method to execute a Python statement.
804
805 @param debuggerId ID of the debugger backend
806 @type str
807 @param stmt the Python statement to execute.
808 @type str
809 """
810 self.__sendJsonCommand("ExecuteStatement", {
811 "statement": stmt,
812 }, debuggerId)
813
814 def remoteStep(self, debuggerId):
815 """
816 Public method to single step the debugged program.
817
818 @param debuggerId ID of the debugger backend
819 @type str
820 """
821 self.__isStepCommand = True
822 self.__sendJsonCommand("RequestStep", {}, debuggerId)
823
824 def remoteStepOver(self, debuggerId):
825 """
826 Public method to step over the debugged program.
827
828 @param debuggerId ID of the debugger backend
829 @type str
830 """
831 self.__isStepCommand = True
832 self.__sendJsonCommand("RequestStepOver", {}, debuggerId)
833
834 def remoteStepOut(self, debuggerId):
835 """
836 Public method to step out the debugged program.
837
838 @param debuggerId ID of the debugger backend
839 @type str
840 """
841 self.__isStepCommand = True
842 self.__sendJsonCommand("RequestStepOut", {}, debuggerId)
843
844 def remoteStepQuit(self, debuggerId):
845 """
846 Public method to stop the debugged program.
847
848 @param debuggerId ID of the debugger backend
849 @type str
850 """
851 self.__isStepCommand = True
852 self.__sendJsonCommand("RequestStepQuit", {}, debuggerId)
853
854 def remoteContinue(self, debuggerId, special=False):
855 """
856 Public method to continue the debugged program.
857
858 @param debuggerId ID of the debugger backend
859 @type str
860 @param special flag indicating a special continue operation
861 @type bool
862 """
863 self.__isStepCommand = False
864 self.__sendJsonCommand("RequestContinue", {
865 "special": special,
866 }, debuggerId)
867
868 def remoteContinueUntil(self, debuggerId, line):
869 """
870 Public method to continue the debugged program to the given line
871 or until returning from the current frame.
872
873 @param debuggerId ID of the debugger backend
874 @type str
875 @param line the new line, where execution should be continued to
876 @type int
877 """
878 self.__isStepCommand = False
879 self.__sendJsonCommand("RequestContinueUntil", {
880 "newLine": line,
881 }, debuggerId)
882
883 def remoteMoveIP(self, debuggerId, line):
884 """
885 Public method to move the instruction pointer to a different line.
886
887 @param debuggerId ID of the debugger backend
888 @type str
889 @param line the new line, where execution should be continued
890 @type int
891 """
892 self.__sendJsonCommand("RequestMoveIP", {
893 "newLine": line,
894 }, debuggerId)
895
896 def remoteBreakpoint(self, debuggerId, fn, line, setBreakpoint, cond=None,
897 temp=False):
898 """
899 Public method to set or clear a breakpoint.
900
901 @param debuggerId ID of the debugger backend
902 @type str
903 @param fn filename the breakpoint belongs to
904 @type str
905 @param line linenumber of the breakpoint
906 @type int
907 @param setBreakpoint flag indicating setting or resetting a breakpoint
908 @type bool
909 @param cond condition of the breakpoint
910 @type str
911 @param temp flag indicating a temporary breakpoint
912 @type bool
913 """
914 debuggerList = ([debuggerId] if debuggerId
915 else list(self.__connections.keys()))
916 for debuggerId in debuggerList:
917 self.__sendJsonCommand("RequestBreakpoint", {
918 "filename": self.translate(fn, False),
919 "line": line,
920 "temporary": temp,
921 "setBreakpoint": setBreakpoint,
922 "condition": cond,
923 }, debuggerId)
924
925 def remoteBreakpointEnable(self, debuggerId, fn, line, enable):
926 """
927 Public method to enable or disable a breakpoint.
928
929 @param debuggerId ID of the debugger backend
930 @type str
931 @param fn filename the breakpoint belongs to
932 @type str
933 @param line linenumber of the breakpoint
934 @type int
935 @param enable flag indicating enabling or disabling a breakpoint
936 @type bool
937 """
938 debuggerList = ([debuggerId] if debuggerId
939 else list(self.__connections.keys()))
940 for debuggerId in debuggerList:
941 self.__sendJsonCommand("RequestBreakpointEnable", {
942 "filename": self.translate(fn, False),
943 "line": line,
944 "enable": enable,
945 }, debuggerId)
946
947 def remoteBreakpointIgnore(self, debuggerId, fn, line, count):
948 """
949 Public method to ignore a breakpoint the next couple of occurrences.
950
951 @param debuggerId ID of the debugger backend
952 @type str
953 @param fn filename the breakpoint belongs to
954 @type str
955 @param line linenumber of the breakpoint
956 @type int
957 @param count number of occurrences to ignore
958 @type int
959 """
960 debuggerList = ([debuggerId] if debuggerId
961 else list(self.__connections.keys()))
962 for debuggerId in debuggerList:
963 self.__sendJsonCommand("RequestBreakpointIgnore", {
964 "filename": self.translate(fn, False),
965 "line": line,
966 "count": count,
967 }, debuggerId)
968
969 def remoteWatchpoint(self, debuggerId, cond, setWatch, temp=False):
970 """
971 Public method to set or clear a watch expression.
972
973 @param debuggerId ID of the debugger backend
974 @type str
975 @param cond expression of the watch expression
976 @type str
977 @param setWatch flag indicating setting or resetting a watch expression
978 @type bool
979 @param temp flag indicating a temporary watch expression
980 @type bool
981 """
982 debuggerList = ([debuggerId] if debuggerId
983 else list(self.__connections.keys()))
984 for debuggerId in debuggerList:
985 # cond is combination of cond and special (s. watch expression
986 # viewer)
987 self.__sendJsonCommand("RequestWatch", {
988 "temporary": temp,
989 "setWatch": setWatch,
990 "condition": cond,
991 }, debuggerId)
992
993 def remoteWatchpointEnable(self, debuggerId, cond, enable):
994 """
995 Public method to enable or disable a watch expression.
996
997 @param debuggerId ID of the debugger backend
998 @type str
999 @param cond expression of the watch expression
1000 @type str
1001 @param enable flag indicating enabling or disabling a watch expression
1002 @type bool
1003 """
1004 debuggerList = ([debuggerId] if debuggerId
1005 else list(self.__connections.keys()))
1006 for debuggerId in debuggerList:
1007 # cond is combination of cond and special (s. watch expression
1008 # viewer)
1009 self.__sendJsonCommand("RequestWatchEnable", {
1010 "condition": cond,
1011 "enable": enable,
1012 }, debuggerId)
1013
1014 def remoteWatchpointIgnore(self, debuggerId, cond, count):
1015 """
1016 Public method to ignore a watch expression the next couple of
1017 occurrences.
1018
1019 @param debuggerId ID of the debugger backend
1020 @type str
1021 @param cond expression of the watch expression
1022 @type str
1023 @param count number of occurrences to ignore
1024 @type int
1025 """
1026 debuggerList = ([debuggerId] if debuggerId
1027 else list(self.__connections.keys()))
1028 for debuggerId in debuggerList:
1029 # cond is combination of cond and special (s. watch expression
1030 # viewer)
1031 self.__sendJsonCommand("RequestWatchIgnore", {
1032 "condition": cond,
1033 "count": count,
1034 }, debuggerId)
1035
1036 def remoteRawInput(self, debuggerId, inputString):
1037 """
1038 Public method to send the raw input to the debugged program.
1039
1040 @param debuggerId ID of the debugger backend
1041 @type str
1042 @param inputString the raw input
1043 @type str
1044 """
1045 self.__sendJsonCommand("RawInput", {
1046 "input": inputString,
1047 }, debuggerId)
1048
1049 def remoteThreadList(self, debuggerId):
1050 """
1051 Public method to request the list of threads from the client.
1052
1053 @param debuggerId ID of the debugger backend
1054 @type str
1055 """
1056 self.__sendJsonCommand("RequestThreadList", {}, debuggerId)
1057
1058 def remoteSetThread(self, debuggerId, tid):
1059 """
1060 Public method to request to set the given thread as current thread.
1061
1062 @param debuggerId ID of the debugger backend
1063 @type str
1064 @param tid id of the thread
1065 @type int
1066 """
1067 self.__sendJsonCommand("RequestThreadSet", {
1068 "threadID": tid,
1069 }, debuggerId)
1070
1071 def remoteClientStack(self, debuggerId):
1072 """
1073 Public method to request the stack of the main thread.
1074
1075 @param debuggerId ID of the debugger backend
1076 @type str
1077 """
1078 self.__sendJsonCommand("RequestStack", {}, debuggerId)
1079
1080 def remoteClientVariables(self, debuggerId, scope, filterList, framenr=0,
1081 maxSize=0):
1082 """
1083 Public method to request the variables of the debugged program.
1084
1085 @param debuggerId ID of the debugger backend
1086 @type str
1087 @param scope the scope of the variables (0 = local, 1 = global)
1088 @type int
1089 @param filterList list of variable types to filter out
1090 @type list of str
1091 @param framenr framenumber of the variables to retrieve
1092 @type int
1093 @param maxSize maximum size the formatted value of a variable will
1094 be shown. If it is bigger than that, a 'too big' indication will
1095 be given (@@TOO_BIG_TO_SHOW@@).
1096 @type int
1097 """
1098 self.__sendJsonCommand("RequestVariables", {
1099 "frameNumber": framenr,
1100 "scope": scope,
1101 "filters": filterList,
1102 "maxSize": maxSize,
1103 }, debuggerId)
1104
1105 def remoteClientVariable(self, debuggerId, scope, filterList, var,
1106 framenr=0, maxSize=0):
1107 """
1108 Public method to request the variables of the debugged program.
1109
1110 @param debuggerId ID of the debugger backend
1111 @type str
1112 @param scope the scope of the variables (0 = local, 1 = global)
1113 @type int
1114 @param filterList list of variable types to filter out
1115 @type list of str
1116 @param var list encoded name of variable to retrieve
1117 @type list of str
1118 @param framenr framenumber of the variables to retrieve
1119 @type int
1120 @param maxSize maximum size the formatted value of a variable will
1121 be shown. If it is bigger than that, a 'too big' indication will
1122 be given (@@TOO_BIG_TO_SHOW@@).
1123 @type int
1124 """
1125 self.__sendJsonCommand("RequestVariable", {
1126 "variable": var,
1127 "frameNumber": framenr,
1128 "scope": scope,
1129 "filters": filterList,
1130 "maxSize": maxSize,
1131 }, debuggerId)
1132
1133 def remoteClientDisassembly(self, debuggerId):
1134 """
1135 Public method to ask the client for the latest traceback disassembly.
1136
1137 @param debuggerId ID of the debugger backend
1138 @type str
1139 """
1140 self.__sendJsonCommand("RequestDisassembly", {}, debuggerId)
1141
1142 def remoteClientSetFilter(self, debuggerId, scope, filterStr):
1143 """
1144 Public method to set a variables filter list.
1145
1146 @param debuggerId ID of the debugger backend
1147 @type str
1148 @param scope the scope of the variables (0 = local, 1 = global)
1149 @type int
1150 @param filterStr regexp string for variable names to filter out
1151 @type str
1152 """
1153 self.__sendJsonCommand("RequestSetFilter", {
1154 "scope": scope,
1155 "filter": filterStr,
1156 }, debuggerId)
1157
1158 def setCallTraceEnabled(self, debuggerId, on):
1159 """
1160 Public method to set the call trace state.
1161
1162 @param debuggerId ID of the debugger backend
1163 @type str
1164 @param on flag indicating to enable the call trace function
1165 @type bool
1166 """
1167 self.__sendJsonCommand("RequestCallTrace", {
1168 "enable": on,
1169 }, debuggerId)
1170
1171 def remoteNoDebugList(self, debuggerId, noDebugList):
1172 """
1173 Public method to set a list of programs not to be debugged.
1174
1175 The programs given in the list will not be run under the control
1176 of the multi process debugger.
1177
1178 @param debuggerId ID of the debugger backend
1179 @type str
1180 @param noDebugList list of Python programs not to be debugged
1181 @type list of str
1182 """
1183 self.__sendJsonCommand("RequestSetNoDebugList", {
1184 "noDebug": noDebugList,
1185 }, debuggerId)
1186
1187 def remoteBanner(self):
1188 """
1189 Public slot to get the banner info of the remote client.
1190 """
1191 self.__sendJsonCommand("RequestBanner", {})
1192
1193 def remoteCapabilities(self, debuggerId):
1194 """
1195 Public slot to get the debug clients capabilities.
1196
1197 @param debuggerId ID of the debugger backend
1198 @type str
1199 """
1200 self.__sendJsonCommand("RequestCapabilities", {}, debuggerId)
1201
1202 def remoteCompletion(self, debuggerId, text):
1203 """
1204 Public slot to get the a list of possible commandline completions
1205 from the remote client.
1206
1207 @param debuggerId ID of the debugger backend
1208 @type str
1209 @param text the text to be completed
1210 @type str
1211 """
1212 self.__sendJsonCommand("RequestCompletion", {
1213 "text": text,
1214 }, debuggerId)
1215
1216 def remoteUTDiscover(self, syspath, workdir, discoveryStart):
1217 """
1218 Public method to perform a test case discovery.
1219
1220 @param syspath list of directories to be added to sys.path on the
1221 remote side
1222 @type list of str
1223 @param workdir path name of the working directory
1224 @type str
1225 @param discoveryStart directory to start auto-discovery at
1226 @type str
1227 """
1228 self.__sendJsonCommand("RequestUTDiscover", {
1229 "syspath": [] if syspath is None else syspath,
1230 "workdir": workdir,
1231 "discoverystart": discoveryStart,
1232 })
1233
1234 def remoteUTPrepare(self, fn, tn, tfn, failed, cov, covname, coverase,
1235 syspath, workdir, discover, discoveryStart, testCases,
1236 debug):
1237 """
1238 Public method to prepare a new unittest run.
1239
1240 @param fn name of file to load
1241 @type str
1242 @param tn name of test to load
1243 @type str
1244 @param tfn test function name to load tests from
1245 @type str
1246 @param failed list of failed test, if only failed test should be run
1247 @type list of str
1248 @param cov flag indicating collection of coverage data is requested
1249 @type bool
1250 @param covname name of file to be used to assemble the coverage caches
1251 filename
1252 @type str
1253 @param coverase flag indicating erasure of coverage data is requested
1254 @type bool
1255 @param syspath list of directories to be added to sys.path on the
1256 remote side
1257 @type list of str
1258 @param workdir path name of the working directory
1259 @type str
1260 @param discover flag indicating to discover the tests automatically
1261 @type bool
1262 @param discoveryStart directory to start auto-discovery at
1263 @type str
1264 @param testCases list of test cases to be loaded
1265 @type list of str
1266 @param debug flag indicating to run unittest with debugging
1267 @type bool
1268 """
1269 if fn:
1270 self.__scriptName = os.path.abspath(fn)
1271
1272 fn = self.translate(os.path.abspath(fn), False)
1273 else:
1274 self.__scriptName = "unittest discover"
1275
1276 self.__sendJsonCommand("RequestUTPrepare", {
1277 "filename": fn,
1278 "testname": tn,
1279 "testfunctionname": tfn,
1280 "failed": failed,
1281 "coverage": cov,
1282 "coveragefile": covname,
1283 "coverageerase": coverase,
1284 "syspath": [] if syspath is None else syspath,
1285 "workdir": workdir,
1286 "discover": discover,
1287 "discoverystart": discoveryStart,
1288 "testcases": [] if testCases is None else testCases,
1289 "debug": debug,
1290 })
1291
1292 def remoteUTRun(self, debug, failfast):
1293 """
1294 Public method to start a unittest run.
1295
1296 @param debug flag indicating to run unittest with debugging
1297 @type bool
1298 @param failfast flag indicating to stop at the first error
1299 @type bool
1300 """
1301 if debug:
1302 self.__autoContinue = True
1303 self.__sendJsonCommand("RequestUTRun", {
1304 "debug": debug,
1305 "failfast": failfast,
1306 })
1307
1308 def remoteUTStop(self):
1309 """
1310 Public method to stop a unittest run.
1311 """
1312 self.__sendJsonCommand("RequestUTStop", {})
1313
1314 def __parseClientLine(self, sock):
1315 """
1316 Private method to handle data from the client.
1317
1318 @param sock reference to the socket to read data from
1319 @type QTcpSocket
1320 """
1321 while sock and sock.canReadLine():
1322 qs = sock.readLine()
1323 line = bytes(qs).decode(
1324 encoding=Preferences.getSystem("StringEncoding"))
1325
1326 logging.debug("<Debug-Server> %s", line)
1327 ## print("Server: ", line) ## debug # __IGNORE_WARNING_M891__
1328
1329 self.__handleJsonCommand(line, sock)
1330
1331 def __handleJsonCommand(self, jsonStr, sock):
1332 """
1333 Private method to handle a command or response serialized as a
1334 JSON string.
1335
1336 @param jsonStr string containing the command or response received
1337 from the debug backend
1338 @type str
1339 @param sock reference to the socket the data was received from
1340 @type QTcpSocket
1341 """
1342 import json
1343
1344 try:
1345 commandDict = json.loads(jsonStr.strip())
1346 except (TypeError, ValueError) as err:
1347 E5MessageBox.critical(
1348 None,
1349 self.tr("Debug Protocol Error"),
1350 self.tr("""<p>The response received from the debugger"""
1351 """ backend could not be decoded. Please report"""
1352 """ this issue with the received data to the"""
1353 """ eric bugs email address.</p>"""
1354 """<p>Error: {0}</p>"""
1355 """<p>Data:<br/>{1}</p>""").format(
1356 str(err), Utilities.html_encode(jsonStr.strip())),
1357 E5MessageBox.StandardButtons(
1358 E5MessageBox.Ok))
1359 return
1360
1361 method = commandDict["method"]
1362 params = commandDict["params"]
1363
1364 if method == "DebuggerId":
1365 self.__assignDebuggerId(sock, params["debuggerId"])
1366
1367 elif method == "ClientOutput":
1368 self.debugServer.signalClientOutput(
1369 params["text"], params["debuggerId"])
1370
1371 elif method in ["ResponseLine", "ResponseStack"]:
1372 # Check if obsolete thread was clicked
1373 if params["stack"] == []:
1374 # Request updated list
1375 self.remoteThreadList(params["debuggerId"])
1376 return
1377 for s in params["stack"]:
1378 s[0] = self.translate(s[0], True)
1379 cf = params["stack"][0]
1380 if (
1381 self.__autoContinue and
1382 params["debuggerId"] not in self.__autoContinued
1383 ):
1384 self.__autoContinued.append(params["debuggerId"])
1385 QTimer.singleShot(
1386 0, lambda: self.remoteContinue(params["debuggerId"]))
1387 else:
1388 self.debugServer.signalClientLine(
1389 cf[0], int(cf[1]), params["debuggerId"],
1390 method == "ResponseStack", threadName=params["threadName"])
1391 self.debugServer.signalClientStack(
1392 params["stack"], params["debuggerId"],
1393 threadName=params["threadName"])
1394
1395 elif method == "CallTrace":
1396 isCall = params["event"].lower() == "c"
1397 fromInfo = params["from"]
1398 toInfo = params["to"]
1399 self.debugServer.signalClientCallTrace(
1400 isCall,
1401 fromInfo["filename"], str(fromInfo["linenumber"]),
1402 fromInfo["codename"],
1403 toInfo["filename"], str(toInfo["linenumber"]),
1404 toInfo["codename"],
1405 params["debuggerId"])
1406
1407 elif method == "ResponseVariables":
1408 self.debugServer.signalClientVariables(
1409 params["scope"], params["variables"], params["debuggerId"])
1410
1411 elif method == "ResponseVariable":
1412 self.debugServer.signalClientVariable(
1413 params["scope"], [params["variable"]] + params["variables"],
1414 params["debuggerId"])
1415
1416 elif method == "ResponseThreadList":
1417 self.debugServer.signalClientThreadList(
1418 params["currentID"], params["threadList"],
1419 params["debuggerId"])
1420
1421 elif method == "ResponseThreadSet":
1422 self.debugServer.signalClientThreadSet(params["debuggerId"])
1423
1424 elif method == "ResponseCapabilities":
1425 self.clientCapabilities = params["capabilities"]
1426 if params["debuggerId"] == self.__master:
1427 # signal only for the master connection
1428 self.debugServer.signalClientCapabilities(
1429 params["capabilities"],
1430 params["clientType"],
1431 self.__startedVenv,
1432 )
1433
1434 elif method == "ResponseBanner":
1435 if params["debuggerId"] == self.__master:
1436 # signal only for the master connection
1437 self.debugServer.signalClientBanner(
1438 params["version"],
1439 params["platform"],
1440 self.__startedVenv,
1441 )
1442
1443 elif method == "ResponseOK":
1444 self.debugServer.signalClientStatement(False, params["debuggerId"])
1445
1446 elif method == "ResponseContinue":
1447 self.debugServer.signalClientStatement(True, params["debuggerId"])
1448
1449 elif method == "RequestRaw":
1450 self.debugServer.signalClientRawInput(
1451 params["prompt"], params["echo"], params["debuggerId"])
1452
1453 elif method == "ResponseBPConditionError":
1454 fn = self.translate(params["filename"], True)
1455 self.debugServer.signalClientBreakConditionError(
1456 fn, params["line"], params["debuggerId"])
1457
1458 elif method == "ResponseClearBreakpoint":
1459 fn = self.translate(params["filename"], True)
1460 self.debugServer.signalClientClearBreak(
1461 fn, params["line"], params["debuggerId"])
1462
1463 elif method == "ResponseWatchConditionError":
1464 self.debugServer.signalClientWatchConditionError(
1465 params["condition"], params["debuggerId"])
1466
1467 elif method == "ResponseClearWatch":
1468 self.debugServer.signalClientClearWatch(
1469 params["condition"], params["debuggerId"])
1470
1471 elif method == "ResponseDisassembly":
1472 self.debugServer.signalClientDisassembly(
1473 params["disassembly"], params["debuggerId"])
1474
1475 elif method == "ResponseException":
1476 exctype = params["type"]
1477 excmessage = params["message"]
1478 stack = params["stack"]
1479 if stack:
1480 for stackEntry in stack:
1481 stackEntry[0] = self.translate(stackEntry[0], True)
1482 if stack[0] and stack[0][0] == "<string>":
1483 for stackEntry in stack:
1484 if stackEntry[0] == "<string>":
1485 stackEntry[0] = self.__scriptName
1486 else:
1487 break
1488
1489 self.debugServer.signalClientException(
1490 exctype, excmessage, stack, params["debuggerId"],
1491 params["threadName"])
1492
1493 elif method == "ResponseSyntax":
1494 self.debugServer.signalClientSyntaxError(
1495 params["message"], self.translate(params["filename"], True),
1496 params["linenumber"], params["characternumber"],
1497 params["debuggerId"], params["threadName"])
1498
1499 elif method == "ResponseSignal":
1500 self.debugServer.signalClientSignal(
1501 params["message"], self.translate(params["filename"], True),
1502 params["linenumber"], params["function"], params["arguments"],
1503 params["debuggerId"])
1504
1505 elif method == "ResponseExit":
1506 self.__scriptName = ""
1507 self.debugServer.signalClientExit(
1508 params["program"], params["status"], params["message"],
1509 params["debuggerId"])
1510 if params["debuggerId"] == self.__master:
1511 self.debugServer.signalMainClientExit()
1512
1513 elif method == "PassiveStartup":
1514 self.debugServer.passiveStartUp(
1515 self.translate(params["filename"], True), params["exceptions"],
1516 params["debuggerId"])
1517
1518 elif method == "ResponseCompletion":
1519 self.debugServer.signalClientCompletionList(
1520 params["completions"], params["text"], params["debuggerId"])
1521
1522 ###################################################################
1523 ## Unit test related stuff is not done with multi processing
1524 ###################################################################
1525
1526 elif method == "ResponseUTDiscover":
1527 self.debugServer.clientUtDiscovered(
1528 params["testCasesList"], params["exception"],
1529 params["message"])
1530
1531 elif method == "ResponseUTPrepared":
1532 self.debugServer.clientUtPrepared(
1533 params["count"], params["exception"], params["message"])
1534
1535 elif method == "ResponseUTFinished":
1536 self.debugServer.clientUtFinished(params["status"])
1537
1538 elif method == "ResponseUTStartTest":
1539 self.debugServer.clientUtStartTest(
1540 params["testname"], params["description"])
1541
1542 elif method == "ResponseUTStopTest":
1543 self.debugServer.clientUtStopTest()
1544
1545 elif method == "ResponseUTTestFailed":
1546 self.debugServer.clientUtTestFailed(
1547 params["testname"], params["traceback"], params["id"])
1548
1549 elif method == "ResponseUTTestErrored":
1550 self.debugServer.clientUtTestErrored(
1551 params["testname"], params["traceback"], params["id"])
1552
1553 elif method == "ResponseUTTestSkipped":
1554 self.debugServer.clientUtTestSkipped(
1555 params["testname"], params["reason"], params["id"])
1556
1557 elif method == "ResponseUTTestFailedExpected":
1558 self.debugServer.clientUtTestFailedExpected(
1559 params["testname"], params["traceback"], params["id"])
1560
1561 elif method == "ResponseUTTestSucceededUnexpected":
1562 self.debugServer.clientUtTestSucceededUnexpected(
1563 params["testname"], params["id"])
1564
1565 def __sendJsonCommand(self, command, params, debuggerId="", sock=None):
1566 """
1567 Private method to send a single command to the client.
1568
1569 @param command command name to be sent
1570 @type str
1571 @param params dictionary of named parameters for the command
1572 @type dict
1573 @param debuggerId id of the debug client to send the command to
1574 @type str
1575 @param sock reference to the socket object to be used (only used if
1576 debuggerId is not given)
1577 @type QTcpSocket
1578 """
1579 import json
1580
1581 commandDict = {
1582 "jsonrpc": "2.0",
1583 "method": command,
1584 "params": params,
1585 }
1586 cmd = json.dumps(commandDict) + '\n'
1587
1588 if debuggerId and debuggerId in self.__connections:
1589 sock = self.__connections[debuggerId]
1590 elif sock is None and self.__master is not None:
1591 sock = self.__connections[self.__master]
1592 if sock is not None:
1593 self.__writeJsonCommandToSocket(cmd, sock)
1594 else:
1595 self.queue.append(cmd)
1596
1597 def __writeJsonCommandToSocket(self, cmd, sock):
1598 """
1599 Private method to write a JSON command to the socket.
1600
1601 @param cmd JSON command to be sent
1602 @type str
1603 @param sock reference to the socket to write to
1604 @type QTcpSocket
1605 """
1606 data = cmd.encode('utf8', 'backslashreplace')
1607 length = "{0:09d}".format(len(data))
1608 sock.write(length.encode() + data)
1609 sock.flush()
1610
1611
1612 def createDebuggerInterfacePython3(debugServer, passive):
1613 """
1614 Module function to create a debugger interface instance.
1615
1616
1617 @param debugServer reference to the debug server
1618 @type DebugServer
1619 @param passive flag indicating passive connection mode
1620 @type bool
1621 @return instantiated debugger interface
1622 @rtype DebuggerInterfacePython
1623 """
1624 return DebuggerInterfacePython(debugServer, passive)
1625
1626
1627 def getRegistryData():
1628 """
1629 Module function to get characterizing data for the supported debugger
1630 interfaces.
1631
1632 @return list of tuples containing the client type, the client capabilities,
1633 the client file type associations and a reference to the creation
1634 function
1635 @rtype list of tuple of (str, int, list of str, function)
1636 """
1637 py3Exts = []
1638 for ext in Preferences.getDebugger("Python3Extensions").split():
1639 if ext.startswith("."):
1640 py3Exts.append(ext)
1641 else:
1642 py3Exts.append(".{0}".format(ext))
1643
1644 registryData = []
1645 if py3Exts:
1646 registryData.append(
1647 ("Python3", ClientDefaultCapabilities, py3Exts,
1648 createDebuggerInterfacePython3)
1649 )
1650
1651 return registryData

eric ide

mercurial