|
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 |